blob: 1b78ba8699546a3fec84b682a2f3f6f4a37927ef [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.
#ifndef LIB_FIDL_LLCPP_INCLUDE_LIB_FIDL_LLCPP_CLIENT_BASE_H_
#define LIB_FIDL_LLCPP_INCLUDE_LIB_FIDL_LLCPP_CLIENT_BASE_H_
#include <lib/async/dispatcher.h>
#include <lib/async/time.h>
#include <lib/fidl/llcpp/async_binding.h>
#include <lib/fidl/llcpp/extract_resource_on_destruction.h>
#include <lib/fidl/llcpp/internal/client_continuation.h>
#include <lib/fidl/llcpp/internal/client_details.h>
#include <lib/fidl/llcpp/internal/intrusive_container/wavl_tree.h>
#include <lib/fidl/llcpp/message.h>
#include <lib/fidl/llcpp/wire_messaging.h>
#include <lib/fit/traits.h>
#include <lib/zx/channel.h>
#include <zircon/fidl.h>
#include <zircon/listnode.h>
#include <zircon/types.h>
#include <memory>
#include <mutex>
#include <optional>
namespace fidl_testing {
// Forward declaration of test helpers to support friend declaration.
class ClientBaseChecker;
class ClientChecker;
} // namespace fidl_testing
namespace fidl {
namespace internal {
class ClientControlBlock;
// A mixin into |ResponseContext| to handle the asynchronous error delivery
// aspects.
template <typename Derived>
class ResponseContextAsyncErrorTask : private async_task_t {
public:
// Try to schedule an |ResponseContext::OnError| as a task on |dispatcher|.
//
// If successful, ownership of the context is passed to the |dispatcher| until
// the task is executed.
zx_status_t TryAsyncDeliverError(::fidl::Status error, async_dispatcher_t* dispatcher) {
error_ = error;
async_task_t* task = this;
*task = async_task_t{{ASYNC_STATE_INIT},
&ResponseContextAsyncErrorTask::AsyncErrorDelivery,
async_now(dispatcher)};
return async_post_task(dispatcher, task);
}
private:
static void AsyncErrorDelivery(async_dispatcher_t* /*unused*/, async_task_t* task,
zx_status_t status) {
auto* context = static_cast<Derived*>(task);
auto* self = static_cast<ResponseContextAsyncErrorTask*>(task);
context->OnError(self->error_);
}
::fidl::Status error_;
};
// |ResponseContext| contains information about an outstanding asynchronous
// method call. It inherits from an intrusive container node so that
// |ClientBase| can track it without requiring heap allocation.
//
// The generated code will define type-specific response contexts e.g.
// |fidl::WireResponseContext<FooMethod>|, that inherits from |ResponseContext|
// and interprets the bytes passed to the |OnRawResult| call appropriately.
// Users should interact with those subclasses; see the lifecycle notes on
// |WireResponseContext|.
//
// NOTE: |ResponseContext| are additionally referenced with a |list_node_t|
// in order to safely iterate over outstanding transactions on |ClientBase|
// destruction, releasing each outstanding response context.
class ResponseContext : public fidl::internal_wavl::WAVLTreeContainable<ResponseContext*>,
private list_node_t,
private ResponseContextAsyncErrorTask<ResponseContext> {
public:
explicit ResponseContext(uint64_t ordinal)
: fidl::internal_wavl::WAVLTreeContainable<ResponseContext*>(),
list_node_t(LIST_INITIAL_CLEARED_VALUE),
ordinal_(ordinal) {}
virtual ~ResponseContext() = default;
// |ResponseContext| objects are "pinned" in memory.
ResponseContext(const ResponseContext& other) = delete;
ResponseContext& operator=(const ResponseContext& other) = delete;
ResponseContext(ResponseContext&& other) = delete;
ResponseContext& operator=(ResponseContext&& other) = delete;
uint64_t ordinal() const { return ordinal_; }
zx_txid_t Txid() const { return txid_; }
// Invoked when a response has been received or an error was detected for this
// context. |OnRawResult| is allowed to consume the current object.
//
// ## If |result| represents a success
//
// |result| references the incoming message in encoded form.
//
// Ownership of bytes referenced by |result| stays with the caller.
// The callee should not access the bytes in |result| once this method returns.
//
// Ownership of handles referenced by |result| is transferred to the callee.
//
// If there was an error decoding |result|, the implementation should return
// that error as a present |fidl::UnbindInfo|. Otherwise, the implementation
// should return |std::nullopt|.
//
// ## If |result| represents an error
//
// An error occurred while processing this FIDL call:
//
// - Failed to encode the outgoing request specific to this call.
// - The peer endpoint was closed.
// - Error from the |async_dispatcher_t|.
// - Error from the underlying transport.
// - The server sent a malformed message.
// - The user explicitly initiated binding teardown.
// - The call raced with an external error in the meantime that caused binding
// teardown.
//
// See |WireResponseContext<FidlMethod>::OnResult| for more details.
virtual std::optional<fidl::UnbindInfo> OnRawResult(
::fidl::IncomingMessage&& result, internal::MessageStorageViewBase* storage_view) = 0;
// A helper around |OnRawResult| to directly notify an error to the context.
void OnError(::fidl::Status error) { OnRawResult(fidl::IncomingMessage::Create(error), nullptr); }
private:
friend class ResponseContextAsyncErrorTask<ResponseContext>;
friend class ClientBase;
// For use with |fidl::internal_wavl::WAVLTree|.
struct Traits {
static zx_txid_t GetKey(const ResponseContext& context) { return context.txid_; }
static bool LessThan(const zx_txid_t& key1, const zx_txid_t& key2) { return key1 < key2; }
static bool EqualTo(const zx_txid_t& key1, const zx_txid_t& key2) { return key1 == key2; }
};
const uint64_t ordinal_; // Expected ordinal for the response.
zx_txid_t txid_ = 0; // Zircon txid of outstanding transaction.
};
} // namespace internal
// |WireResponseContext| is used to monitor the outcome of an outstanding asynchronous
// |FidlMethod| method call without heap memory allocation. They are used in
// combination with the caller-allocating async API flavors of FIDL clients.
//
// ## Lifecycle
//
// The FIDL runtime has no requirements on how |WireResponseContext|s are
// allocated.
//
// Once a |WireResponseContext| is passed to the client, ownership is
// transferred to the FIDL runtime. Ownership is returned back to the user when
// |OnResult| is invoked. This means that the user must keep the response
// context object alive for the duration of the async method call. |OnResult| is
// guaranteed to be invoked exactly once regardless of success or error.
//
// ## Usage
//
// Subclass |WireResponseContext| and override its |OnResult| method. Example:
//
// // Lets say we have a `Game` object that loads some required asset
// // using the `Disk` FIDL protocol and its `Download` method.
// class Game : public fidl::WireResponseContext<Disk::Download> {
// public:
// void LoadGame() {
// // Passing `this` to the caller-allocating flavor since `Game`
// // implements the corresponding response context.
// disk_client_.buffer(arena_)->Download("foo.zip").ThenExactlyOnce(this);
// }
//
// private:
// void OnResult(fidl::WireUnownedResult<Disk::Download>& result) final {
// if (!result.ok()) {
// std::cerr << "Downloading failed: " << result.error();
// return;
// }
// // Access the response.
// fidl::WireResponse<Disk::Download>& response = result.value();
// }
//
// fidl::WireClient<Disk> disk_client_;
// fidl::Arena<> arena_;
// };
template <typename FidlMethod>
class WireResponseContext : public internal::ResponseContext {
public:
WireResponseContext()
: ::fidl::internal::ResponseContext(internal::WireOrdinal<FidlMethod>::value) {}
// Invoked when a response has been received or an error was detected for this
// call.
//
// ## If |result| respresents a success
//
// |result| borrows the decoded response. The implementation may transfer out
// handles contained in the message, but should not access the bytes in
// |result| once this method returns.
//
// ## If |result| represents an error
//
// An error occurred while processing this FIDL call:
//
// - Failed to encode the outgoing request specific to this call.
// - Failed to decode the incoming response specific to this call.
// - The peer endpoint was closed.
// - Error from the |async_dispatcher_t|.
// - Error from the underlying transport.
// - The server sent a malformed message.
// - The user explicitly initiated binding teardown.
// - The call raced with an external error in the meantime that caused binding
// teardown.
//
// |OnResult| is always invoked asynchronously whether in case of success
// or error, unless the dispatcher is shut down, in which case it will be
// called synchronously.
virtual void OnResult(::fidl::internal::WireUnownedResultType<FidlMethod>& result) = 0;
private:
::std::optional<::fidl::UnbindInfo> OnRawResult(
::fidl::IncomingMessage&& msg, internal::MessageStorageViewBase* storage_view) final {
if (unlikely(!msg.ok())) {
::fidl::internal::WireUnownedResultType<FidlMethod> result{msg.error()};
OnResult(result);
return std::nullopt;
}
::fidl::unstable::DecodedMessage<::fidl::internal::TransactionalResponse<FidlMethod>> decoded{
std::move(msg)};
::fidl::Status maybe_error = decoded;
::fidl::internal::WireUnownedResultType<FidlMethod> result(std::move(decoded), storage_view);
OnResult(result);
if (unlikely(!maybe_error.ok())) {
return ::fidl::UnbindInfo(maybe_error);
}
return std::nullopt;
}
};
namespace internal {
// |ClientBase| sends transactional messages and tracks outstanding replies.
// Different client implementations reference the |ClientBase| to make calls.
//
// It supports multi-threaded asynchronous dispatchers, error handling, and
// thread-safe teardown. Instances are always managed via |std::shared_ptr|.
class ClientBase final : public std::enable_shared_from_this<ClientBase> {
public:
// Creates a |ClientBase| by binding to a transport. Notifies
// |teardown_observer| on binding teardown.
static std::shared_ptr<ClientBase> Create(
AnyTransport&& transport, async_dispatcher_t* dispatcher,
AnyIncomingEventDispatcher&& event_dispatcher, AsyncEventHandler* error_handler,
fidl::AnyTeardownObserver&& teardown_observer, ThreadingPolicy threading_policy,
std::weak_ptr<ClientControlBlock> client_object_lifetime);
// Creates an unbound ClientBase. Only use it with |std::make_shared|.
ClientBase() = default;
~ClientBase() = default;
// Neither copyable nor movable.
ClientBase(const ClientBase& other) = delete;
ClientBase& operator=(const ClientBase& other) = delete;
ClientBase(ClientBase&& other) = delete;
ClientBase& operator=(ClientBase&& other) = delete;
// Asynchronously unbind the client from the dispatcher. |teardown_observer|
// will be notified on a dispatcher thread.
void AsyncTeardown();
// Makes a two-way synchronous call with the transport that is managed by this
// client.
//
// It invokes |sync_call| with a strong reference to the transport to prevent
// its destruction during a |transport.Call|. The |sync_call| callable must
// have a return type that could be instantiated with a |fidl::Status| to
// propagate failures.
//
// If the client has been unbound, returns a result type instantiated with
// a |fidl::Status::Unbound| error.
//
// If the client has a valid binding, returns the return value of |sync_call|.
template <typename Callable>
auto MakeSyncCallWith(Callable&& sync_call) {
using ReturnType = typename fit::callable_traits<Callable>::return_type;
std::shared_ptr<AnyTransport> transport = GetTransport();
if (!transport) {
return ReturnType(fidl::Status::Unbound());
}
// TODO(fxbug.dev/78906): We should report errors to binding teardown
// by calling |HandleSendError|. A naive approach of checking the result
// here doesn't work because the result must be a temporary.
return sync_call(std::move(transport));
}
// Stores the given asynchronous transaction response context, setting the txid field.
void PrepareAsyncTxn(ResponseContext* context);
// Forget the transaction associated with the given context. Used when zx_channel_write() fails.
void ForgetAsyncTxn(ResponseContext* context);
// Releases all outstanding |ResponseContext|s. Invoked when binding has torn
// down.
//
// |info| is the cause of the binding teardown. If |info| represents an error
// that is not specific to any single call (e.g. peer closed), all response
// contexts would be notified of that error.
void ReleaseResponseContexts(fidl::UnbindInfo info);
// Sends a two way message.
//
// In the process, registers |context| for the corresponding reply and mints
// a new transaction ID. |message| will be updated with that transaction ID.
//
// Errors are notified via |context|.
void SendTwoWay(fidl::OutgoingMessage& message, ResponseContext* context,
fidl::WriteOptions write_options = {});
// Sends a one way message.
//
// |message| will have its transaction ID set to zero.
//
// Errors are returned to the caller.
fidl::Status SendOneWay(::fidl::OutgoingMessage& message, fidl::WriteOptions write_options = {});
// For debugging.
size_t GetTransactionCount() {
std::scoped_lock lock(lock_);
return contexts_.size();
}
// Dispatches a generic incoming message.
//
// ## Handling events
//
// If the incoming message is an event, the implementation will dispatch it
// using |event_dispatcher_| which is created when binding the client.
//
// ## Message ownership
//
// If a matching response handler or event handler is found, |msg| is then
// consumed, regardless of decoding error. Otherwise, |msg| is not consumed.
//
// ## Return value
//
// If errors occur during dispatching, the function will return an
// |UnbindInfo| describing the error. Otherwise, it will return
// |std::nullopt|.
std::optional<UnbindInfo> Dispatch(fidl::IncomingMessage& msg,
internal::MessageStorageViewBase* storage_view);
// Returns a weak pointer representing the lifetime of client objects exposed
// to the user, e.g. |fidl::WireClient|.
//
// When the weak pointer is expired, it indicates that the corresponding
// client objects have destructed.
const std::weak_ptr<ClientControlBlock>& client_object_lifetime() const {
return client_object_lifetime_;
}
private:
void Bind(AnyTransport&& transport, async_dispatcher_t* dispatcher,
AnyIncomingEventDispatcher&& event_dispatcher, AsyncEventHandler* error_handler,
fidl::AnyTeardownObserver&& teardown_observer, ThreadingPolicy threading_policy,
std::weak_ptr<ClientControlBlock> client_object_lifetime);
// Handles errors in sending one-way or two-way FIDL requests. This may lead
// to binding teardown.
void HandleSendError(fidl::Status error);
// Try to asynchronously notify |context| of the |error|. If not possible
// (e.g. dispatcher shutting down), notify it synchronously as a last resort.
void TryAsyncDeliverError(::fidl::Status error, ResponseContext* context);
std::shared_ptr<AnyTransport> GetTransport() {
if (auto binding = binding_.lock()) {
return binding->GetTransport();
}
return nullptr;
}
// Allow unit tests to peek into the internals of this class.
friend class ::fidl_testing::ClientBaseChecker;
// Weak reference to the internal binding state.
std::weak_ptr<AsyncClientBinding> binding_;
std::weak_ptr<ClientControlBlock> client_object_lifetime_;
// The dispatcher that is monitoring FIDL messages.
async_dispatcher_t* dispatcher_ = nullptr;
// The event dispatcher to decode and notify FIDL events.
AnyIncomingEventDispatcher event_dispatcher_;
// State for tracking outstanding transactions.
std::mutex lock_;
// The base node of an intrusive container of ResponseContexts corresponding to outstanding
// asynchronous transactions.
fidl::internal_wavl::WAVLTree<zx_txid_t, ResponseContext*, ResponseContext::Traits> contexts_
__TA_GUARDED(lock_);
// Mirror list used to safely invoke OnError() on outstanding ResponseContexts in ~ClientBase().
list_node_t delete_list_ __TA_GUARDED(lock_) = LIST_INITIAL_VALUE(delete_list_);
// Value used to compute the next txid.
zx_txid_t txid_base_ __TA_GUARDED(lock_) = 0;
};
// |ControlBlock| controls the lifecycle of a client binding, such that
// teardown will only happen after all clones of a |Client| managing
// the same transport goes out of scope.
//
// Specifically, all clones of a |Client| will share the same |ControlBlock|
// instance, which in turn references the |ClientBase|, and is responsible
// for its teardown via RAII.
class ClientControlBlock final : public std::enable_shared_from_this<ClientControlBlock> {
public:
explicit ClientControlBlock(std::shared_ptr<ClientBase> client)
: client_impl_(std::move(client)) {}
ClientControlBlock(const ClientControlBlock&) noexcept = delete;
ClientControlBlock& operator=(const ClientControlBlock&) noexcept = delete;
ClientControlBlock(ClientControlBlock&&) noexcept = default;
ClientControlBlock& operator=(ClientControlBlock&&) noexcept = default;
// Triggers teardown, which will cause any strong references to the
// |ClientBase| to be released.
~ClientControlBlock() {
if (client_impl_) {
client_impl_->AsyncTeardown();
}
}
private:
std::shared_ptr<ClientBase> client_impl_;
};
// |ClientController| manages the lifetime of a |ClientBase| instance.
//
// |ClientBase|s are created when binding a client endpoint to a message
// dispatcher, via |Bind|. The destruction of |ClientBase|s is initiated when
// this |ClientController| class destructs, or when |Unbind| is explicitly
// invoked.
class ClientController {
public:
ClientController() = default;
~ClientController() = default;
ClientController(ClientController&& other) noexcept = default;
ClientController& operator=(ClientController&& other) noexcept = default;
ClientController(const ClientController& other) = default;
ClientController& operator=(const ClientController& other) = default;
// Creates a |ClientBase| and binds it to the |dispatcher| and |client_end|,
// starts managing its lifetime.
//
// It is an error to call |Bind| more than once on the same controller.
void Bind(AnyTransport client_end, async_dispatcher_t* dispatcher,
AnyIncomingEventDispatcher&& event_dispatcher, AsyncEventHandler* error_handler,
fidl::AnyTeardownObserver&& teardown_observer, ThreadingPolicy threading_policy);
// Begins to unbind the transport from the dispatcher. In particular, it
// triggers the asynchronous destruction of the bound |ClientBase|. May be
// called from any thread. If provided, the teardown observer is notified
// asynchronously on a dispatcher thread.
//
// |Bind| must have been called before this.
void Unbind();
bool is_valid() const { return static_cast<bool>(client_impl_); }
explicit operator bool() const { return is_valid(); }
ClientBase& get() const {
ZX_ASSERT(is_valid());
return *client_impl_;
}
private:
// Allow unit tests to peek into the internals of this class.
friend ::fidl_testing::ClientChecker;
std::shared_ptr<ClientBase> client_impl_;
std::shared_ptr<ClientControlBlock> control_;
};
} // namespace internal
} // namespace fidl
#endif // LIB_FIDL_LLCPP_INCLUDE_LIB_FIDL_LLCPP_CLIENT_BASE_H_