97.05% Lines (230/237) 100.00% Functions (27/27)
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_SERVICE_HPP 11   #ifndef BOOST_COROSIO_DETAIL_TIMER_SERVICE_HPP
12   #define BOOST_COROSIO_DETAIL_TIMER_SERVICE_HPP 12   #define BOOST_COROSIO_DETAIL_TIMER_SERVICE_HPP
13   13  
14   #include <boost/corosio/detail/timer.hpp> 14   #include <boost/corosio/detail/timer.hpp>
15   #include <boost/corosio/detail/scheduler.hpp> 15   #include <boost/corosio/detail/scheduler.hpp>
16   #include <boost/corosio/detail/scheduler_op.hpp> 16   #include <boost/corosio/detail/scheduler_op.hpp>
17   #include <boost/corosio/detail/intrusive.hpp> 17   #include <boost/corosio/detail/intrusive.hpp>
18   #include <boost/corosio/detail/thread_local_ptr.hpp> 18   #include <boost/corosio/detail/thread_local_ptr.hpp>
19   #include <boost/capy/error.hpp> 19   #include <boost/capy/error.hpp>
20   #include <boost/capy/ex/execution_context.hpp> 20   #include <boost/capy/ex/execution_context.hpp>
21   #include <boost/capy/ex/executor_ref.hpp> 21   #include <boost/capy/ex/executor_ref.hpp>
22   #include <system_error> 22   #include <system_error>
23   23  
24   #include <atomic> 24   #include <atomic>
25   #include <chrono> 25   #include <chrono>
26   #include <coroutine> 26   #include <coroutine>
27   #include <cstddef> 27   #include <cstddef>
28   #include <limits> 28   #include <limits>
29   #include <mutex> 29   #include <mutex>
30   #include <stop_token> 30   #include <stop_token>
31   #include <utility> 31   #include <utility>
32   #include <vector> 32   #include <vector>
33   33  
34   namespace boost::corosio::detail { 34   namespace boost::corosio::detail {
35   35  
36   struct scheduler; 36   struct scheduler;
37   37  
38   /* 38   /*
39   Timer Service 39   Timer Service
40   ============= 40   =============
41   41  
42   Data Structures 42   Data Structures
43   --------------- 43   ---------------
44   waiter_node (defined in timer.hpp) holds per-waiter state: 44   waiter_node (defined in timer.hpp) holds per-waiter state:
45   coroutine handle, executor, error output, embedded 45   coroutine handle, executor, error output, embedded
46   completion_op. Each concurrent co_await t.wait() embeds one 46   completion_op. Each concurrent co_await t.wait() embeds one
47   waiter_node in the awaitable on the suspended coroutine's 47   waiter_node in the awaitable on the suspended coroutine's
48   frame — waits perform no allocation. 48   frame — waits perform no allocation.
49   49  
50   timer::implementation holds per-timer state: expiry, heap 50   timer::implementation holds per-timer state: expiry, heap
51   index, and the single published waiter. Each timer holds 51   index, and the single published waiter. Each timer holds
52   at most one waiter; process_expired's local cross-timer drain 52   at most one waiter; process_expired's local cross-timer drain
53   list still threads waiters through their intrusive hooks when 53   list still threads waiters through their intrusive hooks when
54   collecting several timers' waiters past the lock. 54   collecting several timers' waiters past the lock.
55   55  
56   timer_service owns a min-heap of active timers and a free list 56   timer_service owns a min-heap of active timers and a free list
57   of recycled impls. The heap is ordered by expiry time; the 57   of recycled impls. The heap is ordered by expiry time; the
58   scheduler queries nearest_expiry() to set the epoll/timerfd 58   scheduler queries nearest_expiry() to set the epoll/timerfd
59   timeout. 59   timeout.
60   60  
61   Optimization Strategy 61   Optimization Strategy
62   --------------------- 62   ---------------------
63   1. Deferred heap insertion — expires_after() stores the expiry 63   1. Deferred heap insertion — expires_after() stores the expiry
64   but does not insert into the heap. Insertion happens in wait(). 64   but does not insert into the heap. Insertion happens in wait().
65   2. Thread-local impl cache — single-slot per-thread cache. 65   2. Thread-local impl cache — single-slot per-thread cache.
66   3. Frame-resident waiter_node with embedded completion_op — 66   3. Frame-resident waiter_node with embedded completion_op —
67   eliminates heap allocation per wait/fire/cancel. 67   eliminates heap allocation per wait/fire/cancel.
68   4. Cached nearest expiry — atomic avoids mutex in nearest_expiry(). 68   4. Cached nearest expiry — atomic avoids mutex in nearest_expiry().
69   5. might_have_pending_waits_ flag — skips lock when no wait issued. 69   5. might_have_pending_waits_ flag — skips lock when no wait issued.
70   70  
71   Concurrency 71   Concurrency
72   ----------- 72   -----------
73   stop_token callbacks can fire from any thread. The impl_ 73   stop_token callbacks can fire from any thread. The impl_
74   pointer on waiter_node is used as a "still in list" marker. 74   pointer on waiter_node is used as a "still in list" marker.
75   A waiter_node's storage is the suspended coroutine's frame: 75   A waiter_node's storage is the suspended coroutine's frame:
76   every completion path must finish touching the node before 76   every completion path must finish touching the node before
77   posting the continuation or destroying the handle. 77   posting the continuation or destroying the handle.
78   */ 78   */
79   79  
80   inline void timer_service_invalidate_cache() noexcept; 80   inline void timer_service_invalidate_cache() noexcept;
81   81  
82   // timer_service class body — member function definitions are 82   // timer_service class body — member function definitions are
83   // out-of-class (after implementation and waiter_node are complete) 83   // out-of-class (after implementation and waiter_node are complete)
84   class BOOST_COROSIO_DECL timer_service final 84   class BOOST_COROSIO_DECL timer_service final
85   : public capy::execution_context::service 85   : public capy::execution_context::service
86   , public io_object::io_service 86   , public io_object::io_service
87   { 87   {
88   public: 88   public:
89   using clock_type = std::chrono::steady_clock; 89   using clock_type = std::chrono::steady_clock;
90   using time_point = clock_type::time_point; 90   using time_point = clock_type::time_point;
91   91  
92   /// Type-erased callback for earliest-expiry-changed notifications. 92   /// Type-erased callback for earliest-expiry-changed notifications.
93   class callback 93   class callback
94   { 94   {
95   void* ctx_ = nullptr; 95   void* ctx_ = nullptr;
96   void (*fn_)(void*) = nullptr; 96   void (*fn_)(void*) = nullptr;
97   97  
98   public: 98   public:
99   /// Construct an empty callback. 99   /// Construct an empty callback.
HITCBC 100   2243 callback() = default; 100   2243 callback() = default;
101   101  
102   /// Construct a callback with the given context and function. 102   /// Construct a callback with the given context and function.
HITCBC 103   2243 callback(void* ctx, void (*fn)(void*)) noexcept : ctx_(ctx), fn_(fn) {} 103   2243 callback(void* ctx, void (*fn)(void*)) noexcept : ctx_(ctx), fn_(fn) {}
104   104  
105   /// Return true if the callback is non-empty. 105   /// Return true if the callback is non-empty.
106   explicit operator bool() const noexcept 106   explicit operator bool() const noexcept
107   { 107   {
108   return fn_ != nullptr; 108   return fn_ != nullptr;
109   } 109   }
110   110  
111   /// Invoke the callback. 111   /// Invoke the callback.
HITCBC 112   7130 void operator()() const 112   7057 void operator()() const
113   { 113   {
HITCBC 114   7130 if (fn_) 114   7057 if (fn_)
HITCBC 115   7130 fn_(ctx_); 115   7057 fn_(ctx_);
HITCBC 116   7130 } 116   7057 }
117   }; 117   };
118   118  
119   private: 119   private:
120   struct heap_entry 120   struct heap_entry
121   { 121   {
122   time_point time_; 122   time_point time_;
123   timer::implementation* timer_; 123   timer::implementation* timer_;
124   }; 124   };
125   125  
126   scheduler* sched_ = nullptr; 126   scheduler* sched_ = nullptr;
127   BOOST_COROSIO_MSVC_WARNING_PUSH 127   BOOST_COROSIO_MSVC_WARNING_PUSH
128   BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) // std:: members, dll-interface 128   BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) // std:: members, dll-interface
129   mutable std::mutex mutex_; 129   mutable std::mutex mutex_;
130   std::vector<heap_entry> heap_; 130   std::vector<heap_entry> heap_;
131   timer::implementation* free_list_ = nullptr; 131   timer::implementation* free_list_ = nullptr;
132   callback on_earliest_changed_; 132   callback on_earliest_changed_;
133   bool shutting_down_ = false; 133   bool shutting_down_ = false;
134   // Avoids mutex in nearest_expiry() and empty() 134   // Avoids mutex in nearest_expiry() and empty()
135   mutable std::atomic<std::int64_t> cached_nearest_ns_{ 135   mutable std::atomic<std::int64_t> cached_nearest_ns_{
136   (std::numeric_limits<std::int64_t>::max)()}; 136   (std::numeric_limits<std::int64_t>::max)()};
137   BOOST_COROSIO_MSVC_WARNING_POP 137   BOOST_COROSIO_MSVC_WARNING_POP
138   138  
139   public: 139   public:
140   /// Construct the timer service bound to a scheduler. 140   /// Construct the timer service bound to a scheduler.
HITCBC 141   2243 inline timer_service(capy::execution_context&, scheduler& sched) 141   2243 inline timer_service(capy::execution_context&, scheduler& sched)
HITCBC 142   2243 : sched_(&sched) 142   2243 : sched_(&sched)
143   { 143   {
HITCBC 144   2243 } 144   2243 }
145   145  
146   /// Return the associated scheduler. 146   /// Return the associated scheduler.
HITCBC 147   28439 inline scheduler& get_scheduler() noexcept 147   28225 inline scheduler& get_scheduler() noexcept
148   { 148   {
HITCBC 149   28439 return *sched_; 149   28225 return *sched_;
150   } 150   }
151   151  
152   /// Destroy the timer service. 152   /// Destroy the timer service.
HITCBC 153   4486 ~timer_service() override = default; 153   4486 ~timer_service() override = default;
154   154  
155   timer_service(timer_service const&) = delete; 155   timer_service(timer_service const&) = delete;
156   timer_service& operator=(timer_service const&) = delete; 156   timer_service& operator=(timer_service const&) = delete;
157   157  
158   /// Register a callback invoked when the earliest expiry changes. 158   /// Register a callback invoked when the earliest expiry changes.
HITCBC 159   2243 inline void set_on_earliest_changed(callback cb) 159   2243 inline void set_on_earliest_changed(callback cb)
160   { 160   {
HITCBC 161   2243 on_earliest_changed_ = cb; 161   2243 on_earliest_changed_ = cb;
HITCBC 162   2243 } 162   2243 }
163   163  
164   /// Return true if no timers are in the heap. 164   /// Return true if no timers are in the heap.
165   inline bool empty() const noexcept 165   inline bool empty() const noexcept
166   { 166   {
167   return cached_nearest_ns_.load(std::memory_order_acquire) == 167   return cached_nearest_ns_.load(std::memory_order_acquire) ==
168   (std::numeric_limits<std::int64_t>::max)(); 168   (std::numeric_limits<std::int64_t>::max)();
169   } 169   }
170   170  
171   /// Return the nearest timer expiry without acquiring the mutex. 171   /// Return the nearest timer expiry without acquiring the mutex.
HITCBC 172   283819 inline time_point nearest_expiry() const noexcept 172   288550 inline time_point nearest_expiry() const noexcept
173   { 173   {
HITCBC 174   283819 auto ns = cached_nearest_ns_.load(std::memory_order_acquire); 174   288550 auto ns = cached_nearest_ns_.load(std::memory_order_acquire);
HITCBC 175   283819 return time_point(time_point::duration(ns)); 175   288550 return time_point(time_point::duration(ns));
176   } 176   }
177   177  
178   /// Cancel all pending timers and free cached resources. 178   /// Cancel all pending timers and free cached resources.
179   inline void shutdown() override; 179   inline void shutdown() override;
180   180  
181   /// Construct a new timer implementation. 181   /// Construct a new timer implementation.
182   inline io_object::implementation* construct() override; 182   inline io_object::implementation* construct() override;
183   183  
184   /// Destroy a timer implementation, cancelling pending waiters. 184   /// Destroy a timer implementation, cancelling pending waiters.
185   inline void destroy(io_object::implementation* p) override; 185   inline void destroy(io_object::implementation* p) override;
186   186  
187   /// Cancel and recycle a timer implementation. 187   /// Cancel and recycle a timer implementation.
188   inline void destroy_impl(timer::implementation& impl); 188   inline void destroy_impl(timer::implementation& impl);
189   189  
190   /// Publish the timer's waiter and insert the timer into the heap. 190   /// Publish the timer's waiter and insert the timer into the heap.
191   inline void insert_waiter(timer::implementation& impl, waiter_node* w); 191   inline void insert_waiter(timer::implementation& impl, waiter_node* w);
192   192  
193   /// Cancel the timer's published waiter, if any. 193   /// Cancel the timer's published waiter, if any.
194   inline void cancel_timer(timer::implementation& impl); 194   inline void cancel_timer(timer::implementation& impl);
195   195  
196   /// Cancel one specific waiter ( stop_token callback path ). 196   /// Cancel one specific waiter ( stop_token callback path ).
197   inline void cancel_waiter(waiter_node* w); 197   inline void cancel_waiter(waiter_node* w);
198   198  
199   /// Complete all waiters whose timers have expired. 199   /// Complete all waiters whose timers have expired.
200   inline std::size_t process_expired(); 200   inline std::size_t process_expired();
201   201  
202   private: 202   private:
HITCBC 203   324995 inline void refresh_cached_nearest() noexcept 203   330166 inline void refresh_cached_nearest() noexcept
204   { 204   {
HITCBC 205   324995 auto ns = heap_.empty() ? (std::numeric_limits<std::int64_t>::max)() 205   330166 auto ns = heap_.empty() ? (std::numeric_limits<std::int64_t>::max)()
HITCBC 206   319078 : heap_[0].time_.time_since_epoch().count(); 206   324324 : heap_[0].time_.time_since_epoch().count();
HITCBC 207   324995 cached_nearest_ns_.store(ns, std::memory_order_release); 207   330166 cached_nearest_ns_.store(ns, std::memory_order_release);
HITCBC 208   324995 } 208   330166 }
209   209  
210   inline void remove_timer_impl(timer::implementation& impl); 210   inline void remove_timer_impl(timer::implementation& impl);
211   inline void up_heap(std::size_t index); 211   inline void up_heap(std::size_t index);
212   inline void down_heap(std::size_t index); 212   inline void down_heap(std::size_t index);
213   inline void swap_heap(std::size_t i1, std::size_t i2); 213   inline void swap_heap(std::size_t i1, std::size_t i2);
214   }; 214   };
215   215  
216   // Thread-local cache avoids hot-path mutex acquisitions: 216   // Thread-local cache avoids hot-path mutex acquisitions:
217   // single-slot impl cache, validated by comparing svc_. Cleared by 217   // single-slot impl cache, validated by comparing svc_. Cleared by
218   // timer_service_invalidate_cache() during shutdown. 218   // timer_service_invalidate_cache() during shutdown.
219   219  
220   inline thread_local_ptr<timer::implementation> tl_cached_impl; 220   inline thread_local_ptr<timer::implementation> tl_cached_impl;
221   221  
222   // The POD TLS slot above never runs destructors, so a short-lived 222   // The POD TLS slot above never runs destructors, so a short-lived
223   // run() thread would leak its cached impl. Each push arms this 223   // run() thread would leak its cached impl. Each push arms this
224   // owner, whose destructor frees the slot at thread exit. A cached 224   // owner, whose destructor frees the slot at thread exit. A cached
225   // entry is a quiescent heap object (nothing in the heap or free 225   // entry is a quiescent heap object (nothing in the heap or free
226   // list) and deletion touches no service state, so it is safe after 226   // list) and deletion touches no service state, so it is safe after
227   // the owning service is gone (the stale-entry path in 227   // the owning service is gone (the stale-entry path in
228   // try_pop_tl_cache deletes the same way). 228   // try_pop_tl_cache deletes the same way).
229   struct tl_cache_owner 229   struct tl_cache_owner
230   { 230   {
HITCBC 231   42 ~tl_cache_owner() 231   42 ~tl_cache_owner()
232   { 232   {
HITCBC 233   42 delete tl_cached_impl.get(); 233   42 delete tl_cached_impl.get();
HITCBC 234   42 tl_cached_impl.set(nullptr); 234   42 tl_cached_impl.set(nullptr);
HITCBC 235   42 } 235   42 }
236   }; 236   };
237   237  
238   inline void 238   inline void
HITCBC 239   13908 arm_tl_cache_cleanup() noexcept 239   13908 arm_tl_cache_cleanup() noexcept
240   { 240   {
HITCBC 241   13908 [[maybe_unused]] thread_local tl_cache_owner owner; 241   13908 [[maybe_unused]] thread_local tl_cache_owner owner;
HITCBC 242   13908 } 242   13908 }
243   243  
244   inline timer::implementation* 244   inline timer::implementation*
HITCBC 245   15085 try_pop_tl_cache(timer_service* svc) noexcept 245   14981 try_pop_tl_cache(timer_service* svc) noexcept
246   { 246   {
HITCBC 247   15085 auto* impl = tl_cached_impl.get(); 247   14981 auto* impl = tl_cached_impl.get();
HITCBC 248   15085 if (impl) 248   14981 if (impl)
249   { 249   {
HITCBC 250   13495 tl_cached_impl.set(nullptr); 250   13495 tl_cached_impl.set(nullptr);
HITCBC 251   13495 if (impl->svc_ == svc) 251   13495 if (impl->svc_ == svc)
HITCBC 252   13495 return impl; 252   13495 return impl;
253   // Stale impl from a destroyed service 253   // Stale impl from a destroyed service
MISUBC 254   ✗ delete impl; 254   ✗ delete impl;
255   } 255   }
HITCBC 256   1590 return nullptr; 256   1486 return nullptr;
257   } 257   }
258   258  
259   inline bool 259   inline bool
HITCBC 260   15056 try_push_tl_cache(timer::implementation* impl) noexcept 260   14952 try_push_tl_cache(timer::implementation* impl) noexcept
261   { 261   {
HITCBC 262   15056 if (!tl_cached_impl.get()) 262   14952 if (!tl_cached_impl.get())
263   { 263   {
HITCBC 264   13908 arm_tl_cache_cleanup(); 264   13908 arm_tl_cache_cleanup();
HITCBC 265   13908 tl_cached_impl.set(impl); 265   13908 tl_cached_impl.set(impl);
HITCBC 266   13908 return true; 266   13908 return true;
267   } 267   }
HITCBC 268   1148 return false; 268   1044 return false;
269   } 269   }
270   270  
271   inline void 271   inline void
HITCBC 272   2243 timer_service_invalidate_cache() noexcept 272   2243 timer_service_invalidate_cache() noexcept
273   { 273   {
HITCBC 274   2243 delete tl_cached_impl.get(); 274   2243 delete tl_cached_impl.get();
HITCBC 275   2243 tl_cached_impl.set(nullptr); 275   2243 tl_cached_impl.set(nullptr);
HITCBC 276   2243 } 276   2243 }
277   277  
278   // timer_service out-of-class member function definitions 278   // timer_service out-of-class member function definitions
279   279  
280   inline void 280   inline void
HITCBC 281   2243 timer_service::shutdown() 281   2243 timer_service::shutdown()
282   { 282   {
HITCBC 283   2243 timer_service_invalidate_cache(); 283   2243 timer_service_invalidate_cache();
HITCBC 284   2243 shutting_down_ = true; 284   2243 shutting_down_ = true;
285   285  
286   // Snapshot impls and detach them from the heap so that 286   // Snapshot impls and detach them from the heap so that
287   // coroutine-owned timer destructors (triggered by h.destroy() 287   // coroutine-owned timer destructors (triggered by h.destroy()
288   // below) cannot re-enter remove_timer_impl() and mutate the 288   // below) cannot re-enter remove_timer_impl() and mutate the
289   // vector during iteration. 289   // vector during iteration.
HITCBC 290   2243 std::vector<timer::implementation*> impls; 290   2243 std::vector<timer::implementation*> impls;
HITCBC 291   2243 impls.reserve(heap_.size()); 291   2243 impls.reserve(heap_.size());
HITCBC 292   2272 for (auto& entry : heap_) 292   2272 for (auto& entry : heap_)
293   { 293   {
HITCBC 294   29 entry.timer_->heap_index_.store( 294   29 entry.timer_->heap_index_.store(
295   (std::numeric_limits<std::size_t>::max)(), 295   (std::numeric_limits<std::size_t>::max)(),
296   std::memory_order_relaxed); 296   std::memory_order_relaxed);
HITCBC 297   29 impls.push_back(entry.timer_); 297   29 impls.push_back(entry.timer_);
298   } 298   }
HITCBC 299   2243 heap_.clear(); 299   2243 heap_.clear();
HITCBC 300   2243 cached_nearest_ns_.store( 300   2243 cached_nearest_ns_.store(
301   (std::numeric_limits<std::int64_t>::max)(), std::memory_order_release); 301   (std::numeric_limits<std::int64_t>::max)(), std::memory_order_release);
302   302  
303   // Cancel waiting timers. Each waiter called work_started() 303   // Cancel waiting timers. Each waiter called work_started()
304   // in implementation::wait(). On IOCP the scheduler shutdown 304   // in implementation::wait(). On IOCP the scheduler shutdown
305   // loop exits when outstanding_work_ reaches zero, so we must 305   // loop exits when outstanding_work_ reaches zero, so we must
306   // call work_finished() here to balance it. On other backends 306   // call work_finished() here to balance it. On other backends
307   // this is harmless. 307   // this is harmless.
HITCBC 308   2272 for (auto* impl : impls) 308   2272 for (auto* impl : impls)
309   { 309   {
HITCBC 310   29 if (auto* w = std::exchange(impl->waiter_, nullptr)) 310   29 if (auto* w = std::exchange(impl->waiter_, nullptr))
311   { 311   {
HITCBC 312   29 w->reset_stop_cb(); 312   29 w->reset_stop_cb();
HITCBC 313   29 auto h = std::exchange(w->h_, {}); 313   29 auto h = std::exchange(w->h_, {});
HITCBC 314   29 sched_->work_finished(); 314   29 sched_->work_finished();
315   // Destroying the frame also ends the node's storage 315   // Destroying the frame also ends the node's storage
HITCBC 316   29 if (h) 316   29 if (h)
HITCBC 317   29 h.destroy(); 317   29 h.destroy();
318   } 318   }
HITCBC 319   29 delete impl; 319   29 delete impl;
320   } 320   }
321   321  
322   // Delete free-listed impls 322   // Delete free-listed impls
HITCBC 323   3389 while (free_list_) 323   3285 while (free_list_)
324   { 324   {
HITCBC 325   1146 auto* next = free_list_->next_free_; 325   1042 auto* next = free_list_->next_free_;
HITCBC 326   1146 delete free_list_; 326   1042 delete free_list_;
HITCBC 327   1146 free_list_ = next; 327   1042 free_list_ = next;
328   } 328   }
HITCBC 329   2243 } 329   2243 }
330   330  
331   inline io_object::implementation* 331   inline io_object::implementation*
HITCBC 332   15085 timer_service::construct() 332   14981 timer_service::construct()
333   { 333   {
HITCBC 334   15085 timer::implementation* impl = try_pop_tl_cache(this); 334   14981 timer::implementation* impl = try_pop_tl_cache(this);
HITCBC 335   15085 if (impl) 335   14981 if (impl)
336   { 336   {
HITCBC 337   13495 impl->svc_ = this; 337   13495 impl->svc_ = this;
338   // Reset expiry_ too: a recycled impl must behave like a fresh 338   // Reset expiry_ too: a recycled impl must behave like a fresh
339   // one, whose default expiry reads as already elapsed 339   // one, whose default expiry reads as already elapsed
HITCBC 340   13495 impl->expiry_ = {}; 340   13495 impl->expiry_ = {};
HITCBC 341   13495 impl->heap_index_.store( 341   13495 impl->heap_index_.store(
342   (std::numeric_limits<std::size_t>::max)(), 342   (std::numeric_limits<std::size_t>::max)(),
343   std::memory_order_relaxed); 343   std::memory_order_relaxed);
HITCBC 344   13495 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed); 344   13495 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed);
HITCBC 345   13495 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr); 345   13495 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr);
HITCBC 346   13495 return impl; 346   13495 return impl;
347   } 347   }
348   348  
HITCBC 349   1590 std::lock_guard lock(mutex_); 349   1486 std::lock_guard lock(mutex_);
HITCBC 350   1590 if (free_list_) 350   1486 if (free_list_)
351   { 351   {
HITCBC 352   2 impl = free_list_; 352   2 impl = free_list_;
HITCBC 353   2 free_list_ = impl->next_free_; 353   2 free_list_ = impl->next_free_;
HITCBC 354   2 impl->next_free_ = nullptr; 354   2 impl->next_free_ = nullptr;
HITCBC 355   2 impl->svc_ = this; 355   2 impl->svc_ = this;
HITCBC 356   2 impl->expiry_ = {}; 356   2 impl->expiry_ = {};
HITCBC 357   2 impl->heap_index_.store( 357   2 impl->heap_index_.store(
358   (std::numeric_limits<std::size_t>::max)(), 358   (std::numeric_limits<std::size_t>::max)(),
359   std::memory_order_relaxed); 359   std::memory_order_relaxed);
HITCBC 360   2 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed); 360   2 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed);
HITCBC 361   2 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr); 361   2 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr);
362   } 362   }
363   else 363   else
364   { 364   {
HITCBC 365   1588 impl = new timer::implementation(*this); 365   1484 impl = new timer::implementation(*this);
366   } 366   }
HITCBC 367   1590 return impl; 367   1486 return impl;
HITCBC 368   1590 } 368   1486 }
369   369  
370   inline void 370   inline void
HITCBC 371   15085 timer_service::destroy(io_object::implementation* p) 371   14981 timer_service::destroy(io_object::implementation* p)
372   { 372   {
373   // During shutdown the drain loop owns every impl and deletes 373   // During shutdown the drain loop owns every impl and deletes
374   // them directly. A frame destroyed by that loop can unwind a 374   // them directly. A frame destroyed by that loop can unwind a
375   // handle whose impl was freed in an earlier iteration (a 375   // handle whose impl was freed in an earlier iteration (a
376   // timeout's parent frame owns the timeout timer while 376   // timeout's parent frame owns the timeout timer while
377   // suspended on the inner delay's timer), so bail out before 377   // suspended on the inner delay's timer), so bail out before
378   // even downcasting the pointer. 378   // even downcasting the pointer.
HITCBC 379   15085 if (shutting_down_) 379   14981 if (shutting_down_)
HITCBC 380   29 return; 380   29 return;
HITCBC 381   15056 destroy_impl(static_cast<timer::implementation&>(*p)); 381   14952 destroy_impl(static_cast<timer::implementation&>(*p));
382   } 382   }
383   383  
384   inline void 384   inline void
HITCBC 385   15056 timer_service::destroy_impl(timer::implementation& impl) 385   14952 timer_service::destroy_impl(timer::implementation& impl)
386   { 386   {
387   // During shutdown the impl is owned by the shutdown loop. 387   // During shutdown the impl is owned by the shutdown loop.
388   // Re-entering here (from a coroutine-owned timer destructor 388   // Re-entering here (from a coroutine-owned timer destructor
389   // triggered by h.destroy()) must not modify the heap or 389   // triggered by h.destroy()) must not modify the heap or
390   // recycle the impl — shutdown deletes it directly. 390   // recycle the impl — shutdown deletes it directly.
HITCBC 391   15056 if (shutting_down_) 391   14952 if (shutting_down_)
HITCBC 392   13908 return; 392   13908 return;
393   393  
HITCBC 394   15056 cancel_timer(impl); 394   14952 cancel_timer(impl);
395   395  
HITCBC 396   30112 if (impl.heap_index_.load(std::memory_order_relaxed) != 396   29904 if (impl.heap_index_.load(std::memory_order_relaxed) !=
HITCBC 397   15056 (std::numeric_limits<std::size_t>::max)()) 397   14952 (std::numeric_limits<std::size_t>::max)())
398   { 398   {
MISUBC 399   ✗ std::lock_guard lock(mutex_); 399   ✗ std::lock_guard lock(mutex_);
MISUBC 400   ✗ remove_timer_impl(impl); 400   ✗ remove_timer_impl(impl);
MISUBC 401   ✗ refresh_cached_nearest(); 401   ✗ refresh_cached_nearest();
MISUBC 402   ✗ } 402   ✗ }
403   403  
HITCBC 404   15056 if (try_push_tl_cache(&impl)) 404   14952 if (try_push_tl_cache(&impl))
HITCBC 405   13908 return; 405   13908 return;
406   406  
HITCBC 407   1148 std::lock_guard lock(mutex_); 407   1044 std::lock_guard lock(mutex_);
HITCBC 408   1148 impl.next_free_ = free_list_; 408   1044 impl.next_free_ = free_list_;
HITCBC 409   1148 free_list_ = &impl; 409   1044 free_list_ = &impl;
HITCBC 410   1148 } 410   1044 }
411   411  
412   inline void 412   inline void
HITCBC 413   18233 timer_service::insert_waiter(timer::implementation& impl, waiter_node* w) 413   18281 timer_service::insert_waiter(timer::implementation& impl, waiter_node* w)
414   { 414   {
HITCBC 415   18233 bool notify = false; 415   18281 bool notify = false;
HITCBC 416   18233 bool lost_cancel = false; 416   18281 bool lost_cancel = false;
417   { 417   {
HITCBC 418   18233 std::lock_guard lock(mutex_); 418   18281 std::lock_guard lock(mutex_);
419   // Grow before publishing anything, so the push_back below 419   // Grow before publishing anything, so the push_back below
420   // cannot throw: a failure here leaves the waiter untouched, 420   // cannot throw: a failure here leaves the waiter untouched,
421   // the strong guarantee rearm_wait's recovery relies on. 421   // the strong guarantee rearm_wait's recovery relies on.
HITCBC 422   18233 if (impl.heap_index_.load(std::memory_order_relaxed) == 422   18281 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 423   36466 (std::numeric_limits<std::size_t>::max)() && 423   36562 (std::numeric_limits<std::size_t>::max)() &&
HITCBC 424   18233 heap_.size() == heap_.capacity()) 424   18281 heap_.size() == heap_.capacity())
HITCBC 425   427 heap_.reserve(heap_.capacity() == 0 ? 16 : 2 * heap_.capacity()); 425   425 heap_.reserve(heap_.capacity() == 0 ? 16 : 2 * heap_.capacity());
426   // Publish: from here the waiter is visible to the fire path and 426   // Publish: from here the waiter is visible to the fire path and
427   // to its own stop callback (impl_ non-null enables cancel_waiter). 427   // to its own stop callback (impl_ non-null enables cancel_waiter).
HITCBC 428   18233 w->impl_ = &impl; 428   18281 w->impl_ = &impl;
HITCBC 429   36466 if (impl.heap_index_.load(std::memory_order_relaxed) == 429   36562 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 430   18233 (std::numeric_limits<std::size_t>::max)()) 430   18281 (std::numeric_limits<std::size_t>::max)())
431   { 431   {
HITCBC 432   18233 impl.heap_index_.store(heap_.size(), std::memory_order_relaxed); 432   18281 impl.heap_index_.store(heap_.size(), std::memory_order_relaxed);
HITCBC 433   18233 heap_.push_back({impl.expiry_, &impl}); 433   18281 heap_.push_back({impl.expiry_, &impl});
HITCBC 434   18233 up_heap(heap_.size() - 1); 434   18281 up_heap(heap_.size() - 1);
HITCBC 435   18233 notify = (impl.heap_index_.load(std::memory_order_relaxed) == 0); 435   18281 notify = (impl.heap_index_.load(std::memory_order_relaxed) == 0);
HITCBC 436   18233 refresh_cached_nearest(); 436   18281 refresh_cached_nearest();
437   } 437   }
HITCBC 438   18233 BOOST_COROSIO_ASSERT(impl.waiter_ == nullptr); 438   18281 BOOST_COROSIO_ASSERT(impl.waiter_ == nullptr);
HITCBC 439   18233 impl.waiter_ = w; 439   18281 impl.waiter_ = w;
440   440  
441   // Lost-cancel re-check: a stop requested after the canceller was 441   // Lost-cancel re-check: a stop requested after the canceller was
442   // armed in wait() but before this publication found impl_ null 442   // armed in wait() but before this publication found impl_ null
443   // and returned a no-op. Observe it now and undo the insertion. 443   // and returned a no-op. Observe it now and undo the insertion.
HITCBC 444   18233 if (w->token_->stop_requested()) 444   18281 if (w->token_->stop_requested())
445   { 445   {
HITCBC 446   2 w->impl_ = nullptr; 446   2 w->impl_ = nullptr;
HITCBC 447   2 impl.waiter_ = nullptr; 447   2 impl.waiter_ = nullptr;
HITCBC 448   2 remove_timer_impl(impl); 448   2 remove_timer_impl(impl);
HITCBC 449   2 impl.might_have_pending_waits_.store( 449   2 impl.might_have_pending_waits_.store(
450   false, std::memory_order_relaxed); 450   false, std::memory_order_relaxed);
HITCBC 451   2 refresh_cached_nearest(); 451   2 refresh_cached_nearest();
HITCBC 452   2 lost_cancel = true; 452   2 lost_cancel = true;
HITCBC 453   2 notify = false; // insertion undone; nearest unchanged 453   2 notify = false; // insertion undone; nearest unchanged
454   } 454   }
HITCBC 455   18233 } 455   18281 }
HITCBC 456   18233 if (notify) 456   18281 if (notify)
HITCBC 457   7130 on_earliest_changed_(); 457   7057 on_earliest_changed_();
HITCBC 458   18233 if (lost_cancel) 458   18281 if (lost_cancel)
459   { 459   {
HITCBC 460   2 w->ec_ = make_error_code(capy::error::canceled); 460   2 w->ec_ = make_error_code(capy::error::canceled);
HITCBC 461   2 sched_->post(&w->op_); 461   2 sched_->post(&w->op_);
462   } 462   }
HITCBC 463   18233 } 463   18281 }
464   464  
465   inline void 465   inline void
HITCBC 466   15056 timer_service::cancel_timer(timer::implementation& impl) 466   14952 timer_service::cancel_timer(timer::implementation& impl)
467   { 467   {
HITCBC 468   15056 if (!impl.might_have_pending_waits_.load(std::memory_order_relaxed)) 468   14952 if (!impl.might_have_pending_waits_.load(std::memory_order_relaxed))
HITCBC 469   15054 return; 469   14950 return;
470   470  
471   // No unlocked already-done fast-out here: it would need the 471   // No unlocked already-done fast-out here: it would need the
472   // non-atomic waiter_ (a race with concurrent drains), and an 472   // non-atomic waiter_ (a race with concurrent drains), and an
473   // index-only check is lifetime-unsafe because npos is stored 473   // index-only check is lifetime-unsafe because npos is stored
474   // before the drain finishes touching the impl. A stale-true 474   // before the drain finishes touching the impl. A stale-true
475   // flag is rare with the stateless API; the locked path below 475   // flag is rare with the stateless API; the locked path below
476   // re-validates. 476   // re-validates.
477   477  
HITCBC 478   2 waiter_node* canceled = nullptr; 478   2 waiter_node* canceled = nullptr;
479   479  
480   { 480   {
HITCBC 481   2 std::lock_guard lock(mutex_); 481   2 std::lock_guard lock(mutex_);
HITCBC 482   2 remove_timer_impl(impl); 482   2 remove_timer_impl(impl);
HITCBC 483   2 canceled = std::exchange(impl.waiter_, nullptr); 483   2 canceled = std::exchange(impl.waiter_, nullptr);
HITCBC 484   2 if (canceled) 484   2 if (canceled)
HITCBC 485   2 canceled->impl_ = nullptr; 485   2 canceled->impl_ = nullptr;
486   // Store false as the final touch of the impl under the lock so 486   // Store false as the final touch of the impl under the lock so
487   // a pre-lock false-flag check trusts it unqualified. 487   // a pre-lock false-flag check trusts it unqualified.
HITCBC 488   2 impl.might_have_pending_waits_.store(false, std::memory_order_relaxed); 488   2 impl.might_have_pending_waits_.store(false, std::memory_order_relaxed);
HITCBC 489   2 refresh_cached_nearest(); 489   2 refresh_cached_nearest();
HITCBC 490   2 } 490   2 }
491   491  
HITCBC 492   2 if (canceled) 492   2 if (canceled)
493   { 493   {
HITCBC 494   2 canceled->ec_ = make_error_code(capy::error::canceled); 494   2 canceled->ec_ = make_error_code(capy::error::canceled);
HITCBC 495   2 sched_->post(&canceled->op_); 495   2 sched_->post(&canceled->op_);
496   } 496   }
497   } 497   }
498   498  
499   inline void 499   inline void
HITCBC 500   1648 timer_service::cancel_waiter(waiter_node* w) 500   1595 timer_service::cancel_waiter(waiter_node* w)
501   { 501   {
502   { 502   {
HITCBC 503   1648 std::lock_guard lock(mutex_); 503   1595 std::lock_guard lock(mutex_);
504   // Already removed by another drain: cancel_timer, 504   // Already removed by another drain: cancel_timer,
505   // process_expired, or insert_waiter's lost-cancel recheck 505   // process_expired, or insert_waiter's lost-cancel recheck
HITCBC 506   1648 if (!w->impl_) 506   1595 if (!w->impl_)
HITCBC 507   2 return; 507   3 return;
HITCBC 508   1646 auto* impl = w->impl_; 508   1592 auto* impl = w->impl_;
HITCBC 509   1646 w->impl_ = nullptr; 509   1592 w->impl_ = nullptr;
HITCBC 510   1646 impl->waiter_ = nullptr; 510   1592 impl->waiter_ = nullptr;
HITCBC 511   1646 remove_timer_impl(*impl); 511   1592 remove_timer_impl(*impl);
HITCBC 512   1646 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed); 512   1592 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed);
HITCBC 513   1646 refresh_cached_nearest(); 513   1592 refresh_cached_nearest();
HITCBC 514   1648 } 514   1595 }
515   515  
HITCBC 516   1646 w->ec_ = make_error_code(capy::error::canceled); 516   1592 w->ec_ = make_error_code(capy::error::canceled);
HITCBC 517   1646 sched_->post(&w->op_); 517   1592 sched_->post(&w->op_);
518   } 518   }
519   519  
520   inline std::size_t 520   inline std::size_t
HITCBC 521   305112 timer_service::process_expired() 521   310289 timer_service::process_expired()
522   { 522   {
HITCBC 523   305112 intrusive_list<waiter_node> expired; 523   310289 intrusive_list<waiter_node> expired;
524   524  
525   { 525   {
HITCBC 526   305112 std::lock_guard lock(mutex_); 526   310289 std::lock_guard lock(mutex_);
HITCBC 527   305112 auto now = clock_type::now(); 527   310289 auto now = clock_type::now();
528   528  
HITCBC 529   321666 while (!heap_.empty() && heap_[0].time_ <= now) 529   326945 while (!heap_.empty() && heap_[0].time_ <= now)
530   { 530   {
HITCBC 531   16554 timer::implementation* t = heap_[0].timer_; 531   16656 timer::implementation* t = heap_[0].timer_;
HITCBC 532   16554 remove_timer_impl(*t); 532   16656 remove_timer_impl(*t);
HITCBC 533   16554 if (auto* w = std::exchange(t->waiter_, nullptr)) 533   16656 if (auto* w = std::exchange(t->waiter_, nullptr))
534   { 534   {
HITCBC 535   16554 w->impl_ = nullptr; 535   16656 w->impl_ = nullptr;
HITCBC 536   16554 w->ec_ = {}; 536   16656 w->ec_ = {};
HITCBC 537   16554 expired.push_back(w); 537   16656 expired.push_back(w);
538   } 538   }
HITCBC 539   16554 t->might_have_pending_waits_.store( 539   16656 t->might_have_pending_waits_.store(
540   false, std::memory_order_relaxed); 540   false, std::memory_order_relaxed);
541   } 541   }
542   542  
HITCBC 543   305112 refresh_cached_nearest(); 543   310289 refresh_cached_nearest();
HITCBC 544   305112 } 544   310289 }
545   545  
HITCBC 546   305112 std::size_t count = 0; 546   310289 std::size_t count = 0;
HITCBC 547   321666 while (auto* w = expired.pop_front()) 547   326945 while (auto* w = expired.pop_front())
548   { 548   {
HITCBC 549   16554 sched_->post(&w->op_); 549   16656 sched_->post(&w->op_);
HITCBC 550   16554 ++count; 550   16656 ++count;
HITCBC 551   16554 } 551   16656 }
552   552  
HITCBC 553   305112 return count; 553   310289 return count;
554   } 554   }
555   555  
556   inline void 556   inline void
HITCBC 557   18204 timer_service::remove_timer_impl(timer::implementation& impl) 557   18252 timer_service::remove_timer_impl(timer::implementation& impl)
558   { 558   {
HITCBC 559   18204 std::size_t index = impl.heap_index_.load(std::memory_order_relaxed); 559   18252 std::size_t index = impl.heap_index_.load(std::memory_order_relaxed);
HITCBC 560   18204 if (index >= heap_.size()) 560   18252 if (index >= heap_.size())
MISUBC 561   ✗ return; // Not in heap 561   ✗ return; // Not in heap
562   562  
HITCBC 563   18204 if (index == heap_.size() - 1) 563   18252 if (index == heap_.size() - 1)
564   { 564   {
565   // Last element, just pop 565   // Last element, just pop
HITCBC 566   2495 impl.heap_index_.store( 566   2582 impl.heap_index_.store(
567   (std::numeric_limits<std::size_t>::max)(), 567   (std::numeric_limits<std::size_t>::max)(),
568   std::memory_order_relaxed); 568   std::memory_order_relaxed);
HITCBC 569   2495 heap_.pop_back(); 569   2582 heap_.pop_back();
570   } 570   }
571   else 571   else
572   { 572   {
573   // Swap with last and reheapify 573   // Swap with last and reheapify
HITCBC 574   15709 swap_heap(index, heap_.size() - 1); 574   15670 swap_heap(index, heap_.size() - 1);
HITCBC 575   15709 impl.heap_index_.store( 575   15670 impl.heap_index_.store(
576   (std::numeric_limits<std::size_t>::max)(), 576   (std::numeric_limits<std::size_t>::max)(),
577   std::memory_order_relaxed); 577   std::memory_order_relaxed);
HITCBC 578   15709 heap_.pop_back(); 578   15670 heap_.pop_back();
579   579  
HITCBC 580   15709 if (index > 0 && heap_[index].time_ < heap_[(index - 1) / 2].time_) 580   15670 if (index > 0 && heap_[index].time_ < heap_[(index - 1) / 2].time_)
MISUBC 581   ✗ up_heap(index); 581   ✗ up_heap(index);
582   else 582   else
HITCBC 583   15709 down_heap(index); 583   15670 down_heap(index);
584   } 584   }
585   } 585   }
586   586  
587   inline void 587   inline void
HITCBC 588   18233 timer_service::up_heap(std::size_t index) 588   18281 timer_service::up_heap(std::size_t index)
589   { 589   {
HITCBC 590   25307 while (index > 0) 590   25270 while (index > 0)
591   { 591   {
HITCBC 592   18175 std::size_t parent = (index - 1) / 2; 592   18211 std::size_t parent = (index - 1) / 2;
HITCBC 593   18175 if (!(heap_[index].time_ < heap_[parent].time_)) 593   18211 if (!(heap_[index].time_ < heap_[parent].time_))
HITCBC 594   11101 break; 594   11222 break;
HITCBC 595   7074 swap_heap(index, parent); 595   6989 swap_heap(index, parent);
HITCBC 596   7074 index = parent; 596   6989 index = parent;
597   } 597   }
HITCBC 598   18233 } 598   18281 }
599   599  
600   inline void 600   inline void
HITCBC 601   15709 timer_service::down_heap(std::size_t index) 601   15670 timer_service::down_heap(std::size_t index)
602   { 602   {
HITCBC 603   15709 std::size_t child = index * 2 + 1; 603   15670 std::size_t child = index * 2 + 1;
HITCBC 604   33317 while (child < heap_.size()) 604   33168 while (child < heap_.size())
605   { 605   {
HITCBC 606   19622 std::size_t min_child = (child + 1 == heap_.size() || 606   19517 std::size_t min_child = (child + 1 == heap_.size() ||
HITCBC 607   15803 heap_[child].time_ < heap_[child + 1].time_) 607   16009 heap_[child].time_ < heap_[child + 1].time_)
HITCBC 608   35425 ? child 608   35526 ? child
HITCBC 609   19622 : child + 1; 609   19517 : child + 1;
610   610  
HITCBC 611   19622 if (heap_[index].time_ < heap_[min_child].time_) 611   19517 if (heap_[index].time_ < heap_[min_child].time_)
HITCBC 612   2014 break; 612   2019 break;
613   613  
HITCBC 614   17608 swap_heap(index, min_child); 614   17498 swap_heap(index, min_child);
HITCBC 615   17608 index = min_child; 615   17498 index = min_child;
HITCBC 616   17608 child = index * 2 + 1; 616   17498 child = index * 2 + 1;
617   } 617   }
HITCBC 618   15709 } 618   15670 }
619   619  
620   inline void 620   inline void
HITCBC 621   40391 timer_service::swap_heap(std::size_t i1, std::size_t i2) 621   40157 timer_service::swap_heap(std::size_t i1, std::size_t i2)
622   { 622   {
HITCBC 623   40391 heap_entry tmp = heap_[i1]; 623   40157 heap_entry tmp = heap_[i1];
HITCBC 624   40391 heap_[i1] = heap_[i2]; 624   40157 heap_[i1] = heap_[i2];
HITCBC 625   40391 heap_[i2] = tmp; 625   40157 heap_[i2] = tmp;
HITCBC 626   40391 heap_[i1].timer_->heap_index_.store(i1, std::memory_order_relaxed); 626   40157 heap_[i1].timer_->heap_index_.store(i1, std::memory_order_relaxed);
HITCBC 627   40391 heap_[i2].timer_->heap_index_.store(i2, std::memory_order_relaxed); 627   40157 heap_[i2].timer_->heap_index_.store(i2, std::memory_order_relaxed);
HITCBC 628   40391 } 628   40157 }
629   629  
630   // waiter_node's completion_op and canceller members are defined in 630   // waiter_node's completion_op and canceller members are defined in
631   // timer.cpp alongside implementation::wait(), for the same reason 631   // timer.cpp alongside implementation::wait(), for the same reason
632   // wait() lives there (see below). 632   // wait() lives there (see below).
633   633  
634   // timer::implementation::wait() is defined in timer.cpp, not here. 634   // timer::implementation::wait() is defined in timer.cpp, not here.
635   // It must be a non-inline definition in a translation unit that is 635   // It must be a non-inline definition in a translation unit that is
636   // always pulled into the link whenever detail::timer is used (every 636   // always pulled into the link whenever detail::timer is used (every
637   // consumer needs timer's constructors from that same object file). 637   // consumer needs timer's constructors from that same object file).
638   // An inline definition in this header would only be emitted in 638   // An inline definition in this header would only be emitted in
639   // translation units that happen to also include this header, which 639   // translation units that happen to also include this header, which
640   // is not guaranteed for every caller of wait_awaitable::await_suspend 640   // is not guaranteed for every caller of wait_awaitable::await_suspend
641   // in timer.hpp (e.g. code that only reaches timer.hpp through 641   // in timer.hpp (e.g. code that only reaches timer.hpp through
642   // delay.hpp, without transitively including a scheduler header). 642   // delay.hpp, without transitively including a scheduler header).
643   643  
644   // Free functions 644   // Free functions
645   645  
646   inline timer_service& 646   inline timer_service&
HITCBC 647   2243 get_timer_service(capy::execution_context& ctx, scheduler& sched) 647   2243 get_timer_service(capy::execution_context& ctx, scheduler& sched)
648   { 648   {
HITCBC 649   2243 return ctx.make_service<timer_service>(sched); 649   2243 return ctx.make_service<timer_service>(sched);
650   } 650   }
651   651  
652   } // namespace boost::corosio::detail 652   } // namespace boost::corosio::detail
653   653  
654   #endif 654   #endif