LCOV - code coverage report
Current view: top level - corosio/detail - timeout_coro.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 100.0 % 27 27
Test Date: 2026-07-17 17:14:45 Functions: 100.0 % 11 11

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2026 Steve Gerbino
       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/corosio
       8                 : //
       9                 : 
      10                 : #ifndef BOOST_COROSIO_DETAIL_TIMEOUT_CORO_HPP
      11                 : #define BOOST_COROSIO_DETAIL_TIMEOUT_CORO_HPP
      12                 : 
      13                 : #include <boost/corosio/io_context.hpp>
      14                 : #include <boost/capy/concept/io_awaitable.hpp>
      15                 : #include <boost/capy/ex/frame_allocator.hpp>
      16                 : #include <boost/capy/ex/io_awaitable_promise_base.hpp>
      17                 : #include <boost/capy/ex/io_env.hpp>
      18                 : 
      19                 : #include <coroutine>
      20                 : #include <memory_resource>
      21                 : #include <stop_token>
      22                 : #include <type_traits>
      23                 : #include <utility>
      24                 : 
      25                 : /* Self-destroying coroutine that awaits a timer and signals a
      26                 :    stop_source on expiry. Created suspended (initial_suspend =
      27                 :    suspend_always); the caller sets an owned io_env copy then
      28                 :    resumes, which runs synchronously until the timer wait suspends.
      29                 :    At final_suspend, suspend_never destroys the frame — the
      30                 :    timeout_coro destructor is intentionally a no-op since the
      31                 :    handle is dangling after self-destruction. If the coroutine is
      32                 :    still suspended at shutdown, the timer service drains it via
      33                 :    completion_op::destroy().
      34                 : 
      35                 :    The promise reuses task<>'s transform_awaiter pattern (including
      36                 :    the MSVC symmetric-transfer workaround) to inject io_env into
      37                 :    IoAwaitable co_await expressions.
      38                 : 
      39                 :    The detached frame allocates from the awaiting chain's frame
      40                 :    allocator and can outlive that chain, extending the allocator's
      41                 :    required lifetime until the io_context drains. This is safe with
      42                 :    the default recycling resource. */
      43                 : 
      44                 : namespace boost::corosio::detail {
      45                 : 
      46                 : /** Fire-and-forget coroutine backing `timeout()`.
      47                 : 
      48                 :     The coroutine awaits a timer and signals a stop_source if the
      49                 :     timer fires without being cancelled. It self-destroys at
      50                 :     final_suspend via suspend_never.
      51                 : 
      52                 :     @see make_timeout
      53                 : */
      54                 : struct timeout_coro
      55                 : {
      56                 :     struct promise_type : capy::io_awaitable_promise_base<promise_type>
      57                 :     {
      58                 :         io_context::executor_type owned_ex_;
      59                 :         capy::io_env env_storage_;
      60                 : 
      61                 :         /** Store an owned copy of the environment.
      62                 : 
      63                 :             The timeout coroutine can outlive the awaiting chain
      64                 :             that created it, so it must own its env rather than
      65                 :             pointing to external storage. The executor is owned by
      66                 :             value: the chain's env holds only a ref to chain-owned
      67                 :             executor storage, and the timer waiter's completion can
      68                 :             run after that storage is gone. The ref handed to the
      69                 :             waiter points at `owned_ex_` in this frame, which lives
      70                 :             until final_suspend, after the waiter has completed.
      71                 :         */
      72 HIT        2040 :         void set_env_owned(
      73                 :             io_context::executor_type ex,
      74                 :             std::stop_token token,
      75                 :             std::pmr::memory_resource* alloc)
      76                 :         {
      77            2040 :             owned_ex_ = ex;
      78            4080 :             env_storage_ = {owned_ex_, std::move(token), alloc};
      79            2040 :             set_environment(&env_storage_);
      80            4080 :         }
      81                 : 
      82            2040 :         timeout_coro get_return_object() noexcept
      83                 :         {
      84                 :             return timeout_coro{
      85            2040 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
      86                 :         }
      87                 : 
      88            2040 :         std::suspend_always initial_suspend() noexcept
      89                 :         {
      90            2040 :             return {};
      91                 :         }
      92            2036 :         std::suspend_never final_suspend() noexcept
      93                 :         {
      94            2036 :             return {};
      95                 :         }
      96            2036 :         void return_void() noexcept {}
      97                 :         void unhandled_exception() noexcept {}
      98                 : 
      99                 :         template<class Awaitable>
     100                 :         struct transform_awaiter
     101                 :         {
     102                 :             std::decay_t<Awaitable> a_;
     103                 :             promise_type* p_;
     104                 : 
     105            2040 :             bool await_ready() noexcept
     106                 :             {
     107            2040 :                 return a_.await_ready();
     108                 :             }
     109                 : 
     110            2036 :             decltype(auto) await_resume()
     111                 :             {
     112            2036 :                 capy::set_current_frame_allocator(
     113            2036 :                     p_->environment()->frame_allocator);
     114            2036 :                 return a_.await_resume();
     115                 :             }
     116                 : 
     117                 :             template<class Promise>
     118            2040 :             auto await_suspend(std::coroutine_handle<Promise> h) noexcept
     119                 :             {
     120                 : #ifdef _MSC_VER
     121                 :                 using R = decltype(a_.await_suspend(h, p_->environment()));
     122                 :                 if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
     123                 :                     a_.await_suspend(h, p_->environment()).resume();
     124                 :                 else
     125                 :                     return a_.await_suspend(h, p_->environment());
     126                 : #else
     127            2040 :                 return a_.await_suspend(h, p_->environment());
     128                 : #endif
     129                 :             }
     130                 :         };
     131                 : 
     132                 :         template<class Awaitable>
     133            2040 :         auto transform_awaitable(Awaitable&& a)
     134                 :         {
     135                 :             using A = std::decay_t<Awaitable>;
     136                 :             if constexpr (capy::IoAwaitable<A>)
     137                 :             {
     138                 :                 return transform_awaiter<Awaitable>{
     139            2040 :                     std::forward<Awaitable>(a), this};
     140                 :             }
     141                 :             else
     142                 :             {
     143                 :                 static_assert(sizeof(A) == 0, "requires IoAwaitable");
     144                 :             }
     145                 :         }
     146                 :     };
     147                 : 
     148                 :     std::coroutine_handle<promise_type> h_;
     149                 : 
     150                 :     timeout_coro() noexcept : h_(nullptr) {}
     151                 : 
     152            2040 :     explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
     153            2040 :         : h_(h)
     154                 :     {
     155            2040 :     }
     156                 : 
     157                 :     // Self-destroying via suspend_never at final_suspend
     158                 :     ~timeout_coro() = default;
     159                 : 
     160                 :     timeout_coro(timeout_coro const&)            = delete;
     161                 :     timeout_coro& operator=(timeout_coro const&) = delete;
     162                 : 
     163                 :     timeout_coro(timeout_coro&& o) noexcept : h_(o.h_)
     164                 :     {
     165                 :         o.h_ = nullptr;
     166                 :     }
     167                 : 
     168                 :     timeout_coro& operator=(timeout_coro&& o) noexcept
     169                 :     {
     170                 :         h_   = o.h_;
     171                 :         o.h_ = nullptr;
     172                 :         return *this;
     173                 :     }
     174                 : };
     175                 : 
     176                 : /** Create a fire-and-forget timeout coroutine.
     177                 : 
     178                 :     Wait on the timer. If it fires without cancellation, signal
     179                 :     the stop source to cancel the paired inner operation.
     180                 : 
     181                 :     @tparam Timer The timer type, typically `detail::timer`.
     182                 : 
     183                 :     @param t The timer to wait on (must have expiry set).
     184                 :     @param src Stop source to signal on timeout.
     185                 : */
     186                 : template<typename Timer>
     187                 : timeout_coro
     188            2040 : make_timeout(Timer& t, std::stop_source src)
     189                 : {
     190                 :     auto [ec] = co_await t.wait();
     191                 :     if (!ec)
     192                 :         src.request_stop();
     193            4080 : }
     194                 : 
     195                 : } // namespace boost::corosio::detail
     196                 : 
     197                 : #endif
        

Generated by: LCOV version 2.3