blob: 4c74721895cb982d0dfb44982bd7291bde13349d [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fidl/epitaph.h>
#include <lib/fidl/llcpp/client.h>
#include <lib/fidl/llcpp/client_base.h>
#include <lib/fidl/llcpp/coding.h>
#include <lib/fidl/llcpp/connect_service.h>
#include <lib/fidl/txn_header.h>
#include <lib/sync/completion.h>
#include <lib/zx/channel.h>
#include <lib/zx/time.h>
#include <zircon/fidl.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <mutex>
#include <thread>
#include <unordered_set>
#include <zxtest/zxtest.h>
namespace fidl {
namespace {
class TestProtocol {
TestProtocol() = delete;
public:
// Generated code will define an AsyncEventHandler class.
class AsyncEventHandler {
public:
AsyncEventHandler() = default;
virtual ~AsyncEventHandler() = default;
virtual void Unbound(::fidl::UnbindInfo info) {}
};
class ClientImpl final : private internal::ClientBase {
public:
void PrepareAsyncTxn(internal::ResponseContext* context) {
internal::ClientBase::PrepareAsyncTxn(context);
std::unique_lock lock(lock_);
EXPECT_FALSE(txids_.count(context->Txid()));
txids_.insert(context->Txid());
}
void ForgetAsyncTxn(internal::ResponseContext* context) {
{
std::unique_lock lock(lock_);
txids_.erase(context->Txid());
}
internal::ClientBase::ForgetAsyncTxn(context);
}
void EraseTxid(internal::ResponseContext* context) {
{
std::unique_lock lock(lock_);
txids_.erase(context->Txid());
}
}
std::shared_ptr<internal::ChannelRef> GetChannel() {
return internal::ClientBase::GetChannel();
}
uint32_t GetEventCount() {
std::unique_lock lock(lock_);
return event_count_;
}
bool IsPending(zx_txid_t txid) {
std::unique_lock lock(lock_);
return txids_.count(txid);
}
size_t GetTxidCount() {
std::unique_lock lock(lock_);
EXPECT_EQ(internal::ClientBase::GetTransactionCount(), txids_.size());
return txids_.size();
}
AsyncEventHandler* event_handler() const { return event_handler_.get(); }
private:
friend class Client<TestProtocol>;
friend class internal::ControlBlock<TestProtocol>;
explicit ClientImpl(std::shared_ptr<AsyncEventHandler> event_handler)
: event_handler_(std::move(event_handler)) {}
// For each event, increment the event count.
std::optional<UnbindInfo> DispatchEvent(fidl_incoming_msg_t* msg) override {
event_count_++;
return {};
}
std::shared_ptr<AsyncEventHandler> event_handler_;
std::mutex lock_;
std::unordered_set<zx_txid_t> txids_;
uint32_t event_count_ = 0;
};
};
class TestResponseContext : public internal::ResponseContext {
public:
explicit TestResponseContext(TestProtocol::ClientImpl* client)
: internal::ResponseContext(&::fidl::_llcpp_coding_AnyZeroArgMessageTable, 0),
client_(client) {}
void OnReply(uint8_t* reply) override { client_->EraseTxid(this); }
void OnError() override {}
private:
TestProtocol::ClientImpl* client_;
};
TEST(ClientBindingTestCase, AsyncTxn) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
sync_completion_t unbound;
Client<TestProtocol> client;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
EventHandler(sync_completion_t& unbound, Client<TestProtocol>& client)
: unbound_(unbound), client_(client) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kPeerClosed, info.reason);
EXPECT_EQ(ZX_ERR_PEER_CLOSED, info.status);
EXPECT_EQ(0, client_->GetTxidCount());
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
Client<TestProtocol>& client_;
};
ASSERT_OK(client.Bind(std::move(local), loop.dispatcher(),
std::make_shared<EventHandler>(unbound, client)));
// Generate a txid for a ResponseContext. Send a "response" message with the same txid from the
// remote end of the channel.
TestResponseContext context(client.get());
client->PrepareAsyncTxn(&context);
EXPECT_TRUE(client->IsPending(context.Txid()));
fidl_message_header_t hdr;
fidl_init_txn_header(&hdr, context.Txid(), 0);
ASSERT_OK(remote.write(0, &hdr, sizeof(fidl_message_header_t), nullptr, 0));
// Trigger unbound handler.
remote.reset();
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
}
TEST(ClientBindingTestCase, ParallelAsyncTxns) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
sync_completion_t unbound;
Client<TestProtocol> client;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
EventHandler(sync_completion_t& unbound, Client<TestProtocol>& client)
: unbound_(unbound), client_(client) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kPeerClosed, info.reason);
EXPECT_EQ(ZX_ERR_PEER_CLOSED, info.status);
EXPECT_EQ(0, client_->GetTxidCount());
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
Client<TestProtocol>& client_;
};
ASSERT_OK(client.Bind(std::move(local), loop.dispatcher(),
std::make_shared<EventHandler>(unbound, client)));
// In parallel, simulate 10 async transactions and send "response" messages from the remote end of
// the channel.
std::vector<std::unique_ptr<TestResponseContext>> contexts;
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
contexts.emplace_back(std::make_unique<TestResponseContext>(client.get()));
threads[i] = std::thread([context = contexts[i].get(), &remote, &client] {
client->PrepareAsyncTxn(context);
EXPECT_TRUE(client->IsPending(context->Txid()));
fidl_message_header_t hdr;
fidl_init_txn_header(&hdr, context->Txid(), 0);
ASSERT_OK(remote.write(0, &hdr, sizeof(fidl_message_header_t), nullptr, 0));
});
}
for (int i = 0; i < 10; ++i)
threads[i].join();
// Trigger unbound handler.
remote.reset();
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
}
TEST(ClientBindingTestCase, ForgetAsyncTxn) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
Client<TestProtocol> client(std::move(local), loop.dispatcher());
// Generate a txid for a ResponseContext.
TestResponseContext context(client.get());
client->PrepareAsyncTxn(&context);
EXPECT_TRUE(client->IsPending(context.Txid()));
// Forget the transaction.
client->ForgetAsyncTxn(&context);
EXPECT_EQ(0, client->GetTxidCount());
}
TEST(ClientBindingTestCase, UnknownResponseTxid) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
sync_completion_t unbound;
Client<TestProtocol> client;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
EventHandler(sync_completion_t& unbound, Client<TestProtocol>& client)
: unbound_(unbound), client_(client) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kUnexpectedMessage, info.reason);
EXPECT_EQ(ZX_ERR_NOT_FOUND, info.status);
EXPECT_EQ(0, client_->GetTxidCount());
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
Client<TestProtocol>& client_;
};
ASSERT_OK(client.Bind(std::move(local), loop.dispatcher(),
std::make_shared<EventHandler>(unbound, client)));
// Send a "response" message for which there was no outgoing request.
ASSERT_EQ(0, client->GetTxidCount());
fidl_message_header_t hdr;
fidl_init_txn_header(&hdr, 1, 0);
ASSERT_OK(remote.write(0, &hdr, sizeof(fidl_message_header_t), nullptr, 0));
// on_unbound should be triggered by the erroneous response.
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
}
TEST(ClientBindingTestCase, Events) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
sync_completion_t unbound;
Client<TestProtocol> client;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
EventHandler(sync_completion_t& unbound, Client<TestProtocol>& client)
: unbound_(unbound), client_(client) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kPeerClosed, info.reason);
EXPECT_EQ(ZX_ERR_PEER_CLOSED, info.status);
EXPECT_EQ(10, client_->GetEventCount()); // Expect 10 events.
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
Client<TestProtocol>& client_;
};
ASSERT_OK(client.Bind(std::move(local), loop.dispatcher(),
std::make_shared<EventHandler>(unbound, client)));
// In parallel, send 10 event messages from the remote end of the channel.
std::thread threads[10];
for (int i = 0; i < 10; ++i) {
threads[i] = std::thread([&remote] {
fidl_message_header_t hdr;
fidl_init_txn_header(&hdr, 0, 0);
ASSERT_OK(remote.write(0, &hdr, sizeof(fidl_message_header_t), nullptr, 0));
});
}
for (int i = 0; i < 10; ++i)
threads[i].join();
// Trigger unbound handler.
remote.reset();
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
}
TEST(ClientBindingTestCase, Unbind) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
sync_completion_t unbound;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
explicit EventHandler(sync_completion_t& unbound) : unbound_(unbound) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kUnbind, info.reason);
EXPECT_OK(info.status);
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
};
Client<TestProtocol> client(std::move(local), loop.dispatcher(),
std::make_shared<EventHandler>(unbound));
// Unbind the client and wait for on_unbound to run.
client.Unbind();
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
}
TEST(ClientBindingTestCase, UnbindOnDestroy) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
sync_completion_t unbound;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
explicit EventHandler(sync_completion_t& unbound) : unbound_(unbound) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kUnbind, info.reason);
EXPECT_OK(info.status);
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
};
auto* client = new Client<TestProtocol>(std::move(local), loop.dispatcher(),
std::make_shared<EventHandler>(unbound));
// Delete the client and wait for on_unbound to run.
delete client;
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
}
TEST(ClientBindingTestCase, UnbindWhileActiveChannelRefs) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
sync_completion_t unbound;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
explicit EventHandler(sync_completion_t& unbound) : unbound_(unbound) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kUnbind, info.reason);
EXPECT_OK(info.status);
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
};
Client<TestProtocol> client(std::move(local), loop.dispatcher(),
std::make_shared<EventHandler>(unbound));
// Create a strong reference to the channel.
auto channel = client->GetChannel();
// Unbind() and the unbound handler should not be blocked by the channel reference.
client.Unbind();
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
// Check that the channel handle is still valid.
EXPECT_OK(
zx_object_get_info(channel->handle(), ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr));
}
// Cloned clients should operate on the same |ClientImpl|.
TEST(ClientBindingTestCase, Clone) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
auto endpoints = fidl::CreateEndpoints<TestProtocol>();
ASSERT_OK(endpoints.status_value());
sync_completion_t unbound;
Client<TestProtocol> client;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
EventHandler(sync_completion_t& unbound, Client<TestProtocol>& client)
: unbound_(unbound), client_(client) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kPeerClosed, info.reason);
EXPECT_EQ(ZX_ERR_PEER_CLOSED, info.status);
// All the transactions should be finished by the time the connection is dropped.
EXPECT_EQ(0, client_->GetTxidCount());
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
Client<TestProtocol>& client_;
};
ASSERT_OK(client.Bind(std::move(endpoints->client), loop.dispatcher(),
std::make_shared<EventHandler>(unbound, client)));
// Create 20 clones of the client, and verify that they can all send messages
// through the same internal |ClientImpl|.
constexpr size_t kNumClones = 20;
std::vector<std::unique_ptr<TestResponseContext>> contexts;
for (size_t i = 0; i < kNumClones; i++) {
auto clone = client.Clone();
contexts.emplace_back(std::make_unique<TestResponseContext>(clone.get()));
// Generate a txid for a ResponseContext.
clone->PrepareAsyncTxn(contexts.back().get());
// Both clone and the client should delegate to the same underlying binding.
EXPECT_TRUE(clone->IsPending(contexts.back()->Txid()));
EXPECT_TRUE(client->IsPending(contexts.back()->Txid()));
// Send a "response" message with the same txid from the remote end of the channel.
fidl_message_header_t hdr;
fidl_init_txn_header(&hdr, contexts.back()->Txid(), 0);
ASSERT_OK(
endpoints->server.channel().write(0, &hdr, sizeof(fidl_message_header_t), nullptr, 0));
}
// Trigger unbound handler.
endpoints->server.channel().reset();
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
}
// This test performs the following repeatedly:
// - Clone a |fidl::Client| to another |fidl::Client| variable.
// - Destroy the original by letting it go out of scope.
// - Verify that the new client shares the same internal |ClientImpl|.
TEST(ClientBindingTestCase, CloneCanExtendClientLifetime) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<TestProtocol>();
ASSERT_OK(endpoints.status_value());
bool did_unbind = false;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
explicit EventHandler(bool& did_unbind) : did_unbind_(did_unbind) {}
void Unbound(::fidl::UnbindInfo info) override {
// The reason should be |kUnbind| because |outer_clone| going out of
// scope will trigger unbinding.
EXPECT_EQ(fidl::UnbindInfo::kUnbind, info.reason);
EXPECT_EQ(ZX_OK, info.status);
did_unbind_ = true;
}
private:
bool& did_unbind_;
};
{
TestProtocol::ClientImpl* client_ptr = nullptr;
fidl::Client<TestProtocol> outer_clone;
ASSERT_NULL(outer_clone.get());
{
fidl::Client<TestProtocol> inner_clone;
ASSERT_NULL(inner_clone.get());
{
fidl::Client client(std::move(endpoints->client), loop.dispatcher(),
std::make_shared<EventHandler>(did_unbind));
ASSERT_NOT_NULL(client.get());
client_ptr = client.get();
ASSERT_OK(loop.RunUntilIdle());
ASSERT_FALSE(did_unbind);
// Extend the client lifetime to |inner_clone|.
inner_clone = client.Clone();
}
ASSERT_NOT_NULL(inner_clone.get());
ASSERT_EQ(inner_clone.get(), client_ptr);
ASSERT_OK(loop.RunUntilIdle());
ASSERT_FALSE(did_unbind);
// Extend the client lifetime to |outer_clone|.
outer_clone = inner_clone.Clone();
}
ASSERT_NOT_NULL(outer_clone.get());
ASSERT_EQ(outer_clone.get(), client_ptr);
ASSERT_OK(loop.RunUntilIdle());
ASSERT_FALSE(did_unbind);
}
// Verify that unbinding still happens when all the clients
// referencing the same connection go out of scope.
ASSERT_OK(loop.RunUntilIdle());
ASSERT_TRUE(did_unbind);
}
TEST(ClientBindingTestCase, CloneSupportsExplicitUnbind) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<TestProtocol>();
ASSERT_OK(endpoints.status_value());
bool did_unbind = false;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
explicit EventHandler(bool& did_unbind) : did_unbind_(did_unbind) {}
void Unbound(::fidl::UnbindInfo info) override {
// The reason should be |kUnbind| because we are explicitly calling |Unbind|.
EXPECT_EQ(fidl::UnbindInfo::kUnbind, info.reason);
EXPECT_EQ(ZX_OK, info.status);
did_unbind_ = true;
}
private:
bool& did_unbind_;
};
fidl::Client client(std::move(endpoints->client), loop.dispatcher(),
std::make_shared<EventHandler>(did_unbind));
fidl::Client<TestProtocol> clone = client.Clone();
ASSERT_OK(loop.RunUntilIdle());
ASSERT_FALSE(did_unbind);
// The channel being managed is still alive.
ASSERT_NOT_NULL(clone->GetChannel().get());
// Now we call |Unbind| on the main client, the clone would be unbound too.
client.Unbind();
ASSERT_OK(loop.RunUntilIdle());
EXPECT_TRUE(did_unbind);
EXPECT_NULL(clone->GetChannel().get());
EXPECT_NULL(client->GetChannel().get());
}
TEST(ClientBindingTestCase, CloneSupportsWaitForChannel) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<TestProtocol>();
ASSERT_OK(endpoints.status_value());
sync_completion_t did_unbind;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
explicit EventHandler(sync_completion_t& did_unbind) : did_unbind_(did_unbind) {}
void Unbound(::fidl::UnbindInfo info) override {
// The reason should be |kUnbind| because we are calling |WaitForChannel|
// which triggers unbinding.
EXPECT_EQ(fidl::UnbindInfo::kUnbind, info.reason);
EXPECT_EQ(ZX_OK, info.status);
sync_completion_signal(&did_unbind_);
}
private:
sync_completion_t& did_unbind_;
};
fidl::Client client(std::move(endpoints->client), loop.dispatcher(),
std::make_shared<EventHandler>(did_unbind));
fidl::Client<TestProtocol> clone = client.Clone();
ASSERT_OK(loop.RunUntilIdle());
ASSERT_EQ(ZX_ERR_TIMED_OUT,
sync_completion_wait(&did_unbind, zx::duration::infinite_past().get()));
// The channel being managed is still alive.
ASSERT_NOT_NULL(clone->GetChannel().get());
// Now we call |WaitForChannel| on the main client, the clone would be unbound too.
// Note that |WaitForChannel| itself is blocking, so we cannot block the async loop
// at the same time.
ASSERT_OK(loop.StartThread());
auto client_end = client.WaitForChannel();
EXPECT_TRUE(client_end.is_valid());
// Right after |WaitForChannel| returns, we are guaranteed that the |Client|s
// have lost their access to the channel.
EXPECT_NULL(clone->GetChannel().get());
EXPECT_NULL(client->GetChannel().get());
// |did_unbind| is signalled in the |Unbound| handler.
// It is not required that |WaitForChannel| waits for the execution of
// the |Unbound| handler, hence the only safe way to check for unbinding
// is to wait on a |sync_completion_t|, while the event loop thread executes
// the unbind operation initiated by |WaitForChannel|.
EXPECT_OK(sync_completion_wait(&did_unbind, zx::duration::infinite().get()));
}
class ReleaseTestResponseContext : public internal::ResponseContext {
public:
explicit ReleaseTestResponseContext(sync_completion_t* done)
: internal::ResponseContext(&::fidl::_llcpp_coding_AnyZeroArgMessageTable, 0), done_(done) {}
void OnReply(uint8_t* reply) override { delete this; }
void OnError() override {
sync_completion_signal(done_);
delete this;
}
sync_completion_t* done_;
};
TEST(ClientBindingTestCase, ReleaseOutstandingTxnsOnDestroy) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
auto* client = new Client<TestProtocol>(std::move(local), loop.dispatcher());
// Create and register a response context which will signal when deleted.
sync_completion_t done;
(*client)->PrepareAsyncTxn(new ReleaseTestResponseContext(&done));
// Delete the client and ensure that the response context is deleted.
delete client;
EXPECT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE));
}
TEST(ClientBindingTestCase, ReleaseOutstandingTxnsOnUnbound) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
Client<TestProtocol> client(std::move(local), loop.dispatcher());
// Create and register a response context which will signal when deleted.
sync_completion_t done;
client->PrepareAsyncTxn(new ReleaseTestResponseContext(&done));
// Trigger unbinding and wait for the transaction context to be released.
remote.reset();
EXPECT_OK(sync_completion_wait(&done, ZX_TIME_INFINITE));
}
TEST(ClientBindingTestCase, Epitaph) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
sync_completion_t unbound;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
explicit EventHandler(sync_completion_t& unbound) : unbound_(unbound) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kPeerClosed, info.reason);
EXPECT_EQ(ZX_ERR_BAD_STATE, info.status);
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
};
Client<TestProtocol> client(std::move(local), loop.dispatcher(),
std::make_shared<EventHandler>(unbound));
// Send an epitaph and wait for on_unbound to run.
ASSERT_OK(fidl_epitaph_write(remote.get(), ZX_ERR_BAD_STATE));
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
}
TEST(ClientBindingTestCase, PeerClosedNoEpitaph) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
sync_completion_t unbound;
class EventHandler : public TestProtocol::AsyncEventHandler {
public:
explicit EventHandler(sync_completion_t& unbound) : unbound_(unbound) {}
void Unbound(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::UnbindInfo::kPeerClosed, info.reason);
// No epitaph is equivalent to ZX_ERR_PEER_CLOSED epitaph.
EXPECT_EQ(ZX_ERR_PEER_CLOSED, info.status);
sync_completion_signal(&unbound_);
}
private:
sync_completion_t& unbound_;
};
Client<TestProtocol> client(std::move(local), loop.dispatcher(),
std::make_shared<EventHandler>(unbound));
// Close the server end and wait for on_unbound to run.
remote.reset();
EXPECT_OK(sync_completion_wait(&unbound, ZX_TIME_INFINITE));
}
TEST(ChannelRefTrackerTestCase, NoWaitNoHandleLeak) {
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
// Pass ownership of local end of the channel to the ChannelRefTracker.
auto channel_tracker = new internal::ChannelRefTracker();
channel_tracker->Init(std::move(local));
// Destroy the ChannelRefTracker. ZX_SIGNAL_PEER_CLOSED should be asserted on remote.
delete channel_tracker;
EXPECT_OK(remote.wait_one(ZX_CHANNEL_PEER_CLOSED, zx::time::infinite_past(), nullptr));
}
TEST(ChannelRefTrackerTestCase, WaitForChannelWithoutRefs) {
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
auto local_handle = local.get();
// Pass ownership of local end of the channel to the ChannelRefTracker.
internal::ChannelRefTracker channel_tracker;
channel_tracker.Init(std::move(local));
// Retrieve the channel. Check the validity of the handle.
local = channel_tracker.WaitForChannel();
ASSERT_EQ(local_handle, local.get());
ASSERT_OK(local.get_info(ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr));
// Ensure that no new references can be created.
EXPECT_FALSE(channel_tracker.Get());
}
TEST(ChannelRefTrackerTestCase, WaitForChannelWithRefs) {
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
auto local_handle = local.get();
// Pass ownership of local end of the channel to the ChannelRefTracker.
internal::ChannelRefTracker channel_tracker;
channel_tracker.Init(std::move(local));
// Get a new reference.
auto channel_ref = channel_tracker.Get();
ASSERT_EQ(local_handle, channel_ref->handle());
// Pass the reference to another thread, then wait for it to be released.
// NOTE: This is inherently racy but should never fail regardless of the particular state.
sync_completion_t running;
std::thread([&running, channel_ref = std::move(channel_ref)]() mutable {
sync_completion_signal(&running); // Let the main thread continue.
channel_ref = nullptr; // Release this reference.
}).detach();
ASSERT_OK(sync_completion_wait(&running, ZX_TIME_INFINITE));
// Retrieve the channel. Check the validity of the handle.
local = channel_tracker.WaitForChannel();
ASSERT_EQ(local_handle, local.get());
ASSERT_OK(local.get_info(ZX_INFO_HANDLE_VALID, nullptr, 0, nullptr, nullptr));
// Ensure that no new references can be created.
EXPECT_FALSE(channel_tracker.Get());
}
} // namespace
} // namespace fidl