LCOV - code coverage report
Current view: top level - boost/capy - io_awaitable.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 97.6 % 41 40
Test Date: 2026-01-23 22:12:31 Functions: 72.4 % 116 84

            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 <boost/capy/concept/io_awaitable.hpp>
      18              : #include <boost/capy/concept/io_awaitable_task.hpp>
      19              : #include <boost/capy/concept/io_launchable_task.hpp>
      20              : 
      21              : #include <coroutine>
      22              : #include <exception>
      23              : #include <stop_token>
      24              : #include <type_traits>
      25              : 
      26              : namespace boost {
      27              : namespace capy {
      28              : 
      29              : /** Tag type for coroutine stop token retrieval.
      30              : 
      31              :     This tag is returned by @ref get_stop_token and intercepted by a
      32              :     promise type's `await_transform` to yield the coroutine's current
      33              :     stop token. The tag itself carries no data; it serves only as a
      34              :     sentinel for compile-time dispatch.
      35              : 
      36              :     @see get_stop_token
      37              :     @see io_awaitable_support
      38              : */
      39              : struct get_stop_token_tag {};
      40              : 
      41              : /** Tag type for coroutine executor retrieval.
      42              : 
      43              :     This tag is returned by @ref get_executor and intercepted by a
      44              :     promise type's `await_transform` to yield the coroutine's current
      45              :     executor. The tag itself carries no data; it serves only as a
      46              :     sentinel for compile-time dispatch.
      47              : 
      48              :     @see get_executor
      49              :     @see io_awaitable_support
      50              : */
      51              : struct get_executor_tag {};
      52              : 
      53              : /** Return a tag that yields the current stop token when awaited.
      54              : 
      55              :     Use `co_await get_stop_token()` inside a coroutine whose promise
      56              :     type supports stop token access (e.g., inherits from
      57              :     @ref io_awaitable_support). The returned stop token reflects whatever
      58              :     token was passed to this coroutine when it was awaited.
      59              : 
      60              :     @par Example
      61              :     @code
      62              :     task<void> cancellable_work()
      63              :     {
      64              :         auto token = co_await get_stop_token();
      65              :         for (int i = 0; i < 1000; ++i)
      66              :         {
      67              :             if (token.stop_requested())
      68              :                 co_return;  // Exit gracefully on cancellation
      69              :             co_await process_chunk(i);
      70              :         }
      71              :     }
      72              :     @endcode
      73              : 
      74              :     @par Behavior
      75              :     @li If no stop token was propagated, returns a default-constructed
      76              :         `std::stop_token` (where `stop_possible()` returns `false`).
      77              :     @li The returned token remains valid for the coroutine's lifetime.
      78              :     @li This operation never suspends; `await_ready()` always returns `true`.
      79              : 
      80              :     @return A tag that `await_transform` intercepts to return the stop token.
      81              : 
      82              :     @see get_stop_token_tag
      83              :     @see io_awaitable_support
      84              : */
      85           16 : inline get_stop_token_tag get_stop_token() noexcept
      86              : {
      87           16 :     return {};
      88              : }
      89              : 
      90              : /** Return a tag that yields the current executor when awaited.
      91              : 
      92              :     Use `co_await get_executor()` inside a coroutine whose promise
      93              :     type supports executor access (e.g., inherits from
      94              :     @ref io_awaitable_support). The returned executor reflects the
      95              :     executor this coroutine is bound to.
      96              : 
      97              :     @par Example
      98              :     @code
      99              :     task<void> example()
     100              :     {
     101              :         executor_ref ex = co_await get_executor();
     102              :         // ex is the executor this coroutine is bound to
     103              :     }
     104              :     @endcode
     105              : 
     106              :     @par Behavior
     107              :     @li If no executor was set, returns a default-constructed
     108              :         `executor_ref` (where `operator bool()` returns `false`).
     109              :     @li This operation never suspends; `await_ready()` always returns `true`.
     110              : 
     111              :     @return A tag that `await_transform` intercepts to return the executor.
     112              : 
     113              :     @see get_executor_tag
     114              :     @see io_awaitable_support
     115              : */
     116            4 : inline get_executor_tag get_executor() noexcept
     117              : {
     118            4 :     return {};
     119              : }
     120              : 
     121              : /** CRTP mixin that adds I/O awaitable support to a promise type.
     122              : 
     123              :     Inherit from this class to enable these capabilities in your coroutine:
     124              : 
     125              :     1. **Stop token storage** — The mixin stores the `std::stop_token`
     126              :        that was passed when your coroutine was awaited.
     127              : 
     128              :     2. **Stop token access** — Coroutine code can retrieve the token via
     129              :        `co_await get_stop_token()`.
     130              : 
     131              :     3. **Executor storage** — The mixin stores the `executor_ref`
     132              :        that this coroutine is bound to.
     133              : 
     134              :     4. **Executor access** — Coroutine code can retrieve the executor via
     135              :        `co_await get_executor()`.
     136              : 
     137              :     @tparam Derived The derived promise type (CRTP pattern).
     138              : 
     139              :     @par Basic Usage
     140              : 
     141              :     For coroutines that need to access their stop token or executor:
     142              : 
     143              :     @code
     144              :     struct my_task
     145              :     {
     146              :         struct promise_type : io_awaitable_support<promise_type>
     147              :         {
     148              :             my_task get_return_object();
     149              :             std::suspend_always initial_suspend() noexcept;
     150              :             std::suspend_always final_suspend() noexcept;
     151              :             void return_void();
     152              :             void unhandled_exception();
     153              :         };
     154              : 
     155              :         // ... awaitable interface ...
     156              :     };
     157              : 
     158              :     my_task example()
     159              :     {
     160              :         auto token = co_await get_stop_token();
     161              :         auto ex = co_await get_executor();
     162              :         // Use token and ex...
     163              :     }
     164              :     @endcode
     165              : 
     166              :     @par Custom Awaitable Transformation
     167              : 
     168              :     If your promise needs to transform awaitables (e.g., for affinity or
     169              :     logging), override `transform_awaitable` instead of `await_transform`:
     170              : 
     171              :     @code
     172              :     struct promise_type : io_awaitable_support<promise_type>
     173              :     {
     174              :         template<typename A>
     175              :         auto transform_awaitable(A&& a)
     176              :         {
     177              :             // Your custom transformation logic
     178              :             return std::forward<A>(a);
     179              :         }
     180              :     };
     181              :     @endcode
     182              : 
     183              :     The mixin's `await_transform` intercepts @ref get_stop_token_tag and
     184              :     @ref get_executor_tag, then delegates all other awaitables to your
     185              :     `transform_awaitable`.
     186              : 
     187              :     @par Making Your Coroutine an IoAwaitable
     188              : 
     189              :     The mixin handles the "inside the coroutine" part—accessing the token
     190              :     and executor. To receive these when your coroutine is awaited (satisfying
     191              :     @ref IoAwaitable), implement the `await_suspend` overload on your
     192              :     coroutine return type:
     193              : 
     194              :     @code
     195              :     struct my_task
     196              :     {
     197              :         struct promise_type : io_awaitable_support<promise_type> { ... };
     198              : 
     199              :         std::coroutine_handle<promise_type> h_;
     200              : 
     201              :         // IoAwaitable await_suspend receives and stores the token and executor
     202              :         template<class Ex>
     203              :         coro await_suspend(coro cont, Ex const& ex, std::stop_token token)
     204              :         {
     205              :             h_.promise().set_stop_token(token);
     206              :             h_.promise().set_executor(ex);
     207              :             // ... rest of suspend logic ...
     208              :         }
     209              :     };
     210              :     @endcode
     211              : 
     212              :     @par Thread Safety
     213              :     The stop token and executor are stored during `await_suspend` and read
     214              :     during `co_await get_stop_token()` or `co_await get_executor()`. These
     215              :     occur on the same logical thread of execution, so no synchronization
     216              :     is required.
     217              : 
     218              :     @see get_stop_token
     219              :     @see get_executor
     220              :     @see IoAwaitable
     221              : */
     222              : template<typename Derived>
     223              : class io_awaitable_support
     224              : {
     225              :     executor_ref executor_;
     226              :     std::stop_token stop_token_;
     227              :     coro cont_;
     228              :     executor_ref caller_ex_;
     229              : 
     230              : public:
     231              :     /** Store continuation and caller's executor for completion dispatch.
     232              : 
     233              :         Call this from your coroutine type's `await_suspend` overload to
     234              :         set up the completion path. On completion, the coroutine will
     235              :         resume the continuation, dispatching through the caller's executor
     236              :         if it differs from this coroutine's executor.
     237              : 
     238              :         @param cont The continuation to resume on completion.
     239              :         @param caller_ex The caller's executor for completion dispatch.
     240              :     */
     241          148 :     void set_continuation(coro cont, executor_ref caller_ex) noexcept
     242              :     {
     243          148 :         cont_ = cont;
     244          148 :         caller_ex_ = caller_ex;
     245          148 :     }
     246              : 
     247              :     /** Return the handle to resume on completion with dispatch-awareness.
     248              : 
     249              :         If no continuation was set, returns `std::noop_coroutine()`.
     250              :         If the coroutine's executor matches the caller's executor, returns
     251              :         the continuation directly for symmetric transfer.
     252              :         Otherwise, dispatches through the caller's executor first.
     253              : 
     254              :         Call this from your `final_suspend` awaiter's `await_suspend`.
     255              : 
     256              :         @return A coroutine handle for symmetric transfer.
     257              :     */
     258          181 :     coro complete() const noexcept
     259              :     {
     260          181 :         if(!cont_)
     261           33 :             return std::noop_coroutine();
     262          148 :         if(executor_ == caller_ex_)
     263          148 :             return cont_;
     264            0 :         return caller_ex_.dispatch(cont_);
     265              :     }
     266              : 
     267              :     /** Store a stop token for later retrieval.
     268              : 
     269              :         Call this from your coroutine type's `await_suspend`
     270              :         overload to make the token available via `co_await get_stop_token()`.
     271              : 
     272              :         @param token The stop token to store.
     273              :     */
     274          150 :     void set_stop_token(std::stop_token token) noexcept
     275              :     {
     276          150 :         stop_token_ = token;
     277          150 :     }
     278              : 
     279              :     /** Return the stored stop token.
     280              : 
     281              :         @return The stop token, or a default-constructed token if none was set.
     282              :     */
     283           67 :     std::stop_token const& stop_token() const noexcept
     284              :     {
     285           67 :         return stop_token_;
     286              :     }
     287              : 
     288              :     /** Store an executor for later retrieval.
     289              : 
     290              :         Call this from your coroutine type's `await_suspend`
     291              :         overload to make the executor available via `co_await get_executor()`.
     292              : 
     293              :         @param ex The executor to store.
     294              :     */
     295          150 :     void set_executor(executor_ref ex) noexcept
     296              :     {
     297          150 :         executor_ = ex;
     298          150 :     }
     299              : 
     300              :     /** Return the stored executor.
     301              : 
     302              :         @return The executor, or a default-constructed executor_ref if none was set.
     303              :     */
     304           67 :     executor_ref executor() const noexcept
     305              :     {
     306           67 :         return executor_;
     307              :     }
     308              : 
     309              :     /** Transform an awaitable before co_await.
     310              : 
     311              :         Override this in your derived promise type to customize how
     312              :         awaitables are transformed. The default implementation passes
     313              :         the awaitable through unchanged.
     314              : 
     315              :         @param a The awaitable expression from `co_await a`.
     316              : 
     317              :         @return The transformed awaitable.
     318              :     */
     319              :     template<typename A>
     320              :     decltype(auto) transform_awaitable(A&& a)
     321              :     {
     322              :         return std::forward<A>(a);
     323              :     }
     324              : 
     325              :     /** Intercept co_await expressions.
     326              : 
     327              :         This function handles @ref get_stop_token_tag and @ref get_executor_tag
     328              :         specially, returning an awaiter that yields the stored value. All other
     329              :         awaitables are delegated to @ref transform_awaitable.
     330              : 
     331              :         @param t The awaited expression.
     332              : 
     333              :         @return An awaiter for the expression.
     334              :     */
     335              :     template<typename T>
     336          121 :     auto await_transform(T&& t)
     337              :     {
     338              :         if constexpr (std::is_same_v<std::decay_t<T>, get_stop_token_tag>)
     339              :         {
     340              :             struct awaiter
     341              :             {
     342              :                 std::stop_token token_;
     343              : 
     344           14 :                 bool await_ready() const noexcept
     345              :                 {
     346           14 :                     return true;
     347              :                 }
     348              : 
     349            1 :                 void await_suspend(coro) const noexcept
     350              :                 {
     351            1 :                 }
     352              : 
     353           13 :                 std::stop_token await_resume() const noexcept
     354              :                 {
     355           13 :                     return token_;
     356              :                 }
     357              :             };
     358           15 :             return awaiter{stop_token_};
     359              :         }
     360              :         else if constexpr (std::is_same_v<std::decay_t<T>, get_executor_tag>)
     361              :         {
     362              :             struct awaiter
     363              :             {
     364              :                 executor_ref executor_;
     365              : 
     366            2 :                 bool await_ready() const noexcept
     367              :                 {
     368            2 :                     return true;
     369              :                 }
     370              : 
     371            1 :                 void await_suspend(coro) const noexcept
     372              :                 {
     373            1 :                 }
     374              : 
     375            1 :                 executor_ref await_resume() const noexcept
     376              :                 {
     377            1 :                     return executor_;
     378              :                 }
     379              :             };
     380            3 :             return awaiter{executor_};
     381              :         }
     382              :         else
     383              :         {
     384           52 :             return static_cast<Derived*>(this)->transform_awaitable(
     385          103 :                 std::forward<T>(t));
     386              :         }
     387              :     }
     388              : };
     389              : 
     390              : } // namespace capy
     391              : } // namespace boost
     392              : 
     393              : #endif
        

Generated by: LCOV version 2.3