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