blob: a8ff3d6722377829ae5dd75e9aca2bb253931e05 [file] [log] [blame]
// 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_