blob: 68f92e774e335cb5f724a3cbdb126d976eb353ca [file] [log] [blame]
// Copyright 2019 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_ASYNC_BINDING_H_
#define LIB_FIDL_LLCPP_ASYNC_BINDING_H_
#include <lib/async/dispatcher.h>
#include <lib/async/task.h>
#include <lib/async/wait.h>
#include <lib/fidl/epitaph.h>
#include <lib/fidl/llcpp/extract_resource_on_destruction.h>
#include <lib/fidl/llcpp/internal/client_details.h>
#include <lib/fidl/llcpp/internal/debug_thread_checker.h>
#include <lib/fidl/llcpp/internal/endpoints.h>
#include <lib/fidl/llcpp/message.h>
#include <lib/fidl/llcpp/status.h>
#include <lib/fidl/llcpp/transaction.h>
#include <lib/fidl/llcpp/wire_messaging_declarations.h>
#include <lib/fit/function.h>
#include <lib/sync/completion.h>
#include <lib/zx/channel.h>
#include <zircon/fidl.h>
#include <mutex>
#include <optional>
#include <variant>
namespace fidl {
// TODO(fxbug.dev/85474): A formatter bug causes this enum to be formatted with
// 4 byte indent otherwise.
// clang-format off
// The return value of various TryDispatch and
// |IncomingMessageDispatcher::dispatch_message| functions, which call into the
// appropriate server message handlers based on the method ordinal.
enum class __attribute__((enum_extensibility(closed))) [[nodiscard]] DispatchResult {
// The FIDL method ordinal was not recognized by the dispatch function.
kNotFound = false,
// The FIDL method ordinal matched one of the handlers.
// Note that this does not necessarily mean the message was handled successfully.
// For example, the message could fail to decode.
kFound = true,
};
// clang-format on
namespace internal {
struct DispatchError {
UnbindInfo info;
ErrorOrigin origin;
// Whether the bindings should disregard any unread messages in the transport
// and directly teardown upon encountering this error.
bool RequiresImmediateTeardown();
};
// |AsyncBinding| objects implement the common logic for registering waits
// on channels, and teardown. |AsyncBinding| itself composes |async_wait_t|
// which borrows the channel to wait for messages. The actual responsibilities
// of managing channel ownership falls on the various subclasses, which must
// ensure the channel is not destroyed while there are outstanding waits.
//
// |AsyncBinding| objects are always managed by a |std::shared_ptr|. Messaging
// APIs typically promote a corresponding |std::weak_ptr| briefly when they need
// to write to the transport, and gracefully report an *unbound* error if the
// binding has been destroyed.
class AsyncBinding : public std::enable_shared_from_this<AsyncBinding> {
public:
~AsyncBinding() __TA_EXCLUDES(lock_) = default;
void BeginFirstWait() __TA_EXCLUDES(lock_);
// Checks for the need to teardown and registers the next wait in one critical
// section:
//
// - If we are already in |Lifecycle::MustTeardown|, early return an error.
// - Otherwise, adds the next wait to the dispatcher, recording any error in
// |lifecycle_|.
//
// When used from the message handler, the message handler should immediately
// perform teardown when this method returns an error.
zx_status_t CheckForTeardownAndBeginNextWait() __TA_EXCLUDES(lock_);
// |StartTeardownWithInfo| attempts to post exactly one task to drive the
// teardown process. This enum reflects the result of posting the task.
enum class TeardownTaskPostingResult {
kOk,
// The binding is already tearing down, so we should not post another.
kRacedWithInProgressTeardown,
// Failed to post the task to the dispatcher. This is usually due to
// the dispatcher already shutting down.
//
// If the user shuts down the dispatcher when the binding is already
// established and monitoring incoming messages, then whichever thread
// that was monitoring incoming messages would drive the teardown
// process.
//
// If the user calls |BindServer| on a shut-down dispatcher, there is
// no available thread to drive the teardown process and report errors.
// We consider it a programming error, and panic right away. Note that
// this is inherently racy i.e. shutting down dispatchers while trying
// to also bind new channels to the same dispatcher, so we may want to
// reevaluate whether shutting down the dispatcher is an error whenever
// there is any active binding (fxbug.dev/NNNNN).
kDispatcherError,
};
// Notifies the binding of an |error| while servicing messages.
// This may lead to binding teardown.
void HandleError(std::shared_ptr<AsyncBinding>&& calling_ref, DispatchError error)
__TA_EXCLUDES(lock_);
void StartTeardown(std::shared_ptr<AsyncBinding>&& calling_ref) __TA_EXCLUDES(thread_checker_)
__TA_EXCLUDES(lock_) {
StartTeardownWithInfo(std::move(calling_ref), ::fidl::UnbindInfo::Unbind());
}
// Returns true if the binding will teardown at the next iteration of the
// event loop, or has already torn down and pending deletion.
bool IsDestructionImminent() const __TA_EXCLUDES(lock_);
protected:
AsyncBinding(async_dispatcher_t* dispatcher, internal::AnyUnownedTransport transport,
ThreadingPolicy threading_policy);
// |InitKeepAlive| must be called after a concrete subclass is constructed
// in a shared pointer, to set up the initial circular keep-alive reference.
void InitKeepAlive() {
ZX_DEBUG_ASSERT(!keep_alive_.get());
keep_alive_ = shared_from_this();
}
// Common message handling entrypoint shared by both client and server bindings.
void MessageHandler(fidl::IncomingMessage& msg,
internal::IncomingTransportContext transport_context)
__TA_EXCLUDES(thread_checker_) __TA_EXCLUDES(lock_);
void WaitFailureHandler(UnbindInfo info) __TA_EXCLUDES(thread_checker_) __TA_EXCLUDES(lock_);
// Dispatches a generic incoming message.
//
// ## Message ownership
//
// The client async binding should invoke the matching response handler or
// event handler, if one is found. |msg| is then consumed, regardless of
// decoding error.
//
// The server async binding should invoke the matching request handler if
// one is found. |msg| is then consumed, regardless of decoding error.
//
// In other cases (e.g. unknown message, epitaph), |msg| is not consumed.
//
// The caller should simply ignore the |fidl::IncomingMessage| object once
// it is passed to this function, letting RAII clean up handles as needed.
//
// ## Return value
//
// If errors occur during dispatching, the function will return an
// |DispatchError| describing the error. Otherwise, it will return
// |std::nullopt|.
//
// If `*next_wait_begun_early` is set, the calling code no longer has ownership of
// this |AsyncBinding| object and so must not access its state.
virtual std::optional<DispatchError> Dispatch(
fidl::IncomingMessage& msg, bool* next_wait_begun_early,
internal::IncomingTransportContext transport_context) __TA_REQUIRES(thread_checker_) = 0;
async_dispatcher_t* dispatcher() const { return dispatcher_; }
// Initiates teardown with the provided |info| as reason.
// This does not have to happen in the context of a dispatcher thread.
TeardownTaskPostingResult StartTeardownWithInfo(std::shared_ptr<AsyncBinding>&& calling_ref,
UnbindInfo info) __TA_EXCLUDES(thread_checker_)
__TA_EXCLUDES(lock_);
private:
// Synchronously perform teardown in the context of a dispatcher thread with
// exclusive access of the internal binding reference.
//
// If |lifecycle_| is not yet in |MustTeardown|, |info| must be present to
// specify the teardown reason.
//
// Always releases |thread_checker_| because the binding (the current object)
// is destroyed during this function.
void PerformTeardown(std::optional<UnbindInfo> info) __TA_REQUIRES(thread_checker_)
__TA_RELEASE(thread_checker_) __TA_EXCLUDES(lock_);
// Override |FinishTeardown| to perform cleanup work at the final stage of
// binding teardown.
//
// An important guarantee of this function is up-call exclusion: there will be
// no parallel up-calls to user objects at the point of invocation.
//
// Proof that |AsyncBinding| upholds this property:
//
// The runtime arranges |AsyncBinding::MessageHandler| to be run when an
// incoming message arrives, where it would make up-calls to handle the
// message. There will be at most one pending handler registration at any
// time. |StartTeardownWithInfo| attempts to de-register this interest for a
// new message (`async_cancel_wait`). There are two possible outcomes:
//
// - If the cancellation succeeds, it follows that there no up-calls since the
// |MessageHandler| will no longer run.
//
// - If the cancellation fails, the |MessageHandler| may already be running,
// or has entered an imminent state where it is too late to cancel. In
// either case, |MessageHandler| will detect that teardown is in order when
// it is re-registering the wait, and will run the teardown task right away.
// There is no parallel up-calls because the |MessageHandler| itself is
// synchronously preoccupied with teardown.
//
// |FinishTeardown| will be invoked on a dispatcher thread if the dispatcher is
// running, and will be invoked on the thread that is calling shutdown if the
// dispatcher is shutting down.
//
// Always releases |thread_checker_| because the binding (the current object)
// is destroyed during this function.
virtual void FinishTeardown(std::shared_ptr<AsyncBinding>&& calling_ref, UnbindInfo info)
__TA_RELEASE(thread_checker_) __TA_REQUIRES(thread_checker_) = 0;
async_dispatcher_t* dispatcher_ = nullptr;
// The bound transport.
AnyUnownedTransport transport_;
// Storage for a |TransportWaiter|, which waits for messages and calls back
// into |AsyncBinding| when they are received, or if the channel is closed.
AnyTransportWaiter any_transport_waiter_;
// A circular reference that represents the dispatcher ownership of the
// |AsyncBinding|. When |lifecycle_| is |Lifecycle::Bound|, all mutations of
// |keep_alive_| must happen on a dispatcher thread.
std::shared_ptr<AsyncBinding> keep_alive_ = {};
// |thread_checker_| checks that required operations run on the appropriate
// thread as indicated by the threading policy.
//
// |thread_checker_| is no-op in release builds, and may be completely
// optimized out.
[[no_unique_address]] DebugOnlyThreadChecker thread_checker_;
// A lock protecting the binding |lifecycle|.
//
// It is mutable to support const accessors of the lifecycle.
mutable std::mutex lock_;
// |Lifecycle| is a state machine that captures the lifecycle of a binding.
//
// A binding transitions through the states in their listed order, and may
// be allowed to skip forward certain states as noted below.
class Lifecycle {
public:
enum LifecycleState {
// The binding is created, but message dispatch has not started.
//
// A binding always starts in this state.
kCreated = 0,
// The first |async_wait_t| has been registered with the dispatcher
// i.e. the first wait has begun.
kBound,
// A fatal error happened or the user explicitly requested teardown.
// The binding must stop message processing at its earliest convenience.
kMustTeardown,
// The last stage of the binding before its destruction. The only
// allowed operation is to call |FinishTeardown| to notify the user.
kTorndown
};
// Transitions to the |kBound| state.
//
// One may only transition from |kCreated| to this state.
void TransitionToBound();
// Indicates that waits should no longer be added to the dispatcher.
//
// |info| contains the reason for teardown.
//
// One may transition to this state from |kCreated|, |kBound|, or
// |kMustTeardown|. When transitioning from |kMustTeardown| to itself, the
// previous |info| value is preserved. In other words, the earliest error is
// propagated to the user.
void TransitionToMustTeardown(fidl::UnbindInfo info);
// Transitions to the |kTorndown| state.
//
// One may only transition to this state from |kMustTeardown|.
//
// Returns the stored reason for teardown.
fidl::UnbindInfo TransitionToTorndown();
// Returns whether the binding _ever_ entered the |kBound| state.
bool DidBecomeBound() const { return did_enter_bound_; }
// Checks if the binding is in the specified |state|.
bool Is(LifecycleState state) const { return state_ == state; }
// Returns the current state as an enumeration.
LifecycleState state() const { return state_; }
private:
LifecycleState state_ = kCreated;
bool did_enter_bound_ = false;
// The reason for teardown. Only valid when |state_| is |kMustTeardown|.
fidl::UnbindInfo info_ = {};
} lifecycle_ __TA_GUARDED(lock_) = {};
};
//
// Server binding specifics
//
class IncomingMessageDispatcher;
// A generic callback type handling the completion of server unbinding.
// Note that the first parameter is a pointer to |IncomingMessageDispatcher|,
// which is the common base interface implemented by all server protocol
// message handling interfaces.
//
// The bindings runtime need to convert this pointer to the specific server
// implementation type before invoking the public unbinding completion callback
// that is |fidl::OnUnboundFn<ServerImpl>|.
using AnyOnUnboundFn =
fit::inline_callback<void(IncomingMessageDispatcher*, UnbindInfo, fidl::internal::AnyTransport),
48>;
// The async server binding. It directly owns the transport.
class AsyncServerBinding : public AsyncBinding {
private:
struct ConstructionKey {};
public:
static std::shared_ptr<AsyncServerBinding> Create(async_dispatcher_t* dispatcher,
fidl::internal::AnyTransport&& server_end,
IncomingMessageDispatcher* interface,
AnyOnUnboundFn&& on_unbound_fn);
virtual ~AsyncServerBinding() = default;
fidl::internal::AnyUnownedTransport transport() const { return server_end_.get().borrow(); }
std::shared_ptr<AsyncServerBinding> shared_from_this() {
return std::static_pointer_cast<AsyncServerBinding>(AsyncBinding::shared_from_this());
}
std::optional<DispatchError> Dispatch(
fidl::IncomingMessage& msg, bool* next_wait_begun_early,
internal::IncomingTransportContext transport_context) override;
// Start closing the server connection with an |epitaph|.
void Close(std::shared_ptr<AsyncBinding>&& calling_ref, zx_status_t epitaph) {
StartTeardownWithInfo(std::move(calling_ref), fidl::UnbindInfo::Close(epitaph));
}
// Do not construct this object outside of this class. This constructor takes
// a private type following the pass-key idiom to support |make_shared|.
AsyncServerBinding(async_dispatcher_t* dispatcher, fidl::internal::AnyTransport&& server_end,
IncomingMessageDispatcher* interface, AnyOnUnboundFn&& on_unbound_fn,
ConstructionKey key)
: AsyncBinding(dispatcher, server_end.borrow(),
ThreadingPolicy::kCreateAndTeardownFromAnyThread),
interface_(interface),
server_end_(std::move(server_end)),
on_unbound_fn_(std::move(on_unbound_fn)) {}
IncomingMessageDispatcher* interface() const { return interface_; }
private:
// Waits for all references to the binding to be released.
// Sends epitaph and invokes |on_unbound_fn_| as required.
void FinishTeardown(std::shared_ptr<AsyncBinding>&& calling_ref, UnbindInfo info) override;
// The server interface that handles FIDL method calls.
IncomingMessageDispatcher* interface_ = nullptr;
// The transport is owned by AsyncServerBinding.
ExtractedOnDestruction<fidl::internal::AnyTransport> server_end_;
// The user callback to invoke after teardown has completed.
AnyOnUnboundFn on_unbound_fn_ = {};
};
//
// Client binding specifics
//
class ClientBase;
// The async client binding. The client supports both synchronous and
// asynchronous calls. Because the transport lifetime must outlast the duration
// of any synchronous calls, and that synchronous calls do not yet support
// cancellation, the client binding does not own the transport directly.
// Rather, it co-owns the transport between itself and any in-flight sync
// calls, using shared pointers.
class AsyncClientBinding final : public AsyncBinding {
public:
static std::shared_ptr<AsyncClientBinding> Create(
async_dispatcher_t* dispatcher, std::shared_ptr<fidl::internal::AnyTransport> transport,
std::shared_ptr<ClientBase> client, AsyncEventHandler* error_handler,
AnyTeardownObserver&& teardown_observer, ThreadingPolicy threading_policy);
virtual ~AsyncClientBinding() = default;
std::shared_ptr<fidl::internal::AnyTransport> GetTransport() const { return transport_; }
private:
AsyncClientBinding(async_dispatcher_t* dispatcher,
std::shared_ptr<fidl::internal::AnyTransport> transport,
std::shared_ptr<ClientBase> client, AsyncEventHandler* error_handler,
AnyTeardownObserver&& teardown_observer, ThreadingPolicy threading_policy);
std::optional<DispatchError> Dispatch(
fidl::IncomingMessage& msg, bool* binding_released,
internal::IncomingTransportContext transport_context) override;
void FinishTeardown(std::shared_ptr<AsyncBinding>&& calling_ref, UnbindInfo info) override;
std::shared_ptr<fidl::internal::AnyTransport> transport_;
std::shared_ptr<ClientBase> client_;
AsyncEventHandler* error_handler_;
AnyTeardownObserver teardown_observer_;
};
} // namespace internal
} // namespace fidl
#endif // LIB_FIDL_LLCPP_ASYNC_BINDING_H_