blob: 6a9275f35ad8b476dd7bcef3a0f47741c9a0334d [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_