blob: 0b039a7d1f34e22be4068411e9c80da3d460834f [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_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_