99.35% Lines (152/153) 100.00% Functions (28/28)
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 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
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/boostorg/capy 8   // Official repository: https://github.com/boostorg/capy
9   // 9   //
10   10  
11   #include <boost/capy/ex/thread_pool.hpp> 11   #include <boost/capy/ex/thread_pool.hpp>
12   #include <boost/capy/continuation.hpp> 12   #include <boost/capy/continuation.hpp>
13   #include <boost/capy/detail/thread_local_ptr.hpp> 13   #include <boost/capy/detail/thread_local_ptr.hpp>
14   #include <boost/capy/ex/abandon.hpp> 14   #include <boost/capy/ex/abandon.hpp>
15   #include <boost/capy/ex/frame_allocator.hpp> 15   #include <boost/capy/ex/frame_allocator.hpp>
16   #include <boost/capy/test/thread_name.hpp> 16   #include <boost/capy/test/thread_name.hpp>
17   #include <algorithm> 17   #include <algorithm>
18   #include <atomic> 18   #include <atomic>
19   #include <condition_variable> 19   #include <condition_variable>
20   #include <cstdio> 20   #include <cstdio>
21   #include <mutex> 21   #include <mutex>
22   #include <thread> 22   #include <thread>
23   #include <vector> 23   #include <vector>
24   24  
25   /* 25   /*
26   Thread pool implementation using a shared work queue. 26   Thread pool implementation using a shared work queue.
27   27  
28   Work items are continuations linked via their intrusive next pointer, 28   Work items are continuations linked via their intrusive next pointer,
29   stored in a single queue protected by a mutex. No per-post heap 29   stored in a single queue protected by a mutex. No per-post heap
30   allocation: the continuation is owned by the caller and linked 30   allocation: the continuation is owned by the caller and linked
31   directly. Worker threads wait on a condition_variable until work 31   directly. Worker threads wait on a condition_variable until work
32   is available or stop is requested. 32   is available or stop is requested.
33   33  
34   Threads are started lazily on first post(), under mutex_, to avoid 34   Threads are started lazily on first post(), under mutex_, to avoid
35   spawning threads for pools that are constructed but never used. A 35   spawning threads for pools that are constructed but never used. A
36   stopped pool starts none. Each thread is named 36   stopped pool starts none. Each thread is named
37   with a configurable prefix plus index for debugger visibility. 37   with a configurable prefix plus index for debugger visibility.
38   38  
39   Work tracking: on_work_started/on_work_finished maintain the atomic 39   Work tracking: on_work_started/on_work_finished maintain the atomic
40   outstanding_work_ counter. on_work_started is lock-free, and so is 40   outstanding_work_ counter. on_work_started is lock-free, and so is
41   on_work_finished while the count stays above one. The release that 41   on_work_finished while the count stays above one. The release that
42   may take it to zero is made under mutex_, so join() can't see zero, 42   may take it to zero is made under mutex_, so join() can't see zero,
43   return and let the pool be destroyed while that call still uses it. 43   return and let the pool be destroyed while that call still uses it.
44   join() blocks until this counter reaches zero, then signals workers 44   join() blocks until this counter reaches zero, then signals workers
45   to stop and joins threads. 45   to stop and joins threads.
46   46  
47   Two shutdown paths: 47   Two shutdown paths:
48   - join(): waits for outstanding work to drain, then stops workers. 48   - join(): waits for outstanding work to drain, then stops workers.
49   - stop(): immediately signals workers to exit; queued work stays queued. 49   - stop(): immediately signals workers to exit; queued work stays queued.
50   - Destructor: stop() then join(), then queued work is abandoned. 50   - Destructor: stop() then join(), then queued work is abandoned.
51   */ 51   */
52   52  
53   namespace boost { 53   namespace boost {
54   namespace capy { 54   namespace capy {
55   55  
56   //------------------------------------------------------------------------------ 56   //------------------------------------------------------------------------------
57   57  
58   class thread_pool::impl 58   class thread_pool::impl
59   { 59   {
60   // Identifies the pool owning the current worker thread, or 60   // Identifies the pool owning the current worker thread, or
61   // nullptr if the calling thread is not a pool worker. Checked 61   // nullptr if the calling thread is not a pool worker. Checked
62   // by dispatch() to decide between symmetric transfer (inline 62   // by dispatch() to decide between symmetric transfer (inline
63   // resume) and post. 63   // resume) and post.
64   static inline detail::thread_local_ptr<impl const> current_; 64   static inline detail::thread_local_ptr<impl const> current_;
65   65  
66   // Intrusive queue of continuations: the next link is stored in 66   // Intrusive queue of continuations: the next link is stored in
67   // continuation::reserved (typed continuation* round-tripped through 67   // continuation::reserved (typed continuation* round-tripped through
68   // void*). No per-post allocation: the continuation is owned by the caller. 68   // void*). No per-post allocation: the continuation is owned by the caller.
69   continuation* head_ = nullptr; 69   continuation* head_ = nullptr;
70   continuation* tail_ = nullptr; 70   continuation* tail_ = nullptr;
71   71  
HITCBC 72   23401 void push(continuation* c) noexcept 72   24103 void push(continuation* c) noexcept
73   { 73   {
HITCBC 74   23401 c->reserved = nullptr; 74   24103 c->reserved = nullptr;
HITCBC 75   23401 if(tail_) 75   24103 if(tail_)
HITCBC 76   5000 tail_->reserved = c; 76   3988 tail_->reserved = c;
77   else 77   else
HITCBC 78   18401 head_ = c; 78   20115 head_ = c;
HITCBC 79   23401 tail_ = c; 79   24103 tail_ = c;
HITCBC 80   23401 } 80   24103 }
81   81  
HITCBC 82   26274 continuation* pop() noexcept 82   26954 continuation* pop() noexcept
83   { 83   {
HITCBC 84   26274 if(!head_) 84   26954 if(!head_)
HITCBC 85   2873 return nullptr; 85   2851 return nullptr;
HITCBC 86   23401 continuation* c = head_; 86   24103 continuation* c = head_;
HITCBC 87   23401 head_ = static_cast<continuation*>(head_->reserved); 87   24103 head_ = static_cast<continuation*>(head_->reserved);
HITCBC 88   23401 if(!head_) 88   24103 if(!head_)
HITCBC 89   18401 tail_ = nullptr; 89   20115 tail_ = nullptr;
HITCBC 90   23401 return c; 90   24103 return c;
91   } 91   }
92   92  
HITCBC 93   39852 bool empty() const noexcept 93   42747 bool empty() const noexcept
94   { 94   {
HITCBC 95   39852 return head_ == nullptr; 95   42747 return head_ == nullptr;
96   } 96   }
97   97  
98   std::mutex mutex_; 98   std::mutex mutex_;
99   std::condition_variable work_cv_; 99   std::condition_variable work_cv_;
100   std::condition_variable done_cv_; 100   std::condition_variable done_cv_;
101   std::vector<std::thread> threads_; 101   std::vector<std::thread> threads_;
102   std::atomic<std::size_t> outstanding_work_{0}; 102   std::atomic<std::size_t> outstanding_work_{0};
103   bool stop_{false}; 103   bool stop_{false};
104   bool joined_{false}; 104   bool joined_{false};
105   std::size_t num_threads_; 105   std::size_t num_threads_;
106   char thread_name_prefix_[13]{}; // 12 chars max + null terminator 106   char thread_name_prefix_[13]{}; // 12 chars max + null terminator
107   std::atomic<bool> started_{false}; 107   std::atomic<bool> started_{false};
108   108  
109   public: 109   public:
HITCBC 110   1435 ~impl() = default; 110   1424 ~impl() = default;
111   111  
112   bool 112   bool
HITCBC 113   3216 running_in_this_thread() const noexcept 113   3207 running_in_this_thread() const noexcept
114   { 114   {
HITCBC 115   3216 return current_.get() == this; 115   3207 return current_.get() == this;
116   } 116   }
117   117  
118   // Abandon queued coroutines. Called after the services shut down, 118   // Abandon queued coroutines. Called after the services shut down,
119   // since their shutdown can post more, and again after they are 119   // since their shutdown can post more, and again after they are
120   // destroyed, for anything their destructors post. Each is taken 120   // destroyed, for anything their destructors post. Each is taken
121   // under the lock, as other threads may still post, and abandoned 121   // under the lock, as other threads may still post, and abandoned
122   // outside it, as abandoning can post. Returns whether there was 122   // outside it, as abandoning can post. Returns whether there was
123   // anything to abandon. 123   // anything to abandon.
124   bool 124   bool
HITCBC 125   2873 drain_abandoned() noexcept 125   2851 drain_abandoned() noexcept
126   { 126   {
HITCBC 127   2873 bool any = false; 127   2851 bool any = false;
128   for(;;) 128   for(;;)
129   { 129   {
130   continuation* c; 130   continuation* c;
131   { 131   {
HITCBC 132   5429 std::lock_guard<std::mutex> lock(mutex_); 132   5382 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 133   5429 c = pop(); 133   5382 c = pop();
HITCBC 134   5429 } 134   5382 }
HITCBC 135   5429 if(!c) 135   5382 if(!c)
HITCBC 136   2873 return any; 136   2851 return any;
HITCBC 137   2556 any = true; 137   2531 any = true;
HITCBC 138   2556 if(c->h && c->h != std::noop_coroutine()) 138   2531 if(c->h && c->h != std::noop_coroutine())
HITCBC 139   2305 capy::abandon(*c); 139   2280 capy::abandon(*c);
HITCBC 140   2556 } 140   2531 }
141   } 141   }
142   142  
HITCBC 143   1435 impl(std::size_t num_threads, std::string_view thread_name_prefix) 143   1424 impl(std::size_t num_threads, std::string_view thread_name_prefix)
HITCBC 144   1435 : num_threads_(num_threads) 144   1424 : num_threads_(num_threads)
145   { 145   {
HITCBC 146   1435 if(num_threads_ == 0) 146   1424 if(num_threads_ == 0)
HITCBC 147   8 num_threads_ = std::max( 147   8 num_threads_ = std::max(
HITCBC 148   4 std::thread::hardware_concurrency(), 1u); 148   4 std::thread::hardware_concurrency(), 1u);
149   149  
150   // Truncate prefix to 12 chars, leaving room for up to 3-digit index. 150   // Truncate prefix to 12 chars, leaving room for up to 3-digit index.
HITCBC 151   1435 auto n = thread_name_prefix.copy(thread_name_prefix_, 12); 151   1424 auto n = thread_name_prefix.copy(thread_name_prefix_, 12);
HITCBC 152   1435 thread_name_prefix_[n] = '\0'; 152   1424 thread_name_prefix_[n] = '\0';
HITCBC 153   1435 } 153   1424 }
154   154  
155   void 155   void
HITCBC 156   23401 post(continuation& c) 156   24103 post(continuation& c)
157   { 157   {
HITCBC 158   23401 if(!started_.load(std::memory_order_acquire)) 158   24103 if(!started_.load(std::memory_order_acquire))
159   { 159   {
160   { 160   {
HITCBC 161   2582 std::lock_guard<std::mutex> lock(mutex_); 161   2571 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 162   2582 if(stop_) 162   2571 if(stop_)
163   { 163   {
164   // Stopped before any thread started: nothing will 164   // Stopped before any thread started: nothing will
165   // run it, and the destructor's drain abandons it. 165   // run it, and the destructor's drain abandons it.
HITCBC 166   2204 push(&c); 166   2204 push(&c);
HITCBC 167   2204 return; 167   2204 return;
168   } 168   }
HITCBC 169   2582 } 169   2571 }
HITCBC 170   378 ensure_started(); 170   367 ensure_started();
171   } 171   }
HITCBC 172   21197 std::lock_guard<std::mutex> lock(mutex_); 172   21899 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 173   21197 push(&c); 173   21899 push(&c);
174   // Under the lock so the pool cannot drain, join, and 174   // Under the lock so the pool cannot drain, join, and
175   // destroy the condition variable mid-signal. 175   // destroy the condition variable mid-signal.
HITCBC 176   21197 work_cv_.notify_one(); 176   21899 work_cv_.notify_one();
HITCBC 177   21197 } 177   21899 }
178   178  
179   void 179   void
HITCBC 180   4093 on_work_started() noexcept 180   4084 on_work_started() noexcept
181   { 181   {
HITCBC 182   4093 outstanding_work_.fetch_add(1, std::memory_order_acq_rel); 182   4084 outstanding_work_.fetch_add(1, std::memory_order_acq_rel);
HITCBC 183   4093 } 183   4084 }
184   184  
185   void 185   void
HITCBC 186   3793 on_work_finished() noexcept 186   3784 on_work_finished() noexcept
187   { 187   {
HITCBC 188   3793 auto n = outstanding_work_.load(std::memory_order_relaxed); 188   3784 auto n = outstanding_work_.load(std::memory_order_relaxed);
HITCBC 189   3799 while(n > 1) 189   3794 while(n > 1)
190   { 190   {
HITCBC 191   2620 if(outstanding_work_.compare_exchange_weak( 191   2802 if(outstanding_work_.compare_exchange_weak(
192   n, n - 1, std::memory_order_acq_rel)) 192   n, n - 1, std::memory_order_acq_rel))
HITCBC 193   1304 return; 193   1391 return;
194   } 194   }
HITCBC 195   2489 std::lock_guard<std::mutex> lock(mutex_); 195   2393 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 196   2489 outstanding_work_.fetch_sub(1, std::memory_order_acq_rel); 196   2393 outstanding_work_.fetch_sub(1, std::memory_order_acq_rel);
197   // Re-read: a concurrent on_work_started() may have raised it. 197   // Re-read: a concurrent on_work_started() may have raised it.
HITCBC 198   2489 if(outstanding_work_.load( 198   2393 if(outstanding_work_.load(
HITCBC 199   2489 std::memory_order_acquire) == 0 && joined_ && !stop_) 199   2393 std::memory_order_acquire) == 0 && joined_ && !stop_)
200   { 200   {
HITCBC 201   698 stop_ = true; 201   690 stop_ = true;
HITCBC 202   698 done_cv_.notify_all(); 202   690 done_cv_.notify_all();
HITCBC 203   698 work_cv_.notify_all(); 203   690 work_cv_.notify_all();
204   } 204   }
HITCBC 205   2489 } 205   2393 }
206   206  
207   void 207   void
HITCBC 208   2727 join() noexcept 208   2705 join() noexcept
209   { 209   {
210   { 210   {
HITCBC 211   2727 std::unique_lock<std::mutex> lock(mutex_); 211   2705 std::unique_lock<std::mutex> lock(mutex_);
HITCBC 212   2727 if(joined_) 212   2705 if(joined_)
HITCBC 213   1292 return; 213   1281 return;
HITCBC 214   1435 joined_ = true; 214   1424 joined_ = true;
215   215  
HITCBC 216   1435 if(outstanding_work_.load( 216   1424 if(outstanding_work_.load(
HITCBC 217   1435 std::memory_order_acquire) == 0) 217   1424 std::memory_order_acquire) == 0)
218   { 218   {
HITCBC 219   381 stop_ = true; 219   378 stop_ = true;
HITCBC 220   381 work_cv_.notify_all(); 220   378 work_cv_.notify_all();
221   } 221   }
222   else 222   else
223   { 223   {
HITCBC 224   1054 done_cv_.wait(lock, [this]{ 224   1046 done_cv_.wait(lock, [this]{
HITCBC 225   2053 return stop_; 225   2037 return stop_;
226   }); 226   });
227   } 227   }
HITCBC 228   2727 } 228   2705 }
229   229  
HITCBC 230   1938 for(auto& t : threads_) 230   1916 for(auto& t : threads_)
HITCBC 231   503 if(t.joinable()) 231   492 if(t.joinable())
HITCBC 232   503 t.join(); 232   492 t.join();
233   } 233   }
234   234  
235   void 235   void
HITCBC 236   1738 stop() noexcept 236   1727 stop() noexcept
237   { 237   {
238   // Under the lock: the join() this releases may return and let 238   // Under the lock: the join() this releases may return and let
239   // the pool be destroyed as soon as the lock is free. 239   // the pool be destroyed as soon as the lock is free.
HITCBC 240   1738 std::lock_guard<std::mutex> lock(mutex_); 240   1727 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 241   1738 stop_ = true; 241   1727 stop_ = true;
HITCBC 242   1738 work_cv_.notify_all(); 242   1727 work_cv_.notify_all();
HITCBC 243   1738 done_cv_.notify_all(); 243   1727 done_cv_.notify_all();
HITCBC 244   1738 } 244   1727 }
245   245  
246   private: 246   private:
247   void 247   void
HITCBC 248   378 ensure_started() 248   367 ensure_started()
249   { 249   {
250   // Under the lock, so join() either sees every thread started 250   // Under the lock, so join() either sees every thread started
251   // here or makes this start none. A joined pool still starts them: 251   // here or makes this start none. A joined pool still starts them:
252   // join() may be waiting on work that needs them. 252   // join() may be waiting on work that needs them.
HITCBC 253   378 std::lock_guard<std::mutex> lock(mutex_); 253   367 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 254   378 if(started_.load(std::memory_order_relaxed) || stop_) 254   367 if(started_.load(std::memory_order_relaxed) || stop_)
MISUBC 255   ✗ return; 255   ✗ return;
HITCBC 256   378 threads_.reserve(num_threads_); 256   367 threads_.reserve(num_threads_);
HITCBC 257   881 for(std::size_t i = 0; i < num_threads_; ++i) 257   859 for(std::size_t i = 0; i < num_threads_; ++i)
HITCBC 258   1006 threads_.emplace_back([this, i]{ run(i); }); 258   984 threads_.emplace_back([this, i]{ run(i); });
HITCBC 259   378 started_.store(true, std::memory_order_release); 259   367 started_.store(true, std::memory_order_release);
HITCBC 260   378 } 260   367 }
261   261  
262   void 262   void
HITCBC 263   503 run(std::size_t index) 263   492 run(std::size_t index)
264   { 264   {
265   // Build name; set_current_thread_name truncates to platform limits. 265   // Build name; set_current_thread_name truncates to platform limits.
266   char name[16]; 266   char name[16];
HITCBC 267   503 std::snprintf(name, sizeof(name), "%s%zu", thread_name_prefix_, index); 267   492 std::snprintf(name, sizeof(name), "%s%zu", thread_name_prefix_, index);
HITCBC 268   503 set_current_thread_name(name); 268   492 set_current_thread_name(name);
269   269  
270   // Mark this thread as a worker of this pool so dispatch() 270   // Mark this thread as a worker of this pool so dispatch()
271   // can symmetric-transfer when called from within pool work. 271   // can symmetric-transfer when called from within pool work.
272   struct scoped_pool 272   struct scoped_pool
273   { 273   {
HITCBC 274   503 scoped_pool(impl const* p) noexcept { current_.set(p); } 274   492 scoped_pool(impl const* p) noexcept { current_.set(p); }
HITCBC 275   503 ~scoped_pool() noexcept { current_.set(nullptr); } 275   492 ~scoped_pool() noexcept { current_.set(nullptr); }
HITCBC 276   503 } guard(this); 276   492 } guard(this);
277   277  
278   for(;;) 278   for(;;)
279   { 279   {
HITCBC 280   21348 continuation* c = nullptr; 280   22064 continuation* c = nullptr;
281   { 281   {
HITCBC 282   21348 std::unique_lock<std::mutex> lock(mutex_); 282   22064 std::unique_lock<std::mutex> lock(mutex_);
HITCBC 283   21348 work_cv_.wait(lock, [this]{ 283   22064 work_cv_.wait(lock, [this]{
HITCBC 284   58759 return !empty() || 284   63825 return !empty() ||
HITCBC 285   58759 stop_; 285   63825 stop_;
286   }); 286   });
HITCBC 287   21348 if(stop_) 287   22064 if(stop_)
HITCBC 288   1006 return; 288   984 return;
HITCBC 289   20845 c = pop(); 289   21572 c = pop();
HITCBC 290   21348 } 290   22064 }
HITCBC 291   20845 if(c) 291   21572 if(c)
HITCBC 292   20845 safe_resume(c->h); 292   21572 safe_resume(c->h);
HITCBC 293   20845 } 293   21572 }
HITCBC 294   503 } 294   492 }
295   }; 295   };
296   296  
297   //------------------------------------------------------------------------------ 297   //------------------------------------------------------------------------------
298   298  
HITCBC 299   1435 thread_pool:: 299   1424 thread_pool::
300   ~thread_pool() 300   ~thread_pool()
301   { 301   {
HITCBC 302   1435 impl_->stop(); 302   1424 impl_->stop();
HITCBC 303   1435 impl_->join(); 303   1424 impl_->join();
HITCBC 304   1435 shutdown(); 304   1424 shutdown();
HITCBC 305   1435 impl_->drain_abandoned(); 305   1424 impl_->drain_abandoned();
306   // A frame the drain unwinds can create a service, whose destructor 306   // A frame the drain unwinds can create a service, whose destructor
307   // can post again: repeat until nothing is left of either. 307   // can post again: repeat until nothing is left of either.
308   do 308   do
309   { 309   {
HITCBC 310   1438 destroy(); 310   1427 destroy();
311   } 311   }
HITCBC 312   1438 while(impl_->drain_abandoned()); 312   1427 while(impl_->drain_abandoned());
HITCBC 313   1435 delete impl_; 313   1424 delete impl_;
HITCBC 314   1435 } 314   1424 }
315   315  
HITCBC 316   1435 thread_pool:: 316   1424 thread_pool::
HITCBC 317   1435 thread_pool(std::size_t num_threads, std::string_view thread_name_prefix) 317   1424 thread_pool(std::size_t num_threads, std::string_view thread_name_prefix)
HITCBC 318   1435 : impl_(new impl(num_threads, thread_name_prefix)) 318   1424 : impl_(new impl(num_threads, thread_name_prefix))
319   { 319   {
HITCBC 320   1435 this->set_frame_allocator(std::allocator<void>{}); 320   1424 this->set_frame_allocator(std::allocator<void>{});
HITCBC 321   1435 } 321   1424 }
322   322  
323   void 323   void
HITCBC 324   1292 thread_pool:: 324   1281 thread_pool::
325   join() noexcept 325   join() noexcept
326   { 326   {
HITCBC 327   1292 impl_->join(); 327   1281 impl_->join();
HITCBC 328   1292 } 328   1281 }
329   329  
330   void 330   void
HITCBC 331   303 thread_pool:: 331   303 thread_pool::
332   stop() noexcept 332   stop() noexcept
333   { 333   {
HITCBC 334   303 impl_->stop(); 334   303 impl_->stop();
HITCBC 335   303 } 335   303 }
336   336  
337   //------------------------------------------------------------------------------ 337   //------------------------------------------------------------------------------
338   338  
339   thread_pool::executor_type 339   thread_pool::executor_type
HITCBC 340   15352 thread_pool:: 340   15341 thread_pool::
341   get_executor() const noexcept 341   get_executor() const noexcept
342   { 342   {
HITCBC 343   15352 return executor_type( 343   15341 return executor_type(
HITCBC 344   15352 const_cast<thread_pool&>(*this)); 344   15341 const_cast<thread_pool&>(*this));
345   } 345   }
346   346  
347   void 347   void
HITCBC 348   4093 thread_pool::executor_type:: 348   4084 thread_pool::executor_type::
349   on_work_started() const noexcept 349   on_work_started() const noexcept
350   { 350   {
HITCBC 351   4093 pool_->impl_->on_work_started(); 351   4084 pool_->impl_->on_work_started();
HITCBC 352   4093 } 352   4084 }
353   353  
354   void 354   void
HITCBC 355   3793 thread_pool::executor_type:: 355   3784 thread_pool::executor_type::
356   on_work_finished() const noexcept 356   on_work_finished() const noexcept
357   { 357   {
HITCBC 358   3793 pool_->impl_->on_work_finished(); 358   3784 pool_->impl_->on_work_finished();
HITCBC 359   3793 } 359   3784 }
360   360  
361   void 361   void
HITCBC 362   20192 thread_pool::executor_type:: 362   20903 thread_pool::executor_type::
363   post(continuation& c) const 363   post(continuation& c) const
364   { 364   {
HITCBC 365   20192 pool_->impl_->post(c); 365   20903 pool_->impl_->post(c);
HITCBC 366   20192 } 366   20903 }
367   367  
368   std::coroutine_handle<> 368   std::coroutine_handle<>
HITCBC 369   3216 thread_pool::executor_type:: 369   3207 thread_pool::executor_type::
370   dispatch(continuation& c) const 370   dispatch(continuation& c) const
371   { 371   {
HITCBC 372   3216 if(pool_->impl_->running_in_this_thread()) 372   3207 if(pool_->impl_->running_in_this_thread())
HITCBC 373   7 return c.h; 373   7 return c.h;
HITCBC 374   3209 pool_->impl_->post(c); 374   3200 pool_->impl_->post(c);
HITCBC 375   3209 return std::noop_coroutine(); 375   3200 return std::noop_coroutine();
376   } 376   }
377   377  
378   } // capy 378   } // capy
379   } // boost 379   } // boost