blob: e2b3dceb7e234653295aacc06581ff3580b838c3 [file] [log] [blame] [edit]
// Copyright 2023 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_ASYNC_PATTERNS_CPP_DISPATCHER_BOUND_H_
#define LIB_ASYNC_PATTERNS_CPP_DISPATCHER_BOUND_H_
#include <lib/async/dispatcher.h>
#include <lib/async_patterns/cpp/internal/dispatcher_bound_storage.h>
#include <lib/fit/function.h>
#include <lib/fit/function_traits.h>
#include <lib/stdcompat/functional.h>
#include <zircon/assert.h>
#include <cstdlib>
#include <utility>
namespace async_patterns {
// |DispatcherBound<T>| does not allow sending raw pointers to the wrapped
// object. However, it is common for an async object to obtain its associated
// |async_dispatcher_t*|. Often that can be accomplished with
// |async_get_default_dispatcher|, but in case where that's not feasible, one
// may specify the |async_patterns::PassDispatcher| constant in place of an
// |async_dispatcher_t*|, at the argument location where the wrapped async
// object desires a dispatcher, and |DispatcherBound| will automatically supply
// the correct dispatcher that the async object is associated with.
constexpr auto PassDispatcher = internal::PassDispatcherT{};
// |DispatcherBound<T>| enables an owner object living on some arbitrary thread,
// to construct, call methods on, and destroy an object of type |T| that must be
// used from a particular [synchronized async dispatcher][synchronized-dispatcher].
//
// Thread-unsafe asynchronous types should be used from synchronized dispatchers
// (e.g. a single-threaded async loop). Because the dispatcher may be running
// code to manipulate such objects, one should not use the same objects from
// other unrelated threads and cause data races.
//
// However, it may not always be possible for an entire tree of objects to
// live on the same async dispatcher, due to design or legacy constraints.
// |DispatcherBound| helps one divide classes along dispatcher boundaries.
//
// An example:
//
// // |Background| always lives on a background dispatcher, provided
// // at construction time.
// class Background {
// public:
// explicit Background() {
// // Perform some asynchronous work. The work is canceled if
// // |Background| is destroyed.
// task_.Post(async_get_default_dispatcher());
// }
//
// private:
// void DoSomething();
//
// // |task_| manages an async task that borrows the containing
// // |Background| object and is not thread safe. It must be destroyed
// // on the dispatcher to ensure that task cancellation is not racy.
// async::TaskClosureMethod<Background, &Background::DoSomething> task_{this};
// };
//
// class Owner {
// public:
// // Asynchronously constructs a |Background| object on its dispatcher.
// // Code in |Owner| and code in |Background| may run concurrently.
// explicit Owner() :
// background_loop_(&kAsyncLoopConfigAttachToCurrentThread),
// background_{background_loop_.dispatcher(), std::in_place} {}
//
// private:
// // The async loop which will manage |Background| objects.
// // This will always be paired with a |DispatcherBound| object.
// async::Loop background_loop_;
//
// // The |DispatcherBound| which manages |Background| on its loop.
// // During destruction, |background_| will schedule the asynchronous
// // destruction of the wrapped |Background| object on the dispatcher.
// async_patterns::DispatcherBound<Background> background_;
// };
//
// |DispatcherBound| itself is thread-compatible.
//
// ## Safety of sending arguments
//
// When constructing |T| and calling member functions of |T|, it is possible to
// pass additional arguments if the constructor or member function requires it.
// The argument will be forwarded from the caller's thread into a heap data
// structure, and later moved into the thread which would run the dispatcher
// task asynchronously. Each argument must be safe to send to a different
// thread. See |async_patterns::BindForSending| for the detailed requirements.
//
// [synchronized-dispatcher]:
// https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/thread-safe-async#synchronized-dispatcher
template <typename T>
class DispatcherBound {
public:
// Asynchronously constructs |T| on a task posted to |dispatcher|.
//
// Arguments after |std::in_place| are sent to the constructor of |T|.
// See |async_patterns::BindForSending| for detailed requirements on |args|.
//
// If the dispatcher is shutdown, |T| will be synchronously constructed.
template <typename... Args>
explicit DispatcherBound(async_dispatcher_t* dispatcher, std::in_place_t, Args&&... args)
: dispatcher_(dispatcher) {
storage_.Construct<T>(dispatcher, std::forward<Args>(args)...);
}
// Constructs a |DispatcherBound| that does not hold an instance of |T|.
//
// One may later construct |T| using |emplace| on the |dispatcher|.
explicit DispatcherBound(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
// Asynchronously constructs |T| on a task posted to the dispatcher.
//
// If this object already holds an instance of |T|, that older instance will
// be asynchronously destroyed on the dispatcher.
//
// See |async_patterns::BindForSending| for detailed requirements on |args|.
template <typename... Args>
void emplace(Args&&... args) {
reset();
storage_.Construct<T>(dispatcher_, std::forward<Args>(args)...);
}
// Asynchronously calls |member|, a pointer to member function of |T|, using
// the provided |args|.
//
// If |member| returns void, then |AsyncCall| returns void. The behavior is
// fire-and-forget.
//
// If |member| returns some |R| type, then |AsyncCall| returns a builder
// object where one must attach an |async_patterns::Callback<R'>| by calling
// |Then|. |R'| could be identical to |R| or some other compatible type such
// as |const R&|. Typically, the owner will declare a |Receiver| to mint those
// callbacks:
//
// class Owner {
// public:
// Owner(async_dispatcher_t* owner_dispatcher) : receiver_{this, owner_dispatcher} {
// background_.emplace();
// // Tell |background_| to |DoSomething|, then send back the return
// // value to |Owner| using |receiver_|.
// background_
// .AsyncCall(&Background::DoSomething)
// .Then(receiver_.Once(&Owner::DoneSomething));
// }
//
// void DoneSomething(Result result) {
// // |Background::DoSomething| has completed with |result|...
// }
//
// private:
// async::Loop background_loop_;
// async_patterns::DispatcherBound<Background> background_{background_loop_.dispatcher()};
// async_patterns::Receiver<Owner> receiver_;
// };
//
// See |async_patterns::BindForSending| for detailed requirements on |args|.
//
// If |Background::DoSomething| is an overloaded member function, you may
// disambiguate it by spelling out its signature:
//
// background_.AsyncCall<void(Result)>(&Background::DoSomething);
//
// The task will be synchronously called if the dispatcher is shutdown.
template <typename Member, typename... Args>
auto AsyncCall(Member T::*member, Args&&... args) {
ZX_ASSERT(has_value());
constexpr bool kIsInvocable = std::is_invocable_v<Member, Args...>;
static_assert(kIsInvocable,
"|Member| must be callable with the provided |Args|. "
"Check that you specified each argument correctly to the |member| function.");
if constexpr (kIsInvocable) {
CheckArgs(typename fit::callable_traits<Member>::args{});
return UnsafeAsyncCallImpl(member, std::forward<Args>(args)...);
}
}
// Typically, asynchronous classes would contain internal self-pointers that
// make moving dangerous, so we disable moves here for now.
DispatcherBound(DispatcherBound&&) noexcept = delete;
DispatcherBound& operator=(DispatcherBound&&) noexcept = delete;
DispatcherBound(const DispatcherBound&) noexcept = delete;
DispatcherBound& operator=(const DispatcherBound&) noexcept = delete;
// If |has_value|, asynchronously destroys the managed |T| on a task
// posted to the dispatcher.
//
// If the dispatcher is shutdown, |T| will be synchronously destroyed.
~DispatcherBound() { reset(); }
// If |has_value|, asynchronously destroys the managed |T| on a task
// posted to the dispatcher.
//
// If the dispatcher is shutdown, |T| will be synchronously destroyed.
void reset() {
if (!has_value()) {
return;
}
storage_.Destruct(dispatcher_);
}
// Returns if this object holds an instance of |T|.
bool has_value() const { return storage_.has_value(); }
protected:
template <typename Task>
class [[nodiscard]] AsyncCallBuilder;
// Calls an arbitrary |callable| asynchronously on the |dispatcher_|.
template <template <typename> typename Builder = AsyncCallBuilder, typename Callable,
typename... Args>
auto UnsafeAsyncCallImpl(Callable&& callable, Args&&... args) {
using Result = std::invoke_result_t<Callable, T*, Args...>;
if constexpr (std::is_void_v<Result>) {
return storage_.AsyncCall<T>(dispatcher_, std::forward<Callable>(callable),
std::forward<Args>(args)...);
} else {
return storage_.AsyncCallWithReply<Builder, T>(dispatcher_, std::forward<Callable>(callable),
std::forward<Args>(args)...);
}
}
template <typename... Args>
constexpr void CheckArgs(fit::parameter_pack<Args...>) {
internal::CheckArguments<Args...>::Check();
}
private:
async_dispatcher_t* dispatcher_;
internal::DispatcherBoundStorage storage_;
};
// The return type of |DispatcherBound<T>::AsyncCall| when the method has a
// return value. Supports chaining a callback via |Then|.
template <typename T>
template <typename Task>
class [[nodiscard]] DispatcherBound<T>::AsyncCallBuilder {
protected:
using TaskResult = std::invoke_result_t<Task>;
public:
// Arranges the |on_result| callback to be called with the result of the async
// call. See |DispatcherBound<T>::AsyncCall| for documentation.
template <typename R>
void Then(async_patterns::Callback<R> on_result) && {
constexpr bool kReceiverMatchesReturnValue =
std::is_invocable_v<decltype(on_result), TaskResult>;
static_assert(kReceiverMatchesReturnValue,
"The |async_patterns::Callback<R>| must accept the return value "
"of the |Member| being called.");
if constexpr (kReceiverMatchesReturnValue) {
Call(std::move(on_result));
}
}
AsyncCallBuilder(internal::DispatcherBoundStorage* storage, async_dispatcher_t* dispatcher,
Task task)
: storage_(storage), dispatcher_(dispatcher), task_(std::move(task)) {}
~AsyncCallBuilder() { ZX_DEBUG_ASSERT(!storage_); }
protected:
template <typename Continuation>
void Call(Continuation&& continuation) {
ZX_DEBUG_ASSERT(storage_);
storage_->CallInternal(dispatcher_, [task = std::move(task_),
continuation = std::forward<Continuation>(
continuation)]() mutable { continuation(task()); });
storage_ = nullptr;
}
private:
AsyncCallBuilder(const AsyncCallBuilder&) = delete;
AsyncCallBuilder& operator=(const AsyncCallBuilder&) = delete;
AsyncCallBuilder(AsyncCallBuilder&&) = delete;
AsyncCallBuilder& operator=(AsyncCallBuilder&&) = delete;
internal::DispatcherBoundStorage* storage_;
async_dispatcher_t* dispatcher_;
Task task_;
};
// Constructs a |DispatcherBound<T>| that holds an instance of |T| by sending
// the |args| to the constructor of |T| run from a |dispatcher| task.
//
// See |DispatcherBound| constructor for details.
template <typename T, typename... Args>
DispatcherBound<T> MakeDispatcherBound(async_dispatcher_t* dispatcher, Args&&... args) {
return DispatcherBound<T>{dispatcher, std::in_place, std::forward<Args>(args)...};
}
} // namespace async_patterns
#endif // LIB_ASYNC_PATTERNS_CPP_DISPATCHER_BOUND_H_