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