98.31% Lines (58/59) 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   1637 explicit implementation(timer_service& svc) noexcept : svc_(&svc) {} 126   1405 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   28475 bool already_expired() const noexcept 135   25178 bool already_expired() const noexcept
136   { 136   {
HITCBC 137   85425 return heap_index_.load(std::memory_order_relaxed) == npos && 137   75534 return heap_index_.load(std::memory_order_relaxed) == npos &&
HITCBC 138   56286 (expiry_ == (std::chrono::steady_clock::time_point::min)() || 138   49692 (expiry_ == (std::chrono::steady_clock::time_point::min)() ||
HITCBC 139   56286 expiry_ <= std::chrono::steady_clock::now()); 139   49692 expiry_ <= std::chrono::steady_clock::now());
140   } 140   }
141   141  
142   /** Asynchronously wait for the timer to expire. 142   /** Asynchronously wait for the timer to expire.
143   143  
144   Publishes the waiter into the service's heap and the 144   Publishes the waiter into the service's heap and the
145   timer's waiter slot, after which it may complete on any 145   timer's waiter slot, after which it may complete on any
146   thread. If the timer is already expired and not in the 146   thread. If the timer is already expired and not in the
147   heap, completes by posting the continuation without 147   heap, completes by posting the continuation without
148   publishing. 148   publishing.
149   149  
150 - @par Preconditions 150 + @pre @p w is fully initialized, and its storage (the awaitable
151 - @p w is fully initialized, and its storage (the awaitable 151 + on the suspended coroutine's frame) outlives the wait.
152 - on the suspended coroutine's frame) outlives the wait.  
153   152  
154   @param w The waiter to publish. 153   @param w The waiter to publish.
155   */ 154   */
156   // Exported at member level: dllexport on the enclosing timer 155   // Exported at member level: dllexport on the enclosing timer
157   // class does not extend to nested classes, and header-inline 156   // class does not extend to nested classes, and header-inline
158   // callers (wait_awaitable::await_suspend) reference this 157   // callers (wait_awaitable::await_suspend) reference this
159   // symbol from outside the corosio DLL. 158   // symbol from outside the corosio DLL.
160   BOOST_COROSIO_DECL 159   BOOST_COROSIO_DECL
161   std::coroutine_handle<> wait(waiter_node& w); 160   std::coroutine_handle<> wait(waiter_node& w);
162   161  
163   /** Publish a waiter unconditionally. 162   /** Publish a waiter unconditionally.
164   163  
165   Like `wait`, but never takes the elapsed fast path. The 164   Like `wait`, but never takes the elapsed fast path. The
166   fast path posts the continuation directly, bypassing the 165   fast path posts the continuation directly, bypassing the
167   embedded op; hook-driven waits must observe every 166   embedded op; hook-driven waits must observe every
168   completion through the op, where the re-arm hook runs. 167   completion through the op, where the re-arm hook runs.
169   168  
170 - @par Preconditions 169 + @pre Same as `wait`.
171 - Same as `wait`.  
172   170  
173   @param w The waiter to publish. 171   @param w The waiter to publish.
174   */ 172   */
175   std::coroutine_handle<> publish(waiter_node& w); 173   std::coroutine_handle<> publish(waiter_node& w);
176   }; 174   };
177   175  
178   /// The clock type used for time operations. 176   /// The clock type used for time operations.
179   using clock_type = std::chrono::steady_clock; 177   using clock_type = std::chrono::steady_clock;
180   178  
181   /// The time point type for absolute expiry times. 179   /// The time point type for absolute expiry times.
182   using time_point = clock_type::time_point; 180   using time_point = clock_type::time_point;
183   181  
184   /// The duration type for relative expiry times. 182   /// The duration type for relative expiry times.
185   using duration = clock_type::duration; 183   using duration = clock_type::duration;
186   184  
187   /** Destructor. 185   /** Destructor.
188   186  
189   Cancels any pending operations and releases timer resources. 187   Cancels any pending operations and releases timer resources.
190   */ 188   */
191   ~timer() override; 189   ~timer() override;
192   190  
193   /** Construct a timer from an execution context. 191   /** Construct a timer from an execution context.
194   192  
195   @param ctx The execution context that will own this timer. It 193   @param ctx The execution context that will own this timer. It
196   must be a corosio io_context; otherwise the constructor 194   must be a corosio io_context; otherwise the constructor
197   throws (a timer service is required). 195   throws (a timer service is required).
198   196  
199   @throws std::logic_error if @p ctx is not an io_context. 197   @throws std::logic_error if @p ctx is not an io_context.
200   */ 198   */
201   explicit timer(capy::execution_context& ctx); 199   explicit timer(capy::execution_context& ctx);
202   200  
203   /** Move constructor. 201   /** Move constructor.
204   202  
205   Transfers ownership of the timer resources. Required so a 203   Transfers ownership of the timer resources. Required so a
206   disengaged `std::optional<timer>` is movable; a timer is never 204   disengaged `std::optional<timer>` is movable; a timer is never
207   moved while a wait is published. 205   moved while a wait is published.
208   206  
209   @pre No awaitables returned by @p other's methods exist. 207   @pre No awaitables returned by @p other's methods exist.
210   */ 208   */
MISUBC 211   ✗ timer(timer&&) noexcept = default; 209   ✗ timer(timer&&) noexcept = default;
212   210  
213   /** Move assignment operator. 211   /** Move assignment operator.
214   212  
215   Closes any existing timer and transfers ownership. 213   Closes any existing timer and transfers ownership.
216   214  
217   @pre No awaitables returned by either `*this` or @p other's 215   @pre No awaitables returned by either `*this` or @p other's
218   methods exist. 216   methods exist.
219   */ 217   */
220   timer& operator=(timer&&) noexcept = default; 218   timer& operator=(timer&&) noexcept = default;
221   219  
222   timer(timer const&) = delete; 220   timer(timer const&) = delete;
223   timer& operator=(timer const&) = delete; 221   timer& operator=(timer const&) = delete;
224   222  
225   /** Return the timer's expiry time as an absolute time. 223   /** Return the timer's expiry time as an absolute time.
226   224  
227   @return The expiry time point. If no expiry has been set, 225   @return The expiry time point. If no expiry has been set,
228   returns a default-constructed time_point. 226   returns a default-constructed time_point.
229   */ 227   */
230   time_point expiry() const noexcept 228   time_point expiry() const noexcept
231   { 229   {
232   return get().expiry_; 230   return get().expiry_;
233   } 231   }
234   232  
235   /** Set the timer's expiry time as an absolute time. 233   /** Set the timer's expiry time as an absolute time.
236   234  
237 - @par Preconditions 235 + @pre No wait is published on this timer.
238 - No wait is published on this timer.  
239   236  
240   @param t The expiry time to be used for the timer. 237   @param t The expiry time to be used for the timer.
241   */ 238   */
HITCBC 242   16 void expires_at(time_point t) 239   16 void expires_at(time_point t)
243   { 240   {
HITCBC 244   16 auto& impl = get(); 241   16 auto& impl = get();
HITCBC 245   32 BOOST_COROSIO_ASSERT( 242   32 BOOST_COROSIO_ASSERT(
246   impl.heap_index_.load(std::memory_order_relaxed) == 243   impl.heap_index_.load(std::memory_order_relaxed) ==
247   implementation::npos); 244   implementation::npos);
HITCBC 248   16 impl.expiry_ = t; 245   16 impl.expiry_ = t;
HITCBC 249   16 } 246   16 }
250   247  
251   /** Set the timer's expiry time relative to now. 248   /** Set the timer's expiry time relative to now.
252   249  
253 - @par Preconditions 250 + @pre No wait is published on this timer.
254 - No wait is published on this timer.  
255   251  
256   @param d The expiry time relative to now. 252   @param d The expiry time relative to now.
257   */ 253   */
HITCBC 258   18796 void expires_after(duration d) 254   17622 void expires_after(duration d)
259   { 255   {
HITCBC 260   18796 auto& impl = get(); 256   17622 auto& impl = get();
HITCBC 261   37592 BOOST_COROSIO_ASSERT( 257   35244 BOOST_COROSIO_ASSERT(
262   impl.heap_index_.load(std::memory_order_relaxed) == 258   impl.heap_index_.load(std::memory_order_relaxed) ==
263   implementation::npos); 259   implementation::npos);
HITCBC 264   18796 if (d <= duration::zero()) 260   17622 if (d <= duration::zero())
HITCBC 265   682 impl.expiry_ = (time_point::min)(); 261   680 impl.expiry_ = (time_point::min)();
266   else 262   else
267   { 263   {
268   // Saturate rather than overflow: a clamped near-max duration 264   // Saturate rather than overflow: a clamped near-max duration
269   // (e.g. delay(hours::max())) would wrap now() + d past the 265   // (e.g. delay(hours::max())) would wrap now() + d past the
270   // clock's range and appear already elapsed. 266   // clock's range and appear already elapsed.
HITCBC 271   18114 auto const now = clock_type::now(); 267   16942 auto const now = clock_type::now();
HITCBC 272   18114 impl.expiry_ = 268   16942 impl.expiry_ =
HITCBC 273   18114 ((time_point::max)() - now < d) ? (time_point::max)() : now + d; 269   16942 ((time_point::max)() - now < d) ? (time_point::max)() : now + d;
274   } 270   }
HITCBC 275   18796 } 271   17622 }
276   272  
277   /** Set the timer's expiry time relative to now. 273   /** Set the timer's expiry time relative to now.
278   274  
279   This is a convenience overload that accepts any duration type 275   This is a convenience overload that accepts any duration type
280   and converts it to the timer's native duration type. 276   and converts it to the timer's native duration type.
281   277  
282   @param d The expiry time relative to now. 278   @param d The expiry time relative to now.
283   */ 279   */
284   template<class Rep, class Period> 280   template<class Rep, class Period>
285   void expires_after(std::chrono::duration<Rep, Period> d) 281   void expires_after(std::chrono::duration<Rep, Period> d)
286   { 282   {
287   expires_after(std::chrono::duration_cast<duration>(d)); 283   expires_after(std::chrono::duration_cast<duration>(d));
288   } 284   }
289   285  
290   /** Wait for the timer to expire. 286   /** Wait for the timer to expire.
291   287  
292   At most one wait may be outstanding at a time. 288   At most one wait may be outstanding at a time.
293   289  
294   The operation supports cancellation via `std::stop_token` through 290   The operation supports cancellation via `std::stop_token` through
295   the affine awaitable protocol. If the associated stop token is 291   the affine awaitable protocol. If the associated stop token is
296   triggered, only that waiter completes with an error that 292   triggered, only that waiter completes with an error that
297   compares equal to `capy::cond::canceled`. 293   compares equal to `capy::cond::canceled`.
298   294  
299   This timer must outlive the returned awaitable. 295   This timer must outlive the returned awaitable.
300   296  
301   @return An awaitable that completes with `io_result<>`. 297   @return An awaitable that completes with `io_result<>`.
302   */ 298   */
303   // Defined below wait_awaitable, which needs timer complete. 299   // Defined below wait_awaitable, which needs timer complete.
304   wait_awaitable wait(); 300   wait_awaitable wait();
305   301  
306   /** Publish a hook-driven wait. 302   /** Publish a hook-driven wait.
307   303  
308   Bypasses the elapsed fast path so every completion is 304   Bypasses the elapsed fast path so every completion is
309   delivered through the waiter's embedded op, where the 305   delivered through the waiter's embedded op, where the
310   re-arm hook is consulted. Used by awaitables that 306   re-arm hook is consulted. Used by awaitables that
311   re-publish the waiter to continue a logical wait across 307   re-publish the waiter to continue a logical wait across
312   several timer expirations. 308   several timer expirations.
313   309  
314 - @par Preconditions 310 + @pre @p w is fully initialized ( handle, executor, stop token,
315 - @p w is fully initialized ( handle, executor, stop token, 311 + hook fields ) and its storage outlives the wait.
316 - hook fields ) and its storage outlives the wait.  
317   312  
318   @param w The waiter to publish. 313   @param w The waiter to publish.
319   314  
320   @return `std::noop_coroutine()`. 315   @return `std::noop_coroutine()`.
321   */ 316   */
322   std::coroutine_handle<> publish_wait(waiter_node& w); 317   std::coroutine_handle<> publish_wait(waiter_node& w);
323   318  
324   /** Re-arm an already-fired waiter with a new relative expiry. 319   /** Re-arm an already-fired waiter with a new relative expiry.
325   320  
326   Stores the ( saturated ) expiry and re-publishes @p w. The 321   Stores the ( saturated ) expiry and re-publishes @p w. The
327   waiter's original work count and stop callback remain in 322   waiter's original work count and stop callback remain in
328   effect. Must only be called from the waiter's re-arm hook, 323   effect. Must only be called from the waiter's re-arm hook,
329   where the waiter has been popped from the service but not 324   where the waiter has been popped from the service but not
330   yet resumed. 325   yet resumed.
331   326  
332 - @par Preconditions 327 + @pre The timer has no other waiters — this is what makes the
333 - The timer has no other waiters — this is what makes the 328 + unlocked expiry write race-free.
334 - unlocked expiry write race-free.  
335   329  
336   Re-publication needs heap capacity and can fail under 330   Re-publication needs heap capacity and can fail under
337   allocation pressure. On failure the waiter is left exactly as 331   allocation pressure. On failure the waiter is left exactly as
338   the hook received it, so the caller completes the wait through 332   the hook received it, so the caller completes the wait through
339   the normal resume path instead of re-arming. 333   the normal resume path instead of re-arming.
340   334  
341   @param w The waiter to re-publish. 335   @param w The waiter to re-publish.
342   @param d The next expiry relative to now. 336   @param d The next expiry relative to now.
343   337  
344   @return `true` if re-published; `false` if allocation failed. 338   @return `true` if re-published; `false` if allocation failed.
345   */ 339   */
346   [[nodiscard]] bool rearm_wait(waiter_node& w, duration d) noexcept; 340   [[nodiscard]] bool rearm_wait(waiter_node& w, duration d) noexcept;
347   341  
348   protected: 342   protected:
349   explicit timer(handle h) noexcept : io_object(std::move(h)) {} 343   explicit timer(handle h) noexcept : io_object(std::move(h)) {}
350   344  
351   private: 345   private:
352   /// Return the underlying implementation. 346   /// Return the underlying implementation.
HITCBC 353   37624 implementation& get() const noexcept 347   35276 implementation& get() const noexcept
354   { 348   {
HITCBC 355   37624 return *static_cast<implementation*>(h_.get()); 349   35276 return *static_cast<implementation*>(h_.get());
356   } 350   }
357   }; 351   };
358   352  
359   /** Frame-resident per-wait state for a timer wait. 353   /** Frame-resident per-wait state for a timer wait.
360   354  
361   One node exists per `co_await` on a timer, embedded in the 355   One node exists per `co_await` on a timer, embedded in the
362   awaitable on the suspended coroutine's frame — never allocated. 356   awaitable on the suspended coroutine's frame — never allocated.
363   Once published by `implementation::wait()` the node may be 357   Once published by `implementation::wait()` the node may be
364   completed from any thread; every completion path finishes 358   completed from any thread; every completion path finishes
365   touching the node before resuming or destroying the coroutine, 359   touching the node before resuming or destroying the coroutine,
366   because either act may end the node's storage. 360   because either act may end the node's storage.
367   361  
368   The node owns no resources: the stop token is borrowed from the 362   The node owns no resources: the stop token is borrowed from the
369   awaiting chain's `io_env` (which outlives the suspension) and 363   awaiting chain's `io_env` (which outlives the suspension) and
370   the stop callback is managed manually in `cb_buf_`, destroyed on 364   the stop callback is managed manually in `cb_buf_`, destroyed on
371   every completion path before the frame can die. 365   every completion path before the frame can die.
372   */ 366   */
373   struct BOOST_COROSIO_SYMBOL_VISIBLE waiter_node 367   struct BOOST_COROSIO_SYMBOL_VISIBLE waiter_node
374   : intrusive_list<waiter_node>::node 368   : intrusive_list<waiter_node>::node
375   { 369   {
376   // Embedded completion op — avoids heap allocation per fire/cancel. 370   // Embedded completion op — avoids heap allocation per fire/cancel.
377   // Members are exported and defined non-inline in timer.cpp: the 371   // Members are exported and defined non-inline in timer.cpp: the
378   // inline waiter_node constructor references do_complete and the 372   // inline waiter_node constructor references do_complete and the
379   // vtable from translation units that reach this header through 373   // vtable from translation units that reach this header through
380   // delay.hpp without ever including timer_service.hpp, so the one 374   // delay.hpp without ever including timer_service.hpp, so the one
381   // strong definition must live in a TU that is always linked. 375   // strong definition must live in a TU that is always linked.
382   struct BOOST_COROSIO_SYMBOL_VISIBLE completion_op final : scheduler_op 376   struct BOOST_COROSIO_SYMBOL_VISIBLE completion_op final : scheduler_op
383   { 377   {
384   waiter_node* waiter_ = nullptr; 378   waiter_node* waiter_ = nullptr;
385   379  
386   BOOST_COROSIO_DECL 380   BOOST_COROSIO_DECL
387   static void do_complete( 381   static void do_complete(
388   void* owner, scheduler_op* base, std::uint32_t, std::uint32_t); 382   void* owner, scheduler_op* base, std::uint32_t, std::uint32_t);
389   383  
HITCBC 390   31832 completion_op() noexcept : scheduler_op(&do_complete) {} 384   28510 completion_op() noexcept : scheduler_op(&do_complete) {}
391   385  
392   BOOST_COROSIO_DECL void operator()() override; 386   BOOST_COROSIO_DECL void operator()() override;
393   BOOST_COROSIO_DECL void destroy() override; 387   BOOST_COROSIO_DECL void destroy() override;
394   }; 388   };
395   389  
396   // Per-waiter stop_token cancellation 390   // Per-waiter stop_token cancellation
397   struct canceller 391   struct canceller
398   { 392   {
399   waiter_node* waiter_; 393   waiter_node* waiter_;
400   BOOST_COROSIO_DECL void operator()() const; 394   BOOST_COROSIO_DECL void operator()() const;
401   }; 395   };
402   396  
403   using stop_cb_type = std::stop_callback<canceller>; 397   using stop_cb_type = std::stop_callback<canceller>;
404   398  
405   // nullptr once unpublished from the timer ( concurrency marker ) 399   // nullptr once unpublished from the timer ( concurrency marker )
406   /// The timer this waiter is published on, or `nullptr`. 400   /// The timer this waiter is published on, or `nullptr`.
407   timer::implementation* impl_ = nullptr; 401   timer::implementation* impl_ = nullptr;
408   402  
409   /// The timer service that completes this waiter. 403   /// The timer service that completes this waiter.
410   timer_service* svc_ = nullptr; 404   timer_service* svc_ = nullptr;
411   405  
412   /// The suspended coroutine, destroyed by the shutdown drains. 406   /// The suspended coroutine, destroyed by the shutdown drains.
413   std::coroutine_handle<> h_; 407   std::coroutine_handle<> h_;
414   408  
415   /// The continuation posted to resume the coroutine. 409   /// The continuation posted to resume the coroutine.
416   capy::continuation cont_; 410   capy::continuation cont_;
417   411  
418   /// The executor the continuation is posted through. 412   /// The executor the continuation is posted through.
419   capy::executor_ref d_; 413   capy::executor_ref d_;
420   414  
421   // Borrowed from the awaiting chain's io_env, which outlives the 415   // Borrowed from the awaiting chain's io_env, which outlives the
422   // suspension; the node holds no owning state. 416   // suspension; the node holds no owning state.
423   /// The stop token observed for cancellation. 417   /// The stop token observed for cancellation.
424   std::stop_token const* token_ = nullptr; 418   std::stop_token const* token_ = nullptr;
425   419  
426   /// The completion result read by `await_resume`. 420   /// The completion result read by `await_resume`.
427   std::error_code ec_; 421   std::error_code ec_;
428   422  
429   // Consulted by the completion op before resuming; lets a 423   // Consulted by the completion op before resuming; lets a
430   // clock-facade wait re-publish itself instead of completing. 424   // clock-facade wait re-publish itself instead of completing.
431   // Never consulted on the shutdown destroy path. Consulted on 425   // Never consulted on the shutdown destroy path. Consulted on
432   // every completion, including cancellation ( `ec_` set ) — the 426   // every completion, including cancellation ( `ec_` set ) — the
433   // hook must inspect `w`'s `ec_` and must not re-arm a canceled 427   // hook must inspect `w`'s `ec_` and must not re-arm a canceled
434   // waiter. Runs inside the completion path; must not throw. 428   // waiter. Runs inside the completion path; must not throw.
435   /// Re-arm hook: return true to skip resumption ( wait continues ). 429   /// Re-arm hook: return true to skip resumption ( wait continues ).
436   bool (*on_fire_)(void*) noexcept = nullptr; 430   bool (*on_fire_)(void*) noexcept = nullptr;
437   431  
438   /// Context passed to `on_fire_` ( the owning awaitable ). 432   /// Context passed to `on_fire_` ( the owning awaitable ).
439   void* on_fire_ctx_ = nullptr; 433   void* on_fire_ctx_ = nullptr;
440   434  
441   /// The embedded completion op posted to the scheduler. 435   /// The embedded completion op posted to the scheduler.
442   completion_op op_; 436   completion_op op_;
443   437  
444   // stop_callback is neither movable nor assignable; construct it 438   // stop_callback is neither movable nor assignable; construct it
445   // in place once the node is pinned on the coroutine frame, and 439   // in place once the node is pinned on the coroutine frame, and
446   // destroy it manually on every completion path. 440   // destroy it manually on every completion path.
447   /// Storage for the armed stop callback. 441   /// Storage for the armed stop callback.
448   alignas(stop_cb_type) unsigned char cb_buf_[sizeof(stop_cb_type)]; 442   alignas(stop_cb_type) unsigned char cb_buf_[sizeof(stop_cb_type)];
449   443  
450   /// True while `cb_buf_` holds a live stop callback. 444   /// True while `cb_buf_` holds a live stop callback.
451   bool cb_active_ = false; 445   bool cb_active_ = false;
452   446  
HITCBC 453   31832 waiter_node() noexcept 447   28510 waiter_node() noexcept
HITCBC 454   31832 { 448   28510 {
HITCBC 455   31832 op_.waiter_ = this; 449   28510 op_.waiter_ = this;
HITCBC 456   31832 } 450   28510 }
457   451  
458   // The embedded op self-points and the list hooks are published 452   // The embedded op self-points and the list hooks are published
459   // to other threads; the node never moves. 453   // to other threads; the node never moves.
460   waiter_node(waiter_node const&) = delete; 454   waiter_node(waiter_node const&) = delete;
461   waiter_node& operator=(waiter_node const&) = delete; 455   waiter_node& operator=(waiter_node const&) = delete;
462   456  
463   /** Bind the coroutine and its environment before publication. 457   /** Bind the coroutine and its environment before publication.
464   458  
465   The single definition of the fields every wait must populate 459   The single definition of the fields every wait must populate
466   before the node is published; hook-driven waits additionally 460   before the node is published; hook-driven waits additionally
467   set `on_fire_` / `on_fire_ctx_`. 461   set `on_fire_` / `on_fire_ctx_`.
468   462  
469   @param h The coroutine to resume on completion. 463   @param h The coroutine to resume on completion.
470   @param env The awaiting chain's environment; must outlive 464   @param env The awaiting chain's environment; must outlive
471   the suspension. 465   the suspension.
472   */ 466   */
HITCBC 473   14912 void bind(std::coroutine_handle<> h, capy::io_env const& env) noexcept 467   13249 void bind(std::coroutine_handle<> h, capy::io_env const& env) noexcept
474   { 468   {
HITCBC 475   14912 h_ = h; 469   13249 h_ = h;
HITCBC 476   14912 cont_.h = h; 470   13249 cont_.h = h;
HITCBC 477   14912 d_ = env.executor; 471   13249 d_ = env.executor;
HITCBC 478   14912 token_ = &env.stop_token; 472   13249 token_ = &env.stop_token;
HITCBC 479   14912 } 473   13249 }
480   474  
481   /** Arm the stop callback. 475   /** Arm the stop callback.
482   476  
483 - @par Preconditions 477 + @pre `token_` is set.
484 - `token_` is set.  
485   */ 478   */
HITCBC 486   1703 void arm_stop_cb() 479   1590 void arm_stop_cb()
487   { 480   {
HITCBC 488   1703 new (cb_buf_) stop_cb_type(*token_, canceller{this}); 481   1590 new (cb_buf_) stop_cb_type(*token_, canceller{this});
HITCBC 489   1703 cb_active_ = true; 482   1590 cb_active_ = true;
HITCBC 490   1703 } 483   1590 }
491   484  
492   /// Destroy the stop callback if armed. 485   /// Destroy the stop callback if armed.
HITCBC 493   14059 void reset_stop_cb() noexcept 486   12422 void reset_stop_cb() noexcept
494   { 487   {
HITCBC 495   14059 if (cb_active_) 488   12422 if (cb_active_)
496   { 489   {
HITCBC 497   1703 std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_)) 490   1590 std::launder(reinterpret_cast<stop_cb_type*>(cb_buf_))
HITCBC 498   1703 ->~stop_cb_type(); 491   1590 ->~stop_cb_type();
HITCBC 499   1703 cb_active_ = false; 492   1590 cb_active_ = false;
500   } 493   }
HITCBC 501   14059 } 494   12422 }
502   }; 495   };
503   496  
504   /** Awaitable returned by `timer::wait()`. 497   /** Awaitable returned by `timer::wait()`.
505   498  
506   Carries the waiter node so a wait performs no allocation. The 499   Carries the waiter node so a wait performs no allocation. The
507   awaitable is movable only before `await_suspend` publishes the 500   awaitable is movable only before `await_suspend` publishes the
508   node (a move builds a fresh, quiescent node); afterwards it is 501   node (a move builds a fresh, quiescent node); afterwards it is
509   pinned on the coroutine frame until the wait completes. 502   pinned on the coroutine frame until the wait completes.
510   */ 503   */
511   struct wait_awaitable 504   struct wait_awaitable
512   { 505   {
513   timer& t_; 506   timer& t_;
514   waiter_node w_; 507   waiter_node w_;
515   508  
HITCBC 516   14663 explicit wait_awaitable(timer& t) noexcept : t_(t) {} 509   13002 explicit wait_awaitable(timer& t) noexcept : t_(t) {}
517   510  
HITCBC 518   14663 wait_awaitable(wait_awaitable&& o) noexcept : t_(o.t_) {} 511   13002 wait_awaitable(wait_awaitable&& o) noexcept : t_(o.t_) {}
519   512  
520   wait_awaitable(wait_awaitable const&) = delete; 513   wait_awaitable(wait_awaitable const&) = delete;
521   wait_awaitable& operator=(wait_awaitable const&) = delete; 514   wait_awaitable& operator=(wait_awaitable const&) = delete;
522   wait_awaitable& operator=(wait_awaitable&&) = delete; 515   wait_awaitable& operator=(wait_awaitable&&) = delete;
523   516  
HITCBC 524   2053 bool await_ready() const noexcept 517   2053 bool await_ready() const noexcept
525   { 518   {
HITCBC 526   2053 return false; 519   2053 return false;
527   } 520   }
528   521  
529   // Cancellation surfaces through w_.ec_: the stop_token path in 522   // Cancellation surfaces through w_.ec_: the stop_token path in
530   // wait() completes the waiter with error::canceled written to 523   // wait() completes the waiter with error::canceled written to
531   // it, so there is no separate token to consult here. 524   // it, so there is no separate token to consult here.
HITCBC 532   14634 [[nodiscard]] capy::io_result<> await_resume() const noexcept 525   12973 [[nodiscard]] capy::io_result<> await_resume() const noexcept
533   { 526   {
HITCBC 534   14634 return {w_.ec_}; 527   12973 return {w_.ec_};
535   } 528   }
536   529  
HITCBC 537   14663 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 530   13002 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
538   -> std::coroutine_handle<> 531   -> std::coroutine_handle<>
539   { 532   {
HITCBC 540   14663 auto& impl = t_.get(); 533   13002 auto& impl = t_.get();
HITCBC 541   14663 w_.bind(h, *env); 534   13002 w_.bind(h, *env);
542   535  
543   // Inline fast path: already expired and not in the heap. 536   // Inline fast path: already expired and not in the heap.
544   // Post instead of dispatch so the coroutine yields to the 537   // Post instead of dispatch so the coroutine yields to the
545   // scheduler, allowing other queued work to run. 538   // scheduler, allowing other queued work to run.
HITCBC 546   14663 if (impl.already_expired()) 539   13002 if (impl.already_expired())
547   { 540   {
HITCBC 548   851 w_.ec_ = {}; 541   826 w_.ec_ = {};
HITCBC 549   851 w_.d_.post(w_.cont_); 542   826 w_.d_.post(w_.cont_);
HITCBC 550   851 return std::noop_coroutine(); 543   826 return std::noop_coroutine();
551   } 544   }
552   545  
HITCBC 553   13812 return impl.wait(w_); 546   12176 return impl.wait(w_);
554   } 547   }
555   }; 548   };
556   549  
557   inline wait_awaitable 550   inline wait_awaitable
HITCBC 558   14663 timer::wait() 551   13002 timer::wait()
559   { 552   {
HITCBC 560   14663 return wait_awaitable(*this); 553   13002 return wait_awaitable(*this);
561   } 554   }
562   555  
563   } // namespace boost::corosio::detail 556   } // namespace boost::corosio::detail
564   557  
565   #endif 558   #endif