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