| // 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_ |