97.07% Lines (232/239) 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   1434 callback() = default; 100   1450 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   1434 callback(void* ctx, void (*fn)(void*)) noexcept : ctx_(ctx), fn_(fn) {} 103   1450 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   10638 void operator()() const 112   10264 void operator()() const
113   { 113   {
HITCBC 114   10638 if (fn_) 114   10264 if (fn_)
HITCBC 115   10638 fn_(ctx_); 115   10264 fn_(ctx_);
HITCBC 116   10638 } 116   10264 }
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   1434 inline timer_service(capy::execution_context&, scheduler& sched) 141   1450 inline timer_service(capy::execution_context&, scheduler& sched)
HITCBC 142   1434 : sched_(&sched) 142   1450 : sched_(&sched)
143   { 143   {
HITCBC 144   1434 } 144   1450 }
145   145  
146   /// Return the associated scheduler. 146   /// Return the associated scheduler.
HITCBC 147   21548 inline scheduler& get_scheduler() noexcept 147   20786 inline scheduler& get_scheduler() noexcept
148   { 148   {
HITCBC 149   21548 return *sched_; 149   20786 return *sched_;
150   } 150   }
151   151  
152   /// Destroy the timer service. 152   /// Destroy the timer service.
HITCBC 153   2868 ~timer_service() override = default; 153   2900 ~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   1434 inline void set_on_earliest_changed(callback cb) 159   1450 inline void set_on_earliest_changed(callback cb)
160   { 160   {
HITCBC 161   1434 on_earliest_changed_ = cb; 161   1450 on_earliest_changed_ = cb;
HITCBC 162   1434 } 162   1450 }
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   370415 inline time_point nearest_expiry() const noexcept 172   363460 inline time_point nearest_expiry() const noexcept
173   { 173   {
HITCBC 174   370415 auto ns = cached_nearest_ns_.load(std::memory_order_acquire); 174   363460 auto ns = cached_nearest_ns_.load(std::memory_order_acquire);
HITCBC 175   370415 return time_point(time_point::duration(ns)); 175   363460 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   416985 inline void refresh_cached_nearest() noexcept 203   409697 inline void refresh_cached_nearest() noexcept
204   { 204   {
HITCBC 205   416985 auto ns = heap_.empty() ? (std::numeric_limits<std::int64_t>::max)() 205   409697 auto ns = heap_.empty() ? (std::numeric_limits<std::int64_t>::max)()
HITCBC 206   413722 : heap_[0].time_.time_since_epoch().count(); 206   406372 : heap_[0].time_.time_since_epoch().count();
HITCBC 207   416985 cached_nearest_ns_.store(ns, std::memory_order_release); 207   409697 cached_nearest_ns_.store(ns, std::memory_order_release);
HITCBC 208   416985 } 208   409697 }
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   37 ~tl_cache_owner() 231   37 ~tl_cache_owner()
232   { 232   {
HITCBC 233   37 delete tl_cached_impl.get(); 233   37 delete tl_cached_impl.get();
HITCBC 234   37 tl_cached_impl.set(nullptr); 234   37 tl_cached_impl.set(nullptr);
HITCBC 235   37 } 235   37 }
236   }; 236   };
237   237  
238   inline void 238   inline void
HITCBC 239   11547 arm_tl_cache_cleanup() noexcept 239   11133 arm_tl_cache_cleanup() noexcept
240   { 240   {
HITCBC 241   11547 thread_local tl_cache_owner owner; 241   11133 thread_local tl_cache_owner owner;
242   (void)owner; 242   (void)owner;
HITCBC 243   11547 } 243   11133 }
244   244  
245   inline timer::implementation* 245   inline timer::implementation*
HITCBC 246   11649 try_pop_tl_cache(timer_service* svc) noexcept 246   11269 try_pop_tl_cache(timer_service* svc) noexcept
247   { 247   {
HITCBC 248   11649 auto* impl = tl_cached_impl.get(); 248   11269 auto* impl = tl_cached_impl.get();
HITCBC 249   11649 if (impl) 249   11269 if (impl)
250   { 250   {
HITCBC 251   11288 tl_cached_impl.set(nullptr); 251   10872 tl_cached_impl.set(nullptr);
HITCBC 252   11288 if (impl->svc_ == svc) 252   10872 if (impl->svc_ == svc)
HITCBC 253   11288 return impl; 253   10872 return impl;
254   // Stale impl from a destroyed service 254   // Stale impl from a destroyed service
MISUBC 255   delete impl; 255   delete impl;
256   } 256   }
HITCBC 257   361 return nullptr; 257   397 return nullptr;
258   } 258   }
259   259  
260   inline bool 260   inline bool
HITCBC 261   11621 try_push_tl_cache(timer::implementation* impl) noexcept 261   11241 try_push_tl_cache(timer::implementation* impl) noexcept
262   { 262   {
HITCBC 263   11621 if (!tl_cached_impl.get()) 263   11241 if (!tl_cached_impl.get())
264   { 264   {
HITCBC 265   11547 arm_tl_cache_cleanup(); 265   11133 arm_tl_cache_cleanup();
HITCBC 266   11547 tl_cached_impl.set(impl); 266   11133 tl_cached_impl.set(impl);
HITCBC 267   11547 return true; 267   11133 return true;
268   } 268   }
HITCBC 269   74 return false; 269   108 return false;
270   } 270   }
271   271  
272   inline void 272   inline void
HITCBC 273   1434 timer_service_invalidate_cache() noexcept 273   1450 timer_service_invalidate_cache() noexcept
274   { 274   {
HITCBC 275   1434 delete tl_cached_impl.get(); 275   1450 delete tl_cached_impl.get();
HITCBC 276   1434 tl_cached_impl.set(nullptr); 276   1450 tl_cached_impl.set(nullptr);
HITCBC 277   1434 } 277   1450 }
278   278  
279   // timer_service out-of-class member function definitions 279   // timer_service out-of-class member function definitions
280   280  
281   inline void 281   inline void
HITCBC 282   1434 timer_service::shutdown() 282   1450 timer_service::shutdown()
283   { 283   {
HITCBC 284   1434 timer_service_invalidate_cache(); 284   1450 timer_service_invalidate_cache();
HITCBC 285   1434 shutting_down_ = true; 285   1450 shutting_down_ = true;
286   286  
287   // Snapshot impls and detach them from the heap so that 287   // Snapshot impls and detach them from the heap so that
288   // coroutine-owned timer destructors (triggered by h.destroy() 288   // coroutine-owned timer destructors (triggered by h.destroy()
289   // below) cannot re-enter remove_timer_impl() and mutate the 289   // below) cannot re-enter remove_timer_impl() and mutate the
290   // vector during iteration. 290   // vector during iteration.
HITCBC 291   1434 std::vector<timer::implementation*> impls; 291   1450 std::vector<timer::implementation*> impls;
HITCBC 292   1434 impls.reserve(heap_.size()); 292   1450 impls.reserve(heap_.size());
HITCBC 293   1462 for (auto& entry : heap_) 293   1478 for (auto& entry : heap_)
294   { 294   {
HITCBC 295   28 entry.timer_->heap_index_.store( 295   28 entry.timer_->heap_index_.store(
296   (std::numeric_limits<std::size_t>::max)(), 296   (std::numeric_limits<std::size_t>::max)(),
297   std::memory_order_relaxed); 297   std::memory_order_relaxed);
HITCBC 298   28 impls.push_back(entry.timer_); 298   28 impls.push_back(entry.timer_);
299   } 299   }
HITCBC 300   1434 heap_.clear(); 300   1450 heap_.clear();
HITCBC 301   1434 cached_nearest_ns_.store( 301   1450 cached_nearest_ns_.store(
302   (std::numeric_limits<std::int64_t>::max)(), std::memory_order_release); 302   (std::numeric_limits<std::int64_t>::max)(), std::memory_order_release);
303   303  
304   // Cancel waiting timers. Each waiter called work_started() 304   // Cancel waiting timers. Each waiter called work_started()
305   // in implementation::wait(). On IOCP the scheduler shutdown 305   // in implementation::wait(). On IOCP the scheduler shutdown
306   // loop exits when outstanding_work_ reaches zero, so we must 306   // loop exits when outstanding_work_ reaches zero, so we must
307   // call work_finished() here to balance it. On other backends 307   // call work_finished() here to balance it. On other backends
308   // this is harmless. 308   // this is harmless.
HITCBC 309   1462 for (auto* impl : impls) 309   1478 for (auto* impl : impls)
310   { 310   {
HITCBC 311   28 if (auto* w = std::exchange(impl->waiter_, nullptr)) 311   28 if (auto* w = std::exchange(impl->waiter_, nullptr))
312   { 312   {
HITCBC 313   28 w->reset_stop_cb(); 313   28 w->reset_stop_cb();
HITCBC 314   28 auto h = std::exchange(w->h_, {}); 314   28 auto h = std::exchange(w->h_, {});
HITCBC 315   28 sched_->work_finished(); 315   28 sched_->work_finished();
316   // Destroying the frame also ends the node's storage 316   // Destroying the frame also ends the node's storage
HITCBC 317   28 if (h) 317   28 if (h)
HITCBC 318   28 h.destroy(); 318   28 h.destroy();
319   } 319   }
HITCBC 320   28 delete impl; 320   28 delete impl;
321   } 321   }
322   322  
323   // Delete free-listed impls 323   // Delete free-listed impls
HITCBC 324   1506 while (free_list_) 324   1556 while (free_list_)
325   { 325   {
HITCBC 326   72 auto* next = free_list_->next_free_; 326   106 auto* next = free_list_->next_free_;
HITCBC 327   72 delete free_list_; 327   106 delete free_list_;
HITCBC 328   72 free_list_ = next; 328   106 free_list_ = next;
329   } 329   }
HITCBC 330   1434 } 330   1450 }
331   331  
332   inline io_object::implementation* 332   inline io_object::implementation*
HITCBC 333   11649 timer_service::construct() 333   11269 timer_service::construct()
334   { 334   {
HITCBC 335   11649 timer::implementation* impl = try_pop_tl_cache(this); 335   11269 timer::implementation* impl = try_pop_tl_cache(this);
HITCBC 336   11649 if (impl) 336   11269 if (impl)
337   { 337   {
HITCBC 338   11288 impl->svc_ = this; 338   10872 impl->svc_ = this;
339   // Reset expiry_ too: a recycled impl must behave like a fresh 339   // Reset expiry_ too: a recycled impl must behave like a fresh
340   // one, whose default expiry reads as already elapsed 340   // one, whose default expiry reads as already elapsed
HITCBC 341   11288 impl->expiry_ = {}; 341   10872 impl->expiry_ = {};
HITCBC 342   11288 impl->heap_index_.store( 342   10872 impl->heap_index_.store(
343   (std::numeric_limits<std::size_t>::max)(), 343   (std::numeric_limits<std::size_t>::max)(),
344   std::memory_order_relaxed); 344   std::memory_order_relaxed);
HITCBC 345   11288 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed); 345   10872 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed);
HITCBC 346   11288 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr); 346   10872 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr);
HITCBC 347   11288 return impl; 347   10872 return impl;
348   } 348   }
349   349  
HITCBC 350   361 std::lock_guard lock(mutex_); 350   397 std::lock_guard lock(mutex_);
HITCBC 351   361 if (free_list_) 351   397 if (free_list_)
352   { 352   {
HITCBC 353   2 impl = free_list_; 353   2 impl = free_list_;
HITCBC 354   2 free_list_ = impl->next_free_; 354   2 free_list_ = impl->next_free_;
HITCBC 355   2 impl->next_free_ = nullptr; 355   2 impl->next_free_ = nullptr;
HITCBC 356   2 impl->svc_ = this; 356   2 impl->svc_ = this;
HITCBC 357   2 impl->expiry_ = {}; 357   2 impl->expiry_ = {};
HITCBC 358   2 impl->heap_index_.store( 358   2 impl->heap_index_.store(
359   (std::numeric_limits<std::size_t>::max)(), 359   (std::numeric_limits<std::size_t>::max)(),
360   std::memory_order_relaxed); 360   std::memory_order_relaxed);
HITCBC 361   2 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed); 361   2 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed);
HITCBC 362   2 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr); 362   2 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr);
363   } 363   }
364   else 364   else
365   { 365   {
HITCBC 366   359 impl = new timer::implementation(*this); 366   395 impl = new timer::implementation(*this);
367   } 367   }
HITCBC 368   361 return impl; 368   397 return impl;
HITCBC 369   361 } 369   397 }
370   370  
371   inline void 371   inline void
HITCBC 372   11649 timer_service::destroy(io_object::implementation* p) 372   11269 timer_service::destroy(io_object::implementation* p)
373   { 373   {
374   // During shutdown the drain loop owns every impl and deletes 374   // During shutdown the drain loop owns every impl and deletes
375   // them directly. A frame destroyed by that loop can unwind a 375   // them directly. A frame destroyed by that loop can unwind a
376   // handle whose impl was freed in an earlier iteration (a 376   // handle whose impl was freed in an earlier iteration (a
377   // timeout's parent frame owns the timeout timer while 377   // timeout's parent frame owns the timeout timer while
378   // suspended on the inner delay's timer), so bail out before 378   // suspended on the inner delay's timer), so bail out before
379   // even downcasting the pointer. 379   // even downcasting the pointer.
HITCBC 380   11649 if (shutting_down_) 380   11269 if (shutting_down_)
HITCBC 381   28 return; 381   28 return;
HITCBC 382   11621 destroy_impl(static_cast<timer::implementation&>(*p)); 382   11241 destroy_impl(static_cast<timer::implementation&>(*p));
383   } 383   }
384   384  
385   inline void 385   inline void
HITCBC 386   11621 timer_service::destroy_impl(timer::implementation& impl) 386   11241 timer_service::destroy_impl(timer::implementation& impl)
387   { 387   {
388   // During shutdown the impl is owned by the shutdown loop. 388   // During shutdown the impl is owned by the shutdown loop.
389   // Re-entering here (from a coroutine-owned timer destructor 389   // Re-entering here (from a coroutine-owned timer destructor
390   // triggered by h.destroy()) must not modify the heap or 390   // triggered by h.destroy()) must not modify the heap or
391   // recycle the impl — shutdown deletes it directly. 391   // recycle the impl — shutdown deletes it directly.
HITCBC 392   11621 if (shutting_down_) 392   11241 if (shutting_down_)
HITCBC 393   11547 return; 393   11133 return;
394   394  
HITCBC 395   11621 cancel_timer(impl); 395   11241 cancel_timer(impl);
396   396  
HITCBC 397   23242 if (impl.heap_index_.load(std::memory_order_relaxed) != 397   22482 if (impl.heap_index_.load(std::memory_order_relaxed) !=
HITCBC 398   11621 (std::numeric_limits<std::size_t>::max)()) 398   11241 (std::numeric_limits<std::size_t>::max)())
399   { 399   {
MISUBC 400   std::lock_guard lock(mutex_); 400   std::lock_guard lock(mutex_);
MISUBC 401   remove_timer_impl(impl); 401   remove_timer_impl(impl);
MISUBC 402   refresh_cached_nearest(); 402   refresh_cached_nearest();
MISUBC 403   } 403   }
404   404  
HITCBC 405   11621 if (try_push_tl_cache(&impl)) 405   11241 if (try_push_tl_cache(&impl))
HITCBC 406   11547 return; 406   11133 return;
407   407  
HITCBC 408   74 std::lock_guard lock(mutex_); 408   108 std::lock_guard lock(mutex_);
HITCBC 409   74 impl.next_free_ = free_list_; 409   108 impl.next_free_ = free_list_;
HITCBC 410   74 free_list_ = &impl; 410   108 free_list_ = &impl;
HITCBC 411   74 } 411   108 }
412   412  
413   inline void 413   inline void
HITCBC 414   10800 timer_service::insert_waiter(timer::implementation& impl, waiter_node* w) 414   10419 timer_service::insert_waiter(timer::implementation& impl, waiter_node* w)
415   { 415   {
HITCBC 416   10800 bool notify = false; 416   10419 bool notify = false;
HITCBC 417   10800 bool lost_cancel = false; 417   10419 bool lost_cancel = false;
418   { 418   {
HITCBC 419   10800 std::lock_guard lock(mutex_); 419   10419 std::lock_guard lock(mutex_);
420   // Grow before publishing anything, so the push_back below 420   // Grow before publishing anything, so the push_back below
421   // cannot throw: a failure here leaves the waiter untouched, 421   // cannot throw: a failure here leaves the waiter untouched,
422   // the strong guarantee rearm_wait's recovery relies on. 422   // the strong guarantee rearm_wait's recovery relies on.
HITCBC 423   10800 if (impl.heap_index_.load(std::memory_order_relaxed) == 423   10419 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 424   21600 (std::numeric_limits<std::size_t>::max)() && 424   20838 (std::numeric_limits<std::size_t>::max)() &&
HITCBC 425   10800 heap_.size() == heap_.capacity()) 425   10419 heap_.size() == heap_.capacity())
HITCBC 426   261 heap_.reserve( 426   265 heap_.reserve(
HITCBC 427   261 heap_.capacity() == 0 ? 16 : 2 * heap_.capacity()); 427   265 heap_.capacity() == 0 ? 16 : 2 * heap_.capacity());
428   // Publish: from here the waiter is visible to the fire path and 428   // Publish: from here the waiter is visible to the fire path and
429   // to its own stop callback (impl_ non-null enables cancel_waiter). 429   // to its own stop callback (impl_ non-null enables cancel_waiter).
HITCBC 430   10800 w->impl_ = &impl; 430   10419 w->impl_ = &impl;
HITCBC 431   21600 if (impl.heap_index_.load(std::memory_order_relaxed) == 431   20838 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 432   10800 (std::numeric_limits<std::size_t>::max)()) 432   10419 (std::numeric_limits<std::size_t>::max)())
433   { 433   {
HITCBC 434   10800 impl.heap_index_.store(heap_.size(), std::memory_order_relaxed); 434   10419 impl.heap_index_.store(heap_.size(), std::memory_order_relaxed);
HITCBC 435   10800 heap_.push_back({impl.expiry_, &impl}); 435   10419 heap_.push_back({impl.expiry_, &impl});
HITCBC 436   10800 up_heap(heap_.size() - 1); 436   10419 up_heap(heap_.size() - 1);
HITCBC 437   10800 notify = 437   10419 notify =
HITCBC 438   10800 (impl.heap_index_.load(std::memory_order_relaxed) == 0); 438   10419 (impl.heap_index_.load(std::memory_order_relaxed) == 0);
HITCBC 439   10800 refresh_cached_nearest(); 439   10419 refresh_cached_nearest();
440   } 440   }
HITCBC 441   10800 BOOST_COROSIO_ASSERT(impl.waiter_ == nullptr); 441   10419 BOOST_COROSIO_ASSERT(impl.waiter_ == nullptr);
HITCBC 442   10800 impl.waiter_ = w; 442   10419 impl.waiter_ = w;
443   443  
444   // Lost-cancel re-check: a stop requested after the canceller was 444   // Lost-cancel re-check: a stop requested after the canceller was
445   // armed in wait() but before this publication found impl_ null 445   // armed in wait() but before this publication found impl_ null
446   // and returned a no-op. Observe it now and undo the insertion. 446   // and returned a no-op. Observe it now and undo the insertion.
HITCBC 447   10800 if (w->token_->stop_requested()) 447   10419 if (w->token_->stop_requested())
448   { 448   {
HITCBC 449   1 w->impl_ = nullptr; 449   2 w->impl_ = nullptr;
HITCBC 450   1 impl.waiter_ = nullptr; 450   2 impl.waiter_ = nullptr;
HITCBC 451   1 remove_timer_impl(impl); 451   2 remove_timer_impl(impl);
HITCBC 452   1 impl.might_have_pending_waits_.store( 452   2 impl.might_have_pending_waits_.store(
453   false, std::memory_order_relaxed); 453   false, std::memory_order_relaxed);
HITCBC 454   1 refresh_cached_nearest(); 454   2 refresh_cached_nearest();
HITCBC 455   1 lost_cancel = true; 455   2 lost_cancel = true;
HITCBC 456   1 notify = false; // insertion undone; nearest unchanged 456   2 notify = false; // insertion undone; nearest unchanged
457   } 457   }
HITCBC 458   10800 } 458   10419 }
HITCBC 459   10800 if (notify) 459   10419 if (notify)
HITCBC 460   10638 on_earliest_changed_(); 460   10264 on_earliest_changed_();
HITCBC 461   10800 if (lost_cancel) 461   10419 if (lost_cancel)
462   { 462   {
HITCBC 463   1 w->ec_ = make_error_code(capy::error::canceled); 463   2 w->ec_ = make_error_code(capy::error::canceled);
HITCBC 464   1 sched_->post(&w->op_); 464   2 sched_->post(&w->op_);
465   } 465   }
HITCBC 466   10800 } 466   10419 }
467   467  
468   inline void 468   inline void
HITCBC 469   11621 timer_service::cancel_timer(timer::implementation& impl) 469   11241 timer_service::cancel_timer(timer::implementation& impl)
470   { 470   {
HITCBC 471   11621 if (!impl.might_have_pending_waits_.load(std::memory_order_relaxed)) 471   11241 if (!impl.might_have_pending_waits_.load(std::memory_order_relaxed))
HITCBC 472   11619 return; 472   11239 return;
473   473  
474   // No unlocked already-done fast-out here: it would need the 474   // No unlocked already-done fast-out here: it would need the
475   // non-atomic waiter_ (a race with concurrent drains), and an 475   // non-atomic waiter_ (a race with concurrent drains), and an
476   // index-only check is lifetime-unsafe because npos is stored 476   // index-only check is lifetime-unsafe because npos is stored
477   // before the drain finishes touching the impl. A stale-true 477   // before the drain finishes touching the impl. A stale-true
478   // flag is rare with the stateless API; the locked path below 478   // flag is rare with the stateless API; the locked path below
479   // re-validates. 479   // re-validates.
480   480  
HITCBC 481   2 waiter_node* canceled = nullptr; 481   2 waiter_node* canceled = nullptr;
482   482  
483   { 483   {
HITCBC 484   2 std::lock_guard lock(mutex_); 484   2 std::lock_guard lock(mutex_);
HITCBC 485   2 remove_timer_impl(impl); 485   2 remove_timer_impl(impl);
HITCBC 486   2 canceled = std::exchange(impl.waiter_, nullptr); 486   2 canceled = std::exchange(impl.waiter_, nullptr);
HITCBC 487   2 if (canceled) 487   2 if (canceled)
HITCBC 488   2 canceled->impl_ = nullptr; 488   2 canceled->impl_ = nullptr;
489   // Store false as the final touch of the impl under the lock so 489   // Store false as the final touch of the impl under the lock so
490   // a pre-lock false-flag check trusts it unqualified. 490   // a pre-lock false-flag check trusts it unqualified.
HITCBC 491   2 impl.might_have_pending_waits_.store(false, std::memory_order_relaxed); 491   2 impl.might_have_pending_waits_.store(false, std::memory_order_relaxed);
HITCBC 492   2 refresh_cached_nearest(); 492   2 refresh_cached_nearest();
HITCBC 493   2 } 493   2 }
494   494  
HITCBC 495   2 if (canceled) 495   2 if (canceled)
496   { 496   {
HITCBC 497   2 canceled->ec_ = make_error_code(capy::error::canceled); 497   2 canceled->ec_ = make_error_code(capy::error::canceled);
HITCBC 498   2 sched_->post(&canceled->op_); 498   2 sched_->post(&canceled->op_);
499   } 499   }
500   } 500   }
501   501  
502   inline void 502   inline void
HITCBC 503   1408 timer_service::cancel_waiter(waiter_node* w) 503   1425 timer_service::cancel_waiter(waiter_node* w)
504   { 504   {
505   { 505   {
HITCBC 506   1408 std::lock_guard lock(mutex_); 506   1425 std::lock_guard lock(mutex_);
507   // Already removed by another drain: cancel_timer, 507   // Already removed by another drain: cancel_timer,
508   // process_expired, or insert_waiter's lost-cancel recheck 508   // process_expired, or insert_waiter's lost-cancel recheck
HITCBC 509   1408 if (!w->impl_) 509   1425 if (!w->impl_)
HITCBC 510   2 return; 510   2 return;
HITCBC 511   1406 auto* impl = w->impl_; 511   1423 auto* impl = w->impl_;
HITCBC 512   1406 w->impl_ = nullptr; 512   1423 w->impl_ = nullptr;
HITCBC 513   1406 impl->waiter_ = nullptr; 513   1423 impl->waiter_ = nullptr;
HITCBC 514   1406 remove_timer_impl(*impl); 514   1423 remove_timer_impl(*impl);
HITCBC 515   1406 impl->might_have_pending_waits_.store( 515   1423 impl->might_have_pending_waits_.store(
516   false, std::memory_order_relaxed); 516   false, std::memory_order_relaxed);
HITCBC 517   1406 refresh_cached_nearest(); 517   1423 refresh_cached_nearest();
HITCBC 518   1408 } 518   1425 }
519   519  
HITCBC 520   1406 w->ec_ = make_error_code(capy::error::canceled); 520   1423 w->ec_ = make_error_code(capy::error::canceled);
HITCBC 521   1406 sched_->post(&w->op_); 521   1423 sched_->post(&w->op_);
522   } 522   }
523   523  
524   inline std::size_t 524   inline std::size_t
HITCBC 525   404776 timer_service::process_expired() 525   397851 timer_service::process_expired()
526   { 526   {
HITCBC 527   404776 intrusive_list<waiter_node> expired; 527   397851 intrusive_list<waiter_node> expired;
528   528  
529   { 529   {
HITCBC 530   404776 std::lock_guard lock(mutex_); 530   397851 std::lock_guard lock(mutex_);
HITCBC 531   404776 auto now = clock_type::now(); 531   397851 auto now = clock_type::now();
532   532  
HITCBC 533   414139 while (!heap_.empty() && heap_[0].time_ <= now) 533   406815 while (!heap_.empty() && heap_[0].time_ <= now)
534   { 534   {
HITCBC 535   9363 timer::implementation* t = heap_[0].timer_; 535   8964 timer::implementation* t = heap_[0].timer_;
HITCBC 536   9363 remove_timer_impl(*t); 536   8964 remove_timer_impl(*t);
HITCBC 537   9363 if (auto* w = std::exchange(t->waiter_, nullptr)) 537   8964 if (auto* w = std::exchange(t->waiter_, nullptr))
538   { 538   {
HITCBC 539   9363 w->impl_ = nullptr; 539   8964 w->impl_ = nullptr;
HITCBC 540   9363 w->ec_ = {}; 540   8964 w->ec_ = {};
HITCBC 541   9363 expired.push_back(w); 541   8964 expired.push_back(w);
542   } 542   }
HITCBC 543   9363 t->might_have_pending_waits_.store( 543   8964 t->might_have_pending_waits_.store(
544   false, std::memory_order_relaxed); 544   false, std::memory_order_relaxed);
545   } 545   }
546   546  
HITCBC 547   404776 refresh_cached_nearest(); 547   397851 refresh_cached_nearest();
HITCBC 548   404776 } 548   397851 }
549   549  
HITCBC 550   404776 std::size_t count = 0; 550   397851 std::size_t count = 0;
HITCBC 551   414139 while (auto* w = expired.pop_front()) 551   406815 while (auto* w = expired.pop_front())
552   { 552   {
HITCBC 553   9363 sched_->post(&w->op_); 553   8964 sched_->post(&w->op_);
HITCBC 554   9363 ++count; 554   8964 ++count;
HITCBC 555   9363 } 555   8964 }
556   556  
HITCBC 557   404776 return count; 557   397851 return count;
558   } 558   }
559   559  
560   inline void 560   inline void
HITCBC 561   10772 timer_service::remove_timer_impl(timer::implementation& impl) 561   10391 timer_service::remove_timer_impl(timer::implementation& impl)
562   { 562   {
HITCBC 563   10772 std::size_t index = impl.heap_index_.load(std::memory_order_relaxed); 563   10391 std::size_t index = impl.heap_index_.load(std::memory_order_relaxed);
HITCBC 564   10772 if (index >= heap_.size()) 564   10391 if (index >= heap_.size())
MISUBC 565   return; // Not in heap 565   return; // Not in heap
566   566  
HITCBC 567   10772 if (index == heap_.size() - 1) 567   10391 if (index == heap_.size() - 1)
568   { 568   {
569   // Last element, just pop 569   // Last element, just pop
HITCBC 570   1673 impl.heap_index_.store( 570   1659 impl.heap_index_.store(
571   (std::numeric_limits<std::size_t>::max)(), 571   (std::numeric_limits<std::size_t>::max)(),
572   std::memory_order_relaxed); 572   std::memory_order_relaxed);
HITCBC 573   1673 heap_.pop_back(); 573   1659 heap_.pop_back();
574   } 574   }
575   else 575   else
576   { 576   {
577   // Swap with last and reheapify 577   // Swap with last and reheapify
HITCBC 578   9099 swap_heap(index, heap_.size() - 1); 578   8732 swap_heap(index, heap_.size() - 1);
HITCBC 579   9099 impl.heap_index_.store( 579   8732 impl.heap_index_.store(
580   (std::numeric_limits<std::size_t>::max)(), 580   (std::numeric_limits<std::size_t>::max)(),
581   std::memory_order_relaxed); 581   std::memory_order_relaxed);
HITCBC 582   9099 heap_.pop_back(); 582   8732 heap_.pop_back();
583   583  
HITCBC 584   9099 if (index > 0 && heap_[index].time_ < heap_[(index - 1) / 2].time_) 584   8732 if (index > 0 && heap_[index].time_ < heap_[(index - 1) / 2].time_)
MISUBC 585   up_heap(index); 585   up_heap(index);
586   else 586   else
HITCBC 587   9099 down_heap(index); 587   8732 down_heap(index);
588   } 588   }
589   } 589   }
590   590  
591   inline void 591   inline void
HITCBC 592   10800 timer_service::up_heap(std::size_t index) 592   10419 timer_service::up_heap(std::size_t index)
593   { 593   {
HITCBC 594   19857 while (index > 0) 594   19097 while (index > 0)
595   { 595   {
HITCBC 596   9218 std::size_t parent = (index - 1) / 2; 596   8831 std::size_t parent = (index - 1) / 2;
HITCBC 597   9218 if (!(heap_[index].time_ < heap_[parent].time_)) 597   8831 if (!(heap_[index].time_ < heap_[parent].time_))
HITCBC 598   161 break; 598   153 break;
HITCBC 599   9057 swap_heap(index, parent); 599   8678 swap_heap(index, parent);
HITCBC 600   9057 index = parent; 600   8678 index = parent;
601   } 601   }
HITCBC 602   10800 } 602   10419 }
603   603  
604   inline void 604   inline void
HITCBC 605   9099 timer_service::down_heap(std::size_t index) 605   8732 timer_service::down_heap(std::size_t index)
606   { 606   {
HITCBC 607   9099 std::size_t child = index * 2 + 1; 607   8732 std::size_t child = index * 2 + 1;
HITCBC 608   9120 while (child < heap_.size()) 608   8807 while (child < heap_.size())
609   { 609   {
HITCBC 610   27 std::size_t min_child = (child + 1 == heap_.size() || 610   84 std::size_t min_child = (child + 1 == heap_.size() ||
HITCBC 611   15 heap_[child].time_ < heap_[child + 1].time_) 611   68 heap_[child].time_ < heap_[child + 1].time_)
HITCBC 612   42 ? child 612   152 ? child
HITCBC 613   27 : child + 1; 613   84 : child + 1;
614   614  
HITCBC 615   27 if (heap_[index].time_ < heap_[min_child].time_) 615   84 if (heap_[index].time_ < heap_[min_child].time_)
HITCBC 616   6 break; 616   9 break;
617   617  
HITCBC 618   21 swap_heap(index, min_child); 618   75 swap_heap(index, min_child);
HITCBC 619   21 index = min_child; 619   75 index = min_child;
HITCBC 620   21 child = index * 2 + 1; 620   75 child = index * 2 + 1;
621   } 621   }
HITCBC 622   9099 } 622   8732 }
623   623  
624   inline void 624   inline void
HITCBC 625   18177 timer_service::swap_heap(std::size_t i1, std::size_t i2) 625   17485 timer_service::swap_heap(std::size_t i1, std::size_t i2)
626   { 626   {
HITCBC 627   18177 heap_entry tmp = heap_[i1]; 627   17485 heap_entry tmp = heap_[i1];
HITCBC 628   18177 heap_[i1] = heap_[i2]; 628   17485 heap_[i1] = heap_[i2];
HITCBC 629   18177 heap_[i2] = tmp; 629   17485 heap_[i2] = tmp;
HITCBC 630   18177 heap_[i1].timer_->heap_index_.store(i1, std::memory_order_relaxed); 630   17485 heap_[i1].timer_->heap_index_.store(i1, std::memory_order_relaxed);
HITCBC 631   18177 heap_[i2].timer_->heap_index_.store(i2, std::memory_order_relaxed); 631   17485 heap_[i2].timer_->heap_index_.store(i2, std::memory_order_relaxed);
HITCBC 632   18177 } 632   17485 }
633   633  
634   // waiter_node's completion_op and canceller members are defined in 634   // waiter_node's completion_op and canceller members are defined in
635   // timer.cpp alongside implementation::wait(), for the same reason 635   // timer.cpp alongside implementation::wait(), for the same reason
636   // wait() lives there (see below). 636   // wait() lives there (see below).
637   637  
638   // timer::implementation::wait() is defined in timer.cpp, not here. 638   // timer::implementation::wait() is defined in timer.cpp, not here.
639   // It must be a non-inline definition in a translation unit that is 639   // It must be a non-inline definition in a translation unit that is
640   // always pulled into the link whenever detail::timer is used (every 640   // always pulled into the link whenever detail::timer is used (every
641   // consumer needs timer's constructors from that same object file). 641   // consumer needs timer's constructors from that same object file).
642   // An inline definition in this header would only be emitted in 642   // An inline definition in this header would only be emitted in
643   // translation units that happen to also include this header, which 643   // translation units that happen to also include this header, which
644   // is not guaranteed for every caller of wait_awaitable::await_suspend 644   // is not guaranteed for every caller of wait_awaitable::await_suspend
645   // in timer.hpp (e.g. code that only reaches timer.hpp through 645   // in timer.hpp (e.g. code that only reaches timer.hpp through
646   // delay.hpp, without transitively including a scheduler header). 646   // delay.hpp, without transitively including a scheduler header).
647   647  
648   // Free functions 648   // Free functions
649   649  
650   inline timer_service& 650   inline timer_service&
HITCBC 651   1434 get_timer_service(capy::execution_context& ctx, scheduler& sched) 651   1450 get_timer_service(capy::execution_context& ctx, scheduler& sched)
652   { 652   {
HITCBC 653   1434 return ctx.make_service<timer_service>(sched); 653   1450 return ctx.make_service<timer_service>(sched);
654   } 654   }
655   655  
656   } // namespace boost::corosio::detail 656   } // namespace boost::corosio::detail
657   657  
658   #endif 658   #endif