96.83% Lines (61/63) 100.00% Functions (15/15)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // Copyright (c) 2026 Steve Gerbino 3   // Copyright (c) 2026 Steve Gerbino
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
9   // 9   //
10   10  
11   #ifndef BOOST_COROSIO_DETAIL_TIMER_HPP 11   #ifndef BOOST_COROSIO_DETAIL_TIMER_HPP
12   #define BOOST_COROSIO_DETAIL_TIMER_HPP 12   #define BOOST_COROSIO_DETAIL_TIMER_HPP
13   13  
14   #include <boost/corosio/detail/config.hpp> 14   #include <boost/corosio/detail/config.hpp>
15   #include <boost/corosio/detail/intrusive.hpp> 15   #include <boost/corosio/detail/intrusive.hpp>
  16 + #include <boost/corosio/detail/scheduler_op.hpp>
16   #include <boost/corosio/io/io_object.hpp> 17   #include <boost/corosio/io/io_object.hpp>
17   #include <boost/capy/continuation.hpp> 18   #include <boost/capy/continuation.hpp>
18   #include <boost/capy/io_result.hpp> 19   #include <boost/capy/io_result.hpp>
19   #include <boost/capy/error.hpp> 20   #include <boost/capy/error.hpp>
20   #include <boost/capy/ex/executor_ref.hpp> 21   #include <boost/capy/ex/executor_ref.hpp>
21   #include <boost/capy/ex/execution_context.hpp> 22   #include <boost/capy/ex/execution_context.hpp>
22   #include <boost/capy/ex/io_env.hpp> 23   #include <boost/capy/ex/io_env.hpp>
23   #include <boost/capy/concept/executor.hpp> 24   #include <boost/capy/concept/executor.hpp>
24   25  
25   #include <atomic> 26   #include <atomic>
26   #include <chrono> 27   #include <chrono>
27   #include <concepts> 28   #include <concepts>
28   #include <coroutine> 29   #include <coroutine>
29   #include <cstddef> 30   #include <cstddef>
30   #include <limits> 31   #include <limits>
  32 + #include <new>
31   #include <stop_token> 33   #include <stop_token>
32   #include <system_error> 34   #include <system_error>
33   #include <type_traits> 35   #include <type_traits>
34   36  
35   namespace boost::corosio::detail { 37   namespace boost::corosio::detail {
36   38  
37 - // Defined in timer_service.hpp, which includes this header. The 39 + // timer_service is defined in timer_service.hpp, which includes this
38 - // implementation::wait() body needs waiter_node and timer_service to 40 + // header. waiter_node and wait_awaitable are defined below the timer
39 - // be complete, so it is declared here and defined there; intrusive_list 41 + // class: waiter_node stores a timer::implementation*, which cannot be
40 - // only stores waiter_node pointers, so the forward declaration suffices 42 + // forward-declared as a nested type. intrusive_list only stores
41 - // for implementation's data layout. 43 + // waiter_node pointers, so this forward declaration suffices for
  44 + // implementation's data layout.
42   class timer_service; 45   class timer_service;
43   struct waiter_node; 46   struct waiter_node;
  47 + struct wait_awaitable;
44   48  
45   /** An asynchronous timer for coroutine I/O. 49   /** An asynchronous timer for coroutine I/O.
46   50  
47   This class provides asynchronous timer operations that return 51   This class provides asynchronous timer operations that return
48   awaitable types. The timer can be used to schedule operations 52   awaitable types. The timer can be used to schedule operations
49   to occur after a specified duration or at a specific time point. 53   to occur after a specified duration or at a specific time point.
50   54  
51   Multiple coroutines may wait concurrently on the same timer. 55   Multiple coroutines may wait concurrently on the same timer.
52   When the timer expires, all waiters complete with success. When 56   When the timer expires, all waiters complete with success. When
53   the timer is cancelled, all waiters complete with an error that 57   the timer is cancelled, all waiters complete with an error that
54   compares equal to `capy::cond::canceled`. 58   compares equal to `capy::cond::canceled`.
55   59  
56   Each timer operation participates in the affine awaitable protocol, 60   Each timer operation participates in the affine awaitable protocol,
57   ensuring coroutines resume on the correct executor. 61   ensuring coroutines resume on the correct executor.
58   62  
59   @par Thread Safety 63   @par Thread Safety
60   Distinct objects: Safe.@n 64   Distinct objects: Safe.@n
61   Shared objects: Unsafe. 65   Shared objects: Unsafe.
62   66  
63   @par Semantics 67   @par Semantics
64   Timers are not backed by per-timer kernel objects. The io_context's 68   Timers are not backed by per-timer kernel objects. The io_context's
65   timer service keeps a process-side min-heap of pending expirations; 69   timer service keeps a process-side min-heap of pending expirations;
66   the nearest expiry drives the reactor's poll timeout, and expirations 70   the nearest expiry drives the reactor's poll timeout, and expirations
67   are processed in the run loop. 71   are processed in the run loop.
68   */ 72   */
69   class BOOST_COROSIO_DECL timer : public io_object 73   class BOOST_COROSIO_DECL timer : public io_object
70   { 74   {
71 - struct wait_awaitable 75 + friend struct wait_awaitable;
72 - {  
73 - timer& t_;  
74 - std::error_code ec_;  
75 - capy::continuation cont_;  
76 -  
DCB 77 - 9450 explicit wait_awaitable(timer& t) noexcept : t_(t) {}  
78 -  
DCB 79 - 2040 bool await_ready() const noexcept  
80 - {  
DCB 81 - 2040 return false;  
82 - }  
83 -  
84 - // Cancellation surfaces through ec_: the stop_token path in  
85 - // wait() completes the waiter with error::canceled written to  
86 - // ec_, so there is no separate token to consult here.  
DCB 87 - 9424 capy::io_result<> await_resume() const noexcept  
88 - {  
DCB 89 - 9424 return {ec_};  
90 - }  
91 -  
DCB 92 - 9450 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)  
93 - -> std::coroutine_handle<>  
94 - {  
DCB 95 - 9450 cont_.h = h;  
DCB 96 - 9450 auto& impl = t_.get();  
97 - // Inline fast path: already expired and not in the heap  
DCB 98 - 9450 if (impl.heap_index_.load(std::memory_order_relaxed) ==  
DCB 99 - 18900 implementation::npos &&  
DCB 100 - 18236 (impl.expiry_ == (time_point::min)() ||  
DCB 101 - 18236 impl.expiry_ <= clock_type::now()))  
102 - {  
DCB 103 - 853 ec_ = {};  
DCB 104 - 853 auto d = env->executor;  
DCB 105 - 853 d.post(cont_);  
DCB 106 - 853 return std::noop_coroutine();  
107 - }  
DCB 108 - 8597 return impl.wait(h, env->executor, env->stop_token, &ec_, &cont_);  
109 - }  
110 - };  
111   76  
112   public: 77   public:
113   /** Backend state and wait entry point for a timer. 78   /** Backend state and wait entry point for a timer.
114   79  
115   Holds per-timer state (expiry, heap position, waiter list) and 80   Holds per-timer state (expiry, heap position, waiter list) and
116   the `wait` entry point used by the awaitable returned from 81   the `wait` entry point used by the awaitable returned from
117   @ref timer::wait. There is exactly one concrete timer backend, 82   @ref timer::wait. There is exactly one concrete timer backend,
118   so `wait` is a plain member function rather than a virtual 83   so `wait` is a plain member function rather than a virtual
119   dispatch point. 84   dispatch point.
120   */ 85   */
121   struct implementation : io_object::implementation 86   struct implementation : io_object::implementation
122   { 87   {
123   /// Sentinel value indicating the timer is not in the heap. 88   /// Sentinel value indicating the timer is not in the heap.
124   static constexpr std::size_t npos = 89   static constexpr std::size_t npos =
125   (std::numeric_limits<std::size_t>::max)(); 90   (std::numeric_limits<std::size_t>::max)();
126   91  
127   // Only mutated by the owning thread (expires_at/expires_after) 92   // Only mutated by the owning thread (expires_at/expires_after)
128   // before a wait is published; cross-thread consumers read the 93   // before a wait is published; cross-thread consumers read the
129   // heap entry's copied time_, never this field, so it needs no 94   // heap entry's copied time_, never this field, so it needs no
130   // atomicity. 95   // atomicity.
131   /// The absolute expiry time point. 96   /// The absolute expiry time point.
132   std::chrono::steady_clock::time_point expiry_{}; 97   std::chrono::steady_clock::time_point expiry_{};
133   98  
134   // heap_index_ and might_have_pending_waits_ are cross-thread 99   // heap_index_ and might_have_pending_waits_ are cross-thread
135   // hints, not authoritative state: the real state lives in the 100   // hints, not authoritative state: the real state lives in the
136   // heap and waiter list under timer_service::mutex_. Every 101   // heap and waiter list under timer_service::mutex_. Every
137   // unlocked fast-out that reads them is either re-validated under 102   // unlocked fast-out that reads them is either re-validated under
138   // the mutex or safe under a stale value in both directions, and 103   // the mutex or safe under a stale value in both directions, and
139   // any locked writer / locked reader pair is already ordered by 104   // any locked writer / locked reader pair is already ordered by
140   // the mutex. All accesses therefore use memory_order_relaxed, 105   // the mutex. All accesses therefore use memory_order_relaxed,
141   // which keeps the lock-free fast paths fence-free while making 106   // which keeps the lock-free fast paths fence-free while making
142   // the concurrent reads well-defined. 107   // the concurrent reads well-defined.
143   /// Index in the timer service's min-heap, or `npos`. 108   /// Index in the timer service's min-heap, or `npos`.
144   std::atomic<std::size_t> heap_index_{npos}; 109   std::atomic<std::size_t> heap_index_{npos};
145   110  
146   /// True if `wait()` has been called since last cancel. 111   /// True if `wait()` has been called since last cancel.
147   std::atomic<bool> might_have_pending_waits_{false}; 112   std::atomic<bool> might_have_pending_waits_{false};
148   113  
149   /// The timer service that owns this implementation. 114   /// The timer service that owns this implementation.
150   timer_service* svc_ = nullptr; 115   timer_service* svc_ = nullptr;
151   116  
152   /// Coroutines currently waiting on this timer's expiry. 117   /// Coroutines currently waiting on this timer's expiry.
153   intrusive_list<waiter_node> waiters_; 118   intrusive_list<waiter_node> waiters_;
154   119  
155   /// Free list linkage, reused when this impl is recycled. 120   /// Free list linkage, reused when this impl is recycled.
156   implementation* next_free_ = nullptr; 121   implementation* next_free_ = nullptr;
157   122  
158   /// Construct bound to the given timer service. 123   /// Construct bound to the given timer service.
HITCBC 159   322 explicit implementation(timer_service& svc) noexcept : svc_(&svc) {} 124   290 explicit implementation(timer_service& svc) noexcept : svc_(&svc) {}
160   125  
161 - /// Initiate an asynchronous wait for the timer to expire. 126 + /** Check whether the timer is expired and absent from the heap.
  127 +
  128 + The single definition of the already-expired fast-path
  129 + predicate: `await_suspend` tests it inline and `wait()`
  130 + re-tests it because the expiry can elapse between the two
  131 + reads.
  132 + */
HITGNC   133 + 17865 bool already_expired() const noexcept
  134 + {
HITGNC   135 + 53595 return heap_index_.load(std::memory_order_relaxed) == npos &&
HITGNC   136 + 17865 (expiry_ ==
HITGNC   137 + 35066 (std::chrono::steady_clock::time_point::min)() ||
HITGNC   138 + 35066 expiry_ <= std::chrono::steady_clock::now());
  139 + }
  140 +
  141 + /** Asynchronously wait for the timer to expire.
  142 +
  143 + Publishes the waiter into the service's heap and waiter
  144 + list, after which it may complete on any thread. If the
  145 + timer is already expired and not in the heap, completes
  146 + by posting the continuation without publishing.
  147 +
  148 + @par Preconditions
  149 + @p w is fully initialized, and its storage (the awaitable
  150 + on the suspended coroutine's frame) outlives the wait.
  151 +
  152 + @param w The waiter to publish.
  153 + */
162   // Exported at member level: dllexport on the enclosing timer 154   // Exported at member level: dllexport on the enclosing timer
163   // class does not extend to nested classes, and header-inline 155   // class does not extend to nested classes, and header-inline
164   // callers (wait_awaitable::await_suspend) reference this 156   // callers (wait_awaitable::await_suspend) reference this
165   // symbol from outside the corosio DLL. 157   // symbol from outside the corosio DLL.
166   BOOST_COROSIO_DECL 158   BOOST_COROSIO_DECL
167 - std::coroutine_handle<> wait( 159 + std::coroutine_handle<> wait(waiter_node& w);
168 - std::coroutine_handle<>,  
169 - capy::executor_ref,  
170 - std::stop_token,  
171 - std::error_code*,  
172 - capy::continuation*);  
173   }; 160   };
174   161  
175   /// The clock type used for time operations. 162   /// The clock type used for time operations.
176   using clock_type = std::chrono::steady_clock; 163   using clock_type = std::chrono::steady_clock;
177   164  
178   /// The time point type for absolute expiry times. 165   /// The time point type for absolute expiry times.
179   using time_point = clock_type::time_point; 166   using time_point = clock_type::time_point;
180   167  
181   /// The duration type for relative expiry times. 168   /// The duration type for relative expiry times.
182   using duration = clock_type::duration; 169   using duration = clock_type::duration;
183   170  
184   /** Destructor. 171   /** Destructor.
185   172  
186   Cancels any pending operations and releases timer resources. 173   Cancels any pending operations and releases timer resources.
187   */ 174   */
188   ~timer() override; 175   ~timer() override;
189   176  
190   /** Construct a timer from an execution context. 177   /** Construct a timer from an execution context.
191   178  
192   @param ctx The execution context that will own this timer. It 179   @param ctx The execution context that will own this timer. It
193   must be a corosio io_context; otherwise the constructor 180   must be a corosio io_context; otherwise the constructor
194   throws (a timer service is required). 181   throws (a timer service is required).
195   182  
196   @throws std::logic_error if @p ctx is not an io_context. 183   @throws std::logic_error if @p ctx is not an io_context.
197   */ 184   */
198   explicit timer(capy::execution_context& ctx); 185   explicit timer(capy::execution_context& ctx);
199   186  
200   /** Construct a timer with an initial absolute expiry time. 187   /** Construct a timer with an initial absolute expiry time.
201   188  
202   @param ctx The execution context that will own this timer. It 189   @param ctx The execution context that will own this timer. It
203   must be a corosio io_context; otherwise the constructor 190   must be a corosio io_context; otherwise the constructor
204   throws (a timer service is required). 191   throws (a timer service is required).
205   @param t The initial expiry time point. 192   @param t The initial expiry time point.
206   193  
207   @throws std::logic_error if @p ctx is not an io_context. 194   @throws std::logic_error if @p ctx is not an io_context.
208   */ 195   */
209   timer(capy::execution_context& ctx, time_point t); 196   timer(capy::execution_context& ctx, time_point t);
210   197  
211   /** Construct a timer with an initial relative expiry time. 198   /** Construct a timer with an initial relative expiry time.
212   199  
213   @param ctx The execution context that will own this timer. It 200   @param ctx The execution context that will own this timer. It
214   must be a corosio io_context; otherwise the constructor 201   must be a corosio io_context; otherwise the constructor
215   throws (a timer service is required). 202   throws (a timer service is required).
216   @param d The initial expiry duration relative to now. 203   @param d The initial expiry duration relative to now.
217   204  
218   @throws std::logic_error if @p ctx is not an io_context. 205   @throws std::logic_error if @p ctx is not an io_context.
219   */ 206   */
220   template<class Rep, class Period> 207   template<class Rep, class Period>
221   timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d) 208   timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d)
222   : timer(ctx) 209   : timer(ctx)
223   { 210   {
224   expires_after(d); 211   expires_after(d);
225   } 212   }
226   213  
227   /** Construct a timer from an executor. 214   /** Construct a timer from an executor.
228   215  
229   The timer is associated with the executor's context, which must 216   The timer is associated with the executor's context, which must
230   be a corosio io_context. 217   be a corosio io_context.
231   218  
232   @param ex The executor whose context will own this timer. 219   @param ex The executor whose context will own this timer.
233   220  
234   @throws std::logic_error if the executor's context is not an 221   @throws std::logic_error if the executor's context is not an
235   io_context. 222   io_context.
236   */ 223   */
237   template<class Ex> 224   template<class Ex>
238   requires(!std::same_as<std::remove_cvref_t<Ex>, timer>) && 225   requires(!std::same_as<std::remove_cvref_t<Ex>, timer>) &&
239   capy::Executor<Ex> 226   capy::Executor<Ex>
240   explicit timer(Ex const& ex) : timer(ex.context()) 227   explicit timer(Ex const& ex) : timer(ex.context())
241   { 228   {
242   } 229   }
243   230  
244   /** Construct a timer from an executor with an absolute expiry time. 231   /** Construct a timer from an executor with an absolute expiry time.
245   232  
246   @param ex The executor whose context will own this timer. 233   @param ex The executor whose context will own this timer.
247   @param t The initial expiry time point. 234   @param t The initial expiry time point.
248   235  
249   @throws std::logic_error if the executor's context is not an 236   @throws std::logic_error if the executor's context is not an
250   io_context. 237   io_context.
251   */ 238   */
252   template<class Ex> 239   template<class Ex>
253   requires capy::Executor<Ex> 240   requires capy::Executor<Ex>
254   timer(Ex const& ex, time_point t) : timer(ex.context(), t) 241   timer(Ex const& ex, time_point t) : timer(ex.context(), t)
255   { 242   {
256   } 243   }
257   244  
258   /** Construct a timer from an executor with a relative expiry time. 245   /** Construct a timer from an executor with a relative expiry time.
259   246  
260   @param ex The executor whose context will own this timer. 247   @param ex The executor whose context will own this timer.
261   @param d The initial expiry duration relative to now. 248   @param d The initial expiry duration relative to now.
262   249  
263   @throws std::logic_error if the executor's context is not an 250   @throws std::logic_error if the executor's context is not an
264   io_context. 251   io_context.
265   */ 252   */
266   template<class Ex, class Rep, class Period> 253   template<class Ex, class Rep, class Period>
267   requires capy::Executor<Ex> 254   requires capy::Executor<Ex>
268   timer(Ex const& ex, std::chrono::duration<Rep, Period> d) 255   timer(Ex const& ex, std::chrono::duration<Rep, Period> d)
269   : timer(ex.context(), d) 256   : timer(ex.context(), d)
270   { 257   {
271   } 258   }
272   259  
273   /** Move constructor. 260   /** Move constructor.
274   261  
275   Transfers ownership of the timer resources. 262   Transfers ownership of the timer resources.
276   263  
277   @param other The timer to move from. 264   @param other The timer to move from.
278   265  
279   @pre No awaitables returned by @p other's methods exist. 266   @pre No awaitables returned by @p other's methods exist.
280   @pre The execution context associated with @p other must 267   @pre The execution context associated with @p other must
281   outlive this timer. 268   outlive this timer.
282   */ 269   */
283   timer(timer&& other) noexcept; 270   timer(timer&& other) noexcept;
284   271  
285   /** Move assignment operator. 272   /** Move assignment operator.
286   273  
287   Closes any existing timer and transfers ownership. 274   Closes any existing timer and transfers ownership.
288   275  
289   @param other The timer to move from. 276   @param other The timer to move from.
290   277  
291   @pre No awaitables returned by either `*this` or @p other's 278   @pre No awaitables returned by either `*this` or @p other's
292   methods exist. 279   methods exist.
293   @pre The execution context associated with @p other must 280   @pre The execution context associated with @p other must
294   outlive this timer. 281   outlive this timer.
295   282  
296   @return Reference to this timer. 283   @return Reference to this timer.
297   */ 284   */
298   timer& operator=(timer&& other) noexcept; 285   timer& operator=(timer&& other) noexcept;
299   286  
300   timer(timer const&) = delete; 287   timer(timer const&) = delete;
301   timer& operator=(timer const&) = delete; 288   timer& operator=(timer const&) = delete;
302   289  
303   /** Cancel all pending asynchronous wait operations. 290   /** Cancel all pending asynchronous wait operations.
304   291  
305   All outstanding operations complete with an error code that 292   All outstanding operations complete with an error code that
306   compares equal to `capy::cond::canceled`. 293   compares equal to `capy::cond::canceled`.
307   294  
308   @return The number of operations that were cancelled. 295   @return The number of operations that were cancelled.
309   */ 296   */
310   std::size_t cancel() 297   std::size_t cancel()
311   { 298   {
312   if (!get().might_have_pending_waits_.load(std::memory_order_relaxed)) 299   if (!get().might_have_pending_waits_.load(std::memory_order_relaxed))
313   return 0; 300   return 0;
314   return do_cancel(); 301   return do_cancel();
315   } 302   }
316   303  
317   /** Cancel one pending asynchronous wait operation. 304   /** Cancel one pending asynchronous wait operation.
318   305  
319   The oldest pending wait is cancelled (FIFO order). It 306   The oldest pending wait is cancelled (FIFO order). It
320   completes with an error code that compares equal to 307   completes with an error code that compares equal to
321   `capy::cond::canceled`. 308   `capy::cond::canceled`.
322   309  
323   @return The number of operations that were cancelled (0 or 1). 310   @return The number of operations that were cancelled (0 or 1).
324   */ 311   */
325   std::size_t cancel_one() 312   std::size_t cancel_one()
326   { 313   {
327   if (!get().might_have_pending_waits_.load(std::memory_order_relaxed)) 314   if (!get().might_have_pending_waits_.load(std::memory_order_relaxed))
328   return 0; 315   return 0;
329   return do_cancel_one(); 316   return do_cancel_one();
330   } 317   }
331   318  
332   /** Return the timer's expiry time as an absolute time. 319   /** Return the timer's expiry time as an absolute time.
333   320  
334   @return The expiry time point. If no expiry has been set, 321   @return The expiry time point. If no expiry has been set,
335   returns a default-constructed time_point. 322   returns a default-constructed time_point.
336   */ 323   */
337   time_point expiry() const noexcept 324   time_point expiry() const noexcept
338   { 325   {
339   return get().expiry_; 326   return get().expiry_;
340   } 327   }
341   328  
342   /** Set the timer's expiry time as an absolute time. 329   /** Set the timer's expiry time as an absolute time.
343   330  
344   Any pending asynchronous wait operations will be cancelled. 331   Any pending asynchronous wait operations will be cancelled.
345   332  
346   @param t The expiry time to be used for the timer. 333   @param t The expiry time to be used for the timer.
347   334  
348   @return The number of pending operations that were cancelled. 335   @return The number of pending operations that were cancelled.
349   */ 336   */
HITCBC 350   6 std::size_t expires_at(time_point t) 337   6 std::size_t expires_at(time_point t)
351   { 338   {
HITCBC 352   6 auto& impl = get(); 339   6 auto& impl = get();
HITCBC 353   6 impl.expiry_ = t; 340   6 impl.expiry_ = t;
HITCBC 354   6 if (impl.heap_index_.load(std::memory_order_relaxed) == 341   6 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 355   12 implementation::npos && 342   12 implementation::npos &&
HITCBC 356   6 !impl.might_have_pending_waits_.load(std::memory_order_relaxed)) 343   6 !impl.might_have_pending_waits_.load(std::memory_order_relaxed))
HITCBC 357   6 return 0; 344   6 return 0;
MISUBC 358   return do_update_expiry(); 345   return do_update_expiry();
359   } 346   }
360   347  
361   /** Set the timer's expiry time relative to now. 348   /** Set the timer's expiry time relative to now.
362   349  
363   Any pending asynchronous wait operations will be cancelled. 350   Any pending asynchronous wait operations will be cancelled.
364   351  
365   @param d The expiry time relative to now. 352   @param d The expiry time relative to now.
366   353  
367   @return The number of pending operations that were cancelled. 354   @return The number of pending operations that were cancelled.
368   */ 355   */
HITCBC 369   9444 std::size_t expires_after(duration d) 356   9354 std::size_t expires_after(duration d)
370   { 357   {
HITCBC 371   9444 auto& impl = get(); 358   9354 auto& impl = get();
HITCBC 372   9444 if (d <= duration::zero()) 359   9354 if (d <= duration::zero())
HITCBC 373   664 impl.expiry_ = (time_point::min)(); 360   664 impl.expiry_ = (time_point::min)();
374   else 361   else
375   { 362   {
376   // Saturate rather than overflow: a clamped near-max duration 363   // Saturate rather than overflow: a clamped near-max duration
377   // (e.g. delay(hours::max())) would wrap now() + d past the 364   // (e.g. delay(hours::max())) would wrap now() + d past the
378   // clock's range and appear already elapsed. 365   // clock's range and appear already elapsed.
HITCBC 379   8780 auto const now = clock_type::now(); 366   8690 auto const now = clock_type::now();
HITCBC 380   8780 impl.expiry_ = ((time_point::max)() - now < d) 367   8690 impl.expiry_ = ((time_point::max)() - now < d)
HITCBC 381   17556 ? (time_point::max)() 368   17376 ? (time_point::max)()
HITCBC 382   8776 : now + d; 369   8686 : now + d;
383   } 370   }
HITCBC 384   9444 if (impl.heap_index_.load(std::memory_order_relaxed) == 371   9354 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 385   18888 implementation::npos && 372   18708 implementation::npos &&
HITCBC 386   9444 !impl.might_have_pending_waits_.load(std::memory_order_relaxed)) 373   9354 !impl.might_have_pending_waits_.load(std::memory_order_relaxed))
HITCBC 387   9444 return 0; 374   9354 return 0;
MISUBC 388   return do_update_expiry(); 375   return do_update_expiry();
389   } 376   }
390   377  
391   /** Set the timer's expiry time relative to now. 378   /** Set the timer's expiry time relative to now.
392   379  
393   This is a convenience overload that accepts any duration type 380   This is a convenience overload that accepts any duration type
394   and converts it to the timer's native duration type. Any 381   and converts it to the timer's native duration type. Any
395   pending asynchronous wait operations will be cancelled. 382   pending asynchronous wait operations will be cancelled.
396   383  
397   @param d The expiry time relative to now. 384   @param d The expiry time relative to now.
398   385  
399   @return The number of pending operations that were cancelled. 386   @return The number of pending operations that were cancelled.
400   */ 387   */
401   template<class Rep, class Period> 388   template<class Rep, class Period>
402   std::size_t expires_after(std::chrono::duration<Rep, Period> d) 389   std::size_t expires_after(std::chrono::duration<Rep, Period> d)
403   { 390   {
404   return expires_after(std::chrono::duration_cast<duration>(d)); 391   return expires_after(std::chrono::duration_cast<duration>(d));
405   } 392   }
406   393  
407   /** Wait for the timer to expire. 394   /** Wait for the timer to expire.
408   395  
409   Multiple coroutines may wait on the same timer concurrently. 396   Multiple coroutines may wait on the same timer concurrently.
410   When the timer expires, all waiters complete with success. 397   When the timer expires, all waiters complete with success.
411   398  
412   The operation supports cancellation via `std::stop_token` through 399   The operation supports cancellation via `std::stop_token` through
413   the affine awaitable protocol. If the associated stop token is 400   the affine awaitable protocol. If the associated stop token is
414   triggered, only that waiter completes with an error that 401   triggered, only that waiter completes with an error that
415   compares equal to `capy::cond::canceled`; other waiters are 402   compares equal to `capy::cond::canceled`; other waiters are
416   unaffected. 403   unaffected.
417   404  
418   This timer must outlive the returned awaitable. 405   This timer must outlive the returned awaitable.
419   406  
420   @return An awaitable that completes with `io_result<>`. 407   @return An awaitable that completes with `io_result<>`.
421   */ 408   */
ECB 422 - 9450 auto wait() 409 + // Defined below wait_awaitable, which needs timer complete.
423 - { 410 + wait_awaitable wait();
DCB 424 - 9450 return wait_awaitable(*this);  
425 - }  
426   411  
427   protected: 412   protected:
428   explicit timer(handle h) noexcept : io_object(std::move(h)) {} 413   explicit timer(handle h) noexcept : io_object(std::move(h)) {}
429   414  
430   private: 415   private:
431   // Defined in src/corosio/src/timer.cpp, which includes both this 416   // Defined in src/corosio/src/timer.cpp, which includes both this
432   // header and timer_service.hpp, so the timer_service_* free 417   // header and timer_service.hpp, so the timer_service_* free
433   // functions are visible there. 418   // functions are visible there.
434   std::size_t do_cancel(); 419   std::size_t do_cancel();
435   std::size_t do_cancel_one(); 420   std::size_t do_cancel_one();
436   std::size_t do_update_expiry(); 421   std::size_t do_update_expiry();
437   422  
438   /// Return the underlying implementation. 423   /// Return the underlying implementation.
HITCBC 439   18900 implementation& get() const noexcept 424   18720 implementation& get() const noexcept
440   { 425   {
HITCBC 441   18900 return *static_cast<implementation*>(h_.get()); 426   18720 return *static_cast<implementation*>(h_.get());
442   } 427   }
443   }; 428   };
  429 +
  430 + /** Frame-resident per-wait state for a timer wait.
  431 +
  432 + One node exists per `co_await` on a timer, embedded in the
  433 + awaitable on the suspended coroutine's frame — never allocated.
  434 + Once published by `implementation::wait()` the node may be
  435 + completed from any thread; every completion path finishes
  436 + touching the node before resuming or destroying the coroutine,
  437 + because either act may end the node's storage.
  438 +
  439 + The node owns no resources: the stop token is borrowed from the
  440 + awaiting chain's `io_env` (which outlives the suspension) and
  441 + the stop callback is managed manually in `cb_buf_`, destroyed on
  442 + every completion path before the frame can die.
  443 + */
  444 + struct BOOST_COROSIO_SYMBOL_VISIBLE waiter_node
  445 + : intrusive_list<waiter_node>::node
  446 + {
  447 + // Embedded completion op — avoids heap allocation per fire/cancel.
  448 + // Members are exported and defined non-inline in timer.cpp: the
  449 + // inline waiter_node constructor references do_complete and the
  450 + // vtable from translation units that reach this header through
  451 + // delay.hpp without ever including timer_service.hpp, so the one
  452 + // strong definition must live in a TU that is always linked.
  453 + struct BOOST_COROSIO_SYMBOL_VISIBLE completion_op final : scheduler_op
  454 + {
  455 + waiter_node* waiter_ = nullptr;
  456 +
  457 + BOOST_COROSIO_DECL
  458 + static void do_complete(
  459 + void* owner, scheduler_op* base, std::uint32_t, std::uint32_t);
  460 +
HITGNC   461 + 18720 completion_op() noexcept : scheduler_op(&do_complete) {}
  462 +
  463 + BOOST_COROSIO_DECL void operator()() override;
  464 + BOOST_COROSIO_DECL void destroy() override;
  465 + };
  466 +
  467 + // Per-waiter stop_token cancellation
  468 + struct canceller
  469 + {
  470 + waiter_node* waiter_;
  471 + BOOST_COROSIO_DECL void operator()() const;
  472 + };
  473 +
  474 + using stop_cb_type = std::stop_callback<canceller>;
  475 +
  476 + // nullptr once removed from timer's waiter list (concurrency marker)
  477 + /// The timer this waiter is published on, or `nullptr`.
  478 + timer::implementation* impl_ = nullptr;
  479 +
  480 + /// The timer service that completes this waiter.
  481 + timer_service* svc_ = nullptr;
  482 +
  483 + /// The suspended coroutine, destroyed by the shutdown drains.
  484 + std::coroutine_handle<> h_;
  485 +
  486 + /// The continuation posted to resume the coroutine.
  487 + capy::continuation cont_;
  488 +
  489 + /// The executor the continuation is posted through.
  490 + capy::executor_ref d_;
  491 +
  492 + // Borrowed from the awaiting chain's io_env, which outlives the
  493 + // suspension; the node holds no owning state.
  494 + /// The stop token observed for cancellation.
  495 + std::stop_token const* token_ = nullptr;
  496 +
  497 + /// The completion result read by `await_resume`.
  498 + std::error_code ec_;
  499 +
  500 + /// The embedded completion op posted to the scheduler.
  501 + completion_op op_;
  502 +
  503 + // stop_callback is neither movable nor assignable; construct it
  504 + // in place once the node is pinned on the coroutine frame, and
  505 + // destroy it manually on every completion path.
  506 + /// Storage for the armed stop callback.
  507 + alignas(stop_cb_type) unsigned char cb_buf_[sizeof(stop_cb_type)];
  508 +
  509 + /// True while `cb_buf_` holds a live stop callback.
  510 + bool cb_active_ = false;
  511 +
HITGNC   512 + 18720 waiter_node() noexcept
HITGNC   513 + 18720 {
HITGNC   514 + 18720 op_.waiter_ = this;
HITGNC   515 + 18720 }
  516 +
  517 + // The embedded op self-points and the list hooks are published
  518 + // to other threads; the node never moves.
  519 + waiter_node(waiter_node const&) = delete;
  520 + waiter_node& operator=(waiter_node const&) = delete;
  521 +
  522 + /** Arm the stop callback.
  523 +
  524 + @par Preconditions
  525 + `token_` is set.
  526 + */
HITGNC   527 + 1418 void arm_stop_cb()
  528 + {
HITGNC   529 + 1418 new (cb_buf_) stop_cb_type(*token_, canceller{this});
HITGNC   530 + 1418 cb_active_ = true;
HITGNC   531 + 1418 }
  532 +
  533 + /// Destroy the stop callback if armed.
HITGNC   534 + 8505 void reset_stop_cb() noexcept
  535 + {
HITGNC   536 + 8505 if (cb_active_)
  537 + {
HITGNC   538 + 1418 std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_))
HITGNC   539 + 1418 ->~stop_cb_type();
HITGNC   540 + 1418 cb_active_ = false;
  541 + }
HITGNC   542 + 8505 }
  543 + };
  544 +
  545 + /** Awaitable returned by `timer::wait()`.
  546 +
  547 + Carries the waiter node so a wait performs no allocation. The
  548 + awaitable is movable only before `await_suspend` publishes the
  549 + node (a move builds a fresh, quiescent node); afterwards it is
  550 + pinned on the coroutine frame until the wait completes.
  551 + */
  552 + struct wait_awaitable
  553 + {
  554 + timer& t_;
  555 + waiter_node w_;
  556 +
HITGNC   557 + 9360 explicit wait_awaitable(timer& t) noexcept : t_(t) {}
  558 +
HITGNC   559 + 9360 wait_awaitable(wait_awaitable&& o) noexcept : t_(o.t_) {}
  560 +
  561 + wait_awaitable(wait_awaitable const&) = delete;
  562 + wait_awaitable& operator=(wait_awaitable const&) = delete;
  563 + wait_awaitable& operator=(wait_awaitable&&) = delete;
  564 +
HITGNC   565 + 2040 bool await_ready() const noexcept
  566 + {
HITGNC   567 + 2040 return false;
  568 + }
  569 +
  570 + // Cancellation surfaces through w_.ec_: the stop_token path in
  571 + // wait() completes the waiter with error::canceled written to
  572 + // it, so there is no separate token to consult here.
HITGNC   573 + 9332 capy::io_result<> await_resume() const noexcept
  574 + {
HITGNC   575 + 9332 return {w_.ec_};
  576 + }
  577 +
HITGNC   578 + 9360 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
  579 + -> std::coroutine_handle<>
  580 + {
HITGNC   581 + 9360 auto& impl = t_.get();
HITGNC   582 + 9360 w_.h_ = h;
HITGNC   583 + 9360 w_.cont_.h = h;
HITGNC   584 + 9360 w_.d_ = env->executor;
  585 +
  586 + // Inline fast path: already expired and not in the heap
HITGNC   587 + 9360 if (impl.already_expired())
  588 + {
HITGNC   589 + 855 w_.ec_ = {};
HITGNC   590 + 855 w_.d_.post(w_.cont_);
HITGNC   591 + 855 return std::noop_coroutine();
  592 + }
  593 +
HITGNC   594 + 8505 w_.token_ = &env->stop_token;
HITGNC   595 + 8505 return impl.wait(w_);
  596 + }
  597 + };
  598 +
  599 + inline wait_awaitable
HITGNC   600 + 9360 timer::wait()
  601 + {
HITGNC   602 + 9360 return wait_awaitable(*this);
  603 + }
444   604  
445   } // namespace boost::corosio::detail 605   } // namespace boost::corosio::detail
446   606  
447   #endif 607   #endif