From a54614d1baad12884db289c5724d792a9996db1e Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sat, 19 Jul 2025 15:59:10 -0700 Subject: [PATCH] some high-level user docs --- docs/CMakeLists.txt | 1 + docs/source/conf.py | 7 +- docs/source/index.rst | 325 ++++++++++++++++++++++-- docs/source/reference/index.rst | 42 ++- docs/source/style.css | 3 + include/stdexec/__detail/__config.hpp | 2 +- include/stdexec/__detail/__read_env.hpp | 15 +- test/exec/test_system_context.cpp | 1 - 8 files changed, 369 insertions(+), 27 deletions(-) create mode 100644 docs/source/style.css diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index b3ff4f4f7..2254b1fe6 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -60,6 +60,7 @@ set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html) file(GLOB_RECURSE STDEXEC_DOCUMENTATION_SOURCE_FILES + CONFIGURE_DEPENDS ${SPHINX_SOURCE}/*.rst ${SPHINX_SOURCE}/*.md) # Only regenerate Sphinx when: diff --git a/docs/source/conf.py b/docs/source/conf.py index 0fbcba4e0..a17051f9c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -22,6 +22,8 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import os + project = 'stdexec' copyright = '2025, NVIDIA Corporation' author = 'NVIDIA Corporation' @@ -75,5 +77,6 @@ highlight_language = "cpp" -# def setup(app): -# app.add_css_file("params.css") +def setup(app): + print(os.path.join(app.srcdir, "style.css")) + app.add_css_file(os.path.join(app.srcdir, "style.css")) diff --git a/docs/source/index.rst b/docs/source/index.rst index 0dedc00ef..10f511f0f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,12 +1,12 @@ .. ============================================================================= -.. Copyright 2023 NVIDIA Corporation -.. +.. Copyright 2025 NVIDIA Corporation +.. .. Licensed under the Apache License, Version 2.0 (the "License"); .. you may not use this file except in compliance with the License. .. You may obtain a copy of the License at -.. +.. .. http://www.apache.org/licenses/LICENSE-2.0 -.. +.. .. Unless required by applicable law or agreed to in writing, software .. distributed under the License is distributed on an "AS IS" BASIS, .. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,31 +14,326 @@ .. limitations under the License. .. ============================================================================= +.. highlight:: cpp + :linenothreshold: 5 + Welcome to ``stdexec`` ====================== -``stdexec`` is the reference implementation for `P2300 `_, the -``std::execution`` proposal to the C++ standard library which will bring to C++26 a standard -async programming model. +``stdexec`` is the reference implementation for `C++26's asynchronous execution framework +`_, ``std::execution``. It provides a modern, +composable, and efficient abstraction for asynchronous programming in C++. + +Sender/Receiver Abstraction in C++26 +==================================== + +The **sender/receiver abstraction** is a foundational model for asynchronous programming in +modern C++, proposed for standardization in **C++26** (originally targeted for C++23). It aims +to unify and modernize asynchronous workflows across the C++ ecosystem. + +🔧 Motivation +-------------- + +C++'s legacy async mechanisms — ``std::async``, futures, coroutines — have several limitations: + +- Inflexible and hard to compose. + +- Inefficient or heap-heavy. + +- Difficult to customize or extend. + +- Incompatible across libraries (e.g., Boost, Folly). + +The sender/receiver abstraction offers: + +- Composable async operations. + +- Customizable schedulers and execution strategies. + +- Clean cancellation handling. + +- Coroutine integration. + +- Zero-cost abstractions. + +🧱 Core Concepts +---------------- + +1. Scheduler +^^^^^^^^^^^^ + +A **scheduler** is an object that provides a way to schedule work. They are lightweight +handles to what is often a heavy-weight and immovable **execution context**. Execution +contexts are where work actually happens, and they can be anything that can execute code. +Examples of execution contexts: + +- Thread pools. +- Event loops. +- GPUs. +- An I/O subsystem. +- Any other execution model. + +.. code-block:: cpp + + auto sched = exec::get_parallel_scheduler(); // Obtain the default system scheduler + auto sndr = stdexec::schedule(sched); // Create a sender from the scheduler + +The sender you get back from ``stdexec::schedule`` is a factory for operations that, +when started, will immediately call ``set_value()`` on the receiver the operation was +constructed with. And crucially, it does so from the context of the scheduler. Work +is executed on a context by chaining continuations to one of these senders, and passing +it to one of the algorithms that starts work, like ``stdexec::sync_wait``. + +2. Sender +^^^^^^^^^ + +A **sender** is an object that describes an asynchronous computation that may happen +later. It can do nothing on its own, but when connected to a receiver, it returns an +**operation state** that can start the work described by the sender. + +``stdexec`` provides a set of generic **sender algorithms** that can be used to chain +operations together. There are also algorithms, like ``stdexec::sync_wait``, that can +be used to launch the sender. The sender algorithms take care of connecting the sender +to a receiver and managing the lifetime the operation state. + +- Produces values (or errors) asynchronously. + +- Can be requested to stop early. + +- Supports composition. + +- Is lazy (does nothing until connected and started). + +.. code-block:: cpp + + auto sndr = stdexec::just(42); // Sender that yields 42 immediately + auto [result] = stdexec::sync_wait(sndr).value(); // Start the work & wait for the result + assert(result.value() == 42); + +3. Receiver +^^^^^^^^^^^ + +A **receiver** is an object that consumes the result of a sender. It defines three +member functions that handle completion: + +- ``.set_value(args...)``: Called on success. + +- ``.set_error(err)``: Called on error. + +- ``.set_stopped()``: Called if the operation is canceled. + +.. code-block:: cpp + + struct MyReceiver { + using receiver_concept = stdexec::receiver_t; + + void set_value(int v) noexcept { /* success */ } + void set_error(std::exception_ptr e) noexcept { /* error */ } + void set_stopped() noexcept { /* cancellation */ } + }; + +As a user, you typically won't deal with receivers directly. +They are an implementation detail of sender algorithms. + +4. Operation State +^^^^^^^^^^^^^^^^^^ + +Connecting a sender to a receiver yields an **operation state**, which: + +- Represents the in-progress computation. + +- Is started explicitly via ``.start()``. + +.. code-block:: cpp + + auto op = stdexec::connect(sndr, MyReceiver{}); // Connect sender to receiver + stdexec::start(op); // Start the operation + +Operation states are immovable, and once started, they must be kept alive until the +operation completes. As a user though, you typically will not manage them directly. +They are handled by the sender algorithms. + +5. Environments +^^^^^^^^^^^^^^^^^^ -It provides: +Environments are a key concept in the sender/receiver model. An **environment** is an +unordered collection of key/value pairs, queryable at runtime via tag types. Every +receiver has a (possibly empty) environment that can be obtained by passing the receiver +to ``stdexec::get_env()``. -* TODO +Environments provide a way to pass contextual information like stop tokens, allocators, or +schedulers to asynchronous operations. That information is then used by the operation to +customize its behavior. + +🧮 Composition via Algorithms +----------------------------- + +One benefit of the lazy evaluation of senders is that it makes it possible to create +reusable algorithms that can be composed together. ``stdexec`` provides a rich set of +algorithms that can be used to build complex asynchronous workflows. + +A **sender factory algorithm** creates a sender that can be used to start an operation. +Below are some of the key sender factory algorithms provided by ``stdexec``: + +.. list-table:: Sender Factory Algorithms + + * - **CPO** + - **Description** + * - ``stdexec::schedule`` + - Obtains a sender from a scheduler. + * - ``stdexec::just`` + - Creates a sender that will immediately complete with a set of values. + * - ``stdexec::read_env`` + - Reads a value from the receiver's environment and completes with it. + +A **sender adaptor algorithm** takes an existing sender (or several senders) and +transforms it into a new sender with additional behavior. Below are some key sender +adaptor algorithms. Check the :ref:`Reference` section for additional algorithms. + +.. list-table:: Sender Adaptor Algorithms + :class: tight-table + + * - **CPO** + - **Description** + * - ``stdexec::then`` + - Applies a function to the value from a sender. + * - ``stdexec::starts_on`` + - Executes an async operation on the specified scheduler. + * - ``stdexec::continues_on`` + - Executes an async operation on the current scheduler and then transfers + execution to the specified scheduler. + * - ``stdexec::on`` + - Executes an async operation on a different scheduler and then transitions + back to the original scheduler. + * - ``stdexec::when_all`` + - Combines multiple senders, making it possible to execute them in parallel. + * - ``stdexec::let_value`` + - Executes an async operation dynamically based on the results of a specified + sender. + * - ``stdexec::write_env`` + - Writes a value to the receiver's environment, allowing it to be used by + child operations. + +A **sender consumer algorithm** takes a sender connects it to a receiver and starts the +resulting operation. Here are some key sender consumer algorithms: + +.. list-table:: Sender Consumer Algorithms + + * - **CPO** + - **Description** + * - ``stdexec::sync_wait`` + - Blocks the calling thread until the sender completes and returns the result. + * - ``stdexec::start_detached`` + - Starts the operation without waiting for it to complete. + +Sender algorithms are defined in terms of **core customization points**. Below are the +core customization points that define how senders and receivers interact: + +.. list-table:: Core customization points + + * - **CPO** + - **Description** + * - ``stdexec::connect`` + - Connects a sender to a receiver resulting in an operation state. + * - ``stdexec::start`` + - Starts the operation. + * - ``stdexec::set_value`` + - Called by the operation state to deliver a value to the receiver. + * - ``stdexec::set_error`` + - Called by the operation state to deliver an error to the receiver. + * - ``stdexec::set_stopped`` + - Called by the operation state to indicate that the operation was stopped. + * - ``stdexec::get_env`` + - Retrieves the environment from a receiver. + +Here is an example of using sender algorithms to create a simple async pipeline: + +.. code-block:: cpp + + // Create a sender that produces a value: + auto pipeline = stdexec::just(42) + // Transform the value using `then` on the specified scheduler: + | stdexec::on(some_scheduler, stdexec::then([](int i) { return i * 2; })) + // Further transform the value using `then` on the starting scheduler: + | stdexec::then([](int i) { return i + 1; }); + // Finally, wait for the result: + auto [result] = stdexec::sync_wait(std::move(pipeline)).value(); + + +🔄 Coroutine Integration +------------------------ + +Senders can be ``co_await``-ed in coroutines if they model ``awaitable_sender``. Any sender +that can complete successfully in exactly one way is an awaitable sender. + +.. code-block:: cpp + + auto my_task() -> exec::task { + int x = co_await some_sender(); + co_return x + 1; + } + +If a sender that is being ``co_await``-ed completes with an error, the coroutine will +throw an exception. If it completes with a stop, the coroutine will be canceled. That is, +the coroutine will never be resumed; rather, it and its calling coroutines will be +destroyed. + +In addition, all awaitable types can be used as senders, allowing them to be composed with +sender algorithms. + +This allows ergonomic, coroutine-based async programming with sender semantics under the +hood. + +🚦 Standardization Status (as of 2025) +-------------------------------------- + +- The core sender/receiver model has been accepted into the C++ standard for C++26. + +- Additional facilities have also been accepted such as + + * a system scheduler, + * a sender-aware coroutine task type, + * an async scope for spawning tasks dynamically. + +- Interop with networking is being explored for C++29. + +- Widely prototyped and tested in libraries and production settings. + +🚀 Benefits +----------- + +- ✅ Zero-cost abstractions: No heap allocations or runtime overhead. + +- ✅ Composable: Express async pipelines clearly. + +- ✅ Customizable: Plug in your own schedulers, tokens, adapters. + +- ✅ Coroutine-friendly: Clean ``co_await`` support. + +- ✅ Unified async model: Works for I/O, compute, UI, etc. + +🔚 Summary +----------- + +The sender/receiver abstraction: + +- Brings modern, composable async programming to C++. + +- Serves as a foundation for future concurrency features. + +- Enables high-performance, coroutine-friendly workflows. + +- Is set to become the standard async model in C++26. .. toctree:: :maxdepth: 2 :caption: Contents: - reference/index - user/index developer/index -.. .. toctree: : -.. :maxdepth: 1 - -.. versions + reference/index Indices and tables ------------------ diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index 6d8dc268f..ed2980fd5 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -8,6 +8,8 @@ without an express license agreement from NVIDIA CORPORATION or its affiliates is strictly prohibited. +.. _Reference: + Reference ========= @@ -17,27 +19,65 @@ Reference Concepts -------- +TODO: Add concepts section Sender Factories ---------------- +TODO: more sender factory algorithms + +.. doxygenvariable:: stdexec::schedule + +.. doxygenvariable:: stdexec::just + +.. doxygenvariable:: stdexec::just_error + +.. doxygenvariable:: stdexec::just_stopped + .. doxygenvariable:: stdexec::read_env -.. doxygenstruct:: exec::just_from_t +.. .. doxygenstruct:: exec::just_from_t .. doxygenvariable:: exec::just_from +.. doxygenvariable:: exec::just_error_from + +.. doxygenvariable:: exec::just_stopped_from + Sender Adaptors --------------- +TODO: More sender adaptor algorithms + .. doxygenvariable:: stdexec::then +.. doxygenvariable:: stdexec::starts_on + +.. doxygenvariable:: stdexec::continues_on + +.. doxygenvariable:: stdexec::on + +.. doxygenvariable:: stdexec::when_all + +.. doxygenvariable:: stdexec::let_value + +.. doxygenvariable:: stdexec::let_error + +.. doxygenvariable:: stdexec::let_stopped + +.. .. doxygenvariable:: stdexec::write_env + Sender Consumers ---------------- +TODO: More sender consumer algorithms + .. doxygenvariable:: stdexec::sync_wait +.. doxygenvariable:: stdexec::start_detached + Utilities --------- +TODO: Add utilities section diff --git a/docs/source/style.css b/docs/source/style.css new file mode 100644 index 000000000..10f55d62b --- /dev/null +++ b/docs/source/style.css @@ -0,0 +1,3 @@ +.tight-table td { + white-space: normal !important; +} diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index ac85a7afd..e8d9c6ccb 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -40,7 +40,7 @@ #include #include #include // IWYU pragma: keep -#include // IWYU pragma: keep for std::unreachable +#include // IWYU pragma: keep for std::unreachable // When used with no arguments, these macros expand to 1 if the current // compiler corresponds to the macro name; 0, otherwise. When used with arguments, diff --git a/include/stdexec/__detail/__read_env.hpp b/include/stdexec/__detail/__read_env.hpp index 0633f895c..5516b823f 100644 --- a/include/stdexec/__detail/__read_env.hpp +++ b/include/stdexec/__detail/__read_env.hpp @@ -26,7 +26,7 @@ #include "__optional.hpp" #include "__meta.hpp" #include "__receivers.hpp" -#include "__submit.hpp" +#include "__submit.hpp" // IWYU pragma: keep #include @@ -70,10 +70,10 @@ namespace stdexec { using __result = _Ty; }; - struct __read_env_t { + struct read_env_t { template constexpr auto operator()(_Tag) const noexcept { - return __make_sexpr<__read_env_t>(_Tag()); + return __make_sexpr(_Tag()); } }; @@ -123,7 +123,7 @@ namespace stdexec { [](const _Sender&, _Receiver __rcvr) noexcept requires std::is_reference_v<__call_result_t<__data_of<_Sender>, env_of_t<_Receiver>>> { - static_assert(sender_expr_for<_Sender, __read_env_t>); + static_assert(sender_expr_for<_Sender, read_env_t>); using __query = __data_of<_Sender>; stdexec::__set_value_invoke( static_cast<_Receiver&&>(__rcvr), __query(), stdexec::get_env(__rcvr)); @@ -131,12 +131,13 @@ namespace stdexec { }; } // namespace __read + using __read::read_env_t; [[deprecated("read has been renamed to read_env")]] - inline constexpr __read::__read_env_t read{}; - inline constexpr __read::__read_env_t read_env{}; + inline constexpr read_env_t read{}; + inline constexpr read_env_t read_env{}; template <> - struct __sexpr_impl<__read::__read_env_t> : __read::__read_env_impl { }; + struct __sexpr_impl<__read::read_env_t> : __read::__read_env_impl { }; namespace __queries { template diff --git a/test/exec/test_system_context.cpp b/test/exec/test_system_context.cpp index bfe926897..502c43361 100644 --- a/test/exec/test_system_context.cpp +++ b/test/exec/test_system_context.cpp @@ -28,7 +28,6 @@ #include #include - namespace ex = stdexec; namespace scr = exec::system_context_replaceability;