| // 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_TESTING_CPP_DISPATCHER_BOUND_H_ |
| #define LIB_ASYNC_PATTERNS_TESTING_CPP_DISPATCHER_BOUND_H_ |
| |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async_patterns/cpp/dispatcher_bound.h> |
| #include <lib/async_patterns/cpp/receiver.h> |
| |
| #include <future> |
| |
| namespace async_patterns { |
| |
| // |TestDispatcherBound| adds the ability to issue synchronous calls on top of |
| // |DispatcherBound|. Synchronous test code may use this class instead of |
| // manually running an event loop. |
| // |
| // See |async_patterns::DispatcherBound| for general information. |
| template <typename T> |
| class TestDispatcherBound final : public DispatcherBound<T> { |
| public: |
| using DispatcherBound<T>::DispatcherBound; |
| |
| // Refer to |DispatcherBound<T>::AsyncCall| for documentation. |
| // |
| // This test version of |AsyncCall| adds the ability to encapsulate the result |
| // of the call into a |std::future<R>|. This way, tests can synchronously block |
| // on that future, leading to more direct code: |
| // |
| // std::future<std::string> fut = some_dispatcher_bound |
| // .AsyncCall(&Object::GetSomeString) |
| // .ToFuture(); |
| // |
| template <typename Member, typename... Args> |
| auto AsyncCall(Member T::*member, Args&&... args) { |
| ZX_ASSERT(DispatcherBound<T>::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) { |
| DispatcherBound<T>::CheckArgs(typename fit::callable_traits<Member>::args{}); |
| return DispatcherBound<T>::template UnsafeAsyncCallImpl<TestAsyncCallBuilder>( |
| member, std::forward<Args>(args)...); |
| } |
| } |
| |
| // Schedules an asynchronous |callable| on |dispatcher| with |args| and then |
| // block the caller until the asynchronous call is processed, returning the |
| // result of the asynchronous call. |
| // |
| // |callable| should take |T*| as the first argument, followed by optionally |
| // |Args| if any. A special case of |callable| is a pointer-to-member, e.g. |
| // |&T::SomeFunction|. Example: |
| // |
| // struct Foo { |
| // int Add(int a, int b) { return a + b; } |
| // }; |
| // async_patterns::TestDispatcherBound<Foo> object{foo_loop.dispatcher()}; |
| // |
| // int result = object.SyncCall(&Object::Add, 1, 2); // result == 3 |
| // |
| // If |Object::Add| is an overloaded member function, you may disambiguate it |
| // by spelling out its signature: |
| // |
| // int result = object.SyncCall<int(int, int)>(&Object::Add, 1, 2); |
| // |
| // General callables are also supported. The provided callable can safely |
| // capture state without data races, because the current thread will be |
| // suspended while the dispatcher thread is accessing the captures. However, |
| // if you capture a pointer or reference, be careful to not let them escape to |
| // the wrapped object. When in doubt, only use the captured data inside the |
| // lambda scope: |
| // |
| // int result = object.SyncCall([&] (Foo* foo) { |
| // return foo->Add(1, 2); |
| // }); // result == 3 |
| // |
| // If |callable| returns |void|, |SyncCall| will still block until the |
| // |callable| finishes executing on the target |dispatcher|. To schedule a |
| // fire-and-forget call, use |AsyncCall|. |
| template <typename Callable, typename... Args> |
| auto SyncCall(Callable&& callable, Args&&... args) { |
| ZX_ASSERT(DispatcherBound<T>::has_value()); |
| constexpr bool kIsInvocable = std::is_invocable_v<Callable, T*, Args...>; |
| static_assert(kIsInvocable, |
| "|Callable| must be callable with |T*| and the provided |Args|. " |
| "Check that you specified each argument correctly to the |callable| function."); |
| if constexpr (kIsInvocable) { |
| using Result = std::invoke_result_t<Callable, T*, Args...>; |
| if constexpr (std::is_void_v<Result>) { |
| // Wrap a void-returning function into one that returns a unit type. |
| [[maybe_unused]] std::monostate unit = BlockOn(DispatcherBound<T>::UnsafeAsyncCallImpl( |
| [callable = std::forward<Callable>(callable)](T* ptr, auto&&... args) mutable { |
| std::invoke(callable, ptr, std::move(args)...); |
| return std::monostate{}; |
| }, |
| std::forward<Args>(args)...)); |
| return; |
| } else { |
| return BlockOn(DispatcherBound<T>::UnsafeAsyncCallImpl(std::forward<Callable>(callable), |
| std::forward<Args>(args)...)); |
| } |
| } |
| } |
| template <typename Member, typename... Args> |
| auto SyncCall(Member T::*member, Args&&... args) { |
| return SyncCall(std::mem_fn(member), std::forward<Args>(args)...); |
| } |
| |
| private: |
| template <typename Task> |
| class [[nodiscard]] TestAsyncCallBuilder; |
| |
| template <typename Task> |
| std::invoke_result_t<Task> BlockOn( |
| typename async_patterns::DispatcherBound<T>::template AsyncCallBuilder<Task>&& builder) { |
| using R = std::invoke_result_t<Task>; |
| struct Slot { |
| async::Loop loop{&kAsyncLoopConfigNeverAttachToThread}; |
| std::optional<R> result; |
| async_patterns::Receiver<Slot> receiver{this, loop.dispatcher()}; |
| } slot; |
| std::move(builder).Then(slot.receiver.Once([](Slot* slot, R result) { |
| slot->result.emplace(std::move(result)); |
| slot->loop.Quit(); |
| })); |
| ZX_ASSERT(slot.loop.Run() == ZX_ERR_CANCELED); |
| return std::move(slot.result.value()); |
| } |
| }; |
| |
| // The return type of |TestDispatcherBound<T>::AsyncCall| when the method has a |
| // return value. Supports chaining a callback via |Then|, or transforming to a |
| // |std::future|. |
| template <typename T> |
| template <typename Task> |
| class [[nodiscard]] TestDispatcherBound<T>::TestAsyncCallBuilder |
| : public DispatcherBound<T>::template AsyncCallBuilder<Task> { |
| private: |
| using Base = typename DispatcherBound<T>::template AsyncCallBuilder<Task>; |
| using TaskResult = typename Base::TaskResult; |
| |
| public: |
| // Arranges the |on_result| callback to be called with the result of the async |
| // call. See |DispatcherBound<T>::AsyncCall| for documentation. |
| using Base::Then; |
| |
| // Gets a |std::future| that will resolve with the return value of the |
| // async call. Because one needs to block on an |std::future| to obtain its |
| // value, this function is only for use in tests. Use |Then| and attach a |
| // |Callback| in production code instead. |
| std::future<TaskResult> ToFuture() && { |
| std::promise<TaskResult> promise; |
| std::future<TaskResult> fut = promise.get_future(); |
| Base::Call( |
| [promise = std::move(promise)](TaskResult r) mutable { promise.set_value(std::move(r)); }); |
| return fut; |
| } |
| |
| using Base::Base; |
| }; |
| |
| // Constructs a |TestDispatcherBound<T>| that holds an instance of |T| by sending |
| // the |args| to the constructor of |T| run from a |dispatcher| task. |
| // |
| // See |TestDispatcherBound| constructor for details. |
| template <typename T, typename... Args> |
| TestDispatcherBound<T> MakeTestDispatcherBound(async_dispatcher_t* dispatcher, Args&&... args) { |
| return TestDispatcherBound<T>{dispatcher, std::in_place, std::forward<Args>(args)...}; |
| } |
| |
| } // namespace async_patterns |
| |
| #endif // LIB_ASYNC_PATTERNS_TESTING_CPP_DISPATCHER_BOUND_H_ |