blob: 856f4bfb4b1ffd11210f7776a95c23352ab3436f [file] [log] [blame]
// Copyright 2018 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_FIT_PROMISE_INCLUDE_LIB_FPROMISE_PROMISE_H_
#define LIB_FIT_PROMISE_INCLUDE_LIB_FPROMISE_PROMISE_H_
#include <assert.h>
#include <lib/fit/function.h>
#include <lib/stdcompat/variant.h>
#include <tuple>
#include <type_traits>
#include <utility>
#include "promise_internal.h"
#include "result.h"
namespace fpromise {
// A |fpromise::promise| is a building block for asynchronous control flow that
// wraps an asynchronous task in the form of a "continuation" that is
// repeatedly invoked by an executor until it produces a result.
//
// Additional asynchronous tasks can be chained onto the promise using
// a variety of combinators such as |then()|.
//
// Use |fpromise::make_promise()| to create a promise.
// Use |fpromise::make_ok_promise()| to create a promise that immediately returns a value.
// Use |fpromise::make_error_promise()| to create a promise that immediately returns an error.
// Use |fpromise::make_result_promise()| to create a promise that immediately returns a result.
// Use |fpromise::future| to more conveniently hold a promise or its result.
// Use |fpromise::pending_task| to wrap a promise as a pending task for execution.
// Use |fpromise::executor| to execute a pending task.
// See examples below.
//
// Always look to the future; never look back.
//
// SYNOPSIS
//
// |V| is the type of value produced when the completes successfully.
// Defaults to |void|.
//
// |E| is the type of error produced when the completes with an error.
// Defaults to |void|.
//
// Class members are documented in |fpromise::promise_impl|.
//
// CHAINING PROMISES USING COMBINATORS
//
// Promises can be chained together using combinators such as |then()|
// which consume the original promise(s) and return a new combined promise.
//
// For example, the |then()| combinator returns a promise that has the effect
// of asynchronously awaiting completion of the prior promise (the instance
// upon which |then()| was called) then delivering its result to a handler
// function.
//
// Available combinators defined in this library:
//
// |then()|: run a handler when prior promise completes
// |and_then()|: run a handler when prior promise completes successfully
// |or_else()|: run a handler when prior promise completes with an error
// |inspect()|: examine result of prior promise
// |discard_result()|: discard result and unconditionally return
// fpromise::result<> when prior promise completes
// |wrap_with()|: applies a wrapper to the promise
// |box()|: wraps the promise's continuation into a |fit::function|
// |fpromise::join_promises()|: await multiple promises in an argument list,
// once they all complete return a tuple of
// their results
// |fpromise::join_promise_vector()|: await multiple promises in a vector,
// once they all complete return a vector
// of their results
//
// You can also create your own custom combinators by crafting new
// types of continuations.
//
// CONTINUATIONS AND HANDLERS
//
// Internally, |fpromise::promise| wraps a continuation (a kind of callable
// object) that holds the state of the asynchronous task and provides a
// means for making progress through repeated invocation.
//
// A promise's continuation is generated through the use of factories
// such as |make_promise()| and combinators such as |then()|. Most of
// these functions accept a client-supplied "handler" (another kind
// of callable object, often a lambda expression) which performs the actual
// computations.
//
// Continuations have a very regular interface: they always accept a
// |fpromise::context&| argument and return a |fpromise::result|. Conversely, handlers
// have a very flexible interface: clients can provide them in many forms
// all of which are documented by the individual functions which consume them.
// It's pretty easy to use: the library takes care of wrapping client-supplied
// handlers of all supported forms into the continuations it uses internally.
//
// THEORY OF OPERATION
//
// On its own, a promise is "inert"; it only makes progress in response to
// actions taken by its owner. The state of the promise never changes
// spontaneously or concurrently.
//
// Typically, a promise is executed by wrapping it into a |fpromise::pending_task|
// and scheduling it for execution using |fpromise::executor::schedule_task()|.
// A promise's |operator(fpromise::context&)| can also be invoked directly by its owner
// from within the scope of another task (this is used to implement combinators
// and futures) though the principle is the same.
//
// |fpromise::executor| is an abstract class that encapsulates a strategy for
// executing tasks. The executor is responsible for invoking each tasks's
// continuation until the task returns a non-pending result, indicating that
// the task has been completed.
//
// The method of execution and scheduling of each continuation call is left
// to the discretion of each executor implementation. Typical executor
// implementations may dispatch tasks on an event-driven message loop or on
// a thread pool. Developers are responsible for selecting appropriate
// executor implementations for their programs.
//
// During each invocation, the executor passes the continuation an execution
// context object represented by a subclass of |fpromise::context|. The continuation
// attempts to make progress then returns a value of type |fpromise::result| to
// indicate whether it completed successfully (signaled by |fpromise::ok()|),
// failed with an error (signaled by |fpromise::error()|, or was unable to complete
// the task during that invocation (signaled by |fpromise::pending()|).
// For example, a continuation may be unable to complete the task if it must
// asynchronously await completion of an I/O or IPC operation before it
// can proceed any further.
//
// If the continuation was unable to complete the task during its invocation,
// it may to call |fpromise::context::suspend_task()| to acquire a
// |fpromise::suspended_task| object. The continuation then arranges for the
// task to be resumed asynchronously (with |fpromise::suspended_task::resume_task()|)
// once it becomes possible for the promise to make forward progress again.
// Finally, the continuation returns returns |fpromise::pending()| to indicate to
// the executor that it was unable to complete the task during that invocation.
//
// When the executor receives a pending result from a task's continuation,
// it moves the task into a table of suspended tasks. A suspended task
// is considered abandoned if has not been resume and all remaining
// |fpromise::suspended_task| handles representing it have been dropped.
// When a task is abandoned, the executor removes it from its table of
// suspended tasks and destroys the task because it is not possible for the task
// to be resumed or to make progress from that state.
//
// See also |fpromise::single_threaded_executor| for a simple executor implementation.
//
// BOXED AND UNBOXED PROMISES
//
// To make combination and execution as efficient as possible, the promises
// returned by |fpromise::make_promise| and by combinators are parameterized by
// complicated continuation types that are hard to describe, often consisting of
// nested templates and lambdas. These are referred to as "unboxed"
// promises. In contrast, "boxed" promises are parameterized by a
// |fit::function| that hides (or "erases") the type of the continuation
// thereby yielding type that is easier to describe.
//
// You can recognize boxed and unboxed promises by their types.
// Here are two examples:
//
// - A boxed promise type: `fpromise::promise<void, void>` which is an alias for
// `fpromise::promise_impl<void, void, std::function<fpromise::result<void, void>>`.
// - An unboxed promise type: `fpromise::promise_impl<void, void,
// fpromise::internal::then_continuation<...something unintelligible...>>`
//
// Although boxed promises are easier to manipulate, they may cause the
// continuation to be allocated on the heap. Chaining boxed promises can
// result in multiple allocations being produced.
//
// Conversely, unboxed promises have full type information. Not only does
// this defer heap allocation but it also makes it easier for the C++
// compiler to fuse a chains of unboxed promises together into a single
// object that is easier to optimize.
//
// Unboxed promises can be boxed by assigning them to a boxed promise
// type (such as |fpromise::promise<>|) or using the |box()| combinator.
//
// As a rule of thumb, always defer boxing of promises until it is necessary
// to transport them using a simpler type.
//
// Do this: (chaining as a single expression performs at most one heap allocation)
//
// fpromise::promise<> f = fpromise::make_promise([] { ... });
// .then([](fpromise::result<>& result) { ... });
// .and_then([] { ... });
//
// Or this: (still only performs at most one heap allocation)
//
// auto f = fpromise::make_promise([] { ... });
// auto g = f.then([](fpromise::result<>& result) { ... });
// auto h = g.and_then([] { ... });
// fpromise::promise<> boxed_h = h;
//
// But don't do this: (incurs up to three heap allocations due to eager boxing)
//
// fpromise::promise<> f = fpromise::make_promise([] { ... });
// fpromise::promise<> g = f.then([](fpromise::result<>& result) { ... });
// fpromise::promise<> h = g.and_then([] { ... });
//
// SINGLE OWNERSHIP MODEL
//
// Promises have single-ownership semantics. This means that there
// can only be at most one reference to the task represented by its
// continuation along with any state held by that continuation.
//
// When a combinator is applied to a promise, ownership of its continuation
// is transferred to the combined promise, leaving the original promise
// in an "empty" state without a continuation. Note that it is an error
// to attempt to invoke an empty promise (will assert at runtime).
//
// This model greatly simplifies reasoning about object lifetime.
// If a promise goes out of scope without completing its task, the task
// is considered "abandoned", causing all associated state to be destroyed.
//
// Note that a promise may capture references to other objects whose lifetime
// differs from that of the promise. It is the responsibility of the promise
// to ensure reachability of the objects whose reference it captures such
// as by using reference counted pointers, weak pointers, or other appropriate
// mechanisms to ensure memory safety.
//
// THREADING MODEL
//
// Promise objects are not thread-safe themselves. You cannot call their
// methods concurrently (or re-entrantly). However, promises can safely
// be moved to other threads and executed there (unless their continuation
// requires thread affinity for some reason but that's beyond the scope
// of this document).
//
// This property of being thread-independent, combined with the single
// ownership model, greatly simplifies the implementation of thread pool
// based executors.
//
// RESULT RETENTION AND FIT::FUTURES
//
// A promise's continuation can only be executed to completion once.
// After it completes, it cannot be run again.
//
// This method of execution is very efficient; the promise's result is returned
// directly to its invoker; it is not copied or retained within the promise
// object itself. It is entirely the caller's responsibility to decide how to
// consume or retain the result if need be.
//
// For example, the caller can move the promise into a |fpromise::future| to
// more conveniently hold either the promise or its result upon completion.
//
// CLARIFICATION OF NOMENCLATURE
//
// In this library, the words "promise" and "future" have the following
// definitions:
//
// - A *promise* holds the function that performs an asynchronous task.
// It is the means to produce a value.
// - A *future* holds the value produced by an asynchronous task or a
// promise to produce that value if the task has not yet completed.
// It is a proxy for a value that is to be computed.
//
// Be aware that other libraries may use these terms slightly differently.
//
// For more information about the theory of futures and promises, see
// https://en.wikipedia.org/wiki/Futures_and_promises.
//
// COMPARISON WITH STD::FUTURE
//
// |std::future| provides a mechanism for running asynchronous tasks
// and awaiting their results on other threads. Waiting can be performed
// either by blocking the waiting thread or by polling the future.
// The manner in which tasks are scheduled and executed is entirely
// controlled by the C++ standard library and offers limited control
// to developers.
//
// |fpromise::promise| and |fpromise::future| provide a mechanism for running asynchronous
// tasks, chaining additional tasks using combinators, and awaiting their
// results. An executor is responsible for suspending tasks awaiting
// results of other tasks and is at liberty to run other tasks on the
// same thread rather than blocking. In addition, developers can create custom
// executors to implement their own policies for running tasks.
//
// Decoupling awaiting from blocking makes |fpromise::promise| quite versatile.
// |fpromise::promise| can also interoperate with other task dispatching mechanisms
// (including |std::future|) using adapters such as |fpromise::bridge|.
//
// EXAMPLE
//
// -
// https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/system/utest/fit/examples/promise_example1.cc
// -
// https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/system/utest/fit/examples/promise_example2.cc
//
template <typename V = void, typename E = void>
using promise = promise_impl<::fit::function<result<V, E>(fpromise::context&)>>;
// Promise implementation details.
// See |fpromise::promise| documentation for more information.
template <typename Continuation>
class promise_impl final {
static_assert(::fpromise::internal::is_continuation<Continuation>::value,
"Continuation type is invalid. A continuation is a callable object "
"with this signature: fpromise::result<V, E>(fpromise::context&).");
using state_type = ::fit::nullable<Continuation>;
public:
// The type of callable object held by the promise.
// Its signature is: result_type(fpromise::context&).
using continuation_type = Continuation;
// The promise's result type.
// Equivalent to fpromise::result<value_type, error_type>.
using result_type = typename ::fpromise::internal::continuation_traits<Continuation>::result_type;
// The type of value produced when the promise completes successfully.
// May be void.
using value_type = typename result_type::value_type;
// The type of value produced when the promise completes with an error.
// May be void.
using error_type = typename result_type::error_type;
// Creates an empty promise without a continuation.
// A continuation must be assigned before the promise can be used.
promise_impl() = default;
explicit promise_impl(decltype(nullptr)) {}
promise_impl(const promise_impl&) = delete;
promise_impl& operator=(const promise_impl&) = delete;
// Constructs the promise by taking the continuation from another promise,
// leaving the other promise empty.
promise_impl(promise_impl&& other) : state_{std::move(other.state_)} { other.state_.reset(); }
// Assigns the promise by taking the continuation from another promise,
// leaving the other promise empty.
promise_impl& operator=(promise_impl&& other) {
if (this != &other) {
state_ = std::move(other.state_);
other.state_.reset();
}
return *this;
}
// Creates a promise with a continuation.
// If |continuation| equals nullptr then the promise is empty.
explicit promise_impl(continuation_type continuation) : state_(std::move(continuation)) {}
// Converts from a promise holding a continuation that is assignable to
// to this promise's continuation type.
//
// This is typically used to create a promise with a boxed continuation
// type (such as |fit::function|) from an unboxed promise produced by
// |fpromise::make_promise| or by combinators.
//
// EXAMPLE
//
// // f is a promise_impl with a complicated unboxed type
// auto f = fpromise::make_promise([] { ... });
//
// // g wraps f's continuation
// fpromise::promise<> g = std::move(f);
//
template <
typename OtherContinuation,
std::enable_if_t<!std::is_same<continuation_type, OtherContinuation>::value &&
std::is_constructible<continuation_type, OtherContinuation&&>::value,
bool> = true>
promise_impl(promise_impl<OtherContinuation> other)
: state_(other.state_.has_value() ? state_type(continuation_type(std::move(*other.state_)))
: state_type()) {}
// Destroys the promise, releasing its continuation.
~promise_impl() = default;
// Returns true if the promise is non-empty (has a valid continuation).
explicit operator bool() const { return state_.has_value(); }
// Invokes the promise's continuation.
//
// This method should be called by an executor to evaluate the promise.
// If the result's state is |result_state::pending| then the executor
// is responsible for arranging to invoke the promise's continuation
// again once it determines that it is possible to make progress
// towards completion of the promise encapsulated within the promise.
//
// Once the continuation returns a result with status |result_state::ok|
// or |result_state::error|, the promise is assigned an empty continuation.
//
// Asserts that the promise is non-empty.
result_type operator()(context& context) {
result_type result = (state_.value())(context);
if (!result.is_pending())
state_.reset();
return result;
}
// Takes the promise's continuation, leaving it in an empty state.
// Asserts that the promise is non-empty.
continuation_type take_continuation() {
auto continuation = std::move(state_.value());
state_.reset();
return continuation;
}
// Discards the promise's continuation, leaving it empty.
promise_impl& operator=(decltype(nullptr)) {
state_.reset();
return *this;
}
// Assigns the promise's continuation.
promise_impl& operator=(continuation_type continuation) {
state_ = std::move(continuation);
return *this;
}
// Swaps the promises' continuations.
void swap(promise_impl& other) {
using std::swap;
swap(state_, other.state_);
}
// Returns an unboxed promise which invokes the specified handler
// function after this promise completes (successfully or unsuccessfully),
// passing its result.
//
// The received result's state is guaranteed to be either
// |fpromise::result_state::ok| or |fpromise::result_state::error|, never
// |fpromise::result_state::pending|.
//
// |handler| is a callable object (such as a lambda) which consumes the
// result of this promise and returns a new result with any value type
// and error type. Must not be null.
//
// The handler must return one of the following types:
// - void
// - fpromise::result<new_value_type, new_error_type>
// - fpromise::ok<new_value_type>
// - fpromise::error<new_error_type>
// - fpromise::pending
// - fpromise::promise<new_value_type, new_error_type>
// - any callable or unboxed promise with the following signature:
// fpromise::result<new_value_type, new_error_type>(fpromise::context&)
//
// The handler must accept one of the following argument lists:
// - (result_type&)
// - (const result_type&)
// - (fpromise::context&, result_type&)
// - (fpromise::context&, const result_type&)
//
// Asserts that the promise is non-empty.
// This method consumes the promise's continuation, leaving it empty.
//
// EXAMPLE
//
// auto f = fpromise::make_promise(...)
// .then([] (fpromise::result<int, std::string>& result)
// -> fpromise::result<std::string, void> {
// if (result.is_ok()) {
// printf("received value: %d\n", result.value());
// if (result.value() % 15 == 0)
// return ::fpromise::ok("fizzbuzz");
// if (result.value() % 3 == 0)
// return ::fpromise::ok("fizz");
// if (result.value() % 5 == 0)
// return ::fpromise::ok("buzz");
// return ::fpromise::ok(std::to_string(result.value()));
// } else {
// printf("received error: %s\n", result.error().c_str());
// return ::fpromise::error();
// }
// })
// .then(...);
//
template <typename ResultHandler>
promise_impl<::fpromise::internal::then_continuation<promise_impl, ResultHandler>> then(
ResultHandler handler) {
static_assert(::fit::is_callable<ResultHandler>::value,
"ResultHandler must be a callable object.");
assert(!fit::is_null(handler));
assert(state_.has_value());
return make_promise_with_continuation(
::fpromise::internal::then_continuation<promise_impl, ResultHandler>(std::move(*this),
std::move(handler)));
}
// Returns an unboxed promise which invokes the specified handler
// function after this promise completes successfully, passing its
// resulting value.
//
// |handler| is a callable object (such as a lambda) which consumes the
// result of this promise and returns a new result with any value type
// but the same error type. Must not be null.
//
// The handler must return one of the following types:
// - void
// - fpromise::result<new_value_type, error_type>
// - fpromise::ok<new_value_type>
// - fpromise::error<error_type>
// - fpromise::pending
// - fpromise::promise<new_value_type, error_type>
// - any callable or unboxed promise with the following signature:
// fpromise::result<new_value_type, error_type>(fpromise::context&)
//
// The handler must accept one of the following argument lists:
// - (value_type&)
// - (const value_type&)
// - (fpromise::context&, value_type&)
// - (fpromise::context&, const value_type&)
//
// Asserts that the promise is non-empty.
// This method consumes the promise's continuation, leaving it empty.
//
// EXAMPLE
//
// auto f = fpromise::make_promise(...)
// .and_then([] (const int& value) {
// printf("received value: %d\n", value);
// if (value % 15 == 0)
// return ::fpromise::ok("fizzbuzz");
// if (value % 3 == 0)
// return ::fpromise::ok("fizz");
// if (value % 5 == 0)
// return ::fpromise::ok("buzz");
// return ::fpromise::ok(std::to_string(value));
// })
// .then(...);
//
template <typename ValueHandler>
promise_impl<::fpromise::internal::and_then_continuation<promise_impl, ValueHandler>> and_then(
ValueHandler handler) {
static_assert(::fit::is_callable<ValueHandler>::value,
"ValueHandler must be a callable object.");
assert(!fit::is_null(handler));
assert(state_.has_value());
return make_promise_with_continuation(
::fpromise::internal::and_then_continuation<promise_impl, ValueHandler>(
std::move(*this), std::move(handler)));
}
// Returns an unboxed promise which invokes the specified handler
// function after this promise completes with an error, passing its
// resulting error.
//
// |handler| is a callable object (such as a lambda) which consumes the
// result of this promise and returns a new result with any error type
// but the same value type. Must not be null.
//
// The handler must return one of the following types:
// - void
// - fpromise::result<value_type, new_error_type>
// - fpromise::ok<value_type>
// - fpromise::error<new_error_type>
// - fpromise::pending
// - fpromise::promise<value_type, new_error_type>
// - any callable or unboxed promise with the following signature:
// fpromise::result<value_type, new_error_type>(fpromise::context&)
//
// The handler must accept one of the following argument lists:
// - (error_type&)
// - (const error_type&)
// - (fpromise::context&, error_type&)
// - (fpromise::context&, const error_type&)
//
// Asserts that the promise is non-empty.
// This method consumes the promise's continuation, leaving it empty.
//
// EXAMPLE
//
// auto f = fpromise::make_promise(...)
// .or_else([] (const std::string& error) {
// printf("received error: %s\n", error.c_str());
// return ::fpromise::error();
// })
// .then(...);
//
template <typename ErrorHandler>
promise_impl<::fpromise::internal::or_else_continuation<promise_impl, ErrorHandler>> or_else(
ErrorHandler handler) {
static_assert(::fit::is_callable<ErrorHandler>::value,
"ErrorHandler must be a callable object.");
assert(!fit::is_null(handler));
assert(state_.has_value());
return make_promise_with_continuation(
::fpromise::internal::or_else_continuation<promise_impl, ErrorHandler>(std::move(*this),
std::move(handler)));
}
// Returns an unboxed promise which invokes the specified handler
// function after this promise completes (successfully or unsuccessfully),
// passing it the promise's result then delivering the result onwards
// to the next promise once the handler returns.
//
// The handler receives a const reference, or non-const reference
// depending on the signature of the handler's last argument.
//
// - Const references are especially useful for inspecting a
// result mid-stream without modification, such as printing it for
// debugging.
// - Non-const references are especially useful for synchronously
// modifying a result mid-stream, such as clamping its bounds or
// injecting a default value.
//
// |handler| is a callable object (such as a lambda) which can examine
// or modify the incoming result. Unlike |then()|, the handler does
// not need to propagate the result onwards. Must not be null.
//
// The handler must return one of the following types:
// - void
//
// The handler must accept one of the following argument lists:
// - (result_type&)
// - (const result_type&)
// - (fpromise::context&, result_type&)
// - (fpromise::context&, const result_type&)
//
// Asserts that the promise is non-empty.
// This method consumes the promise's continuation, leaving it empty.
//
// EXAMPLE
//
// auto f = fpromise::make_promise(...)
// .inspect([] (const fpromise::result<int, std::string>& result) {
// if (result.is_ok())
// printf("received value: %d\n", result.value());
// else
// printf("received error: %s\n", result.error().c_str());
// })
// .then(...);
//
template <typename InspectHandler>
promise_impl<::fpromise::internal::inspect_continuation<promise_impl, InspectHandler>> inspect(
InspectHandler handler) {
static_assert(::fit::is_callable<InspectHandler>::value,
"InspectHandler must be a callable object.");
static_assert(std::is_void<typename ::fit::callable_traits<InspectHandler>::return_type>::value,
"InspectHandler must return void.");
assert(!fit::is_null(handler));
assert(state_.has_value());
return make_promise_with_continuation(
::fpromise::internal::inspect_continuation<promise_impl, InspectHandler>(
std::move(*this), std::move(handler)));
}
// Returns an unboxed promise which discards the result of this promise
// once it completes, thereby always producing a successful result of
// type fpromise::result<void, void> regardless of whether this promise
// succeeded or failed.
//
// Asserts that the promise is non-empty.
// This method consumes the promise's continuation, leaving it empty.
//
// EXAMPLE
//
// auto f = fpromise::make_promise(...)
// .discard_result()
// .then(...);
//
promise_impl<::fpromise::internal::discard_result_continuation<promise_impl>> discard_result() {
assert(state_.has_value());
return make_promise_with_continuation(
::fpromise::internal::discard_result_continuation<promise_impl>(std::move(*this)));
}
// Applies a |wrapper| to the promise. Invokes the wrapper's |wrap()|
// method, passes the promise to the wrapper by value followed by any
// additional |args| passed to |wrap_with()|, then returns the wrapper's
// result.
//
// |Wrapper| is a type that implements a method called |wrap()| which
// accepts a promise as its argument and produces a wrapped result of
// any type, such as another promise.
//
// Asserts that the promise is non-empty.
// This method consumes the promise's continuation, leaving it empty.
//
// EXAMPLE
//
// In this example, |fpromise::sequencer| is a wrapper type that imposes
// FIFO execution order onto a sequence of wrapped promises.
//
// // This wrapper type is intended to be applied to
// // a sequence of promises so we store it in a variable.
// fpromise::sequencer seq;
//
// // This task consists of some amount of work that must be
// // completed sequentially followed by other work that can
// // happen in any order. We use |wrap_with()| to wrap the
// // sequential work with the sequencer.
// fpromise::promise<> perform_complex_task() {
// return fpromise::make_promise([] { /* do sequential work */ })
// .then([] (fpromise::result<> result) { /* this will also be wrapped */ })
// .wrap_with(seq)
// .then([] (fpromise::result<> result) { /* do more work */ });
// }
//
// This example can also be written without using |wrap_with()|.
// The behavior is equivalent but the syntax may seem more awkward.
//
// fpromise::sequencer seq;
//
// promise<> perform_complex_task() {
// return seq.wrap(
// fpromise::make_promise([] { /* sequential work */ })
// ).then([] (fpromise::result<> result) { /* more work */ });
// }
//
template <typename Wrapper, typename... Args>
decltype(auto) wrap_with(Wrapper& wrapper, Args... args) {
assert(state_.has_value());
return wrapper.wrap(std::move(*this), std::forward<Args>(args)...);
}
// Wraps the promise's continuation into a |fit::function|.
//
// A boxed promise is easier to store and pass around than the unboxed
// promises produced by |fpromise::make_promise()| and combinators, though boxing
// may incur a heap allocation.
//
// It is a good idea to defer boxing the promise until after all
// desired combinators have been applied to prevent unnecessary heap
// allocation during intermediate states of the promise's construction.
//
// Returns an empty promise if this promise is empty.
// This method consumes the promise's continuation, leaving it empty.
//
// EXAMPLE
//
// // f's is a fpromise::promise_impl<> whose continuation contains an
// // anonymous type (the lambda)
// auto f = fpromise::make_promise([] {});
//
// // g's type will be fpromise::promise<> due to boxing
// auto boxed_f = f.box();
//
// // alternately, we can get exactly the same effect by assigning
// // the unboxed promise to a variable of a named type instead of
// // calling box()
// fpromise::promise<> boxed_f = std::move(f);
//
promise_impl<::fit::function<result_type(context&)>> box() { return std::move(*this); }
private:
template <typename>
friend class promise_impl;
state_type state_;
};
template <typename Continuation>
void swap(promise_impl<Continuation>& a, promise_impl<Continuation>& b) {
a.swap(b);
}
template <typename Continuation>
bool operator==(const promise_impl<Continuation>& f, decltype(nullptr)) {
return !f;
}
template <typename Continuation>
bool operator==(decltype(nullptr), const promise_impl<Continuation>& f) {
return !f;
}
template <typename Continuation>
bool operator!=(const promise_impl<Continuation>& f, decltype(nullptr)) {
return !!f;
}
template <typename Continuation>
bool operator!=(decltype(nullptr), const promise_impl<Continuation>& f) {
return !!f;
}
// Makes a promise containing the specified continuation.
//
// This function is used for making a promises given a callable object
// that represents a valid continuation type. In contrast,
// |fpromise::make_promise()| supports a wider range of types and should be
// preferred in most situations.
//
// |Continuation| is a callable object with the signature
// fpromise::result<V, E>(fpromise::context&).
template <typename Continuation>
inline promise_impl<Continuation> make_promise_with_continuation(Continuation continuation) {
return promise_impl<Continuation>(std::move(continuation));
}
// Returns an unboxed promise that wraps the specified handler.
// The type of the promise's result is inferred from the handler's result.
//
// |handler| is a callable object (such as a lambda. Must not be null.
//
// The handler must return one of the following types:
// - void
// - fpromise::result<value_type, error_type>
// - fpromise::ok<value_type>
// - fpromise::error<error_type>
// - fpromise::pending
// - fpromise::promise<value_type, error_type>
// - any callable or unboxed promise with the following signature:
// fpromise::result<value_type, error_type>(fpromise::context&)
//
// The handler must accept one of the following argument lists:
// - ()
// - (fpromise::context&)
//
// See documentation of |fpromise::promise| for more information.
//
// SYNOPSIS
//
// |Handler| is the handler function type. It is typically inferred by the
// compiler from the |handler| argument.
//
// EXAMPLE
//
// enum class weather_type { sunny, glorious, cloudy, eerie, ... };
//
// weather_type look_outside() { ... }
// void wait_for_tomorrow(fpromise::suspended_task task) {
// ... arrange to call task.resume_task() tomorrow ...
// }
//
// fpromise::promise<weather_type, std::string> wait_for_good_weather(int max_days) {
// return fpromise::make_promise([days_left = max_days] (fpromise::context& context) mutable
// -> fpromise::result<int, std::string> {
// weather_type weather = look_outside();
// if (weather == weather_type::sunny || weather == weather_type::glorious)
// return fpromise::ok(weather);
// if (days_left > 0) {
// wait_for_tomorrow(context.suspend_task());
// return fpromise::pending();
// }
// days_left--;
// return fpromise::error("nothing but grey skies");
// });
// }
//
// auto f = wait_for_good_weather(7)
// .and_then([] (const weather_type& weather) { ... })
// .or_else([] (const std::string& error) { ... });
//
template <typename PromiseHandler>
inline promise_impl<::fpromise::internal::context_handler_invoker<PromiseHandler>> make_promise(
PromiseHandler handler) {
static_assert(::fit::is_callable<PromiseHandler>::value,
"PromiseHandler must be a callable object.");
assert(!fit::is_null(handler));
return make_promise_with_continuation(
::fpromise::internal::promise_continuation<PromiseHandler>(std::move(handler)));
}
// Returns an unboxed promise that immediately returns the specified result when invoked.
//
// This function is especially useful for returning promises from functions
// that have multiple branches some of which complete synchronously.
//
// |result| is the result for the promise to return.
//
// See documentation of |fpromise::promise| for more information.
template <typename V = void, typename E = void>
inline promise_impl<::fpromise::internal::result_continuation<V, E>> make_result_promise(
fpromise::result<V, E> result) {
return make_promise_with_continuation(
::fpromise::internal::result_continuation<V, E>(std::move(result)));
}
template <typename V = void, typename E = void>
inline promise_impl<::fpromise::internal::result_continuation<V, E>> make_result_promise(
fpromise::ok_result<V> result) {
return make_promise_with_continuation(
::fpromise::internal::result_continuation<V, E>(std::move(result)));
}
template <typename V = void, typename E = void>
inline promise_impl<::fpromise::internal::result_continuation<V, E>> make_result_promise(
fpromise::error_result<E> result) {
return make_promise_with_continuation(
::fpromise::internal::result_continuation<V, E>(std::move(result)));
}
template <typename V = void, typename E = void>
inline promise_impl<::fpromise::internal::result_continuation<V, E>> make_result_promise(
fpromise::pending_result result) {
return make_promise_with_continuation(
::fpromise::internal::result_continuation<V, E>(std::move(result)));
}
// Returns an unboxed promise that immediately returns the specified value when invoked.
//
// This function is especially useful for returning promises from functions
// that have multiple branches some of which complete synchronously.
//
// |value| is the value for the promise to return.
//
// See documentation of |fpromise::promise| for more information.
template <typename V>
inline promise_impl<::fpromise::internal::result_continuation<V, void>> make_ok_promise(V value) {
return make_result_promise(fpromise::ok(std::move(value)));
}
// Overload of |make_ok_promise()| used when the value type is void.
inline promise_impl<::fpromise::internal::result_continuation<void, void>> make_ok_promise() {
return make_result_promise(fpromise::ok());
}
// Returns an unboxed promise that immediately returns the specified error when invoked.
//
// This function is especially useful for returning promises from functions
// that have multiple branches some of which complete synchronously.
//
// |error| is the error for the promise to return.
//
// See documentation of |fpromise::promise| for more information.
template <typename E>
inline promise_impl<::fpromise::internal::result_continuation<void, E>> make_error_promise(
E error) {
return make_result_promise(fpromise::error(std::move(error)));
}
// Overload of |make_error_promise()| used when the error type is void.
inline promise_impl<::fpromise::internal::result_continuation<void, void>> make_error_promise() {
return make_result_promise(fpromise::error());
}
// Jointly evaluates zero or more promises.
// Returns a promise that produces a std::tuple<> containing the result
// of each promise once they all complete.
//
// EXAMPLE
//
// auto get_random_number() {
// return fpromise::make_promise([] { return rand() % 10 });
// }
//
// auto get_random_product() {
// auto f = get_random_number();
// auto g = get_random_number();
// return fpromise::join_promises(std::move(f), std::move(g))
// .and_then([] (std::tuple<fpromise::result<int>, fpromise::result<int>>& results) {
// return fpromise::ok(results.get<0>.value() + results.get<1>.value());
// });
// }
//
template <typename... Promises>
inline promise_impl<::fpromise::internal::join_continuation<Promises...>> join_promises(
Promises... promises) {
return make_promise_with_continuation(
::fpromise::internal::join_continuation<Promises...>(std::move(promises)...));
}
// Jointly evaluates zero or more homogenous promises (same result and error
// type). Returns a promise that produces a std::vector<> containing the
// result of each promise once they all complete.
//
// EXAMPLE
//
// auto get_random_number() {
// return fpromise::make_promise([] { return rand() % 10 });
// }
//
// auto get_random_product() {
// std::vector<fpromise::promise<int>> promises;
// promises.push_back(get_random_number());
// promises.push_back(get_random_number());
// return fpromise::join_promise_vector(std::move(promises))
// .and_then([] (std::vector<fpromise::result<int>>& results) {
// return fpromise::ok(results[0].value() + results[1].value());
// });
// }
//
template <typename V, typename E>
inline promise_impl<::fpromise::internal::join_vector_continuation<fpromise::promise<V, E>>>
join_promise_vector(std::vector<fpromise::promise<V, E>> promises) {
return make_promise_with_continuation(
::fpromise::internal::join_vector_continuation<fpromise::promise<V, E>>(std::move(promises)));
}
// Describes the status of a future.
enum class future_state {
// The future neither holds a result nor a promise that could produce a result.
// An empty future cannot make progress until a promise or result is assigned to it.
empty,
// The future holds a promise that may eventually produce a result but
// it currently doesn't have a result. The future's promise must be
// invoked in order to make progress from this state.
pending,
// The future holds a successful result.
ok,
// The future holds an error result.
error
};
// A |fpromise::future| holds onto a |fpromise::promise| until it has completed then
// provides access to its |fpromise::result|.
//
// SYNOPSIS
//
// |V| is the type of value produced when the completes successfully.
// Defaults to |void|.
//
// |E| is the type of error produced when the completes with an error.
// Defaults to |void|.
//
// THEORY OF OPERATION
//
// A future has a single owner who is responsible for setting its promise
// or result and driving its execution. Unlike |fpromise::promise|, a future retains
// the result produced by completion of its asynchronous task. Result retention
// eases the implementation of combined tasks that need to await the results
// of other tasks before proceeding.
//
// See the example for details.
//
// A future can be in one of four states, depending on whether it holds...
// - a successful result: |fpromise::future_state::ok|
// - an error result: |fpromise::future_state::error|
// - a promise that may eventually produce a result: |fpromise::future_state::pending|
// - neither: |fpromise::future_state_empty|
//
// On its own, a future is "inert"; it only makes progress in response to
// actions taken by its owner. The state of the future never changes
// spontaneously or concurrently.
//
// When the future's state is |fpromise::future_state::empty|, its owner is
// responsible for setting the future's promise or result thereby moving the
// future into the pending or ready state.
//
// When the future's state is |fpromise::future_state::pending|, its owner is
// responsible for calling the future's |operator()| to invoke the promise.
// If the promise completes and returns a result, the future will transition
// to the ok or error state according to the result. The promise itself will
// then be destroyed since it has fulfilled its purpose.
//
// When the future's state is |fpromise::future_state::ok|, its owner is responsible
// for consuming the stored value using |value()|, |take_value()|,
// |result()|, |take_result()|, or |take_ok_result()|.
//
// When the future's state is |fpromise::future_state::error|, its owner is
// responsible for consuming the stored error using |error()|, |take_error()|,
// |result()|, |take_result()|, or |take_error_result()|.
//
// See also |fpromise::promise| for more information about promises and their
// execution.
//
// EXAMPLE
//
// -
// https://fuchsia.googlesource.com/fuchsia/+/HEAD/zircon/system/utest/fit/examples/promise_example2.cc
template <typename V = void, typename E = void>
using future = future_impl<promise<V, E>>;
// Future implementation details.
// See |fpromise::future| documentation for more information.
template <typename Promise>
class future_impl final {
public:
// The type of promise held by the future.
using promise_type = Promise;
// The promise's result type.
// Equivalent to fpromise::result<value_type, error_type>.
using result_type = typename Promise::result_type;
// The type of value produced when the promise completes successfully.
// May be void.
using value_type = typename Promise::value_type;
// The type of value produced when the promise completes with an error.
// May be void.
using error_type = typename Promise::error_type;
// Creates a future in the empty state.
future_impl() = default;
future_impl(decltype(nullptr)) {}
// Creates a future and assigns a promise to compute its result.
// If the promise is empty, the future enters the empty state.
// Otherwise the future enters the pending state.
explicit future_impl(promise_type promise) {
if (promise) {
state_.template emplace<1>(std::move(promise));
}
}
// Creates a future and assigns its result.
// If the result is pending, the future enters the empty state.
// Otherwise the future enters the ok or error state.
explicit future_impl(result_type result) {
if (result) {
state_.template emplace<2>(std::move(result));
}
}
// Moves from another future, leaving the other one in an empty state.
future_impl(future_impl&& other) : state_(std::move(other.state_)) {
other.state_.template emplace<0>();
}
// Destroys the promise, releasing its promise and result (if any).
~future_impl() = default;
// Returns the state of the future: empty, pending, ok, or error.
future_state state() const {
switch (state_.index()) {
case 0:
return future_state::empty;
case 1:
return future_state::pending;
case 2:
return cpp17::get<2>(state_).is_ok() ? future_state::ok : future_state::error;
}
__builtin_unreachable();
}
// Returns true if the future's state is not |fpromise::future_state::empty|:
// it either holds a result or holds a promise that can be invoked to make
// progress towards obtaining a result.
explicit operator bool() const { return !is_empty(); }
// Returns true if the future's state is |fpromise::future_state::empty|:
// it does not hold a result or a promise so it cannot make progress.
bool is_empty() const { return state() == fpromise::future_state::empty; }
// Returns true if the future's state is |fpromise::future_state::pending|:
// it does not hold a result yet but it does hold a promise that can be invoked
// to make progress towards obtaining a result.
bool is_pending() const { return state() == fpromise::future_state::pending; }
// Returns true if the future's state is |fpromise::future_state::ok|:
// it holds a value that can be retrieved using |value()|, |take_value()|,
// |result()|, |take_result()|, or |take_ok_result()|.
bool is_ok() const { return state() == fpromise::future_state::ok; }
// Returns true if the future's state is |fpromise::future_state::error|:
// it holds an error that can be retrieved using |error()|, |take_error()|,
// |result()|, |take_result()|, or |take_error_result()|.
bool is_error() const { return state() == fpromise::future_state::error; }
// Returns true if the future's state is either |fpromise::future_state::ok| or
// |fpromise::future_state::error|.
bool is_ready() const { return state_.index() == 2; }
// Evaluates the future and returns true if its result is ready.
// Asserts that the future is not empty.
//
// If the promise completes and returns a result, the future will transition
// to the ok or error state according to the result. The promise itself will
// then be destroyed since it has fulfilled its purpose.
bool operator()(fpromise::context& context) {
switch (state_.index()) {
case 0:
return false;
case 1: {
result_type result = cpp17::get<1>(state_)(context);
if (!result)
return false;
state_.template emplace<2>(std::move(result));
return true;
}
case 2:
return true;
}
__builtin_unreachable();
}
// Gets a reference to the future's promise.
// Asserts that the future's state is |fpromise::future_state::pending|.
const promise_type& promise() const {
assert(is_pending());
return cpp17::get<1>(state_);
}
// Takes the future's promise, leaving it in an empty state.
// Asserts that the future's state is |fpromise::future_state::pending|.
promise_type take_promise() {
assert(is_pending());
auto promise = std::move(cpp17::get<1>(state_));
state_.template emplace<0>();
return promise;
}
// Gets a reference to the future's result.
// Asserts that the future's state is |fpromise::future_state::ok| or
// |fpromise::future_state::error|.
result_type& result() {
assert(is_ready());
return cpp17::get<2>(state_);
}
const result_type& result() const {
assert(is_ready());
return cpp17::get<2>(state_);
}
// Takes the future's result, leaving it in an empty state.
// Asserts that the future's state is |fpromise::future_state::ok| or
// |fpromise::future_state::error|.
result_type take_result() {
assert(is_ready());
auto result = std::move(cpp17::get<2>(state_));
state_.template emplace<0>();
return result;
}
// Gets a reference to the future's value.
// Asserts that the future's state is |fpromise::future_state::ok|.
template <typename R = value_type, typename = std::enable_if_t<!std::is_void<R>::value>>
R& value() {
assert(is_ok());
return cpp17::get<2>(state_).value();
}
template <typename R = value_type, typename = std::enable_if_t<!std::is_void<R>::value>>
const R& value() const {
assert(is_ok());
return cpp17::get<2>(state_).value();
}
// Takes the future's value, leaving it in an empty state.
// Asserts that the future's state is |fpromise::future_state::ok|.
template <typename R = value_type, typename = std::enable_if_t<!std::is_void<R>::value>>
R take_value() {
assert(is_ok());
auto value = cpp17::get<2>(state_).take_value();
state_.template emplace<0>();
return value;
}
ok_result<value_type> take_ok_result() {
assert(is_ok());
auto result = cpp17::get<2>(state_).take_ok_result();
state_.template emplace<0>();
return result;
}
// Gets a reference to the future's error.
// Asserts that the future's state is |fpromise::future_state::error|.
template <typename R = error_type, typename = std::enable_if_t<!std::is_void<R>::value>>
R& error() {
assert(is_error());
return cpp17::get<2>(state_).error();
}
template <typename R = error_type, typename = std::enable_if_t<!std::is_void<R>::value>>
const R& error() const {
assert(is_error());
return cpp17::get<2>(state_).error();
}
// Takes the future's error, leaving it in an empty state.
// Asserts that the future's state is |fpromise::future_state::error|.
template <typename R = error_type, typename = std::enable_if_t<!std::is_void<R>::value>>
R take_error() {
assert(is_error());
auto error = cpp17::get<2>(state_).take_error();
state_.template emplace<0>();
return error;
}
error_result<error_type> take_error_result() {
assert(is_error());
auto result = cpp17::get<2>(state_).take_error_result();
state_.template emplace<0>();
return result;
}
// Move assigns from another future, leaving the other one in an empty state.
future_impl& operator=(future_impl&& other) = default;
// Discards the future's promise and result, leaving it empty.
future_impl& operator=(decltype(nullptr)) {
state_.template emplace<0>();
return *this;
}
// Assigns a promise to compute the future's result.
// If the promise is empty, the future enters the empty state.
// Otherwise the future enters the pending state.
future_impl& operator=(promise_type promise) {
if (promise) {
state_.template emplace<1>(std::move(promise));
} else {
state_.template emplace<0>();
}
return *this;
}
// Assigns the future's result.
// If the result is pending, the future enters the empty state.
// Otherwise the future enters the ok or error state.
future_impl& operator=(result_type result) {
if (result) {
state_.template emplace<2>(std::move(result));
} else {
state_.template emplace<0>();
}
return *this;
}
// Swaps the futures' contents.
void swap(future_impl& other) {
using std::swap;
swap(state_, other.state_);
}
future_impl(const future_impl&) = delete;
future_impl& operator=(const future_impl&) = delete;
private:
cpp17::variant<cpp17::monostate, promise_type, result_type> state_;
};
template <typename Promise>
void swap(future_impl<Promise>& a, future_impl<Promise>& b) {
a.swap(b);
}
template <typename Promise>
bool operator==(const future_impl<Promise>& f, decltype(nullptr)) {
return !f;
}
template <typename Promise>
bool operator==(decltype(nullptr), const future_impl<Promise>& f) {
return !f;
}
template <typename Promise>
bool operator!=(const future_impl<Promise>& f, decltype(nullptr)) {
return !!f;
}
template <typename Promise>
bool operator!=(decltype(nullptr), const future_impl<Promise>& f) {
return !!f;
}
// Makes a future containing the specified promise.
template <typename Promise>
future_impl<Promise> make_future(Promise promise) {
return future_impl<Promise>(std::move(promise));
}
// A pending task holds a |fpromise::promise| that can be scheduled to run on
// a |fpromise::executor| using |fpromise::executor::schedule_task()|.
//
// An executor repeatedly invokes a pending task until it returns true,
// indicating completion. Note that the promise's resulting value or error
// is discarded since it is not meaningful to the executor. If you need
// to consume the result, use a combinator such as |fpromise::pending::then()|
// to capture it prior to wrapping the promise into a pending task.
//
// See documentation of |fpromise::promise| for more information.
class pending_task final {
public:
// The type of promise held by this task.
using promise_type = promise<void, void>;
// Creates an empty pending task without a promise.
pending_task() = default;
// Creates a pending task that wraps an already boxed promise that returns
// |fpromise::result<void, void>|.
pending_task(promise_type promise) : promise_(std::move(promise)) {}
// Creates a pending task that wraps any kind of promise, boxed or unboxed,
// regardless of its result type and with any context that is assignable
// from this task's context type.
template <typename Continuation>
pending_task(promise_impl<Continuation> promise)
: promise_(promise ? promise.discard_result().box() : promise_type()) {}
pending_task(pending_task&&) = default;
pending_task& operator=(pending_task&&) = default;
// Destroys the pending task, releasing its promise.
~pending_task() = default;
// Returns true if the pending task is non-empty (has a valid promise).
explicit operator bool() const { return !!promise_; }
// Evaluates the pending task.
// If the task completes (returns a non-pending result), the task reverts
// to an empty state (because the promise it holds has reverted to an empty
// state) and returns true.
// It is an error to invoke this method if the pending task is empty.
bool operator()(fpromise::context& context) { return !promise_(context).is_pending(); }
// Extracts the pending task's promise.
promise_type take_promise() { return std::move(promise_); }
pending_task(const pending_task&) = delete;
pending_task& operator=(const pending_task&) = delete;
private:
promise_type promise_;
};
// Execution context for an asynchronous task, such as a |fpromise::promise|,
// |fpromise::future|, or |fpromise::pending_task|.
//
// When a |fpromise::executor| executes a task, it provides the task with an
// execution context which enables the task to communicate with the
// executor and manage its own lifecycle. Specialized executors may subclass
// |fpromise::context| and offer additional methods beyond those which are
// defined here, such as to provide access to platform-specific features
// supported by the executor.
//
// The context provided to a task is only valid within the scope of a single
// invocation; the task must not retain a reference to the context across
// invocations.
//
// See documentation of |fpromise::promise| for more information.
class context {
public:
// Gets the executor that is running the task, never null.
virtual class executor* executor() const = 0;
// Obtains a handle that can be used to resume the task after it has been
// suspended.
//
// Clients should call this method before returning |fpromise::pending()| from
// the task. See documentation on |fpromise::executor|.
virtual suspended_task suspend_task() = 0;
// Converts this context to a derived context type.
template <typename Context, typename = std::enable_if_t<std::is_base_of<context, Context>::value>>
Context& as() & {
// TODO(fxbug.dev/4060): We should perform a run-time type check here rather
// than blindly casting. That's why this method exists.
return static_cast<Context&>(*this);
}
protected:
virtual ~context() = default;
};
// An abstract interface for executing asynchronous tasks, such as promises,
// represented by |fpromise::pending_task|.
//
// EXECUTING TASKS
//
// An executor evaluates its tasks incrementally. During each iteration
// of the executor's main loop, it invokes the next task from its ready queue.
//
// If the task returns true, then the task is deemed to have completed.
// The executor removes the tasks from its queue and destroys it since there
// it nothing left to do.
//
// If the task returns false, then the task is deemed to have voluntarily
// suspended itself pending some event that it is awaiting. Prior to
// returning, the task should acquire at least one |fpromise::suspended_task|
// handle from its execution context using |fpromise::context::suspend_task()|
// to provide a means for the task to be resumed once it can make forward
// progress again.
//
// Once the suspended task is resumed with |fpromise::suspended_task::resume()|, it
// is moved back to the ready queue and it will be invoked again during a later
// iteration of the executor's loop.
//
// If all |fpromise::suspended_task| handles for a given task are destroyed without
// the task ever being resumed then the task is also destroyed since there
// would be no way for the task to be resumed from suspension. We say that
// such a task has been "abandoned".
//
// The executor retains single-ownership of all active and suspended tasks.
// When the executor is destroyed, all of its remaining tasks are also
// destroyed.
//
// Please read |fpromise::promise| for a more detailed explanation of the
// responsibilities of tasks and executors.
//
// NOTES FOR IMPLEMENTORS
//
// This interface is designed to support a variety of different executor
// implementations. For example, one implementation might run its tasks on
// a single thread whereas another might dispatch them on an event-driven
// message loop or use a thread pool.
//
// See also |fpromise::single_threaded_executor| for a concrete implementation.
class executor {
public:
// Destroys the executor along with all of its remaining scheduled tasks
// that have yet to complete.
virtual ~executor() = default;
// Schedules a task for eventual execution by the executor.
//
// This method is thread-safe.
virtual void schedule_task(pending_task task) = 0;
};
// Represents a task that is awaiting resumption.
//
// This object has RAII semantics. If the task is not resumed by at least
// one holder of its |suspended_task| handles, then it will be destroyed
// by the executor since it is no longer possible for the task to make
// progress. The task is said have been "abandoned".
//
// See documentation of |fpromise::executor| for more information.
class suspended_task final {
public:
// A handle that grants the capability to resume a suspended task.
// Each issued ticket must be individually resolved.
using ticket = uint64_t;
// The resolver mechanism implements a lightweight form of reference
// counting for tasks that have been suspended.
//
// When a suspended task is created in a non-empty state, it receives
// a pointer to a resolver interface and a ticket. The ticket is
// a one-time-use handle that represents the task that was suspended
// and provides a means to resume it. The |suspended_task| class ensures
// that every ticket is precisely accounted for.
//
// When |suspended_task::resume_task()| is called on an instance with
// a valid ticket, the resolver's |resolve_ticket()| method is invoked
// passing the ticket's value along with *true* to resume the task. This
// operation consumes the ticket so the |suspended_task| transitions to
// an empty state. The ticket and resolver cannot be used again by
// this |suspended_task| instance.
//
// Similarly, when |suspended_task::reset()| is called on an instance with
// a valid ticket or when the task goes out of scope on such an instance,
// the resolver's |resolve_ticket()| method is invoked but this time passes
// *false* to not resume the task. As before, the ticket is consumed.
//
// Finally, when the |suspended_task| is copied, its ticket is duplicated
// using |duplicate_ticket()| resulting in two tickets, both of which
// must be individually resolved.
//
// Resuming a task that has already been resumed has no effect.
// Conversely, a task is considered "abandoned" if all of its tickets
// have been resolved without it ever being resumed. See documentation
// of |fpromise::promise| for more information.
//
// The methods of this class are safe to call from any thread, including
// threads that may not be managed by the task's executor.
class resolver {
public:
// Duplicates the provided ticket, returning a new ticket.
// Note: The new ticket may have the same numeric value as the
// original ticket but should be considered a distinct instance
// that must be separately resolved.
virtual ticket duplicate_ticket(ticket ticket) = 0;
// Consumes the provided ticket, optionally resuming its associated task.
// The provided ticket must not be used again.
virtual void resolve_ticket(ticket ticket, bool resume_task) = 0;
protected:
virtual ~resolver() = default;
};
suspended_task() : resolver_(nullptr), ticket_(0) {}
suspended_task(resolver* resolver, ticket ticket) : resolver_(resolver), ticket_(ticket) {}
suspended_task(const suspended_task& other);
suspended_task(suspended_task&& other);
// Releases the task without resumption.
//
// Does nothing if this object does not hold a ticket.
~suspended_task();
// Returns true if this object holds a ticket for a suspended task.
explicit operator bool() const { return resolver_ != nullptr; }
// Asks the task's executor to resume execution of the suspended task
// if it has not already been resumed or completed. Also releases
// the task's ticket as a side-effect.
//
// Clients should call this method when it is possible for the task to
// make progress; for example, because some event the task was
// awaiting has occurred. See documentation on |fpromise::executor|.
//
// Does nothing if this object does not hold a ticket.
void resume_task() { resolve(true); }
// Releases the suspended task without resumption.
//
// Does nothing if this object does not hold a ticket.
void reset() { resolve(false); }
// Swaps suspended tasks.
void swap(suspended_task& other);
suspended_task& operator=(const suspended_task& other);
suspended_task& operator=(suspended_task&& other);
private:
void resolve(bool resume_task);
resolver* resolver_;
ticket ticket_;
};
inline void swap(suspended_task& a, suspended_task& b) { a.swap(b); }
} // namespace fpromise
#endif // LIB_FIT_PROMISE_INCLUDE_LIB_FPROMISE_PROMISE_H_