blob: 6f04b9ec8f783efb8e8203d0f1d33d00162c61f7 [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_