| // Copyright 2017 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 SRC_LIB_CALLBACK_SCOPED_TASK_RUNNER_H_ | |
| #define SRC_LIB_CALLBACK_SCOPED_TASK_RUNNER_H_ | |
| #include <lib/async/default.h> | |
| #include <lib/async/dispatcher.h> | |
| #include <lib/fit/function.h> | |
| #include <lib/fit/function_traits.h> | |
| #include <lib/zx/time.h> | |
| #include <memory> | |
| #include "src/lib/fxl/macros.h" | |
| namespace callback { | |
| class TaskController { | |
| public: | |
| template <class T> | |
| struct Tag {}; | |
| TaskController(); | |
| virtual ~TaskController(); | |
| // Indicates that further calls to |RunTask| should no-op. Implementations may | |
| // choose to block until running or outstanding tasks have completed. | |
| // | |
| // This method must be idempotent. | |
| virtual void ShutDown() = 0; | |
| // Runs a task immediately, or no-ops if this controller has shut down. | |
| virtual void RunTask(fit::closure task) = 0; | |
| private: | |
| FXL_DISALLOW_COPY_ASSIGN_AND_MOVE(TaskController); | |
| }; | |
| // A basic task controller that does not synchronize shutdown with task | |
| // execution. Client code is responsible for ensuring that any tasks that may be | |
| // running when the runner is destroyed do not rely on invalid state, typically | |
| // by ensuring that |ShutDown| occurs on the dispatch thread. | |
| class SimpleTaskController : public TaskController { | |
| public: | |
| // For ergonomics, this could be a constexpr static Tag<SimpleTaskController> | |
| // but C++ would then allocate a (small) nonzero size for it. If we only use | |
| // it inline, it can be optimized out. | |
| // http://www.open-std.org/jtc1/sc22/open/n2356/class.html, s9p3 | |
| using Type = Tag<SimpleTaskController>; | |
| private: | |
| // |TaskController| | |
| // This implementation does not block, and should be called on the dispatch | |
| // thread. | |
| void ShutDown() override; | |
| // |TaskController| | |
| void RunTask(fit::closure task) override; | |
| // This could easily be a std::atomic_bool, but since this implementation is | |
| // not synchronized, in correct code I don't think there's a case where this | |
| // makes a difference. | |
| // | |
| // A common idiom is to use |fxl::WeakPtr| for this kind of guard. However, | |
| // that is not thread-safe as |fxl::WeakPtr| contains non-atomic flag | |
| // twiddling, and |fxl::WeakPtr::InvalidateWeakPtrs| allows new valid pointers | |
| // to be created after it is called so it cannot be used as a |ShutDown| | |
| // method. Despite being unsynchronized, |SimpleTaskController| lets the | |
| // caller |ShutDown| on the dispatch thread. | |
| bool alive_ = true; | |
| }; | |
| // An object that wraps the posting logic of an |async_t|, but that is | |
| // not copyable and will generally not run any task after being deleted, though | |
| // edge case handling may differ between |Controller| implementations. | |
| // | |
| // This class is mostly thread-safe, though it must not go out of scope while | |
| // any of its methods are being called, and handling of edge cases varies | |
| // depending on the controller implementation. Notably, the default |Controller| | |
| // is not synchronized, so |ShutDown| should occur on the dispatch thread (after | |
| // which destruction may happen on any thread). | |
| // | |
| // Typically, this class should appear towards the end of the fields of an | |
| // owning class so that it goes out of scope before any state that tasks may | |
| // depend on. | |
| // | |
| // This class may be used without a dispatcher, but the common use case is to | |
| // manage FIDL calls. | |
| class ScopedTaskRunner { | |
| public: | |
| explicit ScopedTaskRunner(async_dispatcher_t* dispatcher = async_get_default_dispatcher()); | |
| template <class Controller> | |
| explicit ScopedTaskRunner(typename TaskController::Tag<Controller> controller_type, | |
| async_dispatcher_t* dispatcher = async_get_default_dispatcher()) | |
| : dispatcher_(dispatcher), controller_(std::make_shared<Controller>()) {} | |
| ScopedTaskRunner(const ScopedTaskRunner&) = delete; | |
| ScopedTaskRunner(ScopedTaskRunner&&); | |
| ~ScopedTaskRunner(); | |
| ScopedTaskRunner& operator=(const ScopedTaskRunner&) = delete; | |
| ScopedTaskRunner& operator=(ScopedTaskRunner&&); | |
| async_dispatcher_t* dispatcher() const { return dispatcher_; } | |
| // Pre-emptively ends this ScopedTaskRunner's lifecycle. All subsequent tasks | |
| // will no-op. This method may block, depending on the |TaskController| | |
| // implementation. The default implementation does not block, so care should | |
| // be taken by callers that any tasks executing during shutdown do not depend | |
| // on state guarded by this instance, typically by calling this method from | |
| // the dispatch thread. | |
| // | |
| // This method is idempotent and will automatically be called when this class | |
| // goes out of scope. | |
| void ShutDown(); | |
| // Shuts down the current controller and assigns a new |SimpleTaskController|. | |
| void Reset(); | |
| // Shuts down the current controller and assigns a new one of the specified type. | |
| template <class Controller> | |
| void Reset(typename TaskController::Tag<Controller> controller_type) { | |
| ShutDown(); | |
| controller_ = std::make_shared<Controller>(); | |
| } | |
| // Posts a task to run as soon as possible on the dispatcher after the current | |
| // dispatch cycle. | |
| void PostTask(fit::closure task); | |
| // Posts a task to run as soon as possible after the specified |target_time|. | |
| void PostTaskForTime(fit::closure task, zx::time target_time); | |
| // Posts a task to run as soon as possible after the specified |delay|. | |
| void PostDelayedTask(fit::closure task, zx::duration delay); | |
| // Convenience function to post a repeating periodic task. If |invoke_now| is | |
| // true, the task is run as soon as possible on the dispatcher after the | |
| // current dispatch cycle as well as periodically. Otherwise, the first | |
| // invocation of the task will be as soon as possible after the specified | |
| // |interval|. | |
| void PostPeriodicTask(fit::closure task, zx::duration interval, bool invoke_now = true); | |
| // Scopes a task to the current task runner without scheduling it. This means | |
| // that the given function will be called when the returned function is called | |
| // if and only if this task runner has not been deleted or shut down. | |
| // Synchronization of the guard depends on the |TaskController| | |
| // implementation; the default implementation is unsynchronized. | |
| template <class Task, class... Args> | |
| auto MakeScoped(Task task, fit::parameter_pack<Args...>) { | |
| return [controller = controller_, task = std::move(task)](Args... args) mutable { | |
| // This differs from |callback::MakeScoped| in that |controller| is aware | |
| // of the task from start to finish, as opposed to |callback::MakeScoped| | |
| // which only consults |witness| when the task begins. | |
| return controller->RunTask( | |
| [task = std::move(task), &args...]() mutable { task(std::forward<Args>(args)...); }); | |
| }; | |
| } | |
| template <class Task> | |
| auto MakeScoped(Task task) { | |
| return MakeScoped(std::move(task), typename fit::function_traits<Task>::args{}); | |
| } | |
| private: | |
| async_dispatcher_t* dispatcher_; | |
| std::shared_ptr<TaskController> controller_; | |
| }; | |
| } // namespace callback | |
| #endif // SRC_LIB_CALLBACK_SCOPED_TASK_RUNNER_H_ |