blob: 0e91fc26ddf302233b13da0a21bdd4946d5b8d01 [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_ASYNC_CPP_ASYNC_BIND_H_
#define LIB_FIDL_ASYNC_CPP_ASYNC_BIND_H_
#include <lib/fidl/llcpp/async_binding.h>
namespace fidl {
// Forward declarations.
class BindingRef;
template <typename Interface>
fit::result<BindingRef, zx_status_t> AsyncBind(async_dispatcher_t* dispatcher, zx::channel channel,
Interface* impl);
template <typename Interface>
fit::result<BindingRef, zx_status_t> AsyncBind(async_dispatcher_t* dispatcher, zx::channel channel,
Interface* impl, OnUnboundFn<Interface> on_unbound);
template <typename Interface>
fit::result<BindingRef, zx_status_t> AsyncBind(async_dispatcher_t* dispatcher, zx::channel channel,
std::unique_ptr<Interface> impl);
namespace internal {
fit::result<BindingRef, zx_status_t> AsyncTypeErasedBindServer(
async_dispatcher_t* dispatcher, zx::channel channel, void* impl,
TypeErasedServerDispatchFn dispatch_fn, TypeErasedOnUnboundFn on_unbound);
} // namespace internal
// This class abstracts a reference to a binding as described in |AsyncBind| functions below.
class BindingRef {
public:
// Same as AsyncBind(async_dispatcher_t*, zx::channel, Interface*) below.
template <typename Interface>
static fit::result<BindingRef, zx_status_t> CreateAsyncBinding(async_dispatcher_t* dispatcher,
zx::channel channel,
Interface* impl) {
return AsyncBind(dispatcher, std::move(channel), impl);
}
// Same as AsyncBind(async_dispatcher_t*, zx::channel, Interface*, OnUnboundFn<Interface>)
// below.
template <typename Interface>
static fit::result<BindingRef, zx_status_t> CreateAsyncBinding(
async_dispatcher_t* dispatcher, zx::channel channel, Interface* impl,
OnUnboundFn<Interface> on_unbound) {
return AsyncBind(dispatcher, std::move(channel), impl, std::move(on_unbound));
}
// Same as AsyncBind(async_dispatcher_t*, zx::channel, std::unique_ptr<Interface>) below.
template <typename Interface>
static fit::result<BindingRef, zx_status_t> CreateAsyncBinding(async_dispatcher_t* dispatcher,
zx::channel channel,
std::unique_ptr<Interface> impl) {
return AsyncBind(dispatcher, std::move(channel), std::move(impl));
}
// Move only.
BindingRef(BindingRef&&) = default;
BindingRef& operator=(BindingRef&&) = default;
BindingRef(const BindingRef&) = delete;
BindingRef& operator=(const BindingRef&) = delete;
// Triggers an asynchronous unbind operation. If specified, |on_unbound| will be invoked on a
// dispatcher thread, passing in the channel and the unbind reason.
//
// This may be called from any thread.
//
// NOTE: For a single-threaded dispatcher, if Unbind() is invoked from a dispatcher thread and
// outside the scope of the message handler for the same binding, the channel will have been fully
// unbound on return, i.e. no other threads will be able to access it. Note that the |on_unbound|
// will still be executed asynchronously if specified.
void Unbind() {
if (auto binding = binding_.lock())
binding->Unbind(std::move(binding));
}
// TODO(madhaviyengar): Re-introduce synchronous Unbind() which returns a zx::channel.
// Triggers an asynchronous unbind operation. Eventually, the epitaph will be sent over the
// channel which will be subsequently closed. If specified, |on_unbound| will be invoked giving
// the unbind reason as an argument.
//
// This may be called from any thread.
void Close(zx_status_t epitaph) {
if (auto binding = binding_.lock())
binding->Close(std::move(binding), epitaph);
}
private:
friend fit::result<BindingRef, zx_status_t> internal::AsyncTypeErasedBindServer(
async_dispatcher_t* dispatcher, zx::channel channel, void* impl,
internal::TypeErasedServerDispatchFn dispatch_fn, internal::TypeErasedOnUnboundFn on_unbound);
explicit BindingRef(std::weak_ptr<internal::AsyncBinding> internal_binding)
: binding_(std::move(internal_binding)) {}
std::weak_ptr<internal::AsyncBinding> binding_;
};
// Binds an implementation of a low-level C++ server interface to |channel| using a potentially
// multi-threaded |dispatcher|. This implementation allows for multiple in-flight synchronously or
// asynchronously handled transactions.
//
// This function adds an asynchronous wait to the given |dispatcher| for new messages to arrive on
// |channel|. When a message arrives, the dispatch function corresponding to the interface is called
// on one of the |dispatcher| threads.
//
// Typically, the dispatch function is generated by the low-level C++ backend for FIDL interfaces.
// These dispatch functions decode the |fidl_msg_t| and call into the implementation of the FIDL
// interface, via its C++ vtable.
//
// Creation:
// - Upon success |AsyncBind| creates a binding that owns |channel|. In this case, the binding is
// initially kept alive even if the returned fit::result with a |BindingRef| is ignored.
// - Upon any error creating the binding, |AsyncBind| returns a fit::error and |channel| is closed.
//
// Destruction:
// - If the returned |BindingRef| is ignored or dropped some time during the server operation,
// then if some error occurs (see below) the binding will be automatically destroyed.
// - If the returned |BindingRef| is kept but an error occurs (see below), the binding will be
// destroyed, though calls may still be made on the |BindingRef|.
// - On an error, |channel| is unbound from the dispatcher, i.e. no dispatcher threads will interact
// with it. Calls on inflight |Transaction|s will have no effect. If |on_unbound| is not
// specified, the |channel| is closed. If specified, |on_unbound| is then executed on a
// |dispatcher| thread allowing the user to process the error. |on_unbound| includes the server
// end of the channel as a parameter, if ignored the channel will be closed at the end of
// |on_unbound|'s scope.
//
// Unbind:
// - The |BindingRef| can be used to explicitly |Unbind| the binding and retrieve the |channel|
// endpoint.
// - |Unbind| is non-blocking with respect to user code paths, i.e. if it blocks, it does so on
// deterministic internal code paths. As such, the user may safely synchronize around an |Unbind|
// call.
// - In order to reclaim the |channel|, the user must specify an |on_unbound| hook. This will be
// invoked after the |channel| has been unbound from the |dispatcher|. The |channel| will be given
// as an argument to the hook, and if unused it will be closed at the end of the hook scope.
//
// Close:
// - |Close| is similar to |Unbind| except that it causes an epitaph message to be sent on the
// |channel|.
// - If specified, the |on_unbound| hook will execute after the epitaph has been sent and like in
// |Unbind| the |channel| will be given as an argument to the hook and if unused it will be closed
// at the end of the hook scope.
//
// Error conditions:
// - When an error occurs in the server implementation as part of handling a message, it may call
// |Close| on the completer to indicate the error condition.
// - If the client end of the channel gets closed (PEER_CLOSED).
// - If an error occurs in the binding itself, e.g. a channel write fails.
//
// Ordering:
// - By default, the message dispatch function for a binding will only ever be invoked by a single
// |dispatcher| thread at a time.
// - To enable more concurrency, the user may invoke |ResumeDispatch| on the
// |fidl::Completer<T>::Sync| from the dispatch function. This will resume the async wait on the
// |dispatcher| before the dispatch function returns, allowing other |dispatcher| threads to
// handle messages for the same binding.
// NOTE: If a particular user does not care about ordering, they may invoke |ResumeDispatch|
// immediately in the message handler. However, this functionality could instead be provided as a
// default configuration. If you have such a usecase, please contact madhaviyengar@ or yifei@.
template <typename Interface>
fit::result<BindingRef, zx_status_t> AsyncBind(async_dispatcher_t* dispatcher, zx::channel channel,
Interface* impl) {
return internal::AsyncTypeErasedBindServer(dispatcher, std::move(channel), impl,
&Interface::_Outer::TypeErasedDispatch, nullptr);
}
// As above, but will invoke |on_unbound| on |impl| when the channel is being unbound, either due to
// error or an explicit |Close| or |Unbind|.
template <typename Interface>
fit::result<BindingRef, zx_status_t> AsyncBind(async_dispatcher_t* dispatcher, zx::channel channel,
Interface* impl, OnUnboundFn<Interface> on_unbound) {
return internal::AsyncTypeErasedBindServer(
dispatcher, std::move(channel), impl, &Interface::_Outer::TypeErasedDispatch,
[fn = std::move(on_unbound)](void* impl, UnboundReason reason, zx_status_t status,
zx::channel channel) mutable {
fn(static_cast<Interface*>(impl), reason, status, std::move(channel));
});
}
// As above, but will destroy |impl| whenever the binding is destroyed.
template <typename Interface>
fit::result<BindingRef, zx_status_t> AsyncBind(async_dispatcher_t* dispatcher, zx::channel channel,
std::unique_ptr<Interface> impl) {
Interface* impl_raw = impl.get();
return internal::AsyncTypeErasedBindServer(
dispatcher, std::move(channel), impl_raw, &Interface::_Outer::TypeErasedDispatch,
[intf = std::move(impl)](void*, UnboundReason, zx_status_t, zx::channel) {});
}
namespace internal {
inline fit::result<BindingRef, zx_status_t> AsyncTypeErasedBindServer(
async_dispatcher_t* dispatcher, zx::channel channel, void* impl,
TypeErasedServerDispatchFn dispatch_fn, TypeErasedOnUnboundFn on_unbound_fn) {
auto internal_binding = AsyncBinding::CreateServerBinding(
dispatcher, std::move(channel), impl, dispatch_fn, std::move(on_unbound_fn));
auto status = internal_binding->BeginWait();
if (status == ZX_OK) {
return fit::ok(fidl::BindingRef(std::move(internal_binding)));
} else {
return fit::error(status);
}
}
} // namespace internal
} // namespace fidl
#endif // LIB_FIDL_ASYNC_CPP_ASYNC_BIND_H_