blob: 62c67e9c959f494036d0bed8bf67a7adac187748 [file] [log] [blame]
// Copyright 2021 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_FASYNC_INCLUDE_LIB_FASYNC_FUTURE_H_
#define SRC_LIB_FASYNC_INCLUDE_LIB_FASYNC_FUTURE_H_
#include <lib/fasync/internal/compiler.h>
LIB_FASYNC_CPP_VERSION_COMPAT_BEGIN
#include <lib/fasync/internal/future.h>
#include <lib/fitx/internal/result.h>
#include <lib/fitx/result.h>
#include <lib/stdcompat/optional.h>
#include <lib/stdcompat/span.h>
#include <lib/stdcompat/type_traits.h>
#include <lib/stdcompat/utility.h>
#include <compare>
#include <future>
#include <iostream>
#include <iterator>
#include <new>
#include <optional>
#include <sstream>
#include <thread>
#include <type_traits>
#include <variant>
#include <vector>
namespace fasync {
// An |fasync::future| 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 future using a variety of combinators such
// as |fasync::then()|.
//
// Use |fasync::make_future()| to create a future.
// Use |fasync::make_value_future()| to create a future that immediately returns a value.
// Use |fasync::make_ok_future()| to create a future that immediately returns a success value.
// Use |fasync::make_error_future()| to create a future that immediately returns an error.
// Use |fasync::make_try_future()| to create a future that immediately returns a result.
// Use |fasync::pending_task| to wrap a future as a pending task for execution.
// Use |fasync::executor| to execute a pending task.
// See examples below.
//
// Always look to the future; never look back.
//
// |FASYNC::FUTURE<T>| SYNOPSIS
//
// |T| or |fasync::future_output_t<F>| is the type of value produced when the future completes.
//
// |FASYNC::TRY_FUTURE<E, T>| SYNOPSIS
//
// |E| or |fasync::future_error_t<F>| is the type of value produced when the future completes with
// an error.
//
// |T| or |fasync::future_value_t<F>| is the type of value produced when the future completes
// successfully. When the underlying |fitx::result| has no |::value_type|, then there is no future
// value type either.
//
// FASYNC::FUTURE AND FASYNC::TRY_FUTURE
//
// For the rest of this document, references to |fasync::future| should be taken to apply to both
// |fasync::future| and |fasync::try_future|. |fasync::future| produces a bare value (which makes it
// easier for us to interact with C++20 coroutines going forward) and |fasync::try_future| is simply
// a typedef for an |fasync::future| that produces a |fitx::result|, which is necessary to use
// combinators like |fasync::and_then()|, |fasync::or_else()|, etc.
//
// The word "output" is used to refer to the type or value produced by the bare future, which for an
// |fasync::try_future| is a |fitx::result|. The word "value" is used (mostly) to refer to the
// success value produced by an |fasync::try_future|, and the word "error" is used to refer to the
// error value produced by an |fasync::try_future|.
//
// CHAINING FUTURES USING COMBINATORS
//
// Futures can be chained together using combinators such as |fasync::then()| which consume the
// original future(s) and return a new combined future.
//
// For example, the |fasync::then()| combinator returns a future that has the effect of
// asynchronously awaiting completion of the prior future (the instance upon which |fasync::then()|
// was called) then delivering its result to a handler function.
//
// Available combinators defined in this library:
//
// |fasync::map()|: run a handler when prior future completes
// |fasync::map_ok()|: run a handler when prior future completes successfully
// |fasync::map_error()|: run a handler when prior future completes with an error
// |fasync::flatten()|: turn a nested future into one with one less layer of nesting
// |fasync::flatten_all()|: turn a nested future into one with all nesting removed up to the
// encounter of a non-future return type
// |fasync::then()|: run a handler when prior future completes, unwrapping returned futures
// |fasync::and_then()|: run a handler when prior future completes successfully, unwrapping
// returned futures
// |fasync::or_else()|: run a handler when prior future completes with an error, unwrapping
// returned futures
// |fasync::inspect()|: examine result of prior future
// |fasync::inspect_ok()|: examine successful result of prior future
// |fasync::inspect_error()|: examine error result of prior future
// |fasync::discard()|: discard result and unconditionally return void when prior future
// completes
// |fasync::wrap_with()|: applies a wrapper to the future
// |fasync::box()|: wraps the future's continuation into a |fit::function|
// |fasync::join()|: await multiple futures in an argument list, once they all complete return
// a corresponding container of their outputs.
// |fasync::join_with()|: like |fasync::join()|, but can be used in the middle of a pipeline
// |fasync::schedule_on()|: schedules the future for execution on the given executor
// |fasync::block_on()|: blocks the current thread to execute the future on the given executor.
//
// You can also create your own custom combinators by crafting new types of continuations.
//
// CONTINUATIONS AND HANDLERS
//
// Internally, |fasync::future| 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 future's continuation is generated through the use of factories such as |fasync::make_future()|
// and combinators such as |fasync::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 |fasync::context&| argument
// and return a |fasync::poll|. 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.
//
// HANDLER TYPES
//
// |fasync::future| is flexible to a fault with respect to acceptable handler signatures; the old
// |fpromise::promise| was very rigid about this due to an outdated callable traits library, but
// |fasync::future| is fully `INVOKE()`-aware (see [func.require] in C++17 and later). This means
// that not only can auto be used for parameter types, but so can variadics and even fancier things
// can happen with automatic destructuring and more.
//
// Here is a situation in the old |fpromise::promise|:
//
// 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());
// });
// }
//
// See how the handler must explicitly take a reference to a tuple of results; imagine how much
// worse it would be with three or more results or results with errors!
//
// Here is a spiced up example with |fasync::future|:
//
// auto get_random_number() {
// return fasync::make_future([] { return fitx::ok(rand() % 10); });
// }
//
// auto get_random_product() {
// return fasync::join(get_random_number(), get_random_number(), get_random_number()) |
// fasync::then([](auto& result1, auto& result2, auto& result3) {
// return fitx::ok(result1.value() + result2.value() + result3.value());
// });
// }
//
// We can get even more ergonomic with an arbitrary number of futures and C++17 fold expressions:
//
// auto get_random_product() {
// return fasync::join(get_random_number(), get_random_number(), get_random_number()) |
// fasync::then([](auto&... results) {
// return fitx::ok((results.value() + ...));
// });
// }
//
// In general a handler can take its argument by any of:
// - T&
// - const T&
// - auto (if copyable)
// - auto&
// - const auto&
// - auto&& (sometimes advisable as a "universal reference")
// - auto&... and other parameter pack variations on the above
//
// Handlers are also allowed to ignore all their parameters.
//
// In addition, a handler can take an optional |fasync::context&| or |const fasync::context&|
// parameter as the first argument, for example in order to suspend itself. Acceptable return types
// are be documented on the corresponding methods.
//
// THEORY OF OPERATION
//
// 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.
//
// Typically, a future is executed by wrapping it into a |fasync::pending_task| and scheduling it
// for execution using |fasync::executor::schedule()| or |fasync::schedule_on|. A future's
// |operator(fasync::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.
//
// |fasync::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 |fasync::context|. The continuation attempts to make progress then
// returns a value of type |fasync::poll| to indicate whether it completed (signaled by
// |fasync::ready|) or was unable to complete the task during that invocation (signaled by
// |fasync::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
// |fasync::context::suspend_task()| to acquire a |fasync::suspended_task| object. The continuation
// then arranges for the task to be resumed asynchronously (with |fasync::suspended_task::resume()|)
// once it becomes possible for the future to make forward progress again. Finally, the continuation
// returns returns |fasync::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 resumed and
// all remaining |fasync::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 |fasync::single_threaded_executor| for a simple executor implementation.
//
// BOXED AND UNBOXED FUTURES
//
// To make combination and execution as efficient as possible, the futures returned by
// |fasync::make_future| 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" futures. In contrast, "boxed" futures 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 futures by their types.
// Here are two examples:
//
// - A boxed future type: |fasync::future<>| which is an alias for
// |fasync::unboxed_future<fit::function<fasync::poll<>(fasync::context&)>|.
// - An unboxed future type: |fasync::unboxed_future<
// fasync::internal::then_future<...something unintelligible...>>|
//
// Even though |fasync::unboxed_future| shows up in the type of the example boxed future, the
// presence of |fit::function| makes it boxed, and the presence of fasync::unboxed_future here is
// normally not discovered by the user.
//
// Although boxed futures are easier to manipulate, they may cause the continuation to be allocated
// on the heap. Chaining boxed futures can result in multiple allocations being produced.
//
// Conversely, unboxed futures 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 futures together
// into a single object that is easier to optimize.
//
// Unboxed futures can be boxed by assigning them to a boxed future type (such as
// |fasync::future<>|) or using the |fasync::box()| combinator.
//
// As a rule of thumb, always defer boxing of futures until it is necessary to transport them using
// a simpler type.
//
// Do this: (chaining as a single expression performs at most one heap allocation)
//
// fasync::future<> f = fasync::make_future([] { ... }) |
// fasync::then([](fitx::result<fitx::failed>& result) { ... }) |
// fasync::and_then([] { ... });
//
// Or this: (still only performs at most one heap allocation)
//
// auto f = fasync::make_future([] { ... });
// auto g = fasync::then(std::move(f), [](fitx::result<fitx::failed>& result) { ... });
// auto h = fasync::and_then(std::move(g), [] { ... });
// fasync::future<> boxed_h = h;
//
// But don't do this: (incurs up to three heap allocations due to eager boxing)
//
// fasync::try_future<fitx::failed> f = fasync::make_future([] { ... });
// fasync::try_future<fitx::failed> g = fasync::then(
// std::move(f),
// [](fitx::result<fitx::failed>& result) { ... });
// fasync::try_future<fitx::failed> h = fasync::and_then(std::move(g), [] { ... });
//
// SINGLE OWNERSHIP MODEL
//
// Futures have a single-ownership semantics. This means that there can only be at most onex
// reference to the task represented by its continuation along with any state held by that
// continuation.
//
// When a combinator is applied to a future, ownership of its continuation is transferred to the
// combined future, leaving the original future in an "empty" state without a continuation. Note
// that it is an error to attempt to invoke an empty future.
//
// This model greatly simplifies reasoning about object lifetime. If a future goes out of scope
// without completing its task, the task is considered "abandoned", causing all associated state to
// be destroyed.
//
// Note that a future may capture references to other objects whose lifetime differs from that of
// the future. It is the responsibility of the future 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
//
// Future objects are not thread-safe themselves. You cannot call their methods concurrently (or
// re-entrantly). However, futures 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
//
// A future'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 future's result is returned directly to its
// invoker; it is not copied or retained within the future object itself. It is entirely the
// caller's responsibility to decide how to consume or retain the result if need be.
//
// CLARIFICATION OF NOMENCLATURE
//
// In this library, the word "future" has the following definition:
//
// - A *future* holds the function that performs an asynchronous task.
// It is the means to produce a value.
//
// Be aware that other libraries may use this term 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.
//
// |fasync::future| and |fasync::try_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 |fasync::future| quite versatile. |fasync::future| can
// also interoperate with other task dispatching mechanisms (including |std::future|) using
// adapters such as |fasync::bridge|.
//
// EXAMPLE
//
// -
// https://fuchsia.googlesource.com/fuchsia/+/HEAD/src/lib/fasync/tests/future_example.cc
// Make a future that immediately resolves with the given value.
template <typename T>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::value_future<T> make_value_future(T&& value) {
return ::fasync::internal::value_future<T>(std::forward<T>(value));
}
// Same as above, but construct in-place.
template <typename T, typename... Args>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::value_future<T> make_value_future(
Args&&... args) {
return ::fasync::internal::value_future<T>(std::forward<Args>(args)...);
}
// Make a future that immediately resolves with a |fitx::result|.
template <typename E, typename... Ts>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::result_future<E, Ts...> make_try_future(
::fitx::result<E, Ts...>&& r) {
return ::fasync::internal::result_future<E, Ts...>(std::forward<::fitx::result<E, Ts...>>(r));
}
// Construct in-place.
template <typename E, typename... Ts, typename... Args>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::result_future<E, Ts...> make_try_future(
Args&&... args) {
return ::fasync::internal::result_future<E, Ts...>(std::forward<Args>(args)...);
}
// Make a future that resolves with |fitx::result<fitx::failed>|, bearing the ok value.
LIB_FASYNC_NODISCARD inline constexpr ::fasync::internal::ok_future<> make_ok_future() {
return ::fasync::internal::ok_future<>(::fitx::ok());
}
// Make a future that resolves with |fitx::result<fitx::failed, T>|, bearing the T value.
template <typename T>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::ok_future<T> make_ok_future(T&& value) {
return ::fasync::internal::ok_future<T>(::fitx::ok(std::forward<T>(value)));
}
// Construct in-place.
template <typename T, typename... Args>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::ok_future<T> make_ok_future(Args&&... args) {
return ::fasync::internal::ok_future<T>(::fitx::ok(std::forward<Args>(args)...));
}
// Make a future that resolves with |fitx::result<E>|, bearing the E value.
template <typename E>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::error_future<E> make_error_future(E&& e) {
return ::fasync::internal::error_future<E>(::fitx::as_error(std::forward<E>(e)));
}
// Construct in-place.
template <typename E, typename... Args>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::error_future<E> make_error_future(
Args&&... args) {
return ::fasync::internal::error_future<E>(::fitx::as_error(std::forward<Args>(args)...));
}
// Make a future that resolves to |fitx::result<fitx::failed>|, bearing the failed value.
LIB_FASYNC_NODISCARD inline constexpr ::fasync::internal::failed_future make_failed_future() {
return ::fasync::internal::failed_future(::fitx::failed());
}
// Make a future whose poll type is |fasync::poll<Ts...>| but always returns pending.
template <typename... Ts>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::pending_future<Ts...> make_pending_future() {
return ::fasync::internal::pending_future<Ts...>();
}
// Make a future whose poll type is |fasync::try_poll<E, Ts...>| but always returns pending.
template <typename E, typename... Ts>
LIB_FASYNC_NODISCARD constexpr ::fasync::internal::pending_try_future<E, Ts...>
make_pending_try_future() {
return ::fasync::internal::pending_try_future<E, Ts...>();
}
// |fasync::unboxed_future<F>|
//
// Can hold any type for which |fasync::is_future_v<F>| holds, and is a future itself.
template <typename F>
class unboxed_future {
static_assert(is_future_v<F>,
"|fasync::unboxed_future<T>| must hold a valid future type for which "
"|fasync::is_future_v<T>| is true.");
static_assert(std::is_same_v<F, std::decay_t<F>>,
"F may not be a reference, array, or function type.");
public:
constexpr unboxed_future() = delete;
constexpr unboxed_future(const unboxed_future&) = delete;
constexpr unboxed_future& operator=(const unboxed_future&) = delete;
constexpr unboxed_future(unboxed_future&&) = default;
constexpr unboxed_future& operator=(unboxed_future&&) = default;
template <typename G, ::fasync::internal::requires_conditions<std::is_constructible<F, G>> = true>
constexpr unboxed_future(G&& future) : future_(std::forward<G>(future)) {}
// Allow throwing away the output of the given future; important for e.g. constructing an
// |fasync::future<>| from anything like in |fasync::pending_task|.
// Implemented with lambdas because |fasync::discard| is not defined yet.
// TODO(schottm): allow throwing away success or error with try_futures?
template <
typename FF = F, typename G,
::fasync::internal::requires_conditions<is_void_future<FF>, is_future<G>,
cpp17::negation<std::is_constructible<F, G>>> = true>
constexpr unboxed_future(G&& future)
: future_(::fasync::internal::discard_future<G>(std::forward<G>(future))) {}
template <typename G, ::fasync::internal::requires_conditions<cpp17::negation<std::is_same<F, G>>,
std::is_constructible<F, G>> = true>
constexpr unboxed_future(unboxed_future<G>&& other) : future_(std::move(other.future_)) {}
// Invokes the future's continuation.
//
// This method should be called by an executor to evaluate the future. If the |poll.is_pending()|
// is true, then the executor is responsible for arranging to invoke the future again once it
// determines that it is possible to make progress towards completion of the encapsulated future.
//
// Once the continuation returns an |fasync::poll| with status |poll.is_ready()|, the future
// should not be invoked again.
//
// The future must be non-empty.
constexpr future_poll_t<F> operator()(context& context) {
return cpp20::invoke(future_, context);
}
private:
template <typename G>
friend class unboxed_future;
F future_;
};
#if LIB_FASYNC_HAS_CPP_FEATURE(deduction_guides)
template <typename F>
unboxed_future(F&&) -> unboxed_future<std::decay_t<F>>;
template <typename F>
unboxed_future(unboxed_future<F>&&) -> unboxed_future<F>;
#endif
// Type-erased future type, usually heap-allocated.
template <typename... Ts>
using future = unboxed_future<::fit::function<poll<Ts...>(context&)>>;
// Same but for |fitx::result|.
template <typename E = ::fitx::failed, typename... Ts>
using try_future = future<::fitx::result<E, Ts...>>;
// |fasync::pending_task|
//
// A pending task holds an |fasync::future| that can be scheduled to run on an |fasync::executor|
// using |fasync::executor::schedule_task()|.
//
// An executor repeatedly invokes a pending task until it returns true, indicating completion. Note
// that the future'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 |fasync::then()| to capture
// it prior to wrapping the future into a pending task.
//
// See documentation of |fasync::future| for more information.
class pending_task final {
public:
// The type of future held by this task.
using future_type = future<>;
pending_task() = delete;
pending_task(const pending_task&) = delete;
pending_task& operator=(const pending_task&) = delete;
pending_task(pending_task&&) = default;
pending_task& operator=(pending_task&&) = default;
// Destroys the pending task, releasing its future.
~pending_task() = default;
// Creates a pending task that wraps any kind of future, boxed or unboxed, regardless of its
// result type and with any context that is assignable from this task's context type.
template <typename F, ::fasync::internal::requires_conditions<is_future<F>> = true>
pending_task(F&& future) : future_(std::forward<F>(future)) {}
// Evaluates the pending task.
// If the task completes (returns a non-pending result), the task returns true and must not be
// invoked again.
bool operator()(fasync::context& context) { return !future_(context).is_pending(); }
// Extracts the pending task's future.
future_type take_future() { return std::move(future_); }
private:
future_type future_;
};
// |fasync::executor|
//
// An abstract interface for executing asynchronous tasks, such as futures, represented by
// |fasync::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 is 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
// |fasync::suspended_task| handle from its execution context using
// |fasync::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 |fasync::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 |fasync::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 |fasync::future| 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 |fasync::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(pending_task&& task) = 0;
};
// |fasync::suspended_task|
//
// 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 |fasync::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()| 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 |fasync::future| 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)
: resolver_(other.resolver_),
ticket_(resolver_ ? resolver_->duplicate_ticket(other.ticket_) : 0) {}
suspended_task(suspended_task&& other) : resolver_(other.resolver_), ticket_(other.ticket_) {
other.resolver_ = nullptr;
}
// Releases the task without resumption.
//
// Does nothing if this object does not hold a ticket.
~suspended_task() { reset(); }
// 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
// |fasync::executor|.
//
// Does nothing if this object does not hold a ticket.
void resume() { 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) {
if (this != &other) {
using std::swap;
swap(resolver_, other.resolver_);
swap(ticket_, other.ticket_);
}
}
suspended_task& operator=(const suspended_task& other) {
if (this != &other) {
reset();
resolver_ = other.resolver_;
ticket_ = resolver_ ? resolver_->duplicate_ticket(other.ticket_) : 0;
}
return *this;
}
suspended_task& operator=(suspended_task&& other) {
if (this != &other) {
reset();
resolver_ = other.resolver_;
ticket_ = other.ticket_;
other.resolver_ = nullptr;
}
return *this;
}
private:
void resolve(bool resume_task) {
if (resolver_) {
// Move the ticket to the stack to guard against possible re-entrance occurring as a
// side-effect of the task's own destructor running.
resolver* cached_resolver = resolver_;
ticket cached_ticket = ticket_;
resolver_ = nullptr;
cached_resolver->resolve_ticket(cached_ticket, resume_task);
}
}
resolver* resolver_;
ticket ticket_;
};
inline void swap(suspended_task& a, suspended_task& b) { a.swap(b); }
// |fasync::context|
//
// Execution context for an asynchronous task, such as a |fasync::future| or |fasync::pending_task|.
//
// When a |fasync::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 |fasync::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 |fasync::future| for more information.
class context {
public:
// Gets the executor that is running the task.
virtual 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 |fasync::pending()| from the task. See
// documentation on |fasync::executor|.
virtual suspended_task suspend_task() = 0;
// Converts this context to a derived context type.
template <typename C, ::fasync::internal::requires_conditions<std::is_base_of<context, C>> = true>
C& 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<C&>(*this);
}
// protected:
virtual ~context() = default;
};
namespace internal {
template <typename F, typename G>
LIB_FASYNC_NODISCARD constexpr compose_wrapper_t<F, G> compose(F&& f, G&& g) {
return compose_wrapper_t<F, G>(std::forward<F>(f), std::forward<G>(g));
}
// |future_adaptor_closure|
//
// This is the bread and butter of our |operator|()| composition, a curiously recurring template
// pattern (CRTP) class that adds the |operator|()| functionality to our closure objects. This
// allows both taking a future from the left (the normal method of operation) and pre-composing
// adaptors from the right.
template <typename T>
struct future_adaptor_closure {
template <typename Future, typename Closure,
requires_conditions<is_future<Future>, is_future_adaptor_closure<Closure>,
std::is_same<T, cpp20::remove_cvref_t<Closure>>,
cpp17::is_invocable<Closure, Future>> = true>
friend constexpr decltype(auto) operator|(Future&& future, Closure&& closure) noexcept(
std::is_nothrow_invocable_v<Closure, Future>) {
return cpp20::invoke(std::forward<Closure>(closure), std::forward<Future>(future));
}
template <typename Closure, typename OtherClosure,
requires_conditions<
is_future_adaptor_closure<Closure>, is_future_adaptor_closure<OtherClosure>,
std::is_same<T, cpp20::remove_cvref_t<Closure>>,
std::is_constructible<std::decay_t<Closure>, Closure>,
std::is_constructible<std::decay_t<OtherClosure>, OtherClosure>> = true>
LIB_FASYNC_NODISCARD friend constexpr auto operator|(Closure&& c1, OtherClosure&& c2) noexcept(
std::is_nothrow_constructible_v<std::decay_t<Closure>, Closure>&&
std::is_nothrow_constructible_v<std::decay_t<OtherClosure>, OtherClosure>) {
return future_adaptor_closure_t<compose_wrapper_t<OtherClosure, Closure>>(
compose(std::forward<OtherClosure>(c2), std::forward<Closure>(c1)));
}
};
// |combinator|
//
// This CRTP class allows us to factor out common combinator functionality via a private |invoke()|
// method that is exposed via friendship to this class. For combinators that take a future as a
// first argument and want to return a closure taking the other arguments as another overload, this
// class takes care of all that.
template <typename Derived>
class combinator {
public:
constexpr combinator() = default;
constexpr combinator(const combinator&) = default;
constexpr combinator& operator=(const combinator&) = default;
constexpr combinator(combinator&&) = default;
constexpr combinator& operator=(combinator&&) = default;
// We don't use |requires_conditions| on the future because of hard failures during
// substitution/overload resolution.
template <typename Invoker = Derived, typename F, typename... Args>
constexpr auto operator()(F&& future, Args&&... args) const
-> decltype(Invoker::invoke(std::forward<F>(future), std::forward<Args>(args)...)) {
static_assert(is_future_v<F>, "");
return Invoker::invoke(std::forward<F>(future), std::forward<Args>(args)...);
}
template <typename... Args>
constexpr auto operator()(Args&&... args) const {
return closure<Derived, Args...>(std::forward<Args>(args)...);
}
private:
template <typename Combinator, typename... GivenArgs>
class closure final : future_adaptor_closure<closure<Combinator, GivenArgs...>> {
public:
template <typename... Args,
requires_conditions<std::is_constructible<GivenArgs, Args>...> = true>
explicit constexpr closure(Args&&... args) : given_args_(std::forward<Args>(args)...) {}
template <typename F, requires_conditions<is_future<F>> = true>
constexpr auto operator()(F&& future) {
return cpp17::apply(
[&](auto&&... args) {
return cpp20::invoke(&Combinator::template invoke<F, decltype(args)...>,
std::forward<F>(future), std::forward<decltype(args)>(args)...);
},
std::move(given_args_));
}
private:
std::tuple<std::decay_t<GivenArgs>...> given_args_;
};
};
} // namespace internal
// |fasync::make_future|
//
// Allows making futures out of all different kinds of callables, returning bare values,
// |fasync::pending|, |fasync::ready|, or other futures.
//
// Allowed handler argument types:
// - no arguments
// - fasync::context&
//
// Allowed handler return types:
// - void
// - any arbitrary type allowed in an |fasync::poll|
// - any unboxed future
// - fasync::pending
// - fasync::ready
// - fasync::poll
template <typename F, ::fasync::internal::requires_conditions<is_future<F>> = true>
LIB_FASYNC_NODISCARD constexpr F make_future(F&& f) {
return std::forward<F>(f);
}
template <typename F, ::fasync::internal::requires_conditions<cpp17::negation<is_future<F>>> = true>
LIB_FASYNC_NODISCARD constexpr auto make_future(F&& fun) {
return ::fasync::internal::make_future_adaptor_t<F>(std::forward<F>(fun));
}
namespace internal {
template <typename E, requires_conditions<is_executor<E>> = true>
class schedule_on_closure final : future_adaptor_closure<schedule_on_closure<E>> {
public:
template <typename F, requires_conditions<std::is_constructible<E&, F&>> = true>
explicit constexpr schedule_on_closure(F& executor) : executor_(executor) {}
template <typename F, requires_conditions<is_future<F>> = true>
constexpr void operator()(F&& future) {
executor_.schedule(std::forward<F>(future));
}
private:
E& executor_;
};
template <typename E>
using schedule_on_closure_t = schedule_on_closure<std::decay_t<E>>;
class schedule_on_combinator final {
public:
template <typename F, typename E, requires_conditions<is_future<F>, is_executor<E>> = true>
constexpr void operator()(F&& future, E& executor) const {
executor.schedule(std::forward<F>(future));
}
template <typename E, requires_conditions<is_executor<E>> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(E& executor) const {
return schedule_on_closure_t<E>(executor);
}
};
} // namespace internal
// |fasync::schedule_on|
//
// Will be the end of many pipelines in the |fasync::future| world. In its pipelined form, it sits
// after the bar and takes a reference to an executor, which must outlive the execution of the
// future pipeline on the left. The future to the left of the bar is then scheduled asynchronously
// for execution on that executor.
//
// Call pattern:
// - fasync::schedule_on(<future>, <executor>)
// - <future> | fasync::schedule_on(<executor>)
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::schedule_on_combinator schedule_on;
namespace internal {
class map_combinator final : public combinator<map_combinator> {
private:
friend class combinator<map_combinator>;
template <typename F, typename H>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return map_future_t<F, H>(std::forward<F>(future), std::forward<H>(handler));
}
};
} // namespace internal
// |fasync::map|
//
// Perhaps the most useful basic combinator, allowing one to supply a handler to consume the output
// of the given future and returning a wrapped future that returns the transformed output.
//
// Call pattern:
// - fasync::map(<future>, <handler>)
// - <future> | fasync::map(<handler>)
//
// See |fasync::then| for a list of valid handler types (other than returning futures).
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::map_combinator map;
namespace internal {
class map_ok_combinator final : public combinator<map_ok_combinator> {
private:
friend class combinator<map_ok_combinator>;
template <typename F, typename H>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return map_ok_future_t<F, H>(std::forward<F>(future), std::forward<H>(handler));
}
};
} // namespace internal
// |fasync::map_ok|
//
// Like |fasync::map|, but acts on the |.value()| of a |fitx::result| returned by the previous
// future.
//
// Call pattern:
// - fasync::map_ok(<result returning future>, <handler>)
// - <result returning future> | fasync::map_ok(<handler>)
//
// See |fasync::and_then| for a list of valid handler types (other than returning futures).
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::map_ok_combinator map_ok;
namespace internal {
class map_error_combinator final : public combinator<map_error_combinator> {
private:
friend class combinator<map_error_combinator>;
template <typename F, typename H>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return map_error_future_t<F, H>(std::forward<F>(future), std::forward<H>(handler));
}
};
} // namespace internal
// |fasync::map_error|
//
// Like |fasync::map_ok|, but acts on the |.error_value()| of a |fitx::result| returned by the
// previous future.
//
// Call pattern:
// - fasync::map_error(<result returning future>, <handler>)
// - <result returning future> | fasync::map_error(<handler>)
//
// See |fasync::or_else| for a list of valid handler types (other than returning futures).
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::map_error_combinator map_error;
namespace internal {
class inspect_combinator final : public combinator<inspect_combinator> {
private:
friend class combinator<inspect_combinator>;
template <typename F, typename H>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return inspect_future_t<F, H>(std::forward<F>(future), std::forward<H>(handler));
}
};
} // namespace internal
// |fasync::inspect|
//
// Takes a future |F| and a callable that takes |const fasync::future_output_t<F>&|, so that the
// callable can inspect the value provided by the future before passing it on to the next
// combinator in the chain. The callable must return |void|.
//
// Call pattern:
// - fasync::inspect(<future>, <handler)
// - <future> | fasync::inspect(<handler>)
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::inspect_combinator inspect;
namespace internal {
class inspect_ok_combinator final : public combinator<inspect_ok_combinator> {
private:
friend class combinator<inspect_ok_combinator>;
template <typename F, typename H>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return inspect_ok_future_t<F, H>(std::forward<F>(future), std::forward<H>(handler));
}
};
} // namespace internal
// |fasync::inspect_ok|
//
// Like |fasync::inspect|, but acts on the |.value()| from the |fitx::result| of an
// |fasync::try_future|. Again, the callable must return |void|.
//
// Call pattern:
// - fasync::inspect_ok(<result returning future>, <handler>)
// - <result returning future> | fasync::inspect_ok(<handler>)
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::inspect_ok_combinator inspect_ok;
namespace internal {
class inspect_error_combinator final : public combinator<inspect_error_combinator> {
private:
friend class combinator<inspect_error_combinator>;
template <typename F, typename H>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return inspect_error_future_t<F, H>(std::forward<F>(future), std::forward<H>(handler));
}
};
} // namespace internal
// |fasync::inspect_error|
//
// Like |fasync::inspect_ok|, but acts on the |.error_value()| from the |fitx::result| of an
// |fasync::try_future|. Again, the callable must return |void|.
//
// Call pattern:
// - fasync::inspect_error(<result returning future>, <handler>)
// - <result returning future> | fasync::inspect_error(<handler>)
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::inspect_error_combinator inspect_error;
namespace internal {
class discard_closure final : public future_adaptor_closure<discard_closure> {
public:
template <typename F, requires_conditions<is_future<F>> = true>
LIB_FASYNC_NODISCARD constexpr discard_future_t<F> operator()(F&& future) const {
return discard_future_t<F>(std::forward<F>(future));
}
};
} // namespace internal
// |fasync::discard|
//
// Turns any future into a wrapped future that discards the output, i.e. its |operator()| returns
// |fasync::poll<>|.
//
// Call pattern:
// - fasync::discard(<future>)
// - <future> | fasync::discard
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::discard_closure discard;
namespace internal {
class flatten_closure final : public future_adaptor_closure<flatten_closure> {
public:
template <typename F, requires_conditions<is_future<F>> = true>
LIB_FASYNC_NODISCARD constexpr flatten_future_t<F> operator()(F&& future) const {
return flatten_future_t<F>(std::forward<F>(future));
}
};
} // namespace internal
// |fasync::flatten|
//
// Takes nested futures, (i.e., futures returning an |fasync::poll| with another future inside) and
// removes one layer of nesting. For example, |fasync::future<fasync::future<int>>| would become
// |fasync::future<int>|.
//
// Call pattern:
// - fasync::flatten(<future returning future>)
// - <future returning future> | fasync::flatten
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::flatten_closure flatten;
namespace internal {
class flatten_all_closure final : public future_adaptor_closure<flatten_all_closure> {
public:
template <typename F, requires_conditions<cpp17::negation<is_future<future_output_t<F>>>> = true>
LIB_FASYNC_NODISCARD constexpr F operator()(F&& future) const {
return std::forward<F>(future);
}
template <typename F, requires_conditions<is_future<future_output_t<F>>> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(F&& future) const {
return operator()(flatten(std::forward<F>(future)));
}
};
} // namespace internal
// |fasync::flatten_all|
//
// Like |fasync::flatten|, but all layers of nesting are removed if possible.
//
// Call pattern:
// - fasync::flatten_all(<future>)
// - <future> | fasync::flatten_all
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::flatten_all_closure flatten_all;
namespace internal {
class then_combinator final : public combinator<then_combinator> {
private:
friend class combinator<then_combinator>;
template <typename F, typename H,
requires_conditions<cpp17::negation<is_future<handler_output_t<H, F>>>> = true>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return map(std::forward<F>(future), std::forward<H>(handler));
}
template <typename F, typename H, requires_conditions<is_future<handler_output_t<H, F>>> = true>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return flatten(map(std::forward<F>(future), std::forward<H>(handler)));
}
};
} // namespace internal
// |fasync::then|
//
// Returns an unboxed future which invokes the specified handler function after this future
// completes (successfully or unsuccessfully), passing its result.
//
// |handler| is a callable object (such as a lambda) which consumes the result of this future and
// returns a new result with any value type and error type. Must not be null.
//
// |fasync::then| is one of the most important combinators; it can act like |fasync::map| but also
// unwraps futures returned by the given handler, giving it monadic properties.
//
// The handler must return one of the following types:
// - void
// - T
// - fitx::result<new_error_type, new_value_type>
// - fitx::success<new_value_type>
// - fitx::error<new_error_type>
// - fasync::pending
// - fasync::try_ready<new_error_type, new_value_type>
// - fasync::try_poll<new_error_type, new_value_type>
// - fasync::try_future<new_error_type, new_value_type>
// - any callable or unboxed future with the following signature:
// T(fasync::context&)
//
// The handler must accept one of the following argument lists:
// - See above documentation at HANDLER TYPES and substitute T with result_type. Note that it can be
// any type, not just a |fitx::result|.
//
// Call pattern:
// - fasync::then(<future>, <handler>)
// - <future> | fasync::then(<handler>)
//
// This method consumes the future's continuation, leaving it empty.
//
// EXAMPLE
//
// auto f = fasync::make_future(...) |
// fasync::then([] (fitx::result<std::string, int>& result)
// -> fitx::result<fitx::failed, std::string> {
// if (result.is_ok()) {
// printf("received value: %d\n", result.value());
// if (result.value() % 15 == 0)
// return fitx::ok("fizzbuzz");
// if (result.value() % 3 == 0)
// return fitx::ok("fizz");
// if (result.value() % 5 == 0)
// return fitx::ok("buzz");
// return fitx::ok(std::to_string(result.value()));
// } else {
// printf("received error: %s\n", result.error().c_str());
// return fitx::failed();
// }
// }) |
// fasync::then(...);
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::then_combinator then;
namespace internal {
class and_then_combinator final : public combinator<and_then_combinator> {
private:
friend class combinator<and_then_combinator>;
template <typename F, typename H, typename P = handle_map_ok_t<H, F>,
requires_conditions<cpp17::negation<is_value_try_poll<P>>> = true>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return map_ok(std::forward<F>(future), std::forward<H>(handler));
}
template <typename F, typename H, typename V = poll_value_t<handle_map_ok_t<H, F>>,
requires_conditions<cpp17::negation<is_future<V>>> = true>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return map_ok(std::forward<F>(future), std::forward<H>(handler));
}
template <typename F, typename H, typename V = poll_value_t<handle_map_ok_t<H, F>>,
requires_conditions<is_future<V>> = true>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return try_flatten_future_t<map_ok_future_t<F, H>>(
map_ok(std::forward<F>(future), std::forward<H>(handler)));
}
};
} // namespace internal
// |fasync::and_then|
//
// Returns an unboxed future which invokes the specified handler function after this future
// completes successfully, passing its resulting value.
//
// |handler| is a callable object (such as a lambda) which consumes the result of this future and
// returns a new result with any value type but the same error type. Must not be null.
//
// |fasync::and_then| is to |fasync::map_ok| as |fasync::then| is to |fasync::map|. That is, it can
// do what |fasync::map_ok| can do and also unwraps returned futures.
//
// The handler must return one of the following types:
// - void
// - fitx::result<error_type, new_value_type>
// - fitx::success<new_value_type>
// - fitx::error<error_type>
// - fasync::pending
// - fasync::try_ready<error_type, new_value_type>
// - fasync::try_poll<error_type, new_value_type>
// - fasync::try_future<error_type, new_value_type>
// - any callable or unboxed future with the following signature:
// fitx::result<error_type, new_value_type>(fasync::context&)
//
// The handler must accept one of the following argument lists:
// - See above documentation at HANDLER TYPES and substitute T with value_type.
//
// Call pattern:
// - fasync::and_then(<result returning future>, <handler>)
// - <result returning future> | fasync::and_then(<handler>)
//
// This method consumes the future's continuation, leaving it empty.
//
// EXAMPLE
//
// auto f = fasync::make_future(...) |
// fasync::and_then([] (const int& value) {
// printf("received value: %d\n", value);
// if (value % 15 == 0)
// return fitx::ok("fizzbuzz");
// if (value % 3 == 0)
// return fitx::ok("fizz");
// if (value % 5 == 0)
// return fitx::ok("buzz");
// return fitx::ok(std::to_string(value));
// }) |
// fasync::then(...);
//
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::and_then_combinator and_then;
namespace internal {
class or_else_combinator final : public combinator<or_else_combinator> {
private:
friend class combinator<or_else_combinator>;
template <typename F, typename H, typename E = poll_error_t<handle_map_error_t<H, F>>,
requires_conditions<cpp17::negation<is_future<E>>> = true>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return map_error(std::forward<F>(future), std::forward<H>(handler));
}
template <typename F, typename H, typename E = poll_error_t<handle_map_error_t<H, F>>,
requires_conditions<is_future<E>> = true>
LIB_FASYNC_NODISCARD static constexpr auto invoke(F&& future, H&& handler) {
return try_flatten_error_future_t<map_error_future_t<F, H>>(
map_error(std::forward<F>(future), std::forward<H>(handler)));
}
};
} // namespace internal
// |fasync::or_else|
//
// Returns an unboxed future which invokes the specified handler function after this future
// completes with an error, passing its resulting error.
//
// |handler| is a callable object (such as a lambda) which consumes the result of this future and
// returns a new result with any error type but the same value type. Must not be null.
//
// |fasync::or_else| is to |fasync::map_error| as |fasync::and_then| is to |fasync::map_ok|. That
// is, it can do what |fasync::map_error| can do and also unwraps returned futures.
//
// The handler must return one of the following types:
// - void
// - fitx::result<new_error_type, value_type>
// - fitx::success<value_type>
// - fitx::error<new_error_type>
// - fasync::pending
// - fasync::try_ready<new_error_type, value_type>
// - fasync::try_poll<new_error_type, value_type>
// - fasync::try_future<new_error_type, value_type>
// - any callable or unboxed future with the following signature:
// fitx::result<new_error_type, value_type>(fasync::context&)
//
// The handler must accept one of the following argument lists:
// - See above documentation at HANDLER TYPES and substitute T with error_type.
//
// Call pattern:
// - fasync::or_else(<result returning future>, <handler>)
// - <result returning future> | fasync::or_else(<handler>)
//
// This method consumes the future's continuation, leaving it empty.
//
// EXAMPLE
//
// auto f = fasync::make_future(...) |
// fasync::or_else([] (const std::string& error) {
// printf("received error: %s\n", error.c_str());
// return fitx::error();
// }) |
// fasync::then(...);
//
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::or_else_combinator or_else;
namespace internal {
class join_closure final : public future_adaptor_closure<join_closure> {
public:
template <typename... Fs, requires_conditions<is_future<Fs>...> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(Fs&&... futures) const {
return join_future_t<Fs...>(std::forward<Fs>(futures)...);
}
template <typename A, requires_conditions<::fasync::internal::is_future_applicable<A>> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(A&& applicable) const {
return cpp17::apply(
[this](auto&&... futures) {
return this->operator()(std::forward<decltype(futures)>(futures)...);
},
std::forward<A>(applicable));
}
// TODO(schottm): does not work with void futures (same with below)
template <template <typename, typename> class C, typename F,
template <typename> class Allocator = std::allocator,
requires_conditions<is_future<F>> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(C<F, Allocator<F>>&& container) const {
return join_container_future<C, F, Allocator>(std::move(container));
}
// This takes care of both std::array<T, N> and std::span<T, N> where N != std::dynamic_extent
template <
template <typename, size_t> class View, typename F, size_t N,
requires_conditions<is_future<F>, cpp17::bool_constant<N != cpp20::dynamic_extent>> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(View<F, N> view) const {
return join_view_future<View, F, N>(std::move(view));
}
template <typename F, requires_conditions<is_future<F>> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(cpp20::span<F> span) const {
return join_span_future<F>(span);
}
// Rvalue references to arrays are uncommon but here it forces the user to use std::move() to
// indicate that the futures are being modified and their outputs will be stored elsewhere.
template <typename F, size_t N>
LIB_FASYNC_NODISCARD constexpr auto operator()(F (&&arr)[N]) const {
return operator()(cpp20::span<F, N>(arr));
}
template <typename F, requires_conditions<is_future<F>> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(F&& future) const {
return then(std::forward<F>(future), [this](auto&& futures) {
return this->operator()(std::forward<decltype(futures)>(futures));
});
}
};
} // namespace internal
// |fasync::join|
//
// Can join several futures into one future in several different ways, and this new future does not
// complete until all of the constituent futures complete. The futures can be given as a parameter
// pack, a |std::tuple| or other type usable by |cpp17::apply|, a container like |std::vector|,
// |std::deque|, or |std::list|, a |cpp20::span| or |std::array|, or a bare array.
//
// Call pattern:
// - fasync::join(<futures>...) -> |std::tuple| of outputs
// - fasync::join(std::tuple<futures...>) -> |std::tuple| of outputs
// - fasync::join(<container of futures with |.push_back()|>) -> same container template of outputs
// - fasync::join(std::array<future, N>) -> |std::array| of outputs
// - fasync::join(cpp20::span<future, N>) -> |std::array| of outputs
// - fasync::join(cpp20::span<future>) -> |std::vector| of outputs
// - fasync::join(future (&&)[N]) -> |std::array| of outputs
// - <future of one of the collections of futures above> | fasync::join -> output as above
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::join_closure join;
namespace internal {
template <typename... Fs>
class join_with_closure final : future_adaptor_closure<join_with_closure<Fs...>> {
static_assert(cpp17::conjunction_v<is_future<Fs>...>, "");
public:
explicit constexpr join_with_closure(Fs&&... futures) : futures_(std::forward<Fs>(futures)...) {}
template <typename F, requires_conditions<is_future<F>> = true>
constexpr auto operator()(F&& future) {
return cpp17::apply(
[&](auto&&... futures) {
return join(std::forward<F>(future), std::forward<decltype(futures)>(futures)...);
},
std::move(futures_));
}
private:
std::tuple<Fs...> futures_;
};
template <typename... Fs>
using join_with_closure_t = join_with_closure<std::decay_t<Fs>...>;
class join_with_combinator final {
public:
template <typename... Fs, requires_conditions<is_future<Fs>...> = true>
LIB_FASYNC_NODISCARD constexpr join_with_closure_t<Fs...> operator()(Fs&&... futures) const {
return join_with_closure_t<Fs...>(std::forward<Fs>(futures)...);
}
template <typename T, requires_conditions<::fasync::internal::is_future_applicable<T>> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(T&& applicable) const {
return with_applicable(
std::forward<T>(applicable),
std::make_index_sequence<cpp17::tuple_size_v<cpp20::remove_cvref_t<T>>>());
}
private:
template <typename T, size_t... Is,
requires_conditions<::fasync::internal::is_future_applicable<T>> = true>
constexpr auto with_applicable(T&& applicable, std::index_sequence<Is...>) const {
return join_with_closure_t<std::tuple_element_t<Is, T>...>(std::get<Is>(applicable)...);
}
};
} // namespace internal
// |fasync::join_with|
//
// Like |fasync::join| except that its first constituent future can be from a pipeline, so it does
// not need to appear at the start of a pipeline.
//
// Call pattern:
// - <future> | fasync::join_with(<futures>...)
// - <future> | fasync::join_with(std::tuple<futures...>)
// - <future> | fasync::join_with(std::array<future, N>)
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::join_with_combinator join_with;
// |fasync::wrap|
//
// Allows futures to be wrapped with wrapper objects, which take futures and can introduce arbitrary
// behavior onto them, like |fasync::scope| and |fasync::sequencer|.
//
// Call pattern:
// - fasync::wrap(<future>, <wrapper>, <args>...)
//
// TODO(schottm): merge wrap and wrap_with?
template <typename F, typename W, typename... Args,
::fasync::internal::requires_conditions<is_future<F>> = true>
LIB_FASYNC_NODISCARD constexpr auto wrap(F&& future, W& wrapper, Args&&... args) {
return wrapper.wrap(std::forward<F>(future), std::forward<Args>(args)...);
}
namespace internal {
template <typename W, typename... Args>
class wrap_with_closure final : future_adaptor_closure<wrap_with_closure<W, Args...>> {
public:
explicit constexpr wrap_with_closure(W& wrapper, Args&&... args)
: wrapper_(wrapper), args_(std::forward<Args>(args)...) {}
template <typename F, requires_conditions<is_future<F>> = true>
constexpr auto operator()(F&& future) const {
return cpp17::apply(
[&](auto&&... args) {
// TODO(schottm): ADL?
return wrap(std::forward<F>(future), wrapper_, std::forward<decltype(args)>(args)...);
},
std::move(args_));
}
private:
W& wrapper_;
std::tuple<Args...> args_;
};
template <typename W, typename... Args>
using wrap_with_closure_t = wrap_with_closure<std::decay_t<W>, std::decay_t<Args>...>;
class wrap_with_combinator final {
public:
template <typename W, typename... Args>
constexpr wrap_with_closure_t<W, Args...> operator()(W& wrapper, Args&&... args) const {
return wrap_with_closure_t<W, Args...>(wrapper, std::forward<Args>(args)...);
}
};
} // namespace internal
// |fasync::wrap_with|
//
// Allows futures to be wrapped with wrapper objects, which take futures and can introduce arbitrary
// behavior onto them, like |fasync::scope| and |fasync::sequencer|.
//
// Call pattern:
// - <future> | fasync::wrap_with(<wrapper>, <args>...)
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::wrap_with_combinator wrap_with;
namespace internal {
class box_closure final : public future_adaptor_closure<box_closure> {
public:
template <typename F, requires_conditions<is_future<F>> = true>
LIB_FASYNC_NODISCARD constexpr auto operator()(F&& future) const {
return ::fasync::future<future_output_t<F>>(std::forward<F>(future));
}
};
} // namespace internal
// |fasync::box|
//
// Takes an unboxed future and puts its continuation in a |fit::function|, boxing it. This always
// happens before a future is executed on an executor, though not necessarily by this combinator.
// This combinator can be useful when making collections of futures with the same return type.
//
// Call pattern:
// - fasync::box(<future>)
// - <future> | fasync::box
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::box_closure box;
// TODO(schottm): make niebloid with full ADL compatibility for executors with different run methods
template <typename E>
constexpr void run(E& executor) {
executor.run();
}
namespace internal {
template <typename E, requires_conditions<is_executor<E>> = true>
class block_on_closure final : future_adaptor_closure<block_on_closure<E>> {
public:
template <typename F, requires_conditions<std::is_constructible<E&, F&>> = true>
explicit constexpr block_on_closure(F& executor) : executor_(executor) {}
template <typename F, requires_conditions<is_future<F>> = true>
constexpr cpp17::optional<future_output_t<F>> operator()(F&& future) const {
return block_on(std::forward<F>(future), executor_);
}
private:
E& executor_;
};
class block_on_combinator final {
public:
template <typename F, typename E,
requires_conditions<is_future<F>, cpp17::negation<is_void_future<F>>> = true>
constexpr cpp17::optional<future_output_t<F>> operator()(F&& future, E& executor) const {
cpp17::optional<future_output_t<F>> output;
executor.schedule(std::forward<F>(future) | then([&output](future_output_t<F>& result) {
move_construct_optional(output, std::move(result));
}));
using ::fasync::run;
run(executor);
return output;
}
template <typename F, typename E, requires_conditions<is_void_future<F>> = true>
constexpr bool operator()(F&& future, E& executor) const {
bool done = false;
executor.schedule(std::forward<F>(future) | then([&done] { done = true; }));
using ::fasync::run;
run(executor);
return done;
}
template <typename E, requires_conditions<is_executor<E>> = true>
LIB_FASYNC_NODISCARD constexpr block_on_closure<E> operator()(E& executor) {
return block_on_closure<E>(executor);
}
};
} // namespace internal
// |fasync::block_on|
//
// Executes a future on the current thread using the given executor. The executor should have a
// |run()| function findable via ADL or a |.run()| method otherwise. This combinator returns a
// |cpp17::optional| or a |bool| (for |void|-returning futures) to indicate whether the future was
// abandoned.
//
// Call pattern:
// - fasync::block_on(<future>, <executor>)
// - <future> | fasync::block_on(<executor>)
LIB_FASYNC_INLINE_CONSTANT constexpr ::fasync::internal::block_on_combinator block_on;
} // namespace fasync
LIB_FASYNC_CPP_VERSION_COMPAT_END
#endif // SRC_LIB_FASYNC_INCLUDE_LIB_FASYNC_FUTURE_H_