95.35% Lines (41/43) 100.00% Functions (9/9)
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/io/io_object.hpp> 16   #include <boost/corosio/io/io_object.hpp>
17   #include <boost/capy/continuation.hpp> 17   #include <boost/capy/continuation.hpp>
18   #include <boost/capy/io_result.hpp> 18   #include <boost/capy/io_result.hpp>
19   #include <boost/capy/error.hpp> 19   #include <boost/capy/error.hpp>
20   #include <boost/capy/ex/executor_ref.hpp> 20   #include <boost/capy/ex/executor_ref.hpp>
21   #include <boost/capy/ex/execution_context.hpp> 21   #include <boost/capy/ex/execution_context.hpp>
22   #include <boost/capy/ex/io_env.hpp> 22   #include <boost/capy/ex/io_env.hpp>
23   #include <boost/capy/concept/executor.hpp> 23   #include <boost/capy/concept/executor.hpp>
24   24  
25   #include <atomic> 25   #include <atomic>
26   #include <chrono> 26   #include <chrono>
27   #include <concepts> 27   #include <concepts>
28   #include <coroutine> 28   #include <coroutine>
29   #include <cstddef> 29   #include <cstddef>
30   #include <limits> 30   #include <limits>
31   #include <stop_token> 31   #include <stop_token>
32   #include <system_error> 32   #include <system_error>
33   #include <type_traits> 33   #include <type_traits>
34   34  
35   namespace boost::corosio::detail { 35   namespace boost::corosio::detail {
36   36  
37   // Defined in timer_service.hpp, which includes this header. The 37   // Defined in timer_service.hpp, which includes this header. The
38   // implementation::wait() body needs waiter_node and timer_service to 38   // implementation::wait() body needs waiter_node and timer_service to
39   // be complete, so it is declared here and defined there; intrusive_list 39   // be complete, so it is declared here and defined there; intrusive_list
40   // only stores waiter_node pointers, so the forward declaration suffices 40   // only stores waiter_node pointers, so the forward declaration suffices
41   // for implementation's data layout. 41   // for implementation's data layout.
42   class timer_service; 42   class timer_service;
43   struct waiter_node; 43   struct waiter_node;
44   44  
45   /** An asynchronous timer for coroutine I/O. 45   /** An asynchronous timer for coroutine I/O.
46   46  
47   This class provides asynchronous timer operations that return 47   This class provides asynchronous timer operations that return
48   awaitable types. The timer can be used to schedule operations 48   awaitable types. The timer can be used to schedule operations
49   to occur after a specified duration or at a specific time point. 49   to occur after a specified duration or at a specific time point.
50   50  
51   Multiple coroutines may wait concurrently on the same timer. 51   Multiple coroutines may wait concurrently on the same timer.
52   When the timer expires, all waiters complete with success. When 52   When the timer expires, all waiters complete with success. When
53   the timer is cancelled, all waiters complete with an error that 53   the timer is cancelled, all waiters complete with an error that
54   compares equal to `capy::cond::canceled`. 54   compares equal to `capy::cond::canceled`.
55   55  
56   Each timer operation participates in the affine awaitable protocol, 56   Each timer operation participates in the affine awaitable protocol,
57   ensuring coroutines resume on the correct executor. 57   ensuring coroutines resume on the correct executor.
58   58  
59   @par Thread Safety 59   @par Thread Safety
60   Distinct objects: Safe.@n 60   Distinct objects: Safe.@n
61   Shared objects: Unsafe. 61   Shared objects: Unsafe.
62   62  
63   @par Semantics 63   @par Semantics
64   Timers are not backed by per-timer kernel objects. The io_context's 64   Timers are not backed by per-timer kernel objects. The io_context's
65   timer service keeps a process-side min-heap of pending expirations; 65   timer service keeps a process-side min-heap of pending expirations;
66   the nearest expiry drives the reactor's poll timeout, and expirations 66   the nearest expiry drives the reactor's poll timeout, and expirations
67   are processed in the run loop. 67   are processed in the run loop.
68   */ 68   */
69   class BOOST_COROSIO_DECL timer : public io_object 69   class BOOST_COROSIO_DECL timer : public io_object
70   { 70   {
71   struct wait_awaitable 71   struct wait_awaitable
72   { 72   {
73   timer& t_; 73   timer& t_;
74   std::error_code ec_; 74   std::error_code ec_;
75   capy::continuation cont_; 75   capy::continuation cont_;
76   76  
HITCBC 77   9452 explicit wait_awaitable(timer& t) noexcept : t_(t) {} 77   8457 explicit wait_awaitable(timer& t) noexcept : t_(t) {}
78   78  
HITCBC 79   2040 bool await_ready() const noexcept 79   2040 bool await_ready() const noexcept
80   { 80   {
HITCBC 81   2040 return false; 81   2040 return false;
82   } 82   }
83   83  
84   // Cancellation surfaces through ec_: the stop_token path in 84   // Cancellation surfaces through ec_: the stop_token path in
85   // wait() completes the waiter with error::canceled written to 85   // wait() completes the waiter with error::canceled written to
86   // ec_, so there is no separate token to consult here. 86   // ec_, so there is no separate token to consult here.
HITCBC 87   9426 capy::io_result<> await_resume() const noexcept 87   8431 capy::io_result<> await_resume() const noexcept
88   { 88   {
HITCBC 89   9426 return {ec_}; 89   8431 return {ec_};
90   } 90   }
91   91  
HITCBC 92   9452 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 92   8457 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
93   -> std::coroutine_handle<> 93   -> std::coroutine_handle<>
94   { 94   {
HITCBC 95   9452 cont_.h = h; 95   8457 cont_.h = h;
HITCBC 96   9452 auto& impl = t_.get(); 96   8457 auto& impl = t_.get();
97   // Inline fast path: already expired and not in the heap 97   // Inline fast path: already expired and not in the heap
HITCBC 98   9452 if (impl.heap_index_.load(std::memory_order_relaxed) == 98   8457 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 99   18904 implementation::npos && 99   16914 implementation::npos &&
HITCBC 100   18240 (impl.expiry_ == (time_point::min)() || 100   16250 (impl.expiry_ == (time_point::min)() ||
HITCBC 101   18240 impl.expiry_ <= clock_type::now())) 101   16250 impl.expiry_ <= clock_type::now()))
102   { 102   {
HITCBC 103   862 ec_ = {}; 103   856 ec_ = {};
HITCBC 104   862 auto d = env->executor; 104   856 auto d = env->executor;
HITCBC 105   862 d.post(cont_); 105   856 d.post(cont_);
HITCBC 106   862 return std::noop_coroutine(); 106   856 return std::noop_coroutine();
107   } 107   }
HITCBC 108   8590 return impl.wait(h, env->executor, env->stop_token, &ec_, &cont_); 108   7601 return impl.wait(h, env->executor, env->stop_token, &ec_, &cont_);
109   } 109   }
110   }; 110   };
111   111  
112   public: 112   public:
113   /** Backend state and wait entry point for a timer. 113   /** Backend state and wait entry point for a timer.
114   114  
115   Holds per-timer state (expiry, heap position, waiter list) and 115   Holds per-timer state (expiry, heap position, waiter list) and
116   the `wait` entry point used by the awaitable returned from 116   the `wait` entry point used by the awaitable returned from
117   @ref timer::wait. There is exactly one concrete timer backend, 117   @ref timer::wait. There is exactly one concrete timer backend,
118   so `wait` is a plain member function rather than a virtual 118   so `wait` is a plain member function rather than a virtual
119   dispatch point. 119   dispatch point.
120   */ 120   */
121   struct implementation : io_object::implementation 121   struct implementation : io_object::implementation
122   { 122   {
123   /// Sentinel value indicating the timer is not in the heap. 123   /// Sentinel value indicating the timer is not in the heap.
124   static constexpr std::size_t npos = 124   static constexpr std::size_t npos =
125   (std::numeric_limits<std::size_t>::max)(); 125   (std::numeric_limits<std::size_t>::max)();
126   126  
127   // Only mutated by the owning thread (expires_at/expires_after) 127   // Only mutated by the owning thread (expires_at/expires_after)
128   // before a wait is published; cross-thread consumers read the 128   // before a wait is published; cross-thread consumers read the
129   // heap entry's copied time_, never this field, so it needs no 129   // heap entry's copied time_, never this field, so it needs no
130   // atomicity. 130   // atomicity.
131   /// The absolute expiry time point. 131   /// The absolute expiry time point.
132   std::chrono::steady_clock::time_point expiry_{}; 132   std::chrono::steady_clock::time_point expiry_{};
133   133  
134   // heap_index_ and might_have_pending_waits_ are cross-thread 134   // heap_index_ and might_have_pending_waits_ are cross-thread
135   // hints, not authoritative state: the real state lives in the 135   // hints, not authoritative state: the real state lives in the
136   // heap and waiter list under timer_service::mutex_. Every 136   // heap and waiter list under timer_service::mutex_. Every
137   // unlocked fast-out that reads them is either re-validated under 137   // unlocked fast-out that reads them is either re-validated under
138   // the mutex or safe under a stale value in both directions, and 138   // the mutex or safe under a stale value in both directions, and
139   // any locked writer / locked reader pair is already ordered by 139   // any locked writer / locked reader pair is already ordered by
140   // the mutex. All accesses therefore use memory_order_relaxed, 140   // the mutex. All accesses therefore use memory_order_relaxed,
141   // which keeps the lock-free fast paths fence-free while making 141   // which keeps the lock-free fast paths fence-free while making
142   // the concurrent reads well-defined. 142   // the concurrent reads well-defined.
143   /// Index in the timer service's min-heap, or `npos`. 143   /// Index in the timer service's min-heap, or `npos`.
144   std::atomic<std::size_t> heap_index_{npos}; 144   std::atomic<std::size_t> heap_index_{npos};
145   145  
146   /// True if `wait()` has been called since last cancel. 146   /// True if `wait()` has been called since last cancel.
147   std::atomic<bool> might_have_pending_waits_{false}; 147   std::atomic<bool> might_have_pending_waits_{false};
148   148  
149   /// The timer service that owns this implementation. 149   /// The timer service that owns this implementation.
150   timer_service* svc_ = nullptr; 150   timer_service* svc_ = nullptr;
151   151  
152   /// Coroutines currently waiting on this timer's expiry. 152   /// Coroutines currently waiting on this timer's expiry.
153   intrusive_list<waiter_node> waiters_; 153   intrusive_list<waiter_node> waiters_;
154   154  
155   /// Free list linkage, reused when this impl is recycled. 155   /// Free list linkage, reused when this impl is recycled.
156   implementation* next_free_ = nullptr; 156   implementation* next_free_ = nullptr;
157   157  
158   /// Construct bound to the given timer service. 158   /// Construct bound to the given timer service.
HITCBC 159   329 explicit implementation(timer_service& svc) noexcept : svc_(&svc) {} 159   293 explicit implementation(timer_service& svc) noexcept : svc_(&svc) {}
160   160  
161   /// Initiate an asynchronous wait for the timer to expire. 161   /// Initiate an asynchronous wait for the timer to expire.
162   // Exported at member level: dllexport on the enclosing timer 162   // Exported at member level: dllexport on the enclosing timer
163   // class does not extend to nested classes, and header-inline 163   // class does not extend to nested classes, and header-inline
164   // callers (wait_awaitable::await_suspend) reference this 164   // callers (wait_awaitable::await_suspend) reference this
165   // symbol from outside the corosio DLL. 165   // symbol from outside the corosio DLL.
166   BOOST_COROSIO_DECL 166   BOOST_COROSIO_DECL
167   std::coroutine_handle<> wait( 167   std::coroutine_handle<> wait(
168   std::coroutine_handle<>, 168   std::coroutine_handle<>,
169   capy::executor_ref, 169   capy::executor_ref,
170   std::stop_token, 170   std::stop_token,
171   std::error_code*, 171   std::error_code*,
172   capy::continuation*); 172   capy::continuation*);
173   }; 173   };
174   174  
175   /// The clock type used for time operations. 175   /// The clock type used for time operations.
176   using clock_type = std::chrono::steady_clock; 176   using clock_type = std::chrono::steady_clock;
177   177  
178   /// The time point type for absolute expiry times. 178   /// The time point type for absolute expiry times.
179   using time_point = clock_type::time_point; 179   using time_point = clock_type::time_point;
180   180  
181   /// The duration type for relative expiry times. 181   /// The duration type for relative expiry times.
182   using duration = clock_type::duration; 182   using duration = clock_type::duration;
183   183  
184   /** Destructor. 184   /** Destructor.
185   185  
186   Cancels any pending operations and releases timer resources. 186   Cancels any pending operations and releases timer resources.
187   */ 187   */
188   ~timer() override; 188   ~timer() override;
189   189  
190   /** Construct a timer from an execution context. 190   /** Construct a timer from an execution context.
191   191  
192   @param ctx The execution context that will own this timer. It 192   @param ctx The execution context that will own this timer. It
193   must be a corosio io_context; otherwise the constructor 193   must be a corosio io_context; otherwise the constructor
194   throws (a timer service is required). 194   throws (a timer service is required).
195   195  
196   @throws std::logic_error if @p ctx is not an io_context. 196   @throws std::logic_error if @p ctx is not an io_context.
197   */ 197   */
198   explicit timer(capy::execution_context& ctx); 198   explicit timer(capy::execution_context& ctx);
199   199  
200   /** Construct a timer with an initial absolute expiry time. 200   /** Construct a timer with an initial absolute expiry time.
201   201  
202   @param ctx The execution context that will own this timer. It 202   @param ctx The execution context that will own this timer. It
203   must be a corosio io_context; otherwise the constructor 203   must be a corosio io_context; otherwise the constructor
204   throws (a timer service is required). 204   throws (a timer service is required).
205   @param t The initial expiry time point. 205   @param t The initial expiry time point.
206   206  
207   @throws std::logic_error if @p ctx is not an io_context. 207   @throws std::logic_error if @p ctx is not an io_context.
208   */ 208   */
209   timer(capy::execution_context& ctx, time_point t); 209   timer(capy::execution_context& ctx, time_point t);
210   210  
211   /** Construct a timer with an initial relative expiry time. 211   /** Construct a timer with an initial relative expiry time.
212   212  
213   @param ctx The execution context that will own this timer. It 213   @param ctx The execution context that will own this timer. It
214   must be a corosio io_context; otherwise the constructor 214   must be a corosio io_context; otherwise the constructor
215   throws (a timer service is required). 215   throws (a timer service is required).
216   @param d The initial expiry duration relative to now. 216   @param d The initial expiry duration relative to now.
217   217  
218   @throws std::logic_error if @p ctx is not an io_context. 218   @throws std::logic_error if @p ctx is not an io_context.
219   */ 219   */
220   template<class Rep, class Period> 220   template<class Rep, class Period>
221   timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d) 221   timer(capy::execution_context& ctx, std::chrono::duration<Rep, Period> d)
222   : timer(ctx) 222   : timer(ctx)
223   { 223   {
224   expires_after(d); 224   expires_after(d);
225   } 225   }
226   226  
227   /** Construct a timer from an executor. 227   /** Construct a timer from an executor.
228   228  
229   The timer is associated with the executor's context, which must 229   The timer is associated with the executor's context, which must
230   be a corosio io_context. 230   be a corosio io_context.
231   231  
232   @param ex The executor whose context will own this timer. 232   @param ex The executor whose context will own this timer.
233   233  
234   @throws std::logic_error if the executor's context is not an 234   @throws std::logic_error if the executor's context is not an
235   io_context. 235   io_context.
236   */ 236   */
237   template<class Ex> 237   template<class Ex>
238   requires(!std::same_as<std::remove_cvref_t<Ex>, timer>) && 238   requires(!std::same_as<std::remove_cvref_t<Ex>, timer>) &&
239   capy::Executor<Ex> 239   capy::Executor<Ex>
240   explicit timer(Ex const& ex) : timer(ex.context()) 240   explicit timer(Ex const& ex) : timer(ex.context())
241   { 241   {
242   } 242   }
243   243  
244   /** Construct a timer from an executor with an absolute expiry time. 244   /** Construct a timer from an executor with an absolute expiry time.
245   245  
246   @param ex The executor whose context will own this timer. 246   @param ex The executor whose context will own this timer.
247   @param t The initial expiry time point. 247   @param t The initial expiry time point.
248   248  
249   @throws std::logic_error if the executor's context is not an 249   @throws std::logic_error if the executor's context is not an
250   io_context. 250   io_context.
251   */ 251   */
252   template<class Ex> 252   template<class Ex>
253   requires capy::Executor<Ex> 253   requires capy::Executor<Ex>
254   timer(Ex const& ex, time_point t) : timer(ex.context(), t) 254   timer(Ex const& ex, time_point t) : timer(ex.context(), t)
255   { 255   {
256   } 256   }
257   257  
258   /** Construct a timer from an executor with a relative expiry time. 258   /** Construct a timer from an executor with a relative expiry time.
259   259  
260   @param ex The executor whose context will own this timer. 260   @param ex The executor whose context will own this timer.
261   @param d The initial expiry duration relative to now. 261   @param d The initial expiry duration relative to now.
262   262  
263   @throws std::logic_error if the executor's context is not an 263   @throws std::logic_error if the executor's context is not an
264   io_context. 264   io_context.
265   */ 265   */
266   template<class Ex, class Rep, class Period> 266   template<class Ex, class Rep, class Period>
267   requires capy::Executor<Ex> 267   requires capy::Executor<Ex>
268   timer(Ex const& ex, std::chrono::duration<Rep, Period> d) 268   timer(Ex const& ex, std::chrono::duration<Rep, Period> d)
269   : timer(ex.context(), d) 269   : timer(ex.context(), d)
270   { 270   {
271   } 271   }
272   272  
273   /** Move constructor. 273   /** Move constructor.
274   274  
275   Transfers ownership of the timer resources. 275   Transfers ownership of the timer resources.
276   276  
277   @param other The timer to move from. 277   @param other The timer to move from.
278   278  
279   @pre No awaitables returned by @p other's methods exist. 279   @pre No awaitables returned by @p other's methods exist.
280   @pre The execution context associated with @p other must 280   @pre The execution context associated with @p other must
281   outlive this timer. 281   outlive this timer.
282   */ 282   */
283   timer(timer&& other) noexcept; 283   timer(timer&& other) noexcept;
284   284  
285   /** Move assignment operator. 285   /** Move assignment operator.
286   286  
287   Closes any existing timer and transfers ownership. 287   Closes any existing timer and transfers ownership.
288   288  
289   @param other The timer to move from. 289   @param other The timer to move from.
290   290  
291   @pre No awaitables returned by either `*this` or @p other's 291   @pre No awaitables returned by either `*this` or @p other's
292   methods exist. 292   methods exist.
293   @pre The execution context associated with @p other must 293   @pre The execution context associated with @p other must
294   outlive this timer. 294   outlive this timer.
295   295  
296   @return Reference to this timer. 296   @return Reference to this timer.
297   */ 297   */
298   timer& operator=(timer&& other) noexcept; 298   timer& operator=(timer&& other) noexcept;
299   299  
300   timer(timer const&) = delete; 300   timer(timer const&) = delete;
301   timer& operator=(timer const&) = delete; 301   timer& operator=(timer const&) = delete;
302   302  
303   /** Cancel all pending asynchronous wait operations. 303   /** Cancel all pending asynchronous wait operations.
304   304  
305   All outstanding operations complete with an error code that 305   All outstanding operations complete with an error code that
306   compares equal to `capy::cond::canceled`. 306   compares equal to `capy::cond::canceled`.
307   307  
308   @return The number of operations that were cancelled. 308   @return The number of operations that were cancelled.
309   */ 309   */
310   std::size_t cancel() 310   std::size_t cancel()
311   { 311   {
312   if (!get().might_have_pending_waits_.load(std::memory_order_relaxed)) 312   if (!get().might_have_pending_waits_.load(std::memory_order_relaxed))
313   return 0; 313   return 0;
314   return do_cancel(); 314   return do_cancel();
315   } 315   }
316   316  
317   /** Cancel one pending asynchronous wait operation. 317   /** Cancel one pending asynchronous wait operation.
318   318  
319   The oldest pending wait is cancelled (FIFO order). It 319   The oldest pending wait is cancelled (FIFO order). It
320   completes with an error code that compares equal to 320   completes with an error code that compares equal to
321   `capy::cond::canceled`. 321   `capy::cond::canceled`.
322   322  
323   @return The number of operations that were cancelled (0 or 1). 323   @return The number of operations that were cancelled (0 or 1).
324   */ 324   */
325   std::size_t cancel_one() 325   std::size_t cancel_one()
326   { 326   {
327   if (!get().might_have_pending_waits_.load(std::memory_order_relaxed)) 327   if (!get().might_have_pending_waits_.load(std::memory_order_relaxed))
328   return 0; 328   return 0;
329   return do_cancel_one(); 329   return do_cancel_one();
330   } 330   }
331   331  
332   /** Return the timer's expiry time as an absolute time. 332   /** Return the timer's expiry time as an absolute time.
333   333  
334   @return The expiry time point. If no expiry has been set, 334   @return The expiry time point. If no expiry has been set,
335   returns a default-constructed time_point. 335   returns a default-constructed time_point.
336   */ 336   */
337   time_point expiry() const noexcept 337   time_point expiry() const noexcept
338   { 338   {
339   return get().expiry_; 339   return get().expiry_;
340   } 340   }
341   341  
342   /** Set the timer's expiry time as an absolute time. 342   /** Set the timer's expiry time as an absolute time.
343   343  
344   Any pending asynchronous wait operations will be cancelled. 344   Any pending asynchronous wait operations will be cancelled.
345   345  
346   @param t The expiry time to be used for the timer. 346   @param t The expiry time to be used for the timer.
347   347  
348   @return The number of pending operations that were cancelled. 348   @return The number of pending operations that were cancelled.
349   */ 349   */
HITCBC 350   6 std::size_t expires_at(time_point t) 350   6 std::size_t expires_at(time_point t)
351   { 351   {
HITCBC 352   6 auto& impl = get(); 352   6 auto& impl = get();
HITCBC 353   6 impl.expiry_ = t; 353   6 impl.expiry_ = t;
HITCBC 354   6 if (impl.heap_index_.load(std::memory_order_relaxed) == 354   6 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 355   12 implementation::npos && 355   12 implementation::npos &&
HITCBC 356   6 !impl.might_have_pending_waits_.load(std::memory_order_relaxed)) 356   6 !impl.might_have_pending_waits_.load(std::memory_order_relaxed))
HITCBC 357   6 return 0; 357   6 return 0;
MISUBC 358   return do_update_expiry(); 358   return do_update_expiry();
359   } 359   }
360   360  
361   /** Set the timer's expiry time relative to now. 361   /** Set the timer's expiry time relative to now.
362   362  
363   Any pending asynchronous wait operations will be cancelled. 363   Any pending asynchronous wait operations will be cancelled.
364   364  
365   @param d The expiry time relative to now. 365   @param d The expiry time relative to now.
366   366  
367   @return The number of pending operations that were cancelled. 367   @return The number of pending operations that were cancelled.
368   */ 368   */
HITCBC 369   9446 std::size_t expires_after(duration d) 369   8451 std::size_t expires_after(duration d)
370   { 370   {
HITCBC 371   9446 auto& impl = get(); 371   8451 auto& impl = get();
HITCBC 372   9446 if (d <= duration::zero()) 372   8451 if (d <= duration::zero())
HITCBC 373   664 impl.expiry_ = (time_point::min)(); 373   664 impl.expiry_ = (time_point::min)();
374   else 374   else
375   { 375   {
376   // Saturate rather than overflow: a clamped near-max duration 376   // Saturate rather than overflow: a clamped near-max duration
377   // (e.g. delay(hours::max())) would wrap now() + d past the 377   // (e.g. delay(hours::max())) would wrap now() + d past the
378   // clock's range and appear already elapsed. 378   // clock's range and appear already elapsed.
HITCBC 379   8782 auto const now = clock_type::now(); 379   7787 auto const now = clock_type::now();
HITCBC 380   8782 impl.expiry_ = ((time_point::max)() - now < d) 380   7787 impl.expiry_ = ((time_point::max)() - now < d)
HITCBC 381   17560 ? (time_point::max)() 381   15570 ? (time_point::max)()
HITCBC 382   8778 : now + d; 382   7783 : now + d;
383   } 383   }
HITCBC 384   9446 if (impl.heap_index_.load(std::memory_order_relaxed) == 384   8451 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 385   18892 implementation::npos && 385   16902 implementation::npos &&
HITCBC 386   9446 !impl.might_have_pending_waits_.load(std::memory_order_relaxed)) 386   8451 !impl.might_have_pending_waits_.load(std::memory_order_relaxed))
HITCBC 387   9446 return 0; 387   8451 return 0;
MISUBC 388   return do_update_expiry(); 388   return do_update_expiry();
389   } 389   }
390   390  
391   /** Set the timer's expiry time relative to now. 391   /** Set the timer's expiry time relative to now.
392   392  
393   This is a convenience overload that accepts any duration type 393   This is a convenience overload that accepts any duration type
394   and converts it to the timer's native duration type. Any 394   and converts it to the timer's native duration type. Any
395   pending asynchronous wait operations will be cancelled. 395   pending asynchronous wait operations will be cancelled.
396   396  
397   @param d The expiry time relative to now. 397   @param d The expiry time relative to now.
398   398  
399   @return The number of pending operations that were cancelled. 399   @return The number of pending operations that were cancelled.
400   */ 400   */
401   template<class Rep, class Period> 401   template<class Rep, class Period>
402   std::size_t expires_after(std::chrono::duration<Rep, Period> d) 402   std::size_t expires_after(std::chrono::duration<Rep, Period> d)
403   { 403   {
404   return expires_after(std::chrono::duration_cast<duration>(d)); 404   return expires_after(std::chrono::duration_cast<duration>(d));
405   } 405   }
406   406  
407   /** Wait for the timer to expire. 407   /** Wait for the timer to expire.
408   408  
409   Multiple coroutines may wait on the same timer concurrently. 409   Multiple coroutines may wait on the same timer concurrently.
410   When the timer expires, all waiters complete with success. 410   When the timer expires, all waiters complete with success.
411   411  
412   The operation supports cancellation via `std::stop_token` through 412   The operation supports cancellation via `std::stop_token` through
413   the affine awaitable protocol. If the associated stop token is 413   the affine awaitable protocol. If the associated stop token is
414   triggered, only that waiter completes with an error that 414   triggered, only that waiter completes with an error that
415   compares equal to `capy::cond::canceled`; other waiters are 415   compares equal to `capy::cond::canceled`; other waiters are
416   unaffected. 416   unaffected.
417   417  
418   This timer must outlive the returned awaitable. 418   This timer must outlive the returned awaitable.
419   419  
420   @return An awaitable that completes with `io_result<>`. 420   @return An awaitable that completes with `io_result<>`.
421   */ 421   */
HITCBC 422   9452 auto wait() 422   8457 auto wait()
423   { 423   {
HITCBC 424   9452 return wait_awaitable(*this); 424   8457 return wait_awaitable(*this);
425   } 425   }
426   426  
427   protected: 427   protected:
428   explicit timer(handle h) noexcept : io_object(std::move(h)) {} 428   explicit timer(handle h) noexcept : io_object(std::move(h)) {}
429   429  
430   private: 430   private:
431   // Defined in src/corosio/src/timer.cpp, which includes both this 431   // Defined in src/corosio/src/timer.cpp, which includes both this
432   // header and timer_service.hpp, so the timer_service_* free 432   // header and timer_service.hpp, so the timer_service_* free
433   // functions are visible there. 433   // functions are visible there.
434   std::size_t do_cancel(); 434   std::size_t do_cancel();
435   std::size_t do_cancel_one(); 435   std::size_t do_cancel_one();
436   std::size_t do_update_expiry(); 436   std::size_t do_update_expiry();
437   437  
438   /// Return the underlying implementation. 438   /// Return the underlying implementation.
HITCBC 439   18904 implementation& get() const noexcept 439   16914 implementation& get() const noexcept
440   { 440   {
HITCBC 441   18904 return *static_cast<implementation*>(h_.get()); 441   16914 return *static_cast<implementation*>(h_.get());
442   } 442   }
443   }; 443   };
444   444  
445   } // namespace boost::corosio::detail 445   } // namespace boost::corosio::detail
446   446  
447   #endif 447   #endif