LCOV - code coverage report
Current view: top level - boost/capy - io_awaitable.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 95.6 % 45 43
Test Date: 2026-01-23 03:27:33 Functions: 72.7 % 132 96

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
       3              : //
       4              : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5              : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6              : //
       7              : // Official repository: https://github.com/cppalliance/capy
       8              : //
       9              : 
      10              : #ifndef BOOST_CAPY_IO_AWAITABLE_HPP
      11              : #define BOOST_CAPY_IO_AWAITABLE_HPP
      12              : 
      13              : #include <boost/capy/detail/config.hpp>
      14              : #include <boost/capy/coro.hpp>
      15              : #include <boost/capy/ex/executor_ref.hpp>
      16              : 
      17              : #include <coroutine>
      18              : #include <exception>
      19              : #include <stop_token>
      20              : #include <type_traits>
      21              : 
      22              : namespace boost {
      23              : namespace capy {
      24              : 
      25              : /** Tag type for coroutine stop token retrieval.
      26              : 
      27              :     This tag is returned by @ref get_stop_token and intercepted by a
      28              :     promise type's `await_transform` to yield the coroutine's current
      29              :     stop token. The tag itself carries no data; it serves only as a
      30              :     sentinel for compile-time dispatch.
      31              : 
      32              :     @see get_stop_token
      33              :     @see io_awaitable_support
      34              : */
      35              : struct get_stop_token_tag {};
      36              : 
      37              : /** Tag type for coroutine executor retrieval.
      38              : 
      39              :     This tag is returned by @ref get_executor and intercepted by a
      40              :     promise type's `await_transform` to yield the coroutine's current
      41              :     executor. The tag itself carries no data; it serves only as a
      42              :     sentinel for compile-time dispatch.
      43              : 
      44              :     @see get_executor
      45              :     @see io_awaitable_support
      46              : */
      47              : struct get_executor_tag {};
      48              : 
      49              : /** Return a tag that yields the current stop token when awaited.
      50              : 
      51              :     Use `co_await get_stop_token()` inside a coroutine whose promise
      52              :     type supports stop token access (e.g., inherits from
      53              :     @ref io_awaitable_support). The returned stop token reflects whatever
      54              :     token was passed to this coroutine when it was awaited.
      55              : 
      56              :     @par Example
      57              :     @code
      58              :     task<void> cancellable_work()
      59              :     {
      60              :         auto token = co_await get_stop_token();
      61              :         for (int i = 0; i < 1000; ++i)
      62              :         {
      63              :             if (token.stop_requested())
      64              :                 co_return;  // Exit gracefully on cancellation
      65              :             co_await process_chunk(i);
      66              :         }
      67              :     }
      68              :     @endcode
      69              : 
      70              :     @par Behavior
      71              :     @li If no stop token was propagated, returns a default-constructed
      72              :         `std::stop_token` (where `stop_possible()` returns `false`).
      73              :     @li The returned token remains valid for the coroutine's lifetime.
      74              :     @li This operation never suspends; `await_ready()` always returns `true`.
      75              : 
      76              :     @return A tag that `await_transform` intercepts to return the stop token.
      77              : 
      78              :     @see get_stop_token_tag
      79              :     @see io_awaitable_support
      80              : */
      81           16 : inline get_stop_token_tag get_stop_token() noexcept
      82              : {
      83           16 :     return {};
      84              : }
      85              : 
      86              : /** Return a tag that yields the current executor when awaited.
      87              : 
      88              :     Use `co_await get_executor()` inside a coroutine whose promise
      89              :     type supports executor access (e.g., inherits from
      90              :     @ref io_awaitable_support). The returned executor reflects the
      91              :     executor this coroutine is bound to.
      92              : 
      93              :     @par Example
      94              :     @code
      95              :     task<void> example()
      96              :     {
      97              :         executor_ref ex = co_await get_executor();
      98              :         // ex is the executor this coroutine is bound to
      99              :     }
     100              :     @endcode
     101              : 
     102              :     @par Behavior
     103              :     @li If no executor was set, returns a default-constructed
     104              :         `executor_ref` (where `operator bool()` returns `false`).
     105              :     @li This operation never suspends; `await_ready()` always returns `true`.
     106              : 
     107              :     @return A tag that `await_transform` intercepts to return the executor.
     108              : 
     109              :     @see get_executor_tag
     110              :     @see io_awaitable_support
     111              : */
     112            4 : inline get_executor_tag get_executor() noexcept
     113              : {
     114            4 :     return {};
     115              : }
     116              : 
     117              : /** Concept for I/O awaitable types.
     118              : 
     119              :     An awaitable is an I/O awaitable if it participates in the I/O awaitable
     120              :     protocol by accepting an executor and a stop_token in its `await_suspend`
     121              :     method. This enables zero-overhead scheduler affinity and cancellation
     122              :     support.
     123              : 
     124              :     @tparam A The awaitable type.
     125              : 
     126              :     @par Requirements
     127              :     @li `A` must provide `await_suspend(coro h, executor_ref const& ex,
     128              :         std::stop_token token)`
     129              :     @li The awaitable must use the executor `ex` to resume the caller
     130              :     @li The awaitable should use the stop_token to support cancellation
     131              : 
     132              :     @par Example
     133              :     @code
     134              :     struct my_io_op
     135              :     {
     136              :         template<typename Executor>
     137              :         auto await_suspend(coro h, Executor const& ex,
     138              :             std::stop_token token)
     139              :         {
     140              :             start_async([h, &ex, token] {
     141              :                 if (token.stop_requested()) {
     142              :                     // Handle cancellation
     143              :                 }
     144              :                 ex.dispatch(h);  // Schedule resumption through executor
     145              :             });
     146              :             return std::noop_coroutine();
     147              :         }
     148              :         // ... await_ready, await_resume ...
     149              :     };
     150              :     @endcode
     151              : */
     152              : template<typename A>
     153              : concept IoAwaitable =
     154              :     requires(
     155              :         A a,
     156              :         coro h,
     157              :         executor_ref ex,
     158              :         std::stop_token token)
     159              :     {
     160              :         a.await_suspend(h, ex, token);
     161              :     };
     162              : 
     163              : /** Concept for I/O awaitable task types.
     164              : 
     165              :     A task is an I/O awaitable task if it satisfies @ref IoAwaitable and
     166              :     its `promise_type` provides the interface for context injection:
     167              : 
     168              :     @li `set_executor(executor_ref)` — stores the executor
     169              :     @li `set_stop_token(std::stop_token)` — stores the stop token
     170              :     @li `executor()` — retrieves the stored executor
     171              :     @li `stop_token()` — retrieves the stored stop token
     172              : 
     173              :     This concept formalizes the contract between launch functions
     174              :     (`run_async`, `run_on`) and task types. Launch functions are the
     175              :     root of a coroutine chain and must set context directly on the
     176              :     promise rather than going through `await_suspend`.
     177              : 
     178              :     @tparam T The task type.
     179              : 
     180              :     @par Requirements
     181              :     @li `T` must satisfy @ref IoAwaitable
     182              :     @li `T::promise_type` must exist
     183              :     @li The promise must provide `set_executor` and `set_stop_token`
     184              :     @li The promise must provide `executor` and `stop_token` accessors
     185              : 
     186              :     @par Example
     187              :     @code
     188              :     struct my_task
     189              :     {
     190              :         struct promise_type : io_awaitable_support<promise_type>
     191              :         {
     192              :             my_task get_return_object();
     193              :             std::suspend_always initial_suspend() noexcept;
     194              :             std::suspend_always final_suspend() noexcept;
     195              :             void return_void();
     196              :             void unhandled_exception();
     197              :         };
     198              : 
     199              :         std::coroutine_handle<promise_type> h_;
     200              : 
     201              :         bool await_ready() const noexcept { return false; }
     202              : 
     203              :         coro await_suspend(coro cont, executor_ref ex, std::stop_token token)
     204              :         {
     205              :             h_.promise().set_executor(ex);
     206              :             h_.promise().set_stop_token(token);
     207              :             // ... set continuation, return handle ...
     208              :         }
     209              : 
     210              :         void await_resume() {}
     211              :     };
     212              : 
     213              :     static_assert(IoAwaitableTask<my_task>);
     214              :     @endcode
     215              : 
     216              :     @see IoAwaitable
     217              :     @see io_awaitable_support
     218              : */
     219              : template<typename T>
     220              : concept IoAwaitableTask =
     221              :     IoAwaitable<T> &&
     222              :     requires { typename T::promise_type; } &&
     223              :     requires(
     224              :         typename T::promise_type& p,
     225              :         typename T::promise_type const& cp,
     226              :         executor_ref ex,
     227              :         std::stop_token st,
     228              :         coro cont)
     229              :     {
     230              :         { p.set_executor(ex) } noexcept;
     231              :         { p.set_stop_token(st) } noexcept;
     232              :         { p.set_continuation(cont, ex) } noexcept;
     233              :         { cp.executor() } noexcept -> std::same_as<executor_ref>;
     234              :         { cp.stop_token() } noexcept -> std::same_as<std::stop_token const&>;
     235              :         { p.complete() } noexcept -> std::same_as<coro>;
     236              :     };
     237              : 
     238              : /** Concept for launchable I/O task types.
     239              : 
     240              :     A task satisfies `IoLaunchableTask` if it satisfies @ref IoAwaitableTask
     241              :     and provides the additional interface needed by launch utilities like
     242              :     `run_async` and `run_on`:
     243              : 
     244              :     @li `handle()` — returns the typed coroutine handle
     245              :     @li `release()` — releases ownership (task won't destroy frame)
     246              :     @li `exception()` — returns stored exception_ptr from the promise
     247              :     @li `result()` — returns stored result from the promise (non-void tasks)
     248              : 
     249              :     This concept formalizes the contract for launching tasks from
     250              :     non-coroutine contexts.
     251              : 
     252              :     @tparam T The task type.
     253              : 
     254              :     @par Requirements
     255              :     @li `T` must satisfy @ref IoAwaitableTask
     256              :     @li `T::handle()` returns `std::coroutine_handle<promise_type>`
     257              :     @li `T::release()` releases ownership without returning the handle
     258              :     @li `T::promise_type::exception()` returns the stored exception
     259              :     @li `T::promise_type::result()` returns the result (for non-void tasks)
     260              : 
     261              :     @see IoAwaitableTask
     262              :     @see run_async
     263              :     @see run_on
     264              : */
     265              : template<typename T>
     266              : concept IoLaunchableTask =
     267              :     IoAwaitableTask<T> &&
     268              :     requires(T& t, T const& ct, typename T::promise_type const& cp)
     269              :     {
     270              :         { ct.handle() } noexcept -> std::same_as<std::coroutine_handle<typename T::promise_type>>;
     271              :         { t.release() } noexcept;
     272              :         { cp.exception() } noexcept -> std::same_as<std::exception_ptr>;
     273              :     } &&
     274              :     (std::is_void_v<decltype(std::declval<T&>().await_resume())> ||
     275              :      requires(typename T::promise_type& p) {
     276              :          p.result();
     277              :      });
     278              : 
     279              : /** CRTP mixin that adds I/O awaitable support to a promise type.
     280              : 
     281              :     Inherit from this class to enable these capabilities in your coroutine:
     282              : 
     283              :     1. **Stop token storage** — The mixin stores the `std::stop_token`
     284              :        that was passed when your coroutine was awaited.
     285              : 
     286              :     2. **Stop token access** — Coroutine code can retrieve the token via
     287              :        `co_await get_stop_token()`.
     288              : 
     289              :     3. **Executor storage** — The mixin stores the `executor_ref`
     290              :        that this coroutine is bound to.
     291              : 
     292              :     4. **Executor access** — Coroutine code can retrieve the executor via
     293              :        `co_await get_executor()`.
     294              : 
     295              :     @tparam Derived The derived promise type (CRTP pattern).
     296              : 
     297              :     @par Basic Usage
     298              : 
     299              :     For coroutines that need to access their stop token or executor:
     300              : 
     301              :     @code
     302              :     struct my_task
     303              :     {
     304              :         struct promise_type : io_awaitable_support<promise_type>
     305              :         {
     306              :             my_task get_return_object();
     307              :             std::suspend_always initial_suspend() noexcept;
     308              :             std::suspend_always final_suspend() noexcept;
     309              :             void return_void();
     310              :             void unhandled_exception();
     311              :         };
     312              : 
     313              :         // ... awaitable interface ...
     314              :     };
     315              : 
     316              :     my_task example()
     317              :     {
     318              :         auto token = co_await get_stop_token();
     319              :         auto ex = co_await get_executor();
     320              :         // Use token and ex...
     321              :     }
     322              :     @endcode
     323              : 
     324              :     @par Custom Awaitable Transformation
     325              : 
     326              :     If your promise needs to transform awaitables (e.g., for affinity or
     327              :     logging), override `transform_awaitable` instead of `await_transform`:
     328              : 
     329              :     @code
     330              :     struct promise_type : io_awaitable_support<promise_type>
     331              :     {
     332              :         template<typename A>
     333              :         auto transform_awaitable(A&& a)
     334              :         {
     335              :             // Your custom transformation logic
     336              :             return std::forward<A>(a);
     337              :         }
     338              :     };
     339              :     @endcode
     340              : 
     341              :     The mixin's `await_transform` intercepts @ref get_stop_token_tag and
     342              :     @ref get_executor_tag, then delegates all other awaitables to your
     343              :     `transform_awaitable`.
     344              : 
     345              :     @par Making Your Coroutine an IoAwaitable
     346              : 
     347              :     The mixin handles the "inside the coroutine" part—accessing the token
     348              :     and executor. To receive these when your coroutine is awaited (satisfying
     349              :     @ref IoAwaitable), implement the `await_suspend` overload on your
     350              :     coroutine return type:
     351              : 
     352              :     @code
     353              :     struct my_task
     354              :     {
     355              :         struct promise_type : io_awaitable_support<promise_type> { ... };
     356              : 
     357              :         std::coroutine_handle<promise_type> h_;
     358              : 
     359              :         // IoAwaitable await_suspend receives and stores the token and executor
     360              :         template<class Ex>
     361              :         coro await_suspend(coro cont, Ex const& ex, std::stop_token token)
     362              :         {
     363              :             h_.promise().set_stop_token(token);
     364              :             h_.promise().set_executor(ex);
     365              :             // ... rest of suspend logic ...
     366              :         }
     367              :     };
     368              :     @endcode
     369              : 
     370              :     @par Thread Safety
     371              :     The stop token and executor are stored during `await_suspend` and read
     372              :     during `co_await get_stop_token()` or `co_await get_executor()`. These
     373              :     occur on the same logical thread of execution, so no synchronization
     374              :     is required.
     375              : 
     376              :     @see get_stop_token
     377              :     @see get_executor
     378              :     @see IoAwaitable
     379              : */
     380              : template<typename Derived>
     381              : class io_awaitable_support
     382              : {
     383              :     executor_ref executor_;
     384              :     std::stop_token stop_token_;
     385              :     coro cont_{std::noop_coroutine()};
     386              :     executor_ref caller_ex_;
     387              : 
     388              : public:
     389              :     /** Store continuation and caller's executor for completion dispatch.
     390              : 
     391              :         Call this from your coroutine type's `await_suspend` overload to
     392              :         set up the completion path. On completion, the coroutine will
     393              :         resume the continuation, dispatching through the caller's executor
     394              :         if it differs from this coroutine's executor.
     395              : 
     396              :         @param cont The continuation to resume on completion.
     397              :         @param caller_ex The caller's executor for completion dispatch.
     398              :     */
     399          118 :     void set_continuation(coro cont, executor_ref caller_ex) noexcept
     400              :     {
     401          118 :         cont_ = cont;
     402          118 :         caller_ex_ = caller_ex;
     403          118 :     }
     404              : 
     405              :     /** Return the handle to resume on completion with dispatch-awareness.
     406              : 
     407              :         If no continuation was set, returns `std::noop_coroutine()`.
     408              :         If the coroutine's executor matches the caller's executor, returns
     409              :         the continuation directly for symmetric transfer.
     410              :         Otherwise, dispatches through the caller's executor first.
     411              : 
     412              :         Call this from your `final_suspend` awaiter's `await_suspend`.
     413              : 
     414              :         @return A coroutine handle for symmetric transfer.
     415              :     */
     416          134 :     coro complete() noexcept
     417              :     {
     418          134 :         if(!cont_)
     419            0 :             return std::noop_coroutine();
     420          134 :         if(executor_ == caller_ex_)
     421          134 :             return std::exchange(cont_, std::noop_coroutine());
     422            0 :         return caller_ex_.dispatch(std::exchange(cont_, std::noop_coroutine()));
     423              :     }
     424              : 
     425              :     /** Store a stop token for later retrieval.
     426              : 
     427              :         Call this from your coroutine type's `await_suspend`
     428              :         overload to make the token available via `co_await get_stop_token()`.
     429              : 
     430              :         @param token The stop token to store.
     431              :     */
     432          120 :     void set_stop_token(std::stop_token token) noexcept
     433              :     {
     434          120 :         stop_token_ = token;
     435          120 :     }
     436              : 
     437              :     /** Return the stored stop token.
     438              : 
     439              :         @return The stop token, or a default-constructed token if none was set.
     440              :     */
     441           40 :     std::stop_token const& stop_token() const noexcept
     442              :     {
     443           40 :         return stop_token_;
     444              :     }
     445              : 
     446              :     /** Store an executor for later retrieval.
     447              : 
     448              :         Call this from your coroutine type's `await_suspend`
     449              :         overload to make the executor available via `co_await get_executor()`.
     450              : 
     451              :         @param ex The executor to store.
     452              :     */
     453          120 :     void set_executor(executor_ref ex) noexcept
     454              :     {
     455          120 :         executor_ = ex;
     456          120 :     }
     457              : 
     458              :     /** Return the stored executor.
     459              : 
     460              :         @return The executor, or a default-constructed executor_ref if none was set.
     461              :     */
     462           40 :     executor_ref executor() const noexcept
     463              :     {
     464           40 :         return executor_;
     465              :     }
     466              : 
     467              :     /** Transform an awaitable before co_await.
     468              : 
     469              :         Override this in your derived promise type to customize how
     470              :         awaitables are transformed. The default implementation passes
     471              :         the awaitable through unchanged.
     472              : 
     473              :         @param a The awaitable expression from `co_await a`.
     474              : 
     475              :         @return The transformed awaitable.
     476              :     */
     477              :     template<typename A>
     478              :     decltype(auto) transform_awaitable(A&& a)
     479              :     {
     480              :         return std::forward<A>(a);
     481              :     }
     482              : 
     483              :     /** Intercept co_await expressions.
     484              : 
     485              :         This function handles @ref get_stop_token_tag and @ref get_executor_tag
     486              :         specially, returning an awaiter that yields the stored value. All other
     487              :         awaitables are delegated to @ref transform_awaitable.
     488              : 
     489              :         @param t The awaited expression.
     490              : 
     491              :         @return An awaiter for the expression.
     492              :     */
     493              :     template<typename T>
     494           57 :     auto await_transform(T&& t)
     495              :     {
     496              :         if constexpr (std::is_same_v<std::decay_t<T>, get_stop_token_tag>)
     497              :         {
     498              :             struct awaiter
     499              :             {
     500              :                 std::stop_token token_;
     501              : 
     502           14 :                 bool await_ready() const noexcept
     503              :                 {
     504           14 :                     return true;
     505              :                 }
     506              : 
     507            1 :                 void await_suspend(coro) const noexcept
     508              :                 {
     509            1 :                 }
     510              : 
     511           13 :                 std::stop_token await_resume() const noexcept
     512              :                 {
     513           13 :                     return token_;
     514              :                 }
     515              :             };
     516           15 :             return awaiter{stop_token_};
     517              :         }
     518              :         else if constexpr (std::is_same_v<std::decay_t<T>, get_executor_tag>)
     519              :         {
     520              :             struct awaiter
     521              :             {
     522              :                 executor_ref executor_;
     523              : 
     524            2 :                 bool await_ready() const noexcept
     525              :                 {
     526            2 :                     return true;
     527              :                 }
     528              : 
     529            1 :                 void await_suspend(coro) const noexcept
     530              :                 {
     531            1 :                 }
     532              : 
     533            1 :                 executor_ref await_resume() const noexcept
     534              :                 {
     535            1 :                     return executor_;
     536              :                 }
     537              :             };
     538            3 :             return awaiter{executor_};
     539              :         }
     540              :         else
     541              :         {
     542            3 :             return static_cast<Derived*>(this)->transform_awaitable(
     543           39 :                 std::forward<T>(t));
     544              :         }
     545              :     }
     546              : 
     547          146 :     constexpr io_awaitable_support() noexcept = default;
     548              :     io_awaitable_support(const io_awaitable_support & ) = delete;
     549          146 :     ~io_awaitable_support()
     550              :     {
     551          146 :         cont_.destroy();
     552          146 :     }
     553              : };
     554              : 
     555              : } // namespace capy
     556              : } // namespace boost
     557              : 
     558              : #endif
        

Generated by: LCOV version 2.3