| .. _module-pw_async2-futures: |
| |
| ======= |
| Futures |
| ======= |
| .. pigweed-module-subpage:: |
| :name: pw_async2 |
| |
| A ``Future`` is an object that represents the value of an asynchronous operation |
| which may not yet be complete. Upon completion, the future produces the result |
| of the operation, if it has one. |
| |
| Futures are the core interface to ``pw_async2`` asynchronous APIs. |
| |
| ------------- |
| Core concepts |
| ------------- |
| Futures operate using the |
| :ref:`informed poll <module-pw_async2-informed-poll>` model on which |
| ``pw_async2`` is built. This model is summarized below, but it is recommended to |
| read the full description for important background knowledge. |
| |
| Future API |
| ========== |
| Futures use a standard API. There is no `Future` class; futures are unique types |
| with a common interface, but no shared base. In C++20 and later, |
| ``pw::async2::Future`` is a C++ concept that describes the future interface. |
| |
| A ``Future<T>`` exposes the following API: |
| |
| - A default constructor that initializes the future to an empty state. An empty |
| future does not represent an asynchronous operation and is neither pendable |
| nor complete. |
| - A destructor that abandons the future so no further operations will access it. |
| - ``value_type``: Type alias for the value produced by the future; ``void`` if |
| the future produces no value. The :cc:`FutureValue<Future> |
| <pw::async2::FutureValue>` helper resolves to the future's ``value_type``, but |
| maps ``void`` to :cc:`ReadyType <pw::async2::ReadyType>` so that a |
| ``FutureValue<Future>`` can always be instantiated and referenced. |
| - ``Poll<value_type> Pend(Context& cx)``: Calling ``Pend`` advances the |
| asynchronous operation until no further progress is possible. Returns |
| :cc:`Ready <pw::async2::Ready>` if the operation completes. Otherwise, uses |
| the provided :cc:`Context <pw::async2::Context>` to store a waker and returns |
| :cc:`Pending <pw::async2::Pending>`. The waker wakes the task when ``Pend`` |
| should be called again. |
| - ``bool is_pendable()``: Returns whether the future represents an active |
| asynchronous operation which can be pended. |
| - ``bool is_complete()``: Returns whether the future has already completed and |
| had its result consumed. |
| |
| Futures are single-use and track their completion status. It is an error |
| to poll a future after it has already completed. |
| |
| ``pw_async2`` provides a ``pw::async2::Future`` `concept |
| <http://go/cppref/cpp/language/constraints.html>`_ that future implementations |
| must satisfy. Futures do not share a common base class, but may use common |
| helpers such as :cc:`FutureCore <pw::async2::FutureCore>`. |
| |
| Ownership and lifetime |
| ====================== |
| Futures are owned by the caller of an asynchronous operation. The task that |
| receives the future is responsible for storing and polling it. |
| |
| The provider of a future must either outlive the future or arrange for the |
| future to be resolved in an error state when the provider is destroyed. |
| |
| Polling |
| ======= |
| Futures are lazy and do nothing on their own. The task owning a future must poll |
| it to drive it to completion. Calling a future's ``Pend`` function advances its |
| operation and returns a :cc:`Poll <pw::async2::Poll>` containing one of two |
| values: |
| |
| * ``Pending()``: The asynchronous operation has not yet finished. The value is |
| not available. The task polling the future is be scheduled to wake when the |
| future can make additional progress. |
| |
| Typically, your task should propagate a ``Pending`` return upwards to notify |
| the dispatcher that it is blocked and should sleep. |
| |
| * ``Ready(T)``: The operation has completed, and the value is now available. |
| |
| Once a future returns ``Ready``, its state is final. Attempting to poll it again |
| results in an assertion. |
| |
| This polling model allows a single thread to manage many concurrent operations |
| without blocking. |
| |
| .. admonition:: Completed future lifetime |
| :class: warning |
| |
| Once a future yields :cc:`Ready <pw::async2::Ready>`, it is considered |
| complete and its state is final. The async2 framework must be free to destroy |
| the future immediately following a ``Ready`` return without invalidating its |
| result. |
| |
| When returning a value from ``Ready``, it must not contain references to the |
| future or its internal state. |
| |
| Composability |
| ============= |
| The power of futures is their ability to compose to construct complex |
| asynchronous logic from smaller building blocks. |
| |
| Futures can be classified into two categories: *leaf* futures and *composite* |
| futures. Leaf futures represent a specific asynchronous operation, such as a |
| read from a channel, or waiting for a timer. They contain the required state |
| for their operations and manage the task waiting on them. |
| |
| Composite futures are built on top of other futures, combining their results |
| to build advanced asynchronous execution graphs. For example, a ``Join`` future |
| waits for multiple other futures to complete, returning all of their results at |
| once. Composite futures can be used to express complex logic in a declarative |
| way. For details on defining custom composite futures and async helper |
| functions, see :ref:`module-pw_async2-futures-composite`. |
| |
| Coroutine support |
| ================= |
| Futures' simple ``Pend`` API makes them easy to use with async2's |
| :ref:`coroutine adapter <module-pw_async2-coro>`. You can ``co_await`` a |
| function that returns a future directly, automatically polling the future to |
| completion. |
| |
| -------------------- |
| Working with futures |
| -------------------- |
| |
| Calling functions that return futures |
| ===================================== |
| Consider some asynchronous call which produces a simple value on completion. |
| Pigweed provides :cc:`ValueFuture<T> <pw::async2::ValueFuture>` for this common |
| case. The async function has the following signature: |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-number-generator] |
| :end-before: [pw_async2-examples-futures-number-generator] |
| |
| You would write a task that calls this operation as follows: |
| |
| .. tab-set:: |
| |
| .. tab-item:: Standard polling |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-my-task] |
| :end-before: [pw_async2-examples-futures-my-task] |
| |
| .. tab-item:: C++20 coroutines |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-my-coro] |
| :end-before: [pw_async2-examples-futures-my-coro] |
| |
| Writing functions that return futures |
| ===================================== |
| All future-based ``pw_async2`` APIs have the signature |
| |
| .. code-block:: |
| |
| Future<T> DoThing(Args... args); |
| |
| Where ``Future<T>`` is some concrete future implementation (e.g. |
| :cc:`ValueFuture <pw::async2::ValueFuture>`) which resolves to a value of type |
| ``T`` and ``Args`` represents any arguments to the operation. |
| |
| When defining an asynchronous API, the function should always return a |
| ``Future`` directly --- not a ``Result<Future>`` or |
| ``std::optional<Future>``. If the operation is fallible, that should be |
| expressed by the future's output, e.g. ``Future<Result<T>>``. |
| |
| This is necessary for proper composability. It makes using asynchronous APIs |
| consistent and enables higher-level futures which compose other futures to |
| function cleanly. Additionally, returning a ``Future`` directly is essential to |
| be able to work with coroutines: ``co_await`` can be used directly and will |
| resolve to a ``Result<T>``. |
| |
| Naming conventions |
| ------------------ |
| Follow these conventions for naming functions that interact with ``pw_async2`` |
| futures. |
| |
| - Name functions that return futures for the operation represented by the |
| future, rather than the future itself. |
| |
| .. admonition:: **Yes**: Function is named for the Read operation. |
| :class: checkmark |
| |
| .. code-block:: cpp |
| |
| ReadFuture<T> Read(); |
| |
| .. admonition:: **No**: Function is named for the future it returns. |
| :class: error |
| |
| .. code-block:: cpp |
| |
| ReadFuture<T> GetReadFuture(); |
| |
| - Do not label future-returning functions as "async". Asynchronicity is implied |
| by the future return value. |
| |
| .. admonition:: **No**: Future-returning function is named as ``Async``. |
| :class: error |
| |
| .. code-block:: cpp |
| |
| ReadFuture<T> AsyncRead(); |
| |
| - Prefix non-blocking functions with ``Try`` to distinguish then from |
| future-returning functions. |
| |
| .. admonition:: **Yes**: Non-blocking function starts with ``Try``. |
| :class: checkmark |
| |
| .. code-block:: cpp |
| |
| std::optional<T> TryRead(); |
| |
| - Prefix functions that block the current thread with ``Blocking``. |
| |
| .. admonition:: **Yes**: Blocking function starts with ``Blocking``. |
| :class: checkmark |
| |
| .. code-block:: cpp |
| |
| std::optional<T> BlockingRead(); |
| |
| Signalling tasks |
| ================ |
| Tasks often need to wait for a single event to occur. ``pw_async2`` provides |
| :cc:`Notification <pw::async2::Notification>` for this purpose. Tasks call |
| ``Wait`` to obtain a future that will resolve once the notifier calls |
| ``Notify``. The future does not resolve to a value. |
| |
| Multiple tasks can wait on the same notification and will all be woken when it |
| is triggered. A notification can be triggered from any context (async, |
| non-async, ISR). ``Notification`` is implemented using a |
| :cc:`BroadcastValueProvider <pw::async2::BroadcastValueProvider>`. |
| |
| Persisting results across suspensions with ``FutureOrValue`` |
| ============================================================ |
| In manual polling state machines (e.g. within a :cc:`Task::DoPend |
| <pw::async2::Task::DoPend>` implementation), a task often needs to wait for |
| multiple independent asynchronous operations to complete. Because futures are |
| single-use and cannot be polled again after resolving to :cc:`Ready |
| <pw::async2::Ready>`, a task that yields to wait for remaining operations must |
| store the results of any operations that completed early across subsequent |
| suspension points. |
| |
| Manually managing separate variables for each future and its resolved value |
| requires significant boilerplate. :cc:`FutureOrValue |
| <pw::async2::FutureOrValue>` solves this by providing a single in-place |
| container that holds either the active pending future or its resolved result: |
| |
| * While an operation is in progress, ``FutureOrValue`` holds the pending future. |
| * When the future resolves to ``Ready``, ``FutureOrValue`` immediately destroys |
| the future (releasing any resources it held) and stores the resulting value in |
| its place. |
| * Calling ``Advance(cx)`` advances a pending future, returning ``true`` if the |
| value is available or ``false`` if still pending. If the value has already |
| been resolved, ``Advance(cx)`` returns ``true`` immediately without polling. |
| |
| .. warning:: |
| |
| ``FutureOrValue`` is designed **only** to be used as internal private member |
| state inside the final consumer task or composite future. |
| |
| * **Never return ``FutureOrValue`` from APIs.** A ``FutureOrValue`` is not a |
| future; it is storage. APIs must always return futures directly. |
| * **Do not pass ``FutureOrValue`` around.** ``FutureOrValue`` should never |
| leave its owning task or composite future. Once a future is moved in, it |
| should stay in place until the value is extracted. |
| * **Do not** use ``FutureOrValue`` when results are consumed immediately |
| upon resolution (store and poll the raw future directly instead). |
| * In C++20 coroutines, use :cc:`Coro <pw::async2::Coro>` or combinators |
| like :cc:`Join <pw::async2::Join>` instead, which automatically preserve |
| state across suspension points without manual wrappers. |
| |
| Remember that a ``FutureOrValue<F>`` is simply a convenience to avoid having |
| to store both ``F future_`` and ``std::optional<F::value_type> value_``. |
| If you were not otherwise going to store those, ``FutureOrValue`` is the |
| wrong thing for you. |
| |
| States |
| ------ |
| An instance of ``FutureOrValue`` is in one of three logical states: |
| |
| 1. **Empty**: The default state, or after the value has been extracted via |
| ``Take()`` or cleared via ``Reset()``. No future is active and no value is |
| stored. ``empty()`` returns ``true``. |
| 2. **Pending**: A future has been assigned but has not yet resolved. |
| ``has_future()`` returns ``true``. |
| 3. **Ready**: The future has resolved and the value is stored. ``has_value()`` |
| returns ``true``. |
| |
| Advancing multiple slots with ``PW_FOV_TRY_ADVANCE`` |
| ---------------------------------------------------- |
| When managing multiple ``FutureOrValue`` members, calling ``Advance`` on each |
| individually with early returns can short-circuit, preventing later futures from |
| being polled and registering their wakers. |
| |
| ``pw_async2`` provides the :cc:`PW_FOV_TRY_ADVANCE` macro to poll multiple |
| ``FutureOrValue`` slots in a single statement without short-circuiting. If any |
| slot is not yet ready, the macro returns ``Pending()`` from the enclosing |
| function after ensuring all provided slots have been advanced. |
| |
| Example |
| ------- |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-future-or-value] |
| :end-before: [pw_async2-examples-futures-future-or-value] |
| |
| .. _module-pw_async2-futures-implementing: |
| |
| --------------------- |
| Implementing a future |
| --------------------- |
| ``pw_async2`` provides futures like :cc:`ValueFuture <pw::async2::ValueFuture>` |
| for common asynchronous patterns. However, you may want to implement a custom |
| leaf future if your operation has complex logic where ``Pend()`` would benefit |
| from reaching deeper into the underlying system, e.g. waiting for a hardware |
| interrupt. |
| |
| :cc:`FutureCore <pw::async2::FutureCore>` is the primary tool for creating |
| futures. |
| |
| FutureCore |
| ========== |
| This class provides the essential machinery for most custom leaf futures: |
| |
| - It stores the :cc:`Waker <pw::async2::Waker>` of the task that polls it. |
| - It manages its membership in an intrusive list of futures. |
| - It tracks future state with a :cc:`FutureState <pw::async2::FutureState>`. |
| |
| Future implementations typically have a :cc:`FutureCore |
| <pw::async2::FutureCore>` member. |
| |
| FutureList |
| ========== |
| After you vend a future from an asynchronous operation, you need a way to track |
| and resolve it once the operation has completed. :cc:`FutureCore |
| <pw::async2::FutureCore>`\s can be stored in a :cc:`FutureList |
| <pw::async2::FutureList>`, which wraps an :cc:`pw::IntrusiveForwardList`. |
| |
| :cc:`FutureList <pw::async2::FutureList>` allows multiple concurrent tasks to |
| wait on an operation. Pending futures are pushed to the list. When an operation |
| completes, futures are popped from the list and resolved. |
| |
| :cc:`FutureList <pw::async2::FutureList>` stores its futures as a linked list of |
| :cc:`FutureCore <pw::async2::FutureCore>`\s in its :cc:`BaseFutureList |
| <pw::async2::BaseFutureList>` base. This maximizes code reuse between different |
| future implementations. |
| |
| A :cc:`FutureList <pw::async2::FutureList>` is declared with a pointer to the |
| future implementation's :cc:`FutureCore <pw::async2::FutureCore>` member: |
| ``FutureList<&FutureType::future_core_>``. For example: |
| |
| .. literalinclude:: examples/custom_future.cc |
| :language: cpp |
| :linenos: |
| :start-after: // DOCSTAG: [pw_async2-examples-future-list] |
| :end-before: // DOCSTAG: [pw_async2-examples-future-list] |
| |
| Waking mechanism |
| ================ |
| When a task polls a future and it returns ``Pending``, the future must store the |
| task's :cc:`Waker <pw::async2::Waker>` from the provided :cc:`Context |
| <pw::async2::Context>`. This is handled automatically by |
| :cc:`FutureCore::DoPend <pw::async2::FutureCore::DoPend>`. |
| |
| On the other side of the asynchronous operation (e.g., in an interrupt handler), |
| when the operation completes, the provider is used to retrieve the future, and |
| its ``Wake()`` function is called. This notifies the dispatcher that the task |
| waiting on this future is ready to make progress and should be polled again. |
| |
| Setting up wakers |
| ================= |
| Futures typically store a waker. When the future is ready to advance, that wake |
| the task that pended them with this waker. Wakers can be set using one of these |
| four macros: |
| |
| - :cc:`PW_ASYNC_STORE_WAKER` and :cc:`PW_ASYNC_CLONE_WAKER` |
| |
| The first creates a waker for a given context. The second clones an |
| existing waker, allowing the original and/or the clone to wake the task. |
| |
| This pair of macros ensure a single task will be woken. They will assert if |
| a waker for a different task is created (or cloned) when the destination |
| waker already is set up for some task. |
| |
| - :cc:`PW_ASYNC_TRY_STORE_WAKER` and :cc:`PW_ASYNC_TRY_CLONE_WAKER` |
| |
| These are alternatives to :cc:`PW_ASYNC_STORE_WAKER` and |
| cc:`PW_ASYNC_CLONE_WAKER` that return ``false`` instead of crashing if the |
| waker is already set. This allows the caller to handle cases when the waker is |
| already in use. |
| |
| .. _module-pw_async2-futures-implementing-example: |
| |
| Example: Waiting for a GPIO interrupt |
| ===================================== |
| Below is an example of a custom future that waits for a GPIO button press using |
| interfaces from ``pw_digital_io``. |
| |
| .. literalinclude:: examples/custom_future.cc |
| :language: cpp |
| :linenos: |
| :start-after: // DOCSTAG: [pw_async2-examples-custom-future] |
| :end-before: // DOCSTAG: [pw_async2-examples-custom-future] |
| |
| This example demonstrates the core mechanics of creating a custom future. This |
| pattern of waiting for a single value from a producer is so common that |
| ``pw_async2`` provides :cc:`ValueFuture <pw::async2::ValueFuture>`, which is |
| produced by a :cc:`ValueProvider <pw::async2::ValueProvider>` or |
| :cc:`OptionalValueProvider <pw::async2::OptionalValueProvider>`, to handle it. |
| In practice, you would return a :cc:`VoidFuture <pw::async2::VoidFuture>` (alias |
| for ``ValueFuture<void>``) from ``WaitForPress`` instead of writing a custom |
| ``ButtonFuture``. |
| |
| .. _module-pw_async2-futures-composite: |
| |
| Implementing a composite future |
| =============================== |
| While leaf futures manage wakers and handle direct interaction with hardware or |
| external providers, non-leaf functions in an execution graph often need to |
| combine multiple asynchronous operations into higher-level business logic. |
| |
| A **composite future** exists in the middle of an async execution graph: |
| |
| * **Top level**: :cc:`Task <pw::async2::Task>` implementations posted directly |
| to the :cc:`Dispatcher <pw::async2::Dispatcher>`. Heavier weight as they hold |
| lists and other dispatcher metadata. |
| * **Middle level**: Composite futures and async helper functions that combine |
| several asynchronous steps into a single logical unit. |
| * **Leaf level**: Wakeable futures (such as :cc:`TimeFuture <pw::async2::TimeFuture>` |
| or :cc:`ValueFuture <pw::async2::ValueFuture>`) that asynchronously wait on |
| external signals, like hardware interrupts, network operations, or timers. |
| |
| Unlike leaf futures, a composite future does **not** use :cc:`FutureCore |
| <pw::async2::FutureCore>`. It has no wakers and does not exist in an intrusive |
| list or provider. Instead, it is owned entirely by its caller as a value object |
| on the stack, with no external backreferences. Waker registration is handled |
| transitively by the child futures stored inline inside the composite future. |
| |
| Async helper function pattern |
| ----------------------------- |
| When defining an async helper function that returns a composite future, follow |
| these conventions: |
| |
| * **Use the factory pattern.** The function acts as a factory constructing |
| composite futures and returning them directly by value. There is no provider, |
| no list or waker management. Those occur within the subfutures that perform |
| wakeable operations. |
| * **Return Future objects directly.** Per ``pw_async2`` conventions, the |
| function must return a future directly instead of wrapping it in a ``Result`` |
| or ``std::optional``. This enables further composability, including allowing |
| callers to ``co_await`` the function. |
| * **Handle errors through resolved futures.** The function can run synchronous |
| validation before triggering the first async operation, returning a future |
| that immediately resolves to an error if invalid. |
| |
| Example: Retry logic with composite futures |
| ------------------------------------------- |
| Below is an example demonstrating a composite future implementation, |
| ``ReadSensorWithRetryFuture`` with its factory helper function |
| ``ReadSensorWithRetry``. It combines a sensor read (via :cc:`ValueFuture |
| <pw::async2::ValueFuture>`) and a delay (via :cc:`TimeFuture |
| <pw::async2::TimeFuture>`) into a single state machine without |
| any dynamic allocation, provider registration, or task overhead. |
| |
| .. literalinclude:: examples/composite_future.cc |
| :language: cpp |
| :linenos: |
| :start-after: // DOCSTAG: [pw_async2-examples-composite-future] |
| :end-before: // DOCSTAG: [pw_async2-examples-composite-future] |
| |
| Derived value futures |
| ===================== |
| Sometimes a provider needs to inspect the specific constraints of a request |
| before deciding to fulfill it (e.g., an allocator checking if the requested |
| size is available). With a standard :cc:`ValueProvider <pw::async2::ValueProvider>`, |
| the provider only knows that a request exists, but cannot attach additional |
| information to it or safely inspect that information. |
| |
| To support this, Pigweed allows you to derive from :cc:`ValueFuture<T> |
| <pw::async2::ValueFuture>` to add custom fields, and use |
| :cc:`DerivedValueProvider<DerivedFuture> <pw::async2::DerivedValueProvider>` |
| to manage them. |
| |
| Creating a derived future |
| ------------------------- |
| To create a derived future, inherit from :cc:`ValueFuture<T> |
| <pw::async2::ValueFuture>` and provide a constructor that accepts the base |
| future by move (``ValueFuture<T>&&``) along with any custom arguments. |
| |
| .. code-block:: c++ |
| |
| class BufferFuture : public pw::async2::ValueFuture<pw::Result<pw::ByteSpan>> { |
| public: |
| BufferFuture(pw::async2::ValueFuture<pw::Result<pw::ByteSpan>>&& base, |
| size_t requested_size) |
| : pw::async2::ValueFuture<pw::Result<pw::ByteSpan>>(std::move(base)), |
| requested_size_(requested_size) {} |
| BufferFuture(BufferFuture&&) = default; |
| BufferFuture& operator=(BufferFuture&&) = default; |
| ~BufferFuture() { this->Cancel(); } |
| |
| size_t requested_size() const { return requested_size_; } |
| |
| private: |
| size_t requested_size_; |
| }; |
| |
| .. important:: |
| Always call ``this->Cancel()`` in the destructor of a derived future so that |
| the future is cancelled before the derived class member fields are destroyed. |
| |
| Atomic inspection and resolution with ResolveIf |
| ----------------------------------------------- |
| The core feature of :cc:`DerivedValueProvider |
| <pw::async2::DerivedValueProvider>` is the ``ResolveIf`` method. |
| It allows the provider to inspect the pending future and conditionally resolve |
| it atomically, preventing race conditions where a future might be cancelled |
| between inspection and resolution. |
| |
| ``ResolveIf`` takes a callback function that receives a reference to your |
| derived future type. The behavior depends on whether the future produces a value: |
| |
| - **Value-returning futures**: The callback returns a ``std::optional<T>``. |
| If it returns a value, the future is popped and resolved with that value. |
| If it returns ``std::nullopt``, the future remains pending in the list. |
| - **Void futures**: The callback returns a ``bool``. If it returns ``true``, |
| the future is popped and resolved. |
| |
| .. code-block:: c++ |
| |
| pw::async2::DerivedValueProvider<BufferFuture> provider; |
| |
| // Attempt to resolve the request. |
| bool resolved = provider.ResolveIf( |
| [](BufferFuture& future) -> std::optional<pw::Result<pw::ByteSpan>> { |
| // Inspect the request parameters to decide if it can be fulfilled. |
| if (future.requested_size() <= available_memory) { |
| return Allocate(future.requested_size()); |
| } |
| return std::nullopt; |
| }); |
| |
| .. warning:: |
| Since ``ResolveIf`` holds a shared async2 lock while executing the callback, |
| the callback code should be **fast and non-blocking**. Avoid slow operations |
| inside the callback if possible as they could stall running tasks. |
| |
| Multi-consumer list providers |
| ============================= |
| A standard :cc:`ValueProvider <pw::async2::ValueProvider>` only allows a single |
| pending future at a time. To manage multiple pending futures, you can use |
| :cc:`ValueListProvider <pw::async2::ValueListProvider>`. |
| |
| With :cc:`ValueListProvider <pw::async2::ValueListProvider>`, any number of |
| tasks can register futures in a list. The provider owner can then inspect, |
| conditionally resolve, or bulk-abort pending futures from anywhere in the |
| list. |
| |
| This is particularly useful for implementing resource reservation systems |
| (e.g., memory allocators, connection pools) where out-of-order resolution |
| is necessary to completely prevent head-of-line blocking. |
| |
| Creating a list provider |
| ------------------------ |
| Declare a ``ValueListProvider`` with the type of value to provide: |
| |
| .. code-block:: c++ |
| |
| pw::async2::ValueListProvider<int> provider; |
| |
| Tasks can register futures using the ``Get`` method, which returns a |
| :cc:`ValueFuture <pw::async2::ValueFuture>` and automatically pushes it to |
| the provider's internal list: |
| |
| .. code-block:: c++ |
| |
| pw::async2::ValueFuture<int> future = provider.Get(); |
| |
| Querying list state |
| ------------------- |
| You can safely query the number of pending futures using ``size()`` or check |
| if the list is empty using ``empty()``. |
| |
| Atomic out-of-order matching |
| ---------------------------- |
| To prevent time-of-check to time-of-use (TOCTOU) race conditions in |
| multithreaded environments, ``ValueListProvider`` does not expose list |
| iteration. Instead, all matching and resolution must be performed atomically |
| using ``ResolveFirstMatching`` and ``ResolveAllMatching``. |
| |
| These methods traverse the list under a shared async2 lock and invoke a |
| callback for each pending future. If the callback returns a value (or ``true`` |
| for ``void`` futures), the matched future is atomically removed from the list |
| and resolved. |
| |
| - ``ResolveFirstMatching``: Resolves the first future for which the callback |
| returns a value. |
| - ``ResolveAllMatching``: Resolves all futures for which the callback returns |
| a value. |
| |
| For non-void futures (producing ``T``), the callback must return |
| ``std::optional<T>``. Returning ``std::nullopt`` leaves the future pending. |
| For ``void`` futures, the callback must return ``bool``. |
| |
| See the section below for a concrete example of using these matching methods |
| with custom derived futures to achieve out-of-order resource allocation. |
| |
| Using custom derived futures |
| ---------------------------- |
| Just like :cc:`DerivedValueProvider <pw::async2::DerivedValueProvider>`, |
| ``ValueListProvider`` can be used with user-defined derived futures. You can |
| use the ``DerivedValueListProvider`` template alias to simplify |
| declarations: |
| |
| .. code-block:: c++ |
| |
| class CustomRequestFuture |
| : public pw::async2::ValueFuture<pw::Result<pw::ByteSpan>> { |
| public: |
| CustomRequestFuture(pw::async2::ValueFuture<pw::Result<pw::ByteSpan>>&& base, |
| size_t requested_size) |
| : pw::async2::ValueFuture(std::move(base)), |
| requested_size_(requested_size) {} |
| CustomRequestFuture(CustomRequestFuture&&) = default; |
| CustomRequestFuture& operator=(CustomRequestFuture&&) = default; |
| ~CustomRequestFuture() { this->Cancel(); } |
| |
| size_t requested_size() const { return requested_size_; } |
| |
| private: |
| size_t requested_size_; |
| }; |
| |
| // Declare a list provider for the derived future type. |
| pw::async2::DerivedValueListProvider<CustomRequestFuture> request_provider; |
| |
| Now, you can pass custom parameters when calling ``Get``: |
| |
| .. code-block:: c++ |
| |
| CustomRequestFuture future = request_provider.Get(/*requested_size=*/128); |
| |
| You can then atomically match and resolve using the custom parameters on the |
| derived future: |
| |
| .. code-block:: c++ |
| |
| bool resolved = request_provider.ResolveFirstMatching( |
| [&](CustomRequestFuture& future) |
| -> std::optional<pw::Result<pw::ByteSpan>> { |
| if (future.requested_size() <= GetAvailableBytes()) { |
| return Allocate(future.requested_size()); |
| } |
| return std::nullopt; |
| }); |
| |
| Bulk resolution and cleanup |
| --------------------------- |
| When a resource is shut down or connection is lost, you may want to resolve |
| all remaining futures at once. Use the ``ResolveAll`` method to resolve and |
| remove all pending futures from the list. |
| |
| The callback is invoked for every pending future: |
| |
| - For non-void futures, the callback must return the value (of type ``T``) to |
| resolve the future with. |
| - For void futures, the callback acts as a notification. |
| |
| .. code-block:: c++ |
| |
| // Abort all pending requests with an error status. |
| request_provider.ResolveAll( |
| [](CustomRequestFuture& future) -> pw::Result<pw::ByteSpan> { |
| return pw::Status::Aborted(); |
| }); |
| |
| .. warning:: |
| Since all callback-based resolution methods hold the shared async2 lock, |
| your callbacks must be fast and non-blocking. |
| |
| .. _module-pw_async2-futures-combinators: |
| |
| ----------- |
| Combinators |
| ----------- |
| Combinators allow you to compose multiple futures into a single future to express |
| complex control flow. |
| |
| Join |
| ==== |
| :cc:`Join` waits for multiple futures to complete and returns a tuple of their |
| results. |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-join-function] |
| :end-before: [pw_async2-examples-futures-join-function] |
| |
| .. tab-set:: |
| |
| .. tab-item:: Standard polling |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-join-task] |
| :end-before: [pw_async2-examples-futures-join-task] |
| |
| .. tab-item:: C++20 coroutines |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-join-coro] |
| :end-before: [pw_async2-examples-futures-join-coro] |
| |
| Select |
| ====== |
| :cc:`Select` waits for the *first* of multiple futures to complete. It returns a |
| :cc:`SelectFuture` which resolves to an :cc:`OptionalTuple` containing the |
| result. If additional futures happen to complete between the first future |
| completing the task re-running, the tuple stores all of their results. |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-select-functions] |
| :end-before: [pw_async2-examples-futures-select-functions] |
| |
| .. tab-set:: |
| |
| .. tab-item:: Standard polling |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-select-task] |
| :end-before: [pw_async2-examples-futures-select-task] |
| |
| .. tab-item:: C++20 coroutines |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-select-coro] |
| :end-before: [pw_async2-examples-futures-select-coro] |
| |
| .. _module-pw_async2-guides-primitives-wakers: |
| |
| .. _module-pw_async2-futures-type-erasure: |
| |
| ----------------------------- |
| Type erasure with BoxedFuture |
| ----------------------------- |
| Because ``pw_async2`` futures heavily use C++ templates, and Future combinators |
| create complex nested future types, it can become difficult to name the return |
| type of an async function to store it in a task. |
| |
| ``pw_async2`` provides :cc:`BoxedFuture <pw::async2::BoxedFuture>` for type |
| erasure when working with futures that return some value type ``T``. |
| |
| Some scenarios where a ``BoxedFuture`` can be useful include: |
| |
| 1. Complex combinators. If you use future combinators, the resulting type is |
| often complex and difficult or impossible to spell out (especially if it |
| relies on a local lambda). |
| 2. Returning different future types from a single function. If you have |
| conditional logic that performs different operations with underlying future |
| implementations, you can use ``BoxedFuture`` to unify them. |
| 3. Writing virtual interfaces. If you are defining an abstract base |
| class with asynchronous operations, ``BoxedFuture`` allows implementers to |
| choose their own future types to return. |
| |
| .. note:: |
| |
| ``pw_async2`` is designed to be allocation-free by default. However, |
| ``BoxedFuture`` requires dynamic memory allocation via ``pw::Allocator``. |
| |
| .. _module-pw_async2-futures-timeout: |
| |
| ------------------ |
| Timing-out Futures |
| ------------------ |
| If you create a :ref:`future <module-pw_async2-futures>`, you can also combine |
| it with a :cc:`TimeFuture <pw::async2::TimeFuture>` to get a new composite |
| future (a :cc:`FutureWithTimeout <pw::async2::FutureWithTimeout>`) that can |
| time out. |
| |
| There are three main factory functions that construct useful variants of the |
| composite type. |
| |
| - ``Timeout(future, [time_provider,] delay)`` |
| |
| This function returns a composite future that times out after the specified |
| delay. If no time provider is given, the function will default to |
| :cc:`GetSystemTimeProvider <pw::async2::GetSystemTimeProvider>`. The time |
| provider will then be used to construct the |
| :cc:`TimeFuture <pw::async2::TimeFuture>` to use in the composite future. |
| |
| If the original value future is for a value of type `T`, the created |
| composite future uses :cs:`pw::Result<T><pw::Result>`. On timeout, the status |
| associated with that result will be ``Status.DeadlineExceeded()`` to make it |
| clear no value is available. |
| |
| The composite future will handle waiting on both futures, and will prefer to |
| resolve to the value provided by the first future if both futures are ready |
| when they are next pended. |
| |
| - ``TimeoutOr(future, [time_provider,] delay, sentinel_value_or_func)`` |
| |
| Like the first function, this function will construct a composite future that |
| will time out after the specified delay. |
| |
| However on timeout, this version will resolve to a sentinel value, either |
| using a value passed in, or calling a function to obtain it, if that is what |
| is passed in. |
| |
| Note that a copy of the value that is passed will be stored as part of the |
| internal data for a future. For a small and trivially constructible type, |
| this makes sense, but for a large type or a type that is not trivially |
| constructible you should prefer to pass a function which constructs the |
| value. |
| |
| .. caution:: |
| |
| You should only use a sentinel when it there is no chance of confusing the |
| sentinel value with the normal values you would obtain from the future when |
| it does not time out. |
| |
| - ``TimeoutOrClosed(channel_future, [time_provider], delay)`` |
| |
| Like the first function, but intended to be used with the |
| :cc:`SendFuture <pw::async2::SendFuture>`, |
| :cc:`ReceiveFuture <pw::async2::ReceiveFuture>`, and |
| :cc:`ReserveSendFuture <pw::async2::ReserveSendFuture>` futures returned from |
| using a channel. |
| |
| On timeout, these act like the channel was closed while waiting, and release |
| their reference to the channel. For ``SendFuture``, this means it resolves to |
| false, and for the other two it means resolving to ``std::nullopt``. |
| |
| Example |
| ======= |
| Using them to construct the composite future is easy. |
| |
| .. literalinclude:: examples/futures.cc |
| :language: cpp |
| :start-after: [pw_async2-examples-futures-timeout] |
| :end-before: [pw_async2-examples-futures-timeout] |
| |
| You can find more examples showing how to use these functions in |
| :cs:`pw_async2/examples/timeout_test.cc`. |