98.36% Lines (60/61) 94.12% Functions (16/17)
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/detail/scheduler_op.hpp>
17   #include <boost/corosio/io/io_object.hpp> 17   #include <boost/corosio/io/io_object.hpp>
18   #include <boost/capy/continuation.hpp> 18   #include <boost/capy/continuation.hpp>
19   #include <boost/capy/io_result.hpp> 19   #include <boost/capy/io_result.hpp>
20   #include <boost/capy/error.hpp> 20   #include <boost/capy/error.hpp>
21   #include <boost/capy/ex/executor_ref.hpp> 21   #include <boost/capy/ex/executor_ref.hpp>
22   #include <boost/capy/ex/execution_context.hpp> 22   #include <boost/capy/ex/execution_context.hpp>
23 - #include <boost/capy/concept/executor.hpp>  
24   #include <boost/capy/ex/io_env.hpp> 23   #include <boost/capy/ex/io_env.hpp>
25   24  
26   #include <atomic> 25   #include <atomic>
27 - #include <concepts>  
28   #include <chrono> 26   #include <chrono>
29   #include <coroutine> 27   #include <coroutine>
30   #include <cstddef> 28   #include <cstddef>
31   #include <limits> 29   #include <limits>
32   #include <new> 30   #include <new>
33   #include <stop_token> 31   #include <stop_token>
34 - #include <type_traits>  
35   #include <system_error> 32   #include <system_error>
36   33  
37   namespace boost::corosio::detail { 34   namespace boost::corosio::detail {
38   35  
39   // timer_service is defined in timer_service.hpp, which includes this 36   // timer_service is defined in timer_service.hpp, which includes this
40   // header. waiter_node and wait_awaitable are defined below the timer 37   // header. waiter_node and wait_awaitable are defined below the timer
41   // class: waiter_node stores a timer::implementation*, which cannot be 38   // class: waiter_node stores a timer::implementation*, which cannot be
42   // forward-declared as a nested type. implementation stores only a 39   // forward-declared as a nested type. implementation stores only a
43   // waiter_node pointer, so this forward declaration suffices for its 40   // waiter_node pointer, so this forward declaration suffices for its
44   // data layout. 41   // data layout.
45   class timer_service; 42   class timer_service;
46   struct waiter_node; 43   struct waiter_node;
47   struct wait_awaitable; 44   struct wait_awaitable;
48   45  
49   /** An asynchronous timer for coroutine I/O. 46   /** An asynchronous timer for coroutine I/O.
50   47  
51   This class provides asynchronous timer operations that return 48   This class provides asynchronous timer operations that return
52   awaitable types. The timer can be used to schedule operations 49   awaitable types. The timer can be used to schedule operations
53   to occur after a specified duration or at a specific time point. 50   to occur after a specified duration or at a specific time point.
54   51  
55   Each timer carries at most one wait: `delay` and `timeout` own a 52   Each timer carries at most one wait: `delay` and `timeout` own a
56   private timer per `co_await`. When the timer expires the waiter 53   private timer per `co_await`. When the timer expires the waiter
57   completes with success; a cancelled wait completes with an error 54   completes with success; a cancelled wait completes with an error
58   that compares equal to `capy::cond::canceled`. 55   that compares equal to `capy::cond::canceled`.
59   56  
60   Each timer operation participates in the affine awaitable protocol, 57   Each timer operation participates in the affine awaitable protocol,
61   ensuring coroutines resume on the correct executor. 58   ensuring coroutines resume on the correct executor.
62   59  
63   @par Thread Safety 60   @par Thread Safety
64   Distinct objects: Safe.@n 61   Distinct objects: Safe.@n
65   Shared objects: Unsafe. 62   Shared objects: Unsafe.
66   63  
67   @par Semantics 64   @par Semantics
68   Timers are not backed by per-timer kernel objects. The io_context's 65   Timers are not backed by per-timer kernel objects. The io_context's
69   timer service keeps a process-side min-heap of pending expirations; 66   timer service keeps a process-side min-heap of pending expirations;
70   the nearest expiry drives the reactor's poll timeout, and expirations 67   the nearest expiry drives the reactor's poll timeout, and expirations
71   are processed in the run loop. 68   are processed in the run loop.
72   */ 69   */
73   class BOOST_COROSIO_DECL timer : public io_object 70   class BOOST_COROSIO_DECL timer : public io_object
74   { 71   {
75   friend struct wait_awaitable; 72   friend struct wait_awaitable;
76   73  
77   public: 74   public:
78   /** Backend state and wait entry point for a timer. 75   /** Backend state and wait entry point for a timer.
79   76  
80   Holds per-timer state ( expiry, heap position, the single waiter ) and 77   Holds per-timer state ( expiry, heap position, the single waiter ) and
81   the `wait` entry point used by the awaitable returned from 78   the `wait` entry point used by the awaitable returned from
82   @ref timer::wait. There is exactly one concrete timer backend, 79   @ref timer::wait. There is exactly one concrete timer backend,
83   so `wait` is a plain member function rather than a virtual 80   so `wait` is a plain member function rather than a virtual
84   dispatch point. 81   dispatch point.
85   */ 82   */
86   struct implementation : io_object::implementation 83   struct implementation : io_object::implementation
87   { 84   {
88   /// Sentinel value indicating the timer is not in the heap. 85   /// Sentinel value indicating the timer is not in the heap.
89   static constexpr std::size_t npos = 86   static constexpr std::size_t npos =
90   (std::numeric_limits<std::size_t>::max)(); 87   (std::numeric_limits<std::size_t>::max)();
91   88  
92   // Only mutated by the owning thread (expires_at/expires_after) 89   // Only mutated by the owning thread (expires_at/expires_after)
93   // before a wait is published; cross-thread consumers read the 90   // before a wait is published; cross-thread consumers read the
94   // heap entry's copied time_, never this field, so it needs no 91   // heap entry's copied time_, never this field, so it needs no
95   // atomicity. 92   // atomicity.
96   /// The absolute expiry time point. 93   /// The absolute expiry time point.
97   std::chrono::steady_clock::time_point expiry_{}; 94   std::chrono::steady_clock::time_point expiry_{};
98   95  
99   // heap_index_ and might_have_pending_waits_ are cross-thread 96   // heap_index_ and might_have_pending_waits_ are cross-thread
100   // hints, not authoritative state: the real state lives in the 97   // hints, not authoritative state: the real state lives in the
101   // heap and the published waiter under timer_service::mutex_. Every 98   // heap and the published waiter under timer_service::mutex_. Every
102   // unlocked fast-out that reads them is either re-validated under 99   // unlocked fast-out that reads them is either re-validated under
103   // the mutex or safe under a stale value in both directions, and 100   // the mutex or safe under a stale value in both directions, and
104   // any locked writer / locked reader pair is already ordered by 101   // any locked writer / locked reader pair is already ordered by
105   // the mutex. All accesses therefore use memory_order_relaxed, 102   // the mutex. All accesses therefore use memory_order_relaxed,
106   // which keeps the lock-free fast paths fence-free while making 103   // which keeps the lock-free fast paths fence-free while making
107   // the concurrent reads well-defined. 104   // the concurrent reads well-defined.
108   /// Index in the timer service's min-heap, or `npos`. 105   /// Index in the timer service's min-heap, or `npos`.
109   std::atomic<std::size_t> heap_index_{npos}; 106   std::atomic<std::size_t> heap_index_{npos};
110   107  
111   // false implies waiter_ is null: both are cleared together 108   // false implies waiter_ is null: both are cleared together
112   // under the service mutex. 109   // under the service mutex.
113   /// True if `wait()` has been called since last cancel. 110   /// True if `wait()` has been called since last cancel.
114   std::atomic<bool> might_have_pending_waits_{false}; 111   std::atomic<bool> might_have_pending_waits_{false};
115   112  
116   /// The timer service that owns this implementation. 113   /// The timer service that owns this implementation.
117   timer_service* svc_ = nullptr; 114   timer_service* svc_ = nullptr;
118   115  
119   // Exactly one wait may be outstanding: delay and timeout own 116   // Exactly one wait may be outstanding: delay and timeout own
120   // a private timer per co_await, and the service's drains rely 117   // a private timer per co_await, and the service's drains rely
121   // on the one-to-one pairing. 118   // on the one-to-one pairing.
122   /// The waiter published on this timer, or `nullptr`. 119   /// The waiter published on this timer, or `nullptr`.
123   waiter_node* waiter_ = nullptr; 120   waiter_node* waiter_ = nullptr;
124   121  
125   /// Free list linkage, reused when this impl is recycled. 122   /// Free list linkage, reused when this impl is recycled.
126   implementation* next_free_ = nullptr; 123   implementation* next_free_ = nullptr;
127   124  
128   /// Construct bound to the given timer service. 125   /// Construct bound to the given timer service.
HITCBC 129   319 explicit implementation(timer_service& svc) noexcept : svc_(&svc) {} 126   341 explicit implementation(timer_service& svc) noexcept : svc_(&svc) {}
130   127  
131   /** Check whether the timer is expired and absent from the heap. 128   /** Check whether the timer is expired and absent from the heap.
132   129  
133   The single definition of the already-expired fast-path 130   The single definition of the already-expired fast-path
134   predicate: `await_suspend` tests it inline and `wait()` 131   predicate: `await_suspend` tests it inline and `wait()`
135   re-tests it because the expiry can elapse between the two 132   re-tests it because the expiry can elapse between the two
136   reads. 133   reads.
137   */ 134   */
HITCBC 138   18953 bool already_expired() const noexcept 135   20604 bool already_expired() const noexcept
139   { 136   {
HITCBC 140   56859 return heap_index_.load(std::memory_order_relaxed) == npos && 137   61812 return heap_index_.load(std::memory_order_relaxed) == npos &&
HITCBC 141   18953 (expiry_ == 138   20604 (expiry_ ==
HITCBC 142   37242 (std::chrono::steady_clock::time_point::min)() || 139   40544 (std::chrono::steady_clock::time_point::min)() ||
HITCBC 143   37242 expiry_ <= std::chrono::steady_clock::now()); 140   40544 expiry_ <= std::chrono::steady_clock::now());
144   } 141   }
145   142  
146   /** Asynchronously wait for the timer to expire. 143   /** Asynchronously wait for the timer to expire.
147   144  
148   Publishes the waiter into the service's heap and the 145   Publishes the waiter into the service's heap and the
149   timer's waiter slot, after which it may complete on any 146   timer's waiter slot, after which it may complete on any
150   thread. If the timer is already expired and not in the 147   thread. If the timer is already expired and not in the
151   heap, completes by posting the continuation without 148   heap, completes by posting the continuation without
152   publishing. 149   publishing.
153   150  
154   @par Preconditions 151   @par Preconditions
155   @p w is fully initialized, and its storage (the awaitable 152   @p w is fully initialized, and its storage (the awaitable
156   on the suspended coroutine's frame) outlives the wait. 153   on the suspended coroutine's frame) outlives the wait.
157   154  
158   @param w The waiter to publish. 155   @param w The waiter to publish.
159   */ 156   */
160   // Exported at member level: dllexport on the enclosing timer 157   // Exported at member level: dllexport on the enclosing timer
161   // class does not extend to nested classes, and header-inline 158   // class does not extend to nested classes, and header-inline
162   // callers (wait_awaitable::await_suspend) reference this 159   // callers (wait_awaitable::await_suspend) reference this
163   // symbol from outside the corosio DLL. 160   // symbol from outside the corosio DLL.
164   BOOST_COROSIO_DECL 161   BOOST_COROSIO_DECL
165   std::coroutine_handle<> wait(waiter_node& w); 162   std::coroutine_handle<> wait(waiter_node& w);
166   163  
167   /** Publish a waiter unconditionally. 164   /** Publish a waiter unconditionally.
168   165  
169   Like `wait`, but never takes the elapsed fast path. The 166   Like `wait`, but never takes the elapsed fast path. The
170   fast path posts the continuation directly, bypassing the 167   fast path posts the continuation directly, bypassing the
171   embedded op; hook-driven waits must observe every 168   embedded op; hook-driven waits must observe every
172   completion through the op, where the re-arm hook runs. 169   completion through the op, where the re-arm hook runs.
173   170  
174   @par Preconditions 171   @par Preconditions
175   Same as `wait`. 172   Same as `wait`.
176   173  
177   @param w The waiter to publish. 174   @param w The waiter to publish.
178   */ 175   */
179   std::coroutine_handle<> publish(waiter_node& w); 176   std::coroutine_handle<> publish(waiter_node& w);
180   }; 177   };
181   178  
182   /// The clock type used for time operations. 179   /// The clock type used for time operations.
183   using clock_type = std::chrono::steady_clock; 180   using clock_type = std::chrono::steady_clock;
184   181  
185   /// The time point type for absolute expiry times. 182   /// The time point type for absolute expiry times.
186   using time_point = clock_type::time_point; 183   using time_point = clock_type::time_point;
187   184  
188   /// The duration type for relative expiry times. 185   /// The duration type for relative expiry times.
189   using duration = clock_type::duration; 186   using duration = clock_type::duration;
190   187  
191   /** Destructor. 188   /** Destructor.
192   189  
193   Cancels any pending operations and releases timer resources. 190   Cancels any pending operations and releases timer resources.
194   */ 191   */
195   ~timer() override; 192   ~timer() override;
196   193  
197   /** Construct a timer from an execution context. 194   /** Construct a timer from an execution context.
198   195  
199   @param ctx The execution context that will own this timer. It 196   @param ctx The execution context that will own this timer. It
200   must be a corosio io_context; otherwise the constructor 197   must be a corosio io_context; otherwise the constructor
201   throws (a timer service is required). 198   throws (a timer service is required).
202   199  
203   @throws std::logic_error if @p ctx is not an io_context. 200   @throws std::logic_error if @p ctx is not an io_context.
204   */ 201   */
205   explicit timer(capy::execution_context& ctx); 202   explicit timer(capy::execution_context& ctx);
206 - /** Construct a timer with an initial absolute expiry time.  
207 -  
208 - @param ctx The execution context that will own this timer. It  
209 - must be a corosio io_context; otherwise the constructor  
210 - throws (a timer service is required).  
211 - @param t The initial expiry time point.  
212 -  
213 - @throws std::logic_error if @p ctx is not an io_context.  
214 - */  
215 - timer(capy::execution_context& ctx, time_point t);  
216 -  
217 - /** Construct a timer with an initial relative expiry time.  
218 -  
219 - @param ctx The execution context that will own this timer. It  
220 - must be a corosio io_context; otherwise the constructor  
221 - throws (a timer service is required).  
222 - @param d The initial expiry duration relative to now.  
223 -  
224 - @throws std::logic_error if @p ctx is not an io_context.  
225 - */  
226 - template<class Rep, class Period>  
227 - timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d)  
228 - : timer(ctx)  
229 - {  
230 - expires_after(d);  
231 - }  
232 -  
233 - /** Construct a timer from an executor.  
234 -  
235 - The timer is associated with the executor's context, which must  
236 - be a corosio io_context.  
237 -  
238 - @param ex The executor whose context will own this timer.  
239 -  
240 - @throws std::logic_error if the executor's context is not an  
241 - io_context.  
242 - */  
243 - template<class Ex>  
244 - requires(!std::same_as<std::remove_cvref_t<Ex>, timer>) &&  
245 - capy::Executor<Ex>  
246 - explicit timer(Ex const& ex) : timer(ex.context())  
247 - {  
248 - }  
249 -  
250 - /** Construct a timer from an executor with an absolute expiry time.  
251 -  
252 - @param ex The executor whose context will own this timer.  
253 - @param t The initial expiry time point.  
254 -  
255 - @throws std::logic_error if the executor's context is not an  
256 - io_context.  
257 - */  
258 - template<class Ex>  
259 - requires capy::Executor<Ex>  
260 - timer(Ex const& ex, time_point t) : timer(ex.context(), t)  
261 - {  
262 - }  
263 -  
264 - /** Construct a timer from an executor with a relative expiry time.  
265 -  
266 - @param ex The executor whose context will own this timer.  
267 - @param d The initial expiry duration relative to now.  
268 -  
269 - @throws std::logic_error if the executor's context is not an  
270 - io_context.  
271 - */  
272 - template<class Ex, class Rep, class Period>  
273 - requires capy::Executor<Ex>  
274 - timer(Ex const& ex, std::chrono::duration<Rep, Period> d)  
275 - : timer(ex.context(), d)  
276 - {  
277 - }  
278 -  
279   203  
280   /** Move constructor. 204   /** Move constructor.
281   205  
282 - Transfers ownership of the timer resources. 206 + Transfers ownership of the timer resources. Required so a
283 - 207 + disengaged `std::optional<timer>` is movable; a timer is never
284 - @param other The timer to move from. 208 + moved while a wait is published.
285   209  
286 - @pre The execution context associated with @p other must  
287 - outlive this timer.  
288   @pre No awaitables returned by @p other's methods exist. 210   @pre No awaitables returned by @p other's methods exist.
289   */ 211   */
MISUIC 290 - timer(timer&& other) noexcept; 212 + timer(timer&&) noexcept = default;
291   213  
292   /** Move assignment operator. 214   /** Move assignment operator.
293   215  
294   Closes any existing timer and transfers ownership. 216   Closes any existing timer and transfers ownership.
295 - @param other The timer to move from.  
296 -  
297   217  
298   @pre No awaitables returned by either `*this` or @p other's 218   @pre No awaitables returned by either `*this` or @p other's
299 - @pre The execution context associated with @p other must  
300 - outlive this timer.  
301 -  
302 - @return Reference to this timer.  
303   methods exist. 219   methods exist.
304   */ 220   */
305 - timer& operator=(timer&& other) noexcept; 221 + timer& operator=(timer&&) noexcept = default;
306   222  
307   timer(timer const&) = delete; 223   timer(timer const&) = delete;
308   timer& operator=(timer const&) = delete; 224   timer& operator=(timer const&) = delete;
309   225  
310   /** Return the timer's expiry time as an absolute time. 226   /** Return the timer's expiry time as an absolute time.
311   227  
312   @return The expiry time point. If no expiry has been set, 228   @return The expiry time point. If no expiry has been set,
313   returns a default-constructed time_point. 229   returns a default-constructed time_point.
314   */ 230   */
315   time_point expiry() const noexcept 231   time_point expiry() const noexcept
316   { 232   {
317   return get().expiry_; 233   return get().expiry_;
318   } 234   }
319   235  
320   /** Set the timer's expiry time as an absolute time. 236   /** Set the timer's expiry time as an absolute time.
321   237  
322   @par Preconditions 238   @par Preconditions
323   No wait is published on this timer. 239   No wait is published on this timer.
324   240  
325   @param t The expiry time to be used for the timer. 241   @param t The expiry time to be used for the timer.
326   */ 242   */
HITCBC 327   16 void expires_at(time_point t) 243   16 void expires_at(time_point t)
328   { 244   {
HITCBC 329   16 auto& impl = get(); 245   16 auto& impl = get();
HITCBC 330   32 BOOST_COROSIO_ASSERT( 246   32 BOOST_COROSIO_ASSERT(
331   impl.heap_index_.load(std::memory_order_relaxed) == 247   impl.heap_index_.load(std::memory_order_relaxed) ==
332   implementation::npos); 248   implementation::npos);
HITCBC 333   16 impl.expiry_ = t; 249   16 impl.expiry_ = t;
HITCBC 334   16 } 250   16 }
335   251  
336   /** Set the timer's expiry time relative to now. 252   /** Set the timer's expiry time relative to now.
337   253  
338   @par Preconditions 254   @par Preconditions
339   No wait is published on this timer. 255   No wait is published on this timer.
340   256  
341   @param d The expiry time relative to now. 257   @param d The expiry time relative to now.
342   */ 258   */
HITCBC 343   9917 void expires_after(duration d) 259   10738 void expires_after(duration d)
344   { 260   {
HITCBC 345   9917 auto& impl = get(); 261   10738 auto& impl = get();
HITCBC 346   19834 BOOST_COROSIO_ASSERT( 262   21476 BOOST_COROSIO_ASSERT(
347   impl.heap_index_.load(std::memory_order_relaxed) == 263   impl.heap_index_.load(std::memory_order_relaxed) ==
348   implementation::npos); 264   implementation::npos);
HITCBC 349   9917 if (d <= duration::zero()) 265   10738 if (d <= duration::zero())
HITCBC 350   682 impl.expiry_ = (time_point::min)(); 266   684 impl.expiry_ = (time_point::min)();
351   else 267   else
352   { 268   {
353   // Saturate rather than overflow: a clamped near-max duration 269   // Saturate rather than overflow: a clamped near-max duration
354   // (e.g. delay(hours::max())) would wrap now() + d past the 270   // (e.g. delay(hours::max())) would wrap now() + d past the
355   // clock's range and appear already elapsed. 271   // clock's range and appear already elapsed.
HITCBC 356   9235 auto const now = clock_type::now(); 272   10054 auto const now = clock_type::now();
HITCBC 357   9235 impl.expiry_ = ((time_point::max)() - now < d) 273   10054 impl.expiry_ = ((time_point::max)() - now < d)
HITCBC 358   18466 ? (time_point::max)() 274   20104 ? (time_point::max)()
HITCBC 359   9231 : now + d; 275   10050 : now + d;
360   } 276   }
HITCBC 361   9917 } 277   10738 }
362   278  
363   /** Set the timer's expiry time relative to now. 279   /** Set the timer's expiry time relative to now.
364   280  
365   This is a convenience overload that accepts any duration type 281   This is a convenience overload that accepts any duration type
366   and converts it to the timer's native duration type. 282   and converts it to the timer's native duration type.
367   283  
368   @param d The expiry time relative to now. 284   @param d The expiry time relative to now.
369   */ 285   */
370   template<class Rep, class Period> 286   template<class Rep, class Period>
371   void expires_after(std::chrono::duration<Rep, Period> d) 287   void expires_after(std::chrono::duration<Rep, Period> d)
372   { 288   {
373   expires_after(std::chrono::duration_cast<duration>(d)); 289   expires_after(std::chrono::duration_cast<duration>(d));
374   } 290   }
375   291  
376   /** Wait for the timer to expire. 292   /** Wait for the timer to expire.
377   293  
378   At most one wait may be outstanding at a time. 294   At most one wait may be outstanding at a time.
379   295  
380   The operation supports cancellation via `std::stop_token` through 296   The operation supports cancellation via `std::stop_token` through
381   the affine awaitable protocol. If the associated stop token is 297   the affine awaitable protocol. If the associated stop token is
382   triggered, only that waiter completes with an error that 298   triggered, only that waiter completes with an error that
383   compares equal to `capy::cond::canceled`. 299   compares equal to `capy::cond::canceled`.
384   300  
385   This timer must outlive the returned awaitable. 301   This timer must outlive the returned awaitable.
386   302  
387   @return An awaitable that completes with `io_result<>`. 303   @return An awaitable that completes with `io_result<>`.
388   */ 304   */
389   // Defined below wait_awaitable, which needs timer complete. 305   // Defined below wait_awaitable, which needs timer complete.
390   wait_awaitable wait(); 306   wait_awaitable wait();
391   307  
392   /** Publish a hook-driven wait. 308   /** Publish a hook-driven wait.
393   309  
394   Bypasses the elapsed fast path so every completion is 310   Bypasses the elapsed fast path so every completion is
395   delivered through the waiter's embedded op, where the 311   delivered through the waiter's embedded op, where the
396   re-arm hook is consulted. Used by awaitables that 312   re-arm hook is consulted. Used by awaitables that
397   re-publish the waiter to continue a logical wait across 313   re-publish the waiter to continue a logical wait across
398   several timer expirations. 314   several timer expirations.
399   315  
400   @par Preconditions 316   @par Preconditions
401   @p w is fully initialized ( handle, executor, stop token, 317   @p w is fully initialized ( handle, executor, stop token,
402   hook fields ) and its storage outlives the wait. 318   hook fields ) and its storage outlives the wait.
403   319  
404   @param w The waiter to publish. 320   @param w The waiter to publish.
405   321  
406   @return `std::noop_coroutine()`. 322   @return `std::noop_coroutine()`.
407   */ 323   */
408   std::coroutine_handle<> publish_wait(waiter_node& w); 324   std::coroutine_handle<> publish_wait(waiter_node& w);
409   325  
410   /** Re-arm an already-fired waiter with a new relative expiry. 326   /** Re-arm an already-fired waiter with a new relative expiry.
411   327  
412   Stores the ( saturated ) expiry and re-publishes @p w. The 328   Stores the ( saturated ) expiry and re-publishes @p w. The
413   waiter's original work count and stop callback remain in 329   waiter's original work count and stop callback remain in
414   effect. Must only be called from the waiter's re-arm hook, 330   effect. Must only be called from the waiter's re-arm hook,
415   where the waiter has been popped from the service but not 331   where the waiter has been popped from the service but not
416   yet resumed. 332   yet resumed.
417   333  
418   @par Preconditions 334   @par Preconditions
419   The timer has no other waiters — this is what makes the 335   The timer has no other waiters — this is what makes the
420   unlocked expiry write race-free. 336   unlocked expiry write race-free.
421   337  
422   Re-publication needs heap capacity and can fail under 338   Re-publication needs heap capacity and can fail under
423   allocation pressure. On failure the waiter is left exactly as 339   allocation pressure. On failure the waiter is left exactly as
424   the hook received it, so the caller completes the wait through 340   the hook received it, so the caller completes the wait through
425   the normal resume path instead of re-arming. 341   the normal resume path instead of re-arming.
426   342  
427   @param w The waiter to re-publish. 343   @param w The waiter to re-publish.
428   @param d The next expiry relative to now. 344   @param d The next expiry relative to now.
429   345  
430   @return `true` if re-published; `false` if allocation failed. 346   @return `true` if re-published; `false` if allocation failed.
431   */ 347   */
432   [[nodiscard]] bool rearm_wait(waiter_node& w, duration d) noexcept; 348   [[nodiscard]] bool rearm_wait(waiter_node& w, duration d) noexcept;
433   349  
434   protected: 350   protected:
435   explicit timer(handle h) noexcept : io_object(std::move(h)) {} 351   explicit timer(handle h) noexcept : io_object(std::move(h)) {}
436   352  
437   private: 353   private:
438   /// Return the underlying implementation. 354   /// Return the underlying implementation.
HITCBC 439   19866 implementation& get() const noexcept 355   21508 implementation& get() const noexcept
440   { 356   {
HITCBC 441   19866 return *static_cast<implementation*>(h_.get()); 357   21508 return *static_cast<implementation*>(h_.get());
442   } 358   }
443   }; 359   };
444   360  
445   /** Frame-resident per-wait state for a timer wait. 361   /** Frame-resident per-wait state for a timer wait.
446   362  
447   One node exists per `co_await` on a timer, embedded in the 363   One node exists per `co_await` on a timer, embedded in the
448   awaitable on the suspended coroutine's frame — never allocated. 364   awaitable on the suspended coroutine's frame — never allocated.
449   Once published by `implementation::wait()` the node may be 365   Once published by `implementation::wait()` the node may be
450   completed from any thread; every completion path finishes 366   completed from any thread; every completion path finishes
451   touching the node before resuming or destroying the coroutine, 367   touching the node before resuming or destroying the coroutine,
452   because either act may end the node's storage. 368   because either act may end the node's storage.
453   369  
454   The node owns no resources: the stop token is borrowed from the 370   The node owns no resources: the stop token is borrowed from the
455   awaiting chain's `io_env` (which outlives the suspension) and 371   awaiting chain's `io_env` (which outlives the suspension) and
456   the stop callback is managed manually in `cb_buf_`, destroyed on 372   the stop callback is managed manually in `cb_buf_`, destroyed on
457   every completion path before the frame can die. 373   every completion path before the frame can die.
458   */ 374   */
459   struct BOOST_COROSIO_SYMBOL_VISIBLE waiter_node 375   struct BOOST_COROSIO_SYMBOL_VISIBLE waiter_node
460   : intrusive_list<waiter_node>::node 376   : intrusive_list<waiter_node>::node
461   { 377   {
462   // Embedded completion op — avoids heap allocation per fire/cancel. 378   // Embedded completion op — avoids heap allocation per fire/cancel.
463   // Members are exported and defined non-inline in timer.cpp: the 379   // Members are exported and defined non-inline in timer.cpp: the
464   // inline waiter_node constructor references do_complete and the 380   // inline waiter_node constructor references do_complete and the
465   // vtable from translation units that reach this header through 381   // vtable from translation units that reach this header through
466   // delay.hpp without ever including timer_service.hpp, so the one 382   // delay.hpp without ever including timer_service.hpp, so the one
467   // strong definition must live in a TU that is always linked. 383   // strong definition must live in a TU that is always linked.
468   struct BOOST_COROSIO_SYMBOL_VISIBLE completion_op final : scheduler_op 384   struct BOOST_COROSIO_SYMBOL_VISIBLE completion_op final : scheduler_op
469   { 385   {
470   waiter_node* waiter_ = nullptr; 386   waiter_node* waiter_ = nullptr;
471   387  
472   BOOST_COROSIO_DECL 388   BOOST_COROSIO_DECL
473   static void do_complete( 389   static void do_complete(
474   void* owner, scheduler_op* base, std::uint32_t, std::uint32_t); 390   void* owner, scheduler_op* base, std::uint32_t, std::uint32_t);
475   391  
HITCBC 476   21850 completion_op() noexcept : scheduler_op(&do_complete) {} 392   23488 completion_op() noexcept : scheduler_op(&do_complete) {}
477   393  
478   BOOST_COROSIO_DECL void operator()() override; 394   BOOST_COROSIO_DECL void operator()() override;
479   BOOST_COROSIO_DECL void destroy() override; 395   BOOST_COROSIO_DECL void destroy() override;
480   }; 396   };
481   397  
482   // Per-waiter stop_token cancellation 398   // Per-waiter stop_token cancellation
483   struct canceller 399   struct canceller
484   { 400   {
485   waiter_node* waiter_; 401   waiter_node* waiter_;
486   BOOST_COROSIO_DECL void operator()() const; 402   BOOST_COROSIO_DECL void operator()() const;
487   }; 403   };
488   404  
489   using stop_cb_type = std::stop_callback<canceller>; 405   using stop_cb_type = std::stop_callback<canceller>;
490   406  
491   // nullptr once unpublished from the timer ( concurrency marker ) 407   // nullptr once unpublished from the timer ( concurrency marker )
492   /// The timer this waiter is published on, or `nullptr`. 408   /// The timer this waiter is published on, or `nullptr`.
493   timer::implementation* impl_ = nullptr; 409   timer::implementation* impl_ = nullptr;
494   410  
495   /// The timer service that completes this waiter. 411   /// The timer service that completes this waiter.
496   timer_service* svc_ = nullptr; 412   timer_service* svc_ = nullptr;
497   413  
498   /// The suspended coroutine, destroyed by the shutdown drains. 414   /// The suspended coroutine, destroyed by the shutdown drains.
499   std::coroutine_handle<> h_; 415   std::coroutine_handle<> h_;
500   416  
501   /// The continuation posted to resume the coroutine. 417   /// The continuation posted to resume the coroutine.
502   capy::continuation cont_; 418   capy::continuation cont_;
503   419  
504   /// The executor the continuation is posted through. 420   /// The executor the continuation is posted through.
505   capy::executor_ref d_; 421   capy::executor_ref d_;
506   422  
507   // Borrowed from the awaiting chain's io_env, which outlives the 423   // Borrowed from the awaiting chain's io_env, which outlives the
508   // suspension; the node holds no owning state. 424   // suspension; the node holds no owning state.
509   /// The stop token observed for cancellation. 425   /// The stop token observed for cancellation.
510   std::stop_token const* token_ = nullptr; 426   std::stop_token const* token_ = nullptr;
511   427  
512   /// The completion result read by `await_resume`. 428   /// The completion result read by `await_resume`.
513   std::error_code ec_; 429   std::error_code ec_;
514   430  
515   // Consulted by the completion op before resuming; lets a 431   // Consulted by the completion op before resuming; lets a
516   // clock-facade wait re-publish itself instead of completing. 432   // clock-facade wait re-publish itself instead of completing.
517   // Never consulted on the shutdown destroy path. Consulted on 433   // Never consulted on the shutdown destroy path. Consulted on
518   // every completion, including cancellation ( `ec_` set ) — the 434   // every completion, including cancellation ( `ec_` set ) — the
519   // hook must inspect `w`'s `ec_` and must not re-arm a canceled 435   // hook must inspect `w`'s `ec_` and must not re-arm a canceled
520   // waiter. Runs inside the completion path; must not throw. 436   // waiter. Runs inside the completion path; must not throw.
521   /// Re-arm hook: return true to skip resumption ( wait continues ). 437   /// Re-arm hook: return true to skip resumption ( wait continues ).
522   bool (*on_fire_)(void*) noexcept = nullptr; 438   bool (*on_fire_)(void*) noexcept = nullptr;
523   439  
524   /// Context passed to `on_fire_` ( the owning awaitable ). 440   /// Context passed to `on_fire_` ( the owning awaitable ).
525   void* on_fire_ctx_ = nullptr; 441   void* on_fire_ctx_ = nullptr;
526   442  
527   /// The embedded completion op posted to the scheduler. 443   /// The embedded completion op posted to the scheduler.
528   completion_op op_; 444   completion_op op_;
529   445  
530   // stop_callback is neither movable nor assignable; construct it 446   // stop_callback is neither movable nor assignable; construct it
531   // in place once the node is pinned on the coroutine frame, and 447   // in place once the node is pinned on the coroutine frame, and
532   // destroy it manually on every completion path. 448   // destroy it manually on every completion path.
533   /// Storage for the armed stop callback. 449   /// Storage for the armed stop callback.
534   alignas(stop_cb_type) unsigned char cb_buf_[sizeof(stop_cb_type)]; 450   alignas(stop_cb_type) unsigned char cb_buf_[sizeof(stop_cb_type)];
535   451  
536   /// True while `cb_buf_` holds a live stop callback. 452   /// True while `cb_buf_` holds a live stop callback.
537   bool cb_active_ = false; 453   bool cb_active_ = false;
538   454  
HITCBC 539   21850 waiter_node() noexcept 455   23488 waiter_node() noexcept
HITCBC 540   21850 { 456   23488 {
HITCBC 541   21850 op_.waiter_ = this; 457   23488 op_.waiter_ = this;
HITCBC 542   21850 } 458   23488 }
543   459  
544   // The embedded op self-points and the list hooks are published 460   // The embedded op self-points and the list hooks are published
545   // to other threads; the node never moves. 461   // to other threads; the node never moves.
546   waiter_node(waiter_node const&) = delete; 462   waiter_node(waiter_node const&) = delete;
547   waiter_node& operator=(waiter_node const&) = delete; 463   waiter_node& operator=(waiter_node const&) = delete;
548   464  
549   /** Bind the coroutine and its environment before publication. 465   /** Bind the coroutine and its environment before publication.
550   466  
551   The single definition of the fields every wait must populate 467   The single definition of the fields every wait must populate
552   before the node is published; hook-driven waits additionally 468   before the node is published; hook-driven waits additionally
553   set `on_fire_` / `on_fire_ctx_`. 469   set `on_fire_` / `on_fire_ctx_`.
554   470  
555   @param h The coroutine to resume on completion. 471   @param h The coroutine to resume on completion.
556   @param env The awaiting chain's environment; must outlive 472   @param env The awaiting chain's environment; must outlive
557   the suspension. 473   the suspension.
558   */ 474   */
HITCBC 559   9921 void bind(std::coroutine_handle<> h, capy::io_env const& env) noexcept 475   10742 void bind(std::coroutine_handle<> h, capy::io_env const& env) noexcept
560   { 476   {
HITCBC 561   9921 h_ = h; 477   10742 h_ = h;
HITCBC 562   9921 cont_.h = h; 478   10742 cont_.h = h;
HITCBC 563   9921 d_ = env.executor; 479   10742 d_ = env.executor;
HITCBC 564   9921 token_ = &env.stop_token; 480   10742 token_ = &env.stop_token;
HITCBC 565   9921 } 481   10742 }
566   482  
567   /** Arm the stop callback. 483   /** Arm the stop callback.
568   484  
569   @par Preconditions 485   @par Preconditions
570   `token_` is set. 486   `token_` is set.
571   */ 487   */
HITCBC 572   1435 void arm_stop_cb() 488   1436 void arm_stop_cb()
573   { 489   {
HITCBC 574   1435 new (cb_buf_) stop_cb_type(*token_, canceller{this}); 490   1436 new (cb_buf_) stop_cb_type(*token_, canceller{this});
HITCBC 575   1435 cb_active_ = true; 491   1436 cb_active_ = true;
HITCBC 576   1435 } 492   1436 }
577   493  
578   /// Destroy the stop callback if armed. 494   /// Destroy the stop callback if armed.
HITCBC 579   9056 void reset_stop_cb() noexcept 495   9889 void reset_stop_cb() noexcept
580   { 496   {
HITCBC 581   9056 if (cb_active_) 497   9889 if (cb_active_)
582   { 498   {
HITCBC 583   1435 std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_)) 499   1436 std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_))
HITCBC 584   1435 ->~stop_cb_type(); 500   1436 ->~stop_cb_type();
HITCBC 585   1435 cb_active_ = false; 501   1436 cb_active_ = false;
586   } 502   }
HITCBC 587   9056 } 503   9889 }
588   }; 504   };
589   505  
590   /** Awaitable returned by `timer::wait()`. 506   /** Awaitable returned by `timer::wait()`.
591   507  
592   Carries the waiter node so a wait performs no allocation. The 508   Carries the waiter node so a wait performs no allocation. The
593   awaitable is movable only before `await_suspend` publishes the 509   awaitable is movable only before `await_suspend` publishes the
594   node (a move builds a fresh, quiescent node); afterwards it is 510   node (a move builds a fresh, quiescent node); afterwards it is
595   pinned on the coroutine frame until the wait completes. 511   pinned on the coroutine frame until the wait completes.
596   */ 512   */
597   struct wait_awaitable 513   struct wait_awaitable
598   { 514   {
599   timer& t_; 515   timer& t_;
600   waiter_node w_; 516   waiter_node w_;
601   517  
HITCBC 602   9909 explicit wait_awaitable(timer& t) noexcept : t_(t) {} 518   10728 explicit wait_awaitable(timer& t) noexcept : t_(t) {}
603   519  
HITCBC 604   9909 wait_awaitable(wait_awaitable&& o) noexcept : t_(o.t_) {} 520   10728 wait_awaitable(wait_awaitable&& o) noexcept : t_(o.t_) {}
605   521  
606   wait_awaitable(wait_awaitable const&) = delete; 522   wait_awaitable(wait_awaitable const&) = delete;
607   wait_awaitable& operator=(wait_awaitable const&) = delete; 523   wait_awaitable& operator=(wait_awaitable const&) = delete;
608   wait_awaitable& operator=(wait_awaitable&&) = delete; 524   wait_awaitable& operator=(wait_awaitable&&) = delete;
609   525  
HITCBC 610   2053 bool await_ready() const noexcept 526   2053 bool await_ready() const noexcept
611   { 527   {
HITCBC 612   2053 return false; 528   2053 return false;
613   } 529   }
614   530  
615   // Cancellation surfaces through w_.ec_: the stop_token path in 531   // Cancellation surfaces through w_.ec_: the stop_token path in
616   // wait() completes the waiter with error::canceled written to 532   // wait() completes the waiter with error::canceled written to
617   // it, so there is no separate token to consult here. 533   // it, so there is no separate token to consult here.
HITCBC 618   9881 capy::io_result<> await_resume() const noexcept 534   10700 capy::io_result<> await_resume() const noexcept
619   { 535   {
HITCBC 620   9881 return {w_.ec_}; 536   10700 return {w_.ec_};
621   } 537   }
622   538  
HITCBC 623   9909 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 539   10728 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
624   -> std::coroutine_handle<> 540   -> std::coroutine_handle<>
625   { 541   {
HITCBC 626   9909 auto& impl = t_.get(); 542   10728 auto& impl = t_.get();
HITCBC 627   9909 w_.bind(h, *env); 543   10728 w_.bind(h, *env);
628   544  
629   // Inline fast path: already expired and not in the heap. 545   // Inline fast path: already expired and not in the heap.
630   // Post instead of dispatch so the coroutine yields to the 546   // Post instead of dispatch so the coroutine yields to the
631   // scheduler, allowing other queued work to run. 547   // scheduler, allowing other queued work to run.
HITCBC 632   9909 if (impl.already_expired()) 548   10728 if (impl.already_expired())
633   { 549   {
HITCBC 634   865 w_.ec_ = {}; 550   852 w_.ec_ = {};
HITCBC 635   865 w_.d_.post(w_.cont_); 551   852 w_.d_.post(w_.cont_);
HITCBC 636   865 return std::noop_coroutine(); 552   852 return std::noop_coroutine();
637   } 553   }
638   554  
HITCBC 639   9044 return impl.wait(w_); 555   9876 return impl.wait(w_);
640   } 556   }
641   }; 557   };
642   558  
643   inline wait_awaitable 559   inline wait_awaitable
HITCBC 644   9909 timer::wait() 560   10728 timer::wait()
645   { 561   {
HITCBC 646   9909 return wait_awaitable(*this); 562   10728 return wait_awaitable(*this);
647   } 563   }
648   564  
649   } // namespace boost::corosio::detail 565   } // namespace boost::corosio::detail
650   566  
651   #endif 567   #endif