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