97.49% Lines (233/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   1790 callback() = default; 100   2095 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   1790 callback(void* ctx, void (*fn)(void*)) noexcept : ctx_(ctx), fn_(fn) {} 103   2095 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   7616 void operator()() const 112   8912 void operator()() const
113   { 113   {
HITCBC 114   7616 if (fn_) 114   8912 if (fn_)
HITCBC 115   7616 fn_(ctx_); 115   8912 fn_(ctx_);
HITCBC 116   7616 } 116   8912 }
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   1790 inline timer_service(capy::execution_context&, scheduler& sched) 141   2095 inline timer_service(capy::execution_context&, scheduler& sched)
HITCBC 142   1790 : sched_(&sched) 142   2095 : sched_(&sched)
143   { 143   {
HITCBC 144   1790 } 144   2095 }
145   145  
146   /// Return the associated scheduler. 146   /// Return the associated scheduler.
HITCBC 147   16647 inline scheduler& get_scheduler() noexcept 147   32689 inline scheduler& get_scheduler() noexcept
148   { 148   {
HITCBC 149   16647 return *sched_; 149   32689 return *sched_;
150   } 150   }
151   151  
152   /// Destroy the timer service. 152   /// Destroy the timer service.
HITCBC 153   3580 ~timer_service() override = default; 153   4190 ~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   1790 inline void set_on_earliest_changed(callback cb) 159   2095 inline void set_on_earliest_changed(callback cb)
160   { 160   {
HITCBC 161   1790 on_earliest_changed_ = cb; 161   2095 on_earliest_changed_ = cb;
HITCBC 162   1790 } 162   2095 }
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   343743 inline time_point nearest_expiry() const noexcept 172   346805 inline time_point nearest_expiry() const noexcept
173   { 173   {
HITCBC 174   343743 auto ns = cached_nearest_ns_.load(std::memory_order_acquire); 174   346805 auto ns = cached_nearest_ns_.load(std::memory_order_acquire);
HITCBC 175   343743 return time_point(time_point::duration(ns)); 175   346805 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   372388 inline void refresh_cached_nearest() noexcept 203   388437 inline void refresh_cached_nearest() noexcept
204   { 204   {
HITCBC 205   372388 auto ns = heap_.empty() ? (std::numeric_limits<std::int64_t>::max)() 205   388437 auto ns = heap_.empty() ? (std::numeric_limits<std::int64_t>::max)()
HITCBC 206   368964 : heap_[0].time_.time_since_epoch().count(); 206   383760 : heap_[0].time_.time_since_epoch().count();
HITCBC 207   372388 cached_nearest_ns_.store(ns, std::memory_order_release); 207   388437 cached_nearest_ns_.store(ns, std::memory_order_release);
HITCBC 208   372388 } 208   388437 }
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   8452 arm_tl_cache_cleanup() noexcept 239   15992 arm_tl_cache_cleanup() noexcept
240   { 240   {
HITCBC 241   8452 [[maybe_unused]] thread_local tl_cache_owner owner; 241   15992 [[maybe_unused]] thread_local tl_cache_owner owner;
HITCBC 242   8452 } 242   15992 }
243   243  
244   inline timer::implementation* 244   inline timer::implementation*
HITCBC 245   9193 try_pop_tl_cache(timer_service* svc) noexcept 245   17213 try_pop_tl_cache(timer_service* svc) noexcept
246   { 246   {
HITCBC 247   9193 auto* impl = tl_cached_impl.get(); 247   17213 auto* impl = tl_cached_impl.get();
HITCBC 248   9193 if (impl) 248   17213 if (impl)
249   { 249   {
HITCBC 250   8159 tl_cached_impl.set(nullptr); 250   15583 tl_cached_impl.set(nullptr);
HITCBC 251   8159 if (impl->svc_ == svc) 251   15583 if (impl->svc_ == svc)
HITCBC 252   8159 return impl; 252   15583 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   1034 return nullptr; 256   1630 return nullptr;
257   } 257   }
258   258  
259   inline bool 259   inline bool
HITCBC 260   9164 try_push_tl_cache(timer::implementation* impl) noexcept 260   17184 try_push_tl_cache(timer::implementation* impl) noexcept
261   { 261   {
HITCBC 262   9164 if (!tl_cached_impl.get()) 262   17184 if (!tl_cached_impl.get())
263   { 263   {
HITCBC 264   8452 arm_tl_cache_cleanup(); 264   15992 arm_tl_cache_cleanup();
HITCBC 265   8452 tl_cached_impl.set(impl); 265   15992 tl_cached_impl.set(impl);
HITCBC 266   8452 return true; 266   15992 return true;
267   } 267   }
HITCBC 268   712 return false; 268   1192 return false;
269   } 269   }
270   270  
271   inline void 271   inline void
HITCBC 272   1790 timer_service_invalidate_cache() noexcept 272   2095 timer_service_invalidate_cache() noexcept
273   { 273   {
HITCBC 274   1790 delete tl_cached_impl.get(); 274   2095 delete tl_cached_impl.get();
HITCBC 275   1790 tl_cached_impl.set(nullptr); 275   2095 tl_cached_impl.set(nullptr);
HITCBC 276   1790 } 276   2095 }
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   1790 timer_service::shutdown() 281   2095 timer_service::shutdown()
282   { 282   {
HITCBC 283   1790 timer_service_invalidate_cache(); 283   2095 timer_service_invalidate_cache();
HITCBC 284   1790 shutting_down_ = true; 284   2095 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   1790 std::vector<timer::implementation*> impls; 290   2095 std::vector<timer::implementation*> impls;
HITCBC 291   1790 impls.reserve(heap_.size()); 291   2095 impls.reserve(heap_.size());
HITCBC 292   1819 for (auto& entry : heap_) 292   2124 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   1790 heap_.clear(); 299   2095 heap_.clear();
HITCBC 300   1790 cached_nearest_ns_.store( 300   2095 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   1819 for (auto* impl : impls) 308   2124 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   2500 while (free_list_) 323   3285 while (free_list_)
324   { 324   {
HITCBC 325   710 auto* next = free_list_->next_free_; 325   1190 auto* next = free_list_->next_free_;
HITCBC 326   710 delete free_list_; 326   1190 delete free_list_;
HITCBC 327   710 free_list_ = next; 327   1190 free_list_ = next;
328   } 328   }
HITCBC 329   1790 } 329   2095 }
330   330  
331   inline io_object::implementation* 331   inline io_object::implementation*
HITCBC 332   9193 timer_service::construct() 332   17213 timer_service::construct()
333   { 333   {
HITCBC 334   9193 timer::implementation* impl = try_pop_tl_cache(this); 334   17213 timer::implementation* impl = try_pop_tl_cache(this);
HITCBC 335   9193 if (impl) 335   17213 if (impl)
336   { 336   {
HITCBC 337   8159 impl->svc_ = this; 337   15583 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   8159 impl->expiry_ = {}; 340   15583 impl->expiry_ = {};
HITCBC 341   8159 impl->heap_index_.store( 341   15583 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   8159 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed); 344   15583 impl->might_have_pending_waits_.store(false, std::memory_order_relaxed);
HITCBC 345   8159 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr); 345   15583 BOOST_COROSIO_ASSERT(impl->waiter_ == nullptr);
HITCBC 346   8159 return impl; 346   15583 return impl;
347   } 347   }
348   348  
HITCBC 349   1034 std::lock_guard lock(mutex_); 349   1630 std::lock_guard lock(mutex_);
HITCBC 350   1034 if (free_list_) 350   1630 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   1032 impl = new timer::implementation(*this); 365   1628 impl = new timer::implementation(*this);
366   } 366   }
HITCBC 367   1034 return impl; 367   1630 return impl;
HITCBC 368   1034 } 368   1630 }
369   369  
370   inline void 370   inline void
HITCBC 371   9193 timer_service::destroy(io_object::implementation* p) 371   17213 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   9193 if (shutting_down_) 379   17213 if (shutting_down_)
HITCBC 380   29 return; 380   29 return;
HITCBC 381   9164 destroy_impl(static_cast<timer::implementation&>(*p)); 381   17184 destroy_impl(static_cast<timer::implementation&>(*p));
382   } 382   }
383   383  
384   inline void 384   inline void
HITCBC 385   9164 timer_service::destroy_impl(timer::implementation& impl) 385   17184 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   9164 if (shutting_down_) 391   17184 if (shutting_down_)
HITCBC 392   8452 return; 392   15992 return;
393   393  
HITCBC 394   9164 cancel_timer(impl); 394   17184 cancel_timer(impl);
395   395  
HITCBC 396   18328 if (impl.heap_index_.load(std::memory_order_relaxed) != 396   34368 if (impl.heap_index_.load(std::memory_order_relaxed) !=
HITCBC 397   9164 (std::numeric_limits<std::size_t>::max)()) 397   17184 (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   9164 if (try_push_tl_cache(&impl)) 404   17184 if (try_push_tl_cache(&impl))
HITCBC 405   8452 return; 405   15992 return;
406   406  
HITCBC 407   712 std::lock_guard lock(mutex_); 407   1192 std::lock_guard lock(mutex_);
HITCBC 408   712 impl.next_free_ = free_list_; 408   1192 impl.next_free_ = free_list_;
HITCBC 409   712 free_list_ = &impl; 409   1192 free_list_ = &impl;
HITCBC 410   712 } 410   1192 }
411   411  
412   inline void 412   inline void
HITCBC 413   8470 timer_service::insert_waiter(timer::implementation& impl, waiter_node* w) 413   20604 timer_service::insert_waiter(timer::implementation& impl, waiter_node* w)
414   { 414   {
HITCBC 415   8470 bool notify = false; 415   20604 bool notify = false;
HITCBC 416   8470 bool lost_cancel = false; 416   20604 bool lost_cancel = false;
417   { 417   {
HITCBC 418   8470 std::lock_guard lock(mutex_); 418   20604 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   8470 if (impl.heap_index_.load(std::memory_order_relaxed) == 422   20604 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 423   16940 (std::numeric_limits<std::size_t>::max)() && 423   41208 (std::numeric_limits<std::size_t>::max)() &&
HITCBC 424   8470 heap_.size() == heap_.capacity()) 424   20604 heap_.size() == heap_.capacity())
HITCBC 425   325 heap_.reserve( 425   442 heap_.reserve(
HITCBC 426   325 heap_.capacity() == 0 ? 16 : 2 * heap_.capacity()); 426   442 heap_.capacity() == 0 ? 16 : 2 * heap_.capacity());
427   // Publish: from here the waiter is visible to the fire path and 427   // Publish: from here the waiter is visible to the fire path and
428   // to its own stop callback (impl_ non-null enables cancel_waiter). 428   // to its own stop callback (impl_ non-null enables cancel_waiter).
HITCBC 429   8470 w->impl_ = &impl; 429   20604 w->impl_ = &impl;
HITCBC 430   16940 if (impl.heap_index_.load(std::memory_order_relaxed) == 430   41208 if (impl.heap_index_.load(std::memory_order_relaxed) ==
HITCBC 431   8470 (std::numeric_limits<std::size_t>::max)()) 431   20604 (std::numeric_limits<std::size_t>::max)())
432   { 432   {
HITCBC 433   8470 impl.heap_index_.store(heap_.size(), std::memory_order_relaxed); 433   20604 impl.heap_index_.store(heap_.size(), std::memory_order_relaxed);
HITCBC 434   8470 heap_.push_back({impl.expiry_, &impl}); 434   20604 heap_.push_back({impl.expiry_, &impl});
HITCBC 435   8470 up_heap(heap_.size() - 1); 435   20604 up_heap(heap_.size() - 1);
HITCBC 436   8470 notify = 436   20604 notify =
HITCBC 437   8470 (impl.heap_index_.load(std::memory_order_relaxed) == 0); 437   20604 (impl.heap_index_.load(std::memory_order_relaxed) == 0);
HITCBC 438   8470 refresh_cached_nearest(); 438   20604 refresh_cached_nearest();
439   } 439   }
HITCBC 440   8470 BOOST_COROSIO_ASSERT(impl.waiter_ == nullptr); 440   20604 BOOST_COROSIO_ASSERT(impl.waiter_ == nullptr);
HITCBC 441   8470 impl.waiter_ = w; 441   20604 impl.waiter_ = w;
442   442  
443   // Lost-cancel re-check: a stop requested after the canceller was 443   // Lost-cancel re-check: a stop requested after the canceller was
444   // armed in wait() but before this publication found impl_ null 444   // armed in wait() but before this publication found impl_ null
445   // and returned a no-op. Observe it now and undo the insertion. 445   // and returned a no-op. Observe it now and undo the insertion.
HITCBC 446   8470 if (w->token_->stop_requested()) 446   20604 if (w->token_->stop_requested())
447   { 447   {
HITCBC 448   13 w->impl_ = nullptr; 448   5 w->impl_ = nullptr;
HITCBC 449   13 impl.waiter_ = nullptr; 449   5 impl.waiter_ = nullptr;
HITCBC 450   13 remove_timer_impl(impl); 450   5 remove_timer_impl(impl);
HITCBC 451   13 impl.might_have_pending_waits_.store( 451   5 impl.might_have_pending_waits_.store(
452   false, std::memory_order_relaxed); 452   false, std::memory_order_relaxed);
HITCBC 453   13 refresh_cached_nearest(); 453   5 refresh_cached_nearest();
HITCBC 454   13 lost_cancel = true; 454   5 lost_cancel = true;
HITCBC 455   13 notify = false; // insertion undone; nearest unchanged 455   5 notify = false; // insertion undone; nearest unchanged
456   } 456   }
HITCBC 457   8470 } 457   20604 }
HITCBC 458   8470 if (notify) 458   20604 if (notify)
HITCBC 459   7616 on_earliest_changed_(); 459   8912 on_earliest_changed_();
HITCBC 460   8470 if (lost_cancel) 460   20604 if (lost_cancel)
461   { 461   {
HITCBC 462   13 w->ec_ = make_error_code(capy::error::canceled); 462   5 w->ec_ = make_error_code(capy::error::canceled);
HITCBC 463   13 sched_->post(&w->op_); 463   5 sched_->post(&w->op_);
464   } 464   }
HITCBC 465   8470 } 465   20604 }
466   466  
467   inline void 467   inline void
HITCBC 468   9164 timer_service::cancel_timer(timer::implementation& impl) 468   17184 timer_service::cancel_timer(timer::implementation& impl)
469   { 469   {
HITCBC 470   9164 if (!impl.might_have_pending_waits_.load(std::memory_order_relaxed)) 470   17184 if (!impl.might_have_pending_waits_.load(std::memory_order_relaxed))
HITCBC 471   9162 return; 471   17182 return;
472   472  
473   // No unlocked already-done fast-out here: it would need the 473   // No unlocked already-done fast-out here: it would need the
474   // non-atomic waiter_ (a race with concurrent drains), and an 474   // non-atomic waiter_ (a race with concurrent drains), and an
475   // index-only check is lifetime-unsafe because npos is stored 475   // index-only check is lifetime-unsafe because npos is stored
476   // before the drain finishes touching the impl. A stale-true 476   // before the drain finishes touching the impl. A stale-true
477   // flag is rare with the stateless API; the locked path below 477   // flag is rare with the stateless API; the locked path below
478   // re-validates. 478   // re-validates.
479   479  
HITCBC 480   2 waiter_node* canceled = nullptr; 480   2 waiter_node* canceled = nullptr;
481   481  
482   { 482   {
HITCBC 483   2 std::lock_guard lock(mutex_); 483   2 std::lock_guard lock(mutex_);
HITCBC 484   2 remove_timer_impl(impl); 484   2 remove_timer_impl(impl);
HITCBC 485   2 canceled = std::exchange(impl.waiter_, nullptr); 485   2 canceled = std::exchange(impl.waiter_, nullptr);
HITCBC 486   2 if (canceled) 486   2 if (canceled)
HITCBC 487   2 canceled->impl_ = nullptr; 487   2 canceled->impl_ = nullptr;
488   // Store false as the final touch of the impl under the lock so 488   // Store false as the final touch of the impl under the lock so
489   // a pre-lock false-flag check trusts it unqualified. 489   // a pre-lock false-flag check trusts it unqualified.
HITCBC 490   2 impl.might_have_pending_waits_.store(false, std::memory_order_relaxed); 490   2 impl.might_have_pending_waits_.store(false, std::memory_order_relaxed);
HITCBC 491   2 refresh_cached_nearest(); 491   2 refresh_cached_nearest();
HITCBC 492   2 } 492   2 }
493   493  
HITCBC 494   2 if (canceled) 494   2 if (canceled)
495   { 495   {
HITCBC 496   2 canceled->ec_ = make_error_code(capy::error::canceled); 496   2 canceled->ec_ = make_error_code(capy::error::canceled);
HITCBC 497   2 sched_->post(&canceled->op_); 497   2 sched_->post(&canceled->op_);
498   } 498   }
499   } 499   }
500   500  
501   inline void 501   inline void
HITCBC 502   1738 timer_service::cancel_waiter(waiter_node* w) 502   1658 timer_service::cancel_waiter(waiter_node* w)
503   { 503   {
504   { 504   {
HITCBC 505   1738 std::lock_guard lock(mutex_); 505   1658 std::lock_guard lock(mutex_);
506   // Already removed by another drain: cancel_timer, 506   // Already removed by another drain: cancel_timer,
507   // process_expired, or insert_waiter's lost-cancel recheck 507   // process_expired, or insert_waiter's lost-cancel recheck
HITCBC 508   1738 if (!w->impl_) 508   1658 if (!w->impl_)
HITCBC 509   14 return; 509   6 return;
HITCBC 510   1724 auto* impl = w->impl_; 510   1652 auto* impl = w->impl_;
HITCBC 511   1724 w->impl_ = nullptr; 511   1652 w->impl_ = nullptr;
HITCBC 512   1724 impl->waiter_ = nullptr; 512   1652 impl->waiter_ = nullptr;
HITCBC 513   1724 remove_timer_impl(*impl); 513   1652 remove_timer_impl(*impl);
HITCBC 514   1724 impl->might_have_pending_waits_.store( 514   1652 impl->might_have_pending_waits_.store(
515   false, std::memory_order_relaxed); 515   false, std::memory_order_relaxed);
HITCBC 516   1724 refresh_cached_nearest(); 516   1652 refresh_cached_nearest();
HITCBC 517   1738 } 517   1658 }
518   518  
HITCBC 519   1724 w->ec_ = make_error_code(capy::error::canceled); 519   1652 w->ec_ = make_error_code(capy::error::canceled);
HITCBC 520   1724 sched_->post(&w->op_); 520   1652 sched_->post(&w->op_);
521   } 521   }
522   522  
523   inline std::size_t 523   inline std::size_t
HITCBC 524   362179 timer_service::process_expired() 524   366174 timer_service::process_expired()
525   { 525   {
HITCBC 526   362179 intrusive_list<waiter_node> expired; 526   366174 intrusive_list<waiter_node> expired;
527   527  
528   { 528   {
HITCBC 529   362179 std::lock_guard lock(mutex_); 529   366174 std::lock_guard lock(mutex_);
HITCBC 530   362179 auto now = clock_type::now(); 530   366174 auto now = clock_type::now();
531   531  
HITCBC 532   368881 while (!heap_.empty() && heap_[0].time_ <= now) 532   385090 while (!heap_.empty() && heap_[0].time_ <= now)
533   { 533   {
HITCBC 534   6702 timer::implementation* t = heap_[0].timer_; 534   18916 timer::implementation* t = heap_[0].timer_;
HITCBC 535   6702 remove_timer_impl(*t); 535   18916 remove_timer_impl(*t);
HITCBC 536   6702 if (auto* w = std::exchange(t->waiter_, nullptr)) 536   18916 if (auto* w = std::exchange(t->waiter_, nullptr))
537   { 537   {
HITCBC 538   6702 w->impl_ = nullptr; 538   18916 w->impl_ = nullptr;
HITCBC 539   6702 w->ec_ = {}; 539   18916 w->ec_ = {};
HITCBC 540   6702 expired.push_back(w); 540   18916 expired.push_back(w);
541   } 541   }
HITCBC 542   6702 t->might_have_pending_waits_.store( 542   18916 t->might_have_pending_waits_.store(
543   false, std::memory_order_relaxed); 543   false, std::memory_order_relaxed);
544   } 544   }
545   545  
HITCBC 546   362179 refresh_cached_nearest(); 546   366174 refresh_cached_nearest();
HITCBC 547   362179 } 547   366174 }
548   548  
HITCBC 549   362179 std::size_t count = 0; 549   366174 std::size_t count = 0;
HITCBC 550   368881 while (auto* w = expired.pop_front()) 550   385090 while (auto* w = expired.pop_front())
551   { 551   {
HITCBC 552   6702 sched_->post(&w->op_); 552   18916 sched_->post(&w->op_);
HITCBC 553   6702 ++count; 553   18916 ++count;
HITCBC 554   6702 } 554   18916 }
555   555  
HITCBC 556   362179 return count; 556   366174 return count;
557   } 557   }
558   558  
559   inline void 559   inline void
HITCBC 560   8441 timer_service::remove_timer_impl(timer::implementation& impl) 560   20575 timer_service::remove_timer_impl(timer::implementation& impl)
561   { 561   {
HITCBC 562   8441 std::size_t index = impl.heap_index_.load(std::memory_order_relaxed); 562   20575 std::size_t index = impl.heap_index_.load(std::memory_order_relaxed);
HITCBC 563   8441 if (index >= heap_.size()) 563   20575 if (index >= heap_.size())
MISUBC 564   return; // Not in heap 564   return; // Not in heap
565   565  
HITCBC 566   8441 if (index == heap_.size() - 1) 566   20575 if (index == heap_.size() - 1)
567   { 567   {
568   // Last element, just pop 568   // Last element, just pop
HITCBC 569   1483 impl.heap_index_.store( 569   2491 impl.heap_index_.store(
570   (std::numeric_limits<std::size_t>::max)(), 570   (std::numeric_limits<std::size_t>::max)(),
571   std::memory_order_relaxed); 571   std::memory_order_relaxed);
HITCBC 572   1483 heap_.pop_back(); 572   2491 heap_.pop_back();
573   } 573   }
574   else 574   else
575   { 575   {
576   // Swap with last and reheapify 576   // Swap with last and reheapify
HITCBC 577   6958 swap_heap(index, heap_.size() - 1); 577   18084 swap_heap(index, heap_.size() - 1);
HITCBC 578   6958 impl.heap_index_.store( 578   18084 impl.heap_index_.store(
579   (std::numeric_limits<std::size_t>::max)(), 579   (std::numeric_limits<std::size_t>::max)(),
580   std::memory_order_relaxed); 580   std::memory_order_relaxed);
HITCBC 581   6958 heap_.pop_back(); 581   18084 heap_.pop_back();
582   582  
HITCBC 583   6958 if (index > 0 && heap_[index].time_ < heap_[(index - 1) / 2].time_) 583   18084 if (index > 0 && heap_[index].time_ < heap_[(index - 1) / 2].time_)
HITGBC 584   up_heap(index); 584   1 up_heap(index);
585   else 585   else
HITCBC 586   6958 down_heap(index); 586   18083 down_heap(index);
587   } 587   }
588   } 588   }
589   589  
590   inline void 590   inline void
HITCBC 591   8470 timer_service::up_heap(std::size_t index) 591   20605 timer_service::up_heap(std::size_t index)
592   { 592   {
HITCBC 593   14973 while (index > 0) 593   29682 while (index > 0)
594   { 594   {
HITCBC 595   7344 std::size_t parent = (index - 1) / 2; 595   20765 std::size_t parent = (index - 1) / 2;
HITCBC 596   7344 if (!(heap_[index].time_ < heap_[parent].time_)) 596   20765 if (!(heap_[index].time_ < heap_[parent].time_))
HITCBC 597   841 break; 597   11688 break;
HITCBC 598   6503 swap_heap(index, parent); 598   9077 swap_heap(index, parent);
HITCBC 599   6503 index = parent; 599   9077 index = parent;
600   } 600   }
HITCBC 601   8470 } 601   20605 }
602   602  
603   inline void 603   inline void
HITCBC 604   6958 timer_service::down_heap(std::size_t index) 604   18083 timer_service::down_heap(std::size_t index)
605   { 605   {
HITCBC 606   6958 std::size_t child = index * 2 + 1; 606   18083 std::size_t child = index * 2 + 1;
HITCBC 607   8003 while (child < heap_.size()) 607   36314 while (child < heap_.size())
608   { 608   {
HITCBC 609   1173 std::size_t min_child = (child + 1 == heap_.size() || 609   20179 std::size_t min_child = (child + 1 == heap_.size() ||
HITCBC 610   1038 heap_[child].time_ < heap_[child + 1].time_) 610   16660 heap_[child].time_ < heap_[child + 1].time_)
HITCBC 611   2211 ? child 611   36839 ? child
HITCBC 612   1173 : child + 1; 612   20179 : child + 1;
613   613  
HITCBC 614   1173 if (heap_[index].time_ < heap_[min_child].time_) 614   20179 if (heap_[index].time_ < heap_[min_child].time_)
HITCBC 615   128 break; 615   1948 break;
616   616  
HITCBC 617   1045 swap_heap(index, min_child); 617   18231 swap_heap(index, min_child);
HITCBC 618   1045 index = min_child; 618   18231 index = min_child;
HITCBC 619   1045 child = index * 2 + 1; 619   18231 child = index * 2 + 1;
620   } 620   }
HITCBC 621   6958 } 621   18083 }
622   622  
623   inline void 623   inline void
HITCBC 624   14506 timer_service::swap_heap(std::size_t i1, std::size_t i2) 624   45392 timer_service::swap_heap(std::size_t i1, std::size_t i2)
625   { 625   {
HITCBC 626   14506 heap_entry tmp = heap_[i1]; 626   45392 heap_entry tmp = heap_[i1];
HITCBC 627   14506 heap_[i1] = heap_[i2]; 627   45392 heap_[i1] = heap_[i2];
HITCBC 628   14506 heap_[i2] = tmp; 628   45392 heap_[i2] = tmp;
HITCBC 629   14506 heap_[i1].timer_->heap_index_.store(i1, std::memory_order_relaxed); 629   45392 heap_[i1].timer_->heap_index_.store(i1, std::memory_order_relaxed);
HITCBC 630   14506 heap_[i2].timer_->heap_index_.store(i2, std::memory_order_relaxed); 630   45392 heap_[i2].timer_->heap_index_.store(i2, std::memory_order_relaxed);
HITCBC 631   14506 } 631   45392 }
632   632  
633   // waiter_node's completion_op and canceller members are defined in 633   // waiter_node's completion_op and canceller members are defined in
634   // timer.cpp alongside implementation::wait(), for the same reason 634   // timer.cpp alongside implementation::wait(), for the same reason
635   // wait() lives there (see below). 635   // wait() lives there (see below).
636   636  
637   // timer::implementation::wait() is defined in timer.cpp, not here. 637   // timer::implementation::wait() is defined in timer.cpp, not here.
638   // It must be a non-inline definition in a translation unit that is 638   // It must be a non-inline definition in a translation unit that is
639   // always pulled into the link whenever detail::timer is used (every 639   // always pulled into the link whenever detail::timer is used (every
640   // consumer needs timer's constructors from that same object file). 640   // consumer needs timer's constructors from that same object file).
641   // An inline definition in this header would only be emitted in 641   // An inline definition in this header would only be emitted in
642   // translation units that happen to also include this header, which 642   // translation units that happen to also include this header, which
643   // is not guaranteed for every caller of wait_awaitable::await_suspend 643   // is not guaranteed for every caller of wait_awaitable::await_suspend
644   // in timer.hpp (e.g. code that only reaches timer.hpp through 644   // in timer.hpp (e.g. code that only reaches timer.hpp through
645   // delay.hpp, without transitively including a scheduler header). 645   // delay.hpp, without transitively including a scheduler header).
646   646  
647   // Free functions 647   // Free functions
648   648  
649   inline timer_service& 649   inline timer_service&
HITCBC 650   1790 get_timer_service(capy::execution_context& ctx, scheduler& sched) 650   2095 get_timer_service(capy::execution_context& ctx, scheduler& sched)
651   { 651   {
HITCBC 652   1790 return ctx.make_service<timer_service>(sched); 652   2095 return ctx.make_service<timer_service>(sched);
653   } 653   }
654   654  
655   } // namespace boost::corosio::detail 655   } // namespace boost::corosio::detail
656   656  
657   #endif 657   #endif