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