LCOV - code coverage report
Current view: top level - corosio/detail - timeout_coro.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 96.6 % 29 28 1
Test Date: 2026-10-17 04:01:32 Functions: 91.7 % 12 11 1

           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/context_awaitable.hpp>
      15                 : #include <boost/capy/ex/frame_allocator.hpp>
      16                 : #include <boost/capy/ex/context_awaitable_promise_base.hpp>
      17                 : #include <boost/capy/ex/await_context.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 await_context 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 await_context into
      37                 :    ContextAwaitable co_await expressions.
      38                 : 
      39                 :    The detached frame can outlive the awaiting chain, so it never
      40                 :    allocates from that chain's frame allocator: timeout_awaitable
      41                 :    creates it under the process-wide recycling resource. */
      42                 : 
      43                 : namespace boost::corosio::detail {
      44                 : 
      45                 : /** Fire-and-forget coroutine backing `timeout()`.
      46                 : 
      47                 :     The coroutine awaits a timer and signals a stop_source if the
      48                 :     timer fires without being cancelled. It self-destroys at
      49                 :     final_suspend via suspend_never.
      50                 : 
      51                 :     @see make_timeout
      52                 : */
      53                 : struct timeout_coro
      54                 : {
      55                 :     struct promise_type : capy::context_awaitable_promise_base<promise_type>
      56                 :     {
      57                 :         io_context::executor_type owned_ex_;
      58                 :         capy::await_context env_storage_;
      59                 : 
      60                 :         /** Store an owned copy of the environment.
      61                 : 
      62                 :             The timeout coroutine can outlive the awaiting chain
      63                 :             that created it, so it must own its env rather than
      64                 :             pointing to external storage. The executor is owned by
      65                 :             value: the chain's env holds only a ref to chain-owned
      66                 :             executor storage, and the timer waiter's completion can
      67                 :             run after that storage is gone. The ref handed to the
      68                 :             waiter points at `owned_ex_` in this frame, which lives
      69                 :             until final_suspend, after the waiter has completed.
      70                 :         */
      71 HIT        2057 :         void set_env_owned(
      72                 :             io_context::executor_type ex,
      73                 :             std::stop_token token,
      74                 :             std::pmr::memory_resource* alloc)
      75                 :         {
      76            2057 :             owned_ex_    = ex;
      77            4114 :             env_storage_ = {owned_ex_, std::move(token), alloc};
      78            2057 :             set_environment(&env_storage_);
      79            4114 :         }
      80                 : 
      81            2057 :         timeout_coro get_return_object() noexcept
      82                 :         {
      83                 :             return timeout_coro{
      84            2057 :                 std::coroutine_handle<promise_type>::from_promise(*this)};
      85                 :         }
      86                 : 
      87            2057 :         std::suspend_always initial_suspend() noexcept
      88                 :         {
      89            2057 :             return {};
      90                 :         }
      91            2051 :         std::suspend_never final_suspend() noexcept
      92                 :         {
      93            2051 :             return {};
      94                 :         }
      95            2051 :         void return_void() noexcept {}
      96 MIS           0 :         void unhandled_exception() noexcept {}
      97                 : 
      98                 :         template<class Awaitable>
      99                 :         struct transform_awaiter
     100                 :         {
     101                 :             std::decay_t<Awaitable> a_;
     102                 :             promise_type* p_;
     103                 : 
     104 HIT        2057 :             bool await_ready() noexcept
     105                 :             {
     106            2057 :                 return a_.await_ready();
     107                 :             }
     108                 : 
     109            2051 :             decltype(auto) await_resume()
     110                 :             {
     111            2051 :                 capy::set_current_frame_allocator(
     112            2051 :                     p_->environment()->frame_allocator);
     113            2051 :                 return a_.await_resume();
     114                 :             }
     115                 : 
     116                 :             template<class Promise>
     117            2057 :             auto await_suspend(std::coroutine_handle<Promise> h) noexcept
     118                 :             {
     119                 : #ifdef _MSC_VER
     120                 :                 using R = decltype(a_.await_suspend(h, p_->environment()));
     121                 :                 if constexpr (std::is_same_v<R, std::coroutine_handle<>>)
     122                 :                     a_.await_suspend(h, p_->environment()).resume();
     123                 :                 else
     124                 :                     return a_.await_suspend(h, p_->environment());
     125                 : #else
     126            2057 :                 return a_.await_suspend(h, p_->environment());
     127                 : #endif
     128                 :             }
     129                 :         };
     130                 : 
     131                 :         template<class Awaitable>
     132            2057 :         auto transform_awaitable(Awaitable&& a)
     133                 :         {
     134                 :             using A = std::decay_t<Awaitable>;
     135                 :             if constexpr (capy::ContextAwaitable<A>)
     136                 :             {
     137                 :                 return transform_awaiter<Awaitable>{
     138            4114 :                     std::forward<Awaitable>(a), this};
     139                 :             }
     140                 :             else
     141                 :             {
     142                 :                 static_assert(sizeof(A) == 0, "requires ContextAwaitable");
     143                 :             }
     144            2057 :         }
     145                 :     };
     146                 : 
     147                 :     std::coroutine_handle<promise_type> h_;
     148                 : 
     149                 :     timeout_coro() noexcept : h_(nullptr) {}
     150                 : 
     151            2057 :     explicit timeout_coro(std::coroutine_handle<promise_type> h) noexcept
     152            2057 :         : h_(h)
     153                 :     {
     154            2057 :     }
     155                 : 
     156                 :     // Self-destroying via suspend_never at final_suspend
     157                 :     ~timeout_coro() = default;
     158                 : 
     159                 :     timeout_coro(timeout_coro const&)            = delete;
     160                 :     timeout_coro& operator=(timeout_coro const&) = delete;
     161                 : 
     162                 :     timeout_coro(timeout_coro&& o) noexcept : h_(o.h_)
     163                 :     {
     164                 :         o.h_ = nullptr;
     165                 :     }
     166                 : 
     167                 :     timeout_coro& operator=(timeout_coro&& o) noexcept
     168                 :     {
     169                 :         h_   = o.h_;
     170                 :         o.h_ = nullptr;
     171                 :         return *this;
     172                 :     }
     173                 : };
     174                 : 
     175                 : /** Create a fire-and-forget timeout coroutine.
     176                 : 
     177                 :     Wait on the timer. If it fires without cancellation, signal
     178                 :     the stop source to cancel the paired inner operation.
     179                 : 
     180                 :     @tparam Timer The timer type, typically `detail::timer`.
     181                 : 
     182                 :     @param t The timer to wait on (must have expiry set).
     183                 :     @param src Stop source to signal on timeout.
     184                 : */
     185                 : template<typename Timer>
     186                 : timeout_coro
     187            2057 : make_timeout(Timer& t, std::stop_source src)
     188                 : {
     189                 :     auto [ec] = co_await t.wait();
     190                 :     if (!ec)
     191                 :         src.request_stop();
     192            4114 : }
     193                 : 
     194                 : } // namespace boost::corosio::detail
     195                 : 
     196                 : #endif
        

Generated by: LCOV version 2.3