| // 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_TASK_SCOPE_H_ |
| #define LIB_ASYNC_PATTERNS_CPP_TASK_SCOPE_H_ |
| |
| #include <lib/async/dispatcher.h> |
| #include <lib/async_patterns/cpp/internal/task_queue.h> |
| #include <lib/fit/function.h> |
| #include <lib/zx/time.h> |
| #include <zircon/compiler.h> |
| |
| namespace async_patterns { |
| |
| /// |TaskScope| lets you post asynchronous tasks that are silently discarded when |
| /// the |TaskScope| object is destroyed. In contrast, tasks posted using |
| /// |async::PostTask|, |async::PostDelayedTask|, and so on will always run unless |
| /// the dispatcher is shutdown -- there is no separate mechanism to cancel them. |
| /// |
| /// The task scope is usually a member of some bigger async business logic |
| /// object. By associating the lifetime of the posted async tasks with this |
| /// object, one can conveniently capture or borrow other members from the async |
| /// tasks without the need for reference counting to achieve memory safety. |
| /// Example: |
| /// |
| /// class AsyncCounter { |
| /// public: |
| /// explicit AsyncCounter(async_dispatcher_t* dispatcher) : tasks_(dispatcher) { |
| /// // Post some tasks to asynchronously count upwards. |
| /// // |
| /// // It is okay if |AsyncCounter| is destroyed before some of these tasks |
| /// // come due. Those tasks will be discarded so they will not end up accessing |
| /// // a destroyed object. |
| /// tasks_.Post([this] { count_++ }); |
| /// tasks_.PostDelayed([this] { count_++ }, zx::sec(1)); |
| /// tasks_.PostDelayed(fit::bind_member<&AsyncCounter::CheckCount>(this), zx::sec(5)); |
| /// } |
| /// |
| /// void CheckCount() { |
| /// assert(count_ == 2); |
| /// } |
| /// |
| /// private: |
| /// TaskScope tasks_; |
| /// int count_ = 0; |
| /// }; |
| /// |
| /// |TaskScope| is thread-unsafe, and must be used and managed from a |
| /// [synchronized dispatcher][synchronized-dispatcher]. |
| /// |
| /// [synchronized-dispatcher]: |
| /// https://fuchsia.dev/fuchsia-src/development/languages/c-cpp/thread-safe-async#synchronized-dispatcher |
| class TaskScope { |
| private: |
| // |F| must take zero arguments ard return void. |
| template <typename F> |
| using require_nullary_fn = std::enable_if_t<std::is_void_v<std::invoke_result_t<F>>>; |
| |
| public: |
| /// Creates a |TaskScope| that will post all tasks to the provided |dispatcher|. |
| explicit TaskScope(async_dispatcher_t* dispatcher); |
| |
| /// Destroying the |TaskScope| synchronously destroys all pending tasks. If a |
| /// task attempts to reentrantly post more tasks into the |TaskScope| within |
| /// its destructor, those tasks will be synchronously destroyed too. |
| ~TaskScope(); |
| |
| /// Schedules to invoke |handler| with a deadline of now. |
| /// |
| /// |handler| should be a |void()| callable object. |
| /// |
| /// The handler will not run if |TaskScope| is destroyed before it comes due. |
| /// The handler will not run if the dispatcher shuts down before it comes due. |
| /// In both cases, the handler will be synchronously destroyed during task scope |
| /// destruction/dispatcher shutdown. |
| /// |
| /// This is a drop-in replacement for |async::PostTask|. |
| template <typename Closure> |
| require_nullary_fn<Closure> Post(Closure&& handler) { |
| PostImpl(internal::Task::Box(std::forward<Closure>(handler))); |
| } |
| |
| /// Schedules to invoke |handler| with a deadline expressed as a |delay| from now. |
| /// |
| /// |handler| should be a |void()| callable object. |
| /// |
| /// The handler will not run if |TaskScope| is destroyed before it comes due. |
| /// The handler will not run if the dispatcher shuts down before it comes due. |
| /// In both cases, the handler will be synchronously destroyed during task scope |
| /// destruction/dispatcher shutdown. |
| /// |
| /// This is a drop-in replacement for |async::PostDelayedTask|. |
| template <typename Closure> |
| require_nullary_fn<Closure> PostDelayed(Closure&& handler, zx::duration delay) { |
| PostImpl(DelayedTask::Box(std::forward<Closure>(handler), this), delay); |
| } |
| |
| /// Schedules to invoke |handler| with the specified |deadline|. |
| /// |
| /// |handler| should be a |void()| callable object. |
| /// |
| /// The handler will not run if |TaskScope| is destroyed before it comes due. |
| /// The handler will not run if the dispatcher shuts down before it comes due. |
| /// In both cases, the handler will be synchronously destroyed during task scope |
| /// destruction/dispatcher shutdown. |
| /// |
| /// This is a drop-in replacement for |async::PostTaskForTime|. |
| template <typename Closure> |
| require_nullary_fn<Closure> PostForTime(Closure&& handler, zx::time deadline) { |
| PostImpl(DelayedTask::Box(std::forward<Closure>(handler), this), deadline); |
| } |
| |
| private: |
| class DelayedTask : public list_node_t, public async_task_t { |
| public: |
| explicit DelayedTask(TaskScope* owner); |
| virtual ~DelayedTask() = default; |
| |
| bool Post(async_dispatcher_t* dispatcher, zx::time deadline); |
| virtual void Run() = 0; |
| |
| template <typename Callable> |
| static std::unique_ptr<DelayedTask> Box(Callable&& callable, TaskScope* owner) { |
| class TaskImpl final : public DelayedTask { |
| public: |
| explicit TaskImpl(Callable&& callable, TaskScope* owner) |
| : DelayedTask(owner), callable_(std::forward<Callable>(callable)) {} |
| void Run() final { callable_(); } |
| |
| private: |
| Callable callable_; |
| }; |
| return std::make_unique<TaskImpl>(std::forward<Callable>(callable), owner); |
| } |
| |
| private: |
| static void Handler(async_dispatcher_t* dispatcher, async_task_t* task, zx_status_t status); |
| |
| TaskScope* owner_; |
| }; |
| |
| void PostImpl(std::unique_ptr<internal::Task> task); |
| void PostImpl(std::unique_ptr<DelayedTask> task, zx::duration delay); |
| void PostImpl(std::unique_ptr<DelayedTask> task, zx::time deadline); |
| void RemoveDelayedTask(DelayedTask* task) __TA_EXCLUDES(checker_); |
| void RemoveDelayedTaskLocked(DelayedTask* task) __TA_REQUIRES(checker_); |
| |
| async_dispatcher_t* dispatcher_; |
| async::synchronization_checker checker_; |
| internal::TaskQueue queue_ __TA_GUARDED(checker_); |
| |
| // A list of |DelayedTask|. |
| list_node_t delayed_task_list_ __TA_GUARDED(checker_) = LIST_INITIAL_VALUE(delayed_task_list_); |
| |
| // If true, tasks are discarded instead of posted. |
| bool stopped_ __TA_GUARDED(checker_) = false; |
| }; |
| |
| } // namespace async_patterns |
| |
| #endif // LIB_ASYNC_PATTERNS_CPP_TASK_SCOPE_H_ |