blob: 0c96a206e7d9aff0d9e7e3dd59ec2cef30a9a4cd [file] [log] [blame]
// 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/async_patterns/cpp/pending_call.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.
/// //
/// // The dispatcher will not be attached to the current thread, but will
/// // be attached to the loop thread. This way, the |Background| object
/// // can obtain a dispatcher from its constructor using
/// // |async_get_default_dispatcher|.
/// explicit Owner() :
/// background_loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
/// 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 you'd like to pass a |dispatcher| to |T| as a constructor argument,
/// see |async_patterns::PassDispatcher|.
///
/// 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, 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.
///
/// If |T2| is specified, it must be same as |T| or a subclass. Then an instance
/// of |T2| will be constructed. This can be useful for mocking: |T| may be some
/// interface, and when constructing the object, either a fake (in unit tests)
/// or a real concrete type (in production) will be specified.
///
/// If you'd like to pass a |dispatcher| to |T| as a constructor argument,
/// see |async_patterns::PassDispatcher|.
///
/// See |async_patterns::BindForSending| for detailed requirements on |args|.
template <typename T2 = T, typename... Args>
void emplace(Args&&... args) {
static_assert(std::is_base_of_v<T, T2>, "|T| must be a base class of |T2|.");
reset();
storage_.Construct<T, T2>(dispatcher_, std::forward<Args>(args)...);
}
/// Asynchronously calls |member|, a pointer to member function of |T|, using
/// the provided |args|.
///
/// |AsyncCall| returns a |PendingCall| object that lets you asynchronously
/// monitor the result. You may either:
///
/// - Make a fire-and-forget call, by discarding the returned object, or
/// - Get a promise carrying the return value of the function by calling
/// `promise()` on the object, yielding a |fpromise::promise<ReturnType>|, or
/// - Call `Then()` on the object and pass a |Callback<void(ReturnType)>|.
///
/// See |PendingCall| for details.
///
/// In particular, if |member| returns void, you could attach promises/callbacks
/// that take void to asynchronously get notified when |member| has finished execution.
///
/// Example:
///
/// 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:
/// Calls an arbitrary |callable| asynchronously on the |dispatcher_|.
template <template <typename, typename, typename> typename Builder = PendingCall,
typename Callable, typename... Args>
auto UnsafeAsyncCallImpl(Callable&& callable, Args&&... args) {
using Result = std::invoke_result_t<Callable, T*, Args...>;
return storage_.AsyncCall<Builder, Result, 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_;
};
/// 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_