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