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