blob: 41a72f9a2a39e215199fc59f0bcbe2962c952ff2 [file] [log] [blame]
// Copyright 2021 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/cpp/wire/client.h>
#include <lib/fidl/cpp/wire/client_base.h>
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/fidl/epitaph.h>
#include <lib/fidl/txn_header.h>
#include <lib/sync/completion.h>
#include <lib/zx/channel.h>
#include <lib/zx/time.h>
#include <mutex>
#include <thread>
#include <vector>
#include <zxtest/zxtest.h>
#include "src/lib/fidl/llcpp/tests/dispatcher/async_loop_and_endpoints_fixture.h"
#include "src/lib/fidl/llcpp/tests/dispatcher/client_checkers.h"
#include "src/lib/fidl/llcpp/tests/dispatcher/fake_sequence_dispatcher.h"
#include "src/lib/fidl/llcpp/tests/dispatcher/lsan_disabler.h"
#include "src/lib/fidl/llcpp/tests/dispatcher/mock_client_impl.h"
#include "src/lib/fidl/llcpp/tests/dispatcher/test_messages.h"
namespace fidl {
namespace {
using ::fidl_testing::ClientBaseSpy;
using ::fidl_testing::ClientChecker;
using ::fidl_testing::TestProtocol;
using ::fidl_testing::TestResponseContext;
// |NormalTeardownObserver| monitors the destruction of an event handler, which
// signals the completion of teardown.
//
// The class also asserts that teardown is initiated by the user, as opposed to
// being triggered by any error.
class NormalTeardownObserver {
public:
// Returns the event handler that may be used to observe the completion of
// unbinding. This method must be called at most once.
std::unique_ptr<fidl::WireAsyncEventHandler<TestProtocol>> GetEventHandler() {
ZX_ASSERT(event_handler_);
return std::move(event_handler_);
}
zx_status_t Wait(zx_duration_t timeout = zx::duration::infinite().get()) {
return sync_completion_wait(&did_teardown_, timeout);
}
bool IsTeardown() { return Wait(zx::duration::infinite_past().get()) == ZX_OK; }
private:
class EventHandler : public fidl::WireAsyncEventHandler<TestProtocol> {
public:
explicit EventHandler(sync_completion_t& did_teardown) : did_teardown_(did_teardown) {}
void on_fidl_error(::fidl::UnbindInfo error) override {
ZX_PANIC("Error happened: %s", error.FormatDescription().c_str());
}
~EventHandler() override { sync_completion_signal(&did_teardown_); }
private:
sync_completion_t& did_teardown_;
};
sync_completion_t did_teardown_;
std::unique_ptr<fidl::WireAsyncEventHandler<TestProtocol>> event_handler_ =
std::make_unique<EventHandler>(did_teardown_);
};
TEST(WireSharedClient, Teardown) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
auto [local, remote] = fidl::Endpoints<TestProtocol>::Create();
NormalTeardownObserver observer;
WireSharedClient<TestProtocol> client(std::move(local), loop.dispatcher(),
observer.GetEventHandler());
// Teardown the client and wait for unbind completion notification to happen.
client.AsyncTeardown();
EXPECT_OK(observer.Wait());
}
TEST(WireSharedClient, TeardownOnDestroy) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
auto [local, remote] = fidl::Endpoints<TestProtocol>::Create();
NormalTeardownObserver observer;
auto* client = new WireSharedClient<TestProtocol>(std::move(local), loop.dispatcher(),
observer.GetEventHandler());
// Delete the client and wait for unbind completion notification to happen.
delete client;
EXPECT_OK(observer.Wait());
}
TEST(WireSharedClient, NotifyTeardownViaTeardownObserver) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
auto [local, remote] = fidl::Endpoints<TestProtocol>::Create();
sync_completion_t torn_down_;
WireSharedClient<TestProtocol> client(
std::move(local), loop.dispatcher(),
fidl::ObserveTeardown([&torn_down_] { sync_completion_signal(&torn_down_); }));
client.AsyncTeardown();
EXPECT_OK(sync_completion_wait(&torn_down_, zx::duration::infinite().get()));
}
// Cloned clients should operate on the same |ClientImpl|.
TEST(WireSharedClient, Clone) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
ASSERT_OK(loop.StartThread());
auto endpoints = fidl::Endpoints<TestProtocol>::Create();
sync_completion_t did_teardown;
WireSharedClient<TestProtocol> client;
class EventHandler : public fidl::WireAsyncEventHandler<TestProtocol> {
public:
EventHandler(sync_completion_t& did_teardown, ClientBaseSpy& spy)
: did_teardown_(did_teardown), spy_(spy) {}
void on_fidl_error(::fidl::UnbindInfo info) override {
EXPECT_EQ(fidl::Reason::kPeerClosedWhileReading, info.reason());
EXPECT_EQ(ZX_ERR_PEER_CLOSED, info.status());
}
~EventHandler() override {
// All the transactions should be finished by the time the connection is dropped.
EXPECT_EQ(0, spy_.GetTxidCount());
sync_completion_signal(&did_teardown_);
}
private:
sync_completion_t& did_teardown_;
ClientBaseSpy& spy_;
};
ClientBaseSpy spy;
client.Bind(std::move(endpoints.client), loop.dispatcher(),
std::make_unique<EventHandler>(did_teardown, spy));
spy.set_client(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();
// Both clone and the client should delegate to the same underlying binding.
EXPECT_EQ(ClientChecker::GetClientBase(client), ClientChecker::GetClientBase(clone));
contexts.emplace_back(std::make_unique<TestResponseContext>(&spy));
// Generate a txid for a ResponseContext.
spy.PrepareAsyncTxn(contexts.back().get());
EXPECT_TRUE(spy.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::InitTxnHeader(&hdr, contexts.back()->Txid(), 0, fidl::MessageDynamicFlags::kStrictMethod);
ASSERT_OK(endpoints.server.channel().write(0, &hdr, sizeof(fidl_message_header_t), nullptr, 0));
}
// Trigger teardown handler.
endpoints.server.channel().reset();
EXPECT_OK(sync_completion_wait(&did_teardown, ZX_TIME_INFINITE));
}
// This test performs the following repeatedly:
// - Clone a |fidl::WireSharedClient| to another |fidl::WireSharedClient| variable.
// - Destroy the original by letting it go out of scope.
// - Verify that the new client shares the same internal |ClientBase|, which
// stores all the transaction state.
TEST(WireSharedClient, CloneCanExtendClientLifetime) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::Endpoints<TestProtocol>::Create();
// We expect normal teardown because it should be triggered by |outer_clone|
// going out of scope.
NormalTeardownObserver observer;
{
using fidl_testing::ClientChecker;
fidl::internal::ClientBase* client_ptr = nullptr;
fidl::WireSharedClient<TestProtocol> outer_clone;
ASSERT_CLIENT_IMPL_NULL(outer_clone);
{
fidl::WireSharedClient<TestProtocol> inner_clone;
ASSERT_CLIENT_IMPL_NULL(inner_clone);
{
fidl::WireSharedClient client(std::move(endpoints.client), loop.dispatcher(),
observer.GetEventHandler());
ASSERT_CLIENT_IMPL_NOT_NULL(client);
client_ptr = ClientChecker::GetClientBase(client);
ASSERT_OK(loop.RunUntilIdle());
ASSERT_FALSE(observer.IsTeardown());
// Extend the client lifetime to |inner_clone|.
inner_clone = client.Clone();
}
ASSERT_CLIENT_IMPL_NOT_NULL(inner_clone);
ASSERT_EQ(ClientChecker::GetClientBase(inner_clone), client_ptr);
ASSERT_OK(loop.RunUntilIdle());
ASSERT_FALSE(observer.IsTeardown());
// Extend the client lifetime to |outer_clone|.
outer_clone = inner_clone.Clone();
}
ASSERT_CLIENT_IMPL_NOT_NULL(outer_clone);
ASSERT_EQ(ClientChecker::GetClientBase(outer_clone), client_ptr);
ASSERT_OK(loop.RunUntilIdle());
ASSERT_FALSE(observer.IsTeardown());
}
// Verify that teardown still happens when all the clients
// referencing the same connection go out of scope.
ASSERT_OK(loop.RunUntilIdle());
ASSERT_TRUE(observer.IsTeardown());
}
// Calling |AsyncTeardown| explicitly will cause all clones to unbind.
TEST(WireSharedClient, CloneSupportsExplicitTeardown) {
async::Loop loop(&kAsyncLoopConfigNoAttachToCurrentThread);
auto endpoints = fidl::CreateEndpoints<TestProtocol>();
ASSERT_OK(endpoints.status_value());
// We expect normal teardown because we are explicitly calling
// |AsyncTeardown|.
NormalTeardownObserver observer;
fidl::WireSharedClient client(std::move(endpoints->client), loop.dispatcher(),
observer.GetEventHandler());
fidl::WireSharedClient<TestProtocol> clone = client.Clone();
ASSERT_OK(loop.RunUntilIdle());
ASSERT_FALSE(observer.IsTeardown());
using ::fidl_testing::ClientChecker;
// The channel being managed is still alive.
ASSERT_NOT_NULL(ClientChecker::GetTransport(clone).get());
// Now we call |AsyncTeardown| on the main client, the clone would be torn
// down too.
client.AsyncTeardown();
ASSERT_OK(loop.RunUntilIdle());
EXPECT_TRUE(observer.IsTeardown());
EXPECT_NULL(ClientChecker::GetTransport(clone).get());
EXPECT_NULL(ClientChecker::GetTransport(client).get());
}
class WireSharedClientTest : public fidl_testing::AsyncLoopAndEndpointsFixture {};
TEST_F(WireSharedClientTest, DefaultConstruction) {
WireSharedClient<TestProtocol> client;
EXPECT_FALSE(client.is_valid());
}
TEST_F(WireSharedClientTest, InvalidAccess) {
WireSharedClient<TestProtocol> client;
ASSERT_DEATH([&] { client.operator->(); });
ASSERT_DEATH([&] {
fidl::Arena arena;
client.buffer(arena);
});
ASSERT_DEATH([&] { client.sync(); });
ASSERT_DEATH([&] { client.AsyncTeardown(); });
}
TEST_F(WireSharedClientTest, Move) {
WireSharedClient<TestProtocol> client;
client.Bind(std::move(endpoints().client), loop().dispatcher());
EXPECT_TRUE(client.is_valid());
WireSharedClient<TestProtocol> client2 = std::move(client);
EXPECT_FALSE(client.is_valid());
EXPECT_TRUE(client2.is_valid());
ASSERT_DEATH([&] { client.operator->(); });
}
TEST_F(WireSharedClientTest, CanMakeCallOnAnotherThread) {
auto [local, remote] = std::move(endpoints());
WireSharedClient client(std::move(local), loop().dispatcher());
std::thread foreign_thread([&] {
fidl_testing::GoodMessage message;
fidl::OutgoingMessage outgoing = message.message();
(void)client->OneWayMethod(outgoing);
});
foreign_thread.join();
}
TEST_F(WireSharedClientTest, CanDestroyOnSameSequence) {
fidl_testing::RunWithLsanDisabled([&] {
auto [local, remote] = std::move(endpoints());
fidl_testing::FakeSequenceDispatcher fake_dispatcher(loop().dispatcher());
fake_dispatcher.SetSequenceId({.value = 1});
WireSharedClient client(std::move(local), &fake_dispatcher);
loop().RunUntilIdle();
ASSERT_NO_DEATH([&] { client = {}; });
});
}
TEST_F(WireSharedClientTest, CanDestroyOnAnotherSequence) {
fidl_testing::RunWithLsanDisabled([&] {
auto [local, remote] = std::move(endpoints());
fidl_testing::FakeSequenceDispatcher fake_dispatcher(loop().dispatcher());
fake_dispatcher.SetSequenceId({.value = 1});
WireSharedClient client(std::move(local), &fake_dispatcher);
loop().RunUntilIdle();
fake_dispatcher.SetSequenceId({.value = 2});
ASSERT_NO_DEATH([&] { client = {}; });
});
}
} // namespace
} // namespace fidl