99.34% Lines (150/151) 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   22470 void push(continuation* c) noexcept 72   46808 void push(continuation* c) noexcept
73   { 73   {
HITCBC 74   22470 c->reserved = nullptr; 74   46808 c->reserved = nullptr;
HITCBC 75   22470 if(tail_) 75   46808 if(tail_)
HITCBC 76   4860 tail_->reserved = c; 76   3252 tail_->reserved = c;
77   else 77   else
HITCBC 78   17610 head_ = c; 78   43556 head_ = c;
HITCBC 79   22470 tail_ = c; 79   46808 tail_ = c;
HITCBC 80   22470 } 80   46808 }
81   81  
HITCBC 82   25350 continuation* pop() noexcept 82   49680 continuation* pop() noexcept
83   { 83   {
HITCBC 84   25350 if(!head_) 84   49680 if(!head_)
HITCBC 85   2880 return nullptr; 85   2872 return nullptr;
HITCBC 86   22470 continuation* c = head_; 86   46808 continuation* c = head_;
HITCBC 87   22470 head_ = static_cast<continuation*>(head_->reserved); 87   46808 head_ = static_cast<continuation*>(head_->reserved);
HITCBC 88   22470 if(!head_) 88   46808 if(!head_)
HITCBC 89   17610 tail_ = nullptr; 89   43556 tail_ = nullptr;
HITCBC 90   22470 return c; 90   46808 return c;
91   } 91   }
92   92  
HITCBC 93   40107 bool empty() const noexcept 93   90343 bool empty() const noexcept
94   { 94   {
HITCBC 95   40107 return head_ == nullptr; 95   90343 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   1440 ~impl() = default; 110   1436 ~impl() = default;
111   111  
112   bool 112   bool
HITCBC 113   3221 running_in_this_thread() const noexcept 113   13213 running_in_this_thread() const noexcept
114   { 114   {
HITCBC 115   3221 return current_.get() == this; 115   13213 return current_.get() == this;
116   } 116   }
117   117  
118   // Abandon queued coroutines. Each is taken under the lock, as other 118   // Abandon queued coroutines. Each is taken under the lock, as other
119   // threads may still post, and abandoned outside it, as abandoning 119   // threads may still post, and abandoned outside it, as abandoning
120   // can post. 120   // can post.
121   void 121   void
HITCBC 122   2880 drain_abandoned() noexcept 122   2872 drain_abandoned() noexcept
123   { 123   {
124   for(;;) 124   for(;;)
125   { 125   {
126   continuation* c; 126   continuation* c;
127   { 127   {
HITCBC 128   3441 std::lock_guard<std::mutex> lock(mutex_); 128   3409 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 129   3441 c = pop(); 129   3409 c = pop();
HITCBC 130   3441 } 130   3409 }
HITCBC 131   3441 if(!c) 131   3409 if(!c)
HITCBC 132   2880 return; 132   2872 return;
HITCBC 133   561 if(c->h && c->h != std::noop_coroutine()) 133   537 if(c->h && c->h != std::noop_coroutine())
HITCBC 134   308 capy::abandon(*c); 134   284 capy::abandon(*c);
HITCBC 135   561 } 135   537 }
136   } 136   }
137   137  
HITCBC 138   1440 impl(std::size_t num_threads, std::string_view thread_name_prefix) 138   1436 impl(std::size_t num_threads, std::string_view thread_name_prefix)
HITCBC 139   1440 : num_threads_(num_threads) 139   1436 : num_threads_(num_threads)
140   { 140   {
HITCBC 141   1440 if(num_threads_ == 0) 141   1436 if(num_threads_ == 0)
HITCBC 142   8 num_threads_ = std::max( 142   8 num_threads_ = std::max(
HITCBC 143   4 std::thread::hardware_concurrency(), 1u); 143   4 std::thread::hardware_concurrency(), 1u);
144   144  
145   // Truncate prefix to 12 chars, leaving room for up to 3-digit index. 145   // Truncate prefix to 12 chars, leaving room for up to 3-digit index.
HITCBC 146   1440 auto n = thread_name_prefix.copy(thread_name_prefix_, 12); 146   1436 auto n = thread_name_prefix.copy(thread_name_prefix_, 12);
HITCBC 147   1440 thread_name_prefix_[n] = '\0'; 147   1436 thread_name_prefix_[n] = '\0';
HITCBC 148   1440 } 148   1436 }
149   149  
150   void 150   void
HITCBC 151   22472 post(continuation& c) 151   46810 post(continuation& c)
152   { 152   {
HITCBC 153   22472 if(!started_.load(std::memory_order_acquire)) 153   46810 if(!started_.load(std::memory_order_acquire))
154   { 154   {
155   { 155   {
HITCBC 156   605 std::lock_guard<std::mutex> lock(mutex_); 156   601 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 157   605 if(stop_) 157   601 if(stop_)
158   { 158   {
159   // Stopped before any thread started: nothing will 159   // Stopped before any thread started: nothing will
160   // run it, and the destructor's drain abandons it. 160   // run it, and the destructor's drain abandons it.
HITCBC 161   220 push(&c); 161   220 push(&c);
HITCBC 162   220 return; 162   220 return;
163   } 163   }
HITCBC 164   605 } 164   601 }
HITCBC 165   385 ensure_started(); 165   381 ensure_started();
166   } 166   }
HITCBC 167   22250 std::lock_guard<std::mutex> lock(mutex_); 167   46588 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 168   22250 push(&c); 168   46588 push(&c);
169   // Under the lock so the pool cannot drain, join, and 169   // Under the lock so the pool cannot drain, join, and
170   // destroy the condition variable mid-signal. 170   // destroy the condition variable mid-signal.
HITCBC 171   22250 work_cv_.notify_one(); 171   46588 work_cv_.notify_one();
HITCBC 172   22250 } 172   46588 }
173   173  
174   void 174   void
HITCBC 175   4098 on_work_started() noexcept 175   14096 on_work_started() noexcept
176   { 176   {
HITCBC 177   4098 outstanding_work_.fetch_add(1, std::memory_order_acq_rel); 177   14096 outstanding_work_.fetch_add(1, std::memory_order_acq_rel);
HITCBC 178   4098 } 178   14096 }
179   179  
180   void 180   void
HITCBC 181   3798 on_work_finished() noexcept 181   13796 on_work_finished() noexcept
182   { 182   {
HITCBC 183   3798 auto n = outstanding_work_.load(std::memory_order_relaxed); 183   13796 auto n = outstanding_work_.load(std::memory_order_relaxed);
HITCBC 184   3806 while(n > 1) 184   13806 while(n > 1)
185   { 185   {
HITCBC 186   2624 if(outstanding_work_.compare_exchange_weak( 186   2014 if(outstanding_work_.compare_exchange_weak(
187   n, n - 1, std::memory_order_acq_rel)) 187   n, n - 1, std::memory_order_acq_rel))
HITCBC 188   1304 return; 188   997 return;
189   } 189   }
HITCBC 190   2494 std::lock_guard<std::mutex> lock(mutex_); 190   12799 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 191   2494 outstanding_work_.fetch_sub(1, std::memory_order_acq_rel); 191   12799 outstanding_work_.fetch_sub(1, std::memory_order_acq_rel);
192   // Re-read: a concurrent on_work_started() may have raised it. 192   // Re-read: a concurrent on_work_started() may have raised it.
HITCBC 193   2494 if(outstanding_work_.load( 193   12799 if(outstanding_work_.load(
HITCBC 194   2494 std::memory_order_acquire) == 0 && joined_ && !stop_) 194   12799 std::memory_order_acquire) == 0 && joined_ && !stop_)
195   { 195   {
HITCBC 196   714 stop_ = true; 196   692 stop_ = true;
HITCBC 197   714 done_cv_.notify_all(); 197   692 done_cv_.notify_all();
HITCBC 198   714 work_cv_.notify_all(); 198   692 work_cv_.notify_all();
199   } 199   }
HITCBC 200   2494 } 200   12799 }
201   201  
202   void 202   void
HITCBC 203   2737 join() noexcept 203   2722 join() noexcept
204   { 204   {
205   { 205   {
HITCBC 206   2737 std::unique_lock<std::mutex> lock(mutex_); 206   2722 std::unique_lock<std::mutex> lock(mutex_);
HITCBC 207   2737 if(joined_) 207   2722 if(joined_)
HITCBC 208   1297 return; 208   1286 return;
HITCBC 209   1440 joined_ = true; 209   1436 joined_ = true;
210   210  
HITCBC 211   1440 if(outstanding_work_.load( 211   1436 if(outstanding_work_.load(
HITCBC 212   1440 std::memory_order_acquire) == 0) 212   1436 std::memory_order_acquire) == 0)
213   { 213   {
HITCBC 214   371 stop_ = true; 214   387 stop_ = true;
HITCBC 215   371 work_cv_.notify_all(); 215   387 work_cv_.notify_all();
216   } 216   }
217   else 217   else
218   { 218   {
HITCBC 219   1069 done_cv_.wait(lock, [this]{ 219   1049 done_cv_.wait(lock, [this]{
HITCBC 220   2084 return stop_; 220   2042 return stop_;
221   }); 221   });
222   } 222   }
HITCBC 223   2737 } 223   2722 }
224   224  
HITCBC 225   1950 for(auto& t : threads_) 225   1946 for(auto& t : threads_)
HITCBC 226   510 if(t.joinable()) 226   510 if(t.joinable())
HITCBC 227   510 t.join(); 227   510 t.join();
228   } 228   }
229   229  
230   void 230   void
HITCBC 231   1743 stop() noexcept 231   1739 stop() noexcept
232   { 232   {
233   // Under the lock: the join() this releases may return and let 233   // Under the lock: the join() this releases may return and let
234   // the pool be destroyed as soon as the lock is free. 234   // the pool be destroyed as soon as the lock is free.
HITCBC 235   1743 std::lock_guard<std::mutex> lock(mutex_); 235   1739 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 236   1743 stop_ = true; 236   1739 stop_ = true;
HITCBC 237   1743 work_cv_.notify_all(); 237   1739 work_cv_.notify_all();
HITCBC 238   1743 done_cv_.notify_all(); 238   1739 done_cv_.notify_all();
HITCBC 239   1743 } 239   1739 }
240   240  
241   private: 241   private:
242   void 242   void
HITCBC 243   385 ensure_started() 243   381 ensure_started()
244   { 244   {
245   // Under the lock, so join() either sees every thread started 245   // Under the lock, so join() either sees every thread started
246   // here or makes this start none. A joined pool still starts them: 246   // here or makes this start none. A joined pool still starts them:
247   // join() may be waiting on work that needs them. 247   // join() may be waiting on work that needs them.
HITCBC 248   385 std::lock_guard<std::mutex> lock(mutex_); 248   381 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 249   385 if(started_.load(std::memory_order_relaxed) || stop_) 249   381 if(started_.load(std::memory_order_relaxed) || stop_)
MISUBC 250   ✗ return; 250   ✗ return;
251   // Start at the threads already running: if an earlier call 251   // Start at the threads already running: if an earlier call
252   // threw partway, this one adds only the missing ones. 252   // threw partway, this one adds only the missing ones.
HITCBC 253   385 threads_.reserve(num_threads_); 253   381 threads_.reserve(num_threads_);
HITCBC 254   895 for(std::size_t i = threads_.size(); i < num_threads_; ++i) 254   891 for(std::size_t i = threads_.size(); i < num_threads_; ++i)
HITCBC 255   1022 threads_.emplace_back([this, i]{ run(i); }); 255   1022 threads_.emplace_back([this, i]{ run(i); });
HITCBC 256   383 started_.store(true, std::memory_order_release); 256   379 started_.store(true, std::memory_order_release);
HITCBC 257   385 } 257   381 }
258   258  
259   void 259   void
HITCBC 260   510 run(std::size_t index) 260   510 run(std::size_t index)
261   { 261   {
262   // Build name; set_current_thread_name truncates to platform limits. 262   // Build name; set_current_thread_name truncates to platform limits.
263   char name[16]; 263   char name[16];
HITCBC 264   510 std::snprintf(name, sizeof(name), "%s%zu", thread_name_prefix_, index); 264   510 std::snprintf(name, sizeof(name), "%s%zu", thread_name_prefix_, index);
HITCBC 265   510 set_current_thread_name(name); 265   510 set_current_thread_name(name);
266   266  
267   // Mark this thread as a worker of this pool so dispatch() 267   // Mark this thread as a worker of this pool so dispatch()
268   // can symmetric-transfer when called from within pool work. 268   // can symmetric-transfer when called from within pool work.
269   struct scoped_pool 269   struct scoped_pool
270   { 270   {
HITCBC 271   510 scoped_pool(impl const* p) noexcept { current_.set(p); } 271   510 scoped_pool(impl const* p) noexcept { current_.set(p); }
HITCBC 272   510 ~scoped_pool() noexcept { current_.set(nullptr); } 272   510 ~scoped_pool() noexcept { current_.set(nullptr); }
HITCBC 273   510 } guard(this); 273   510 } guard(this);
274   274  
275   for(;;) 275   for(;;)
276   { 276   {
HITCBC 277   22419 continuation* c = nullptr; 277   46781 continuation* c = nullptr;
278   { 278   {
HITCBC 279   22419 std::unique_lock<std::mutex> lock(mutex_); 279   46781 std::unique_lock<std::mutex> lock(mutex_);
HITCBC 280   22419 work_cv_.wait(lock, [this]{ 280   46781 work_cv_.wait(lock, [this]{
HITCBC 281   58202 return !empty() || 281   134320 return !empty() ||
HITCBC 282   58202 stop_; 282   134320 stop_;
283   }); 283   });
HITCBC 284   22419 if(stop_) 284   46781 if(stop_)
HITCBC 285   1020 return; 285   1020 return;
HITCBC 286   21909 c = pop(); 286   46271 c = pop();
HITCBC 287   22419 } 287   46781 }
HITCBC 288   21909 if(c) 288   46271 if(c)
HITCBC 289   21909 safe_resume(c->h); 289   46271 safe_resume(c->h);
HITCBC 290   21909 } 290   46271 }
HITCBC 291   510 } 291   510 }
292   }; 292   };
293   293  
294   //------------------------------------------------------------------------------ 294   //------------------------------------------------------------------------------
295   295  
HITCBC 296   1440 thread_pool:: 296   1436 thread_pool::
297   ~thread_pool() 297   ~thread_pool()
298   { 298   {
HITCBC 299   1440 impl_->stop(); 299   1436 impl_->stop();
HITCBC 300   1440 impl_->join(); 300   1436 impl_->join();
HITCBC 301   1440 shutdown(); 301   1436 shutdown();
302   // Takes what the unwinds post too, and the work of any service they 302   // Takes what the unwinds post too, and the work of any service they
303   // create, which is shut down as it is created. 303   // create, which is shut down as it is created.
HITCBC 304   1440 impl_->drain_abandoned(); 304   1436 impl_->drain_abandoned();
HITCBC 305   1440 destroy(); 305   1436 destroy();
306   // Services give up their work in shutdown(), but anything a 306   // Services give up their work in shutdown(), but anything a
307   // destructor posts anyway is still destroyed. 307   // destructor posts anyway is still destroyed.
HITCBC 308   1440 impl_->drain_abandoned(); 308   1436 impl_->drain_abandoned();
HITCBC 309   1440 delete impl_; 309   1436 delete impl_;
HITCBC 310   1440 } 310   1436 }
311   311  
HITCBC 312   1440 thread_pool:: 312   1436 thread_pool::
HITCBC 313   1440 thread_pool(std::size_t num_threads, std::string_view thread_name_prefix) 313   1436 thread_pool(std::size_t num_threads, std::string_view thread_name_prefix)
HITCBC 314   1440 : impl_(new impl(num_threads, thread_name_prefix)) 314   1436 : impl_(new impl(num_threads, thread_name_prefix))
315   { 315   {
HITCBC 316   1440 this->set_frame_allocator(std::allocator<void>{}); 316   1436 this->set_frame_allocator(std::allocator<void>{});
HITCBC 317   1440 } 317   1436 }
318   318  
319   void 319   void
HITCBC 320   1297 thread_pool:: 320   1286 thread_pool::
321   join() noexcept 321   join() noexcept
322   { 322   {
HITCBC 323   1297 impl_->join(); 323   1286 impl_->join();
HITCBC 324   1297 } 324   1286 }
325   325  
326   void 326   void
HITCBC 327   303 thread_pool:: 327   303 thread_pool::
328   stop() noexcept 328   stop() noexcept
329   { 329   {
HITCBC 330   303 impl_->stop(); 330   303 impl_->stop();
HITCBC 331   303 } 331   303 }
332   332  
333   //------------------------------------------------------------------------------ 333   //------------------------------------------------------------------------------
334   334  
335   thread_pool::executor_type 335   thread_pool::executor_type
HITCBC 336   15359 thread_pool:: 336   25360 thread_pool::
337   get_executor() const noexcept 337   get_executor() const noexcept
338   { 338   {
HITCBC 339   15359 return executor_type( 339   25360 return executor_type(
HITCBC 340   15359 const_cast<thread_pool&>(*this)); 340   25360 const_cast<thread_pool&>(*this));
341   } 341   }
342   342  
343   void 343   void
HITCBC 344   4098 thread_pool::executor_type:: 344   14096 thread_pool::executor_type::
345   on_work_started() const noexcept 345   on_work_started() const noexcept
346   { 346   {
HITCBC 347   4098 pool_->impl_->on_work_started(); 347   14096 pool_->impl_->on_work_started();
HITCBC 348   4098 } 348   14096 }
349   349  
350   void 350   void
HITCBC 351   3798 thread_pool::executor_type:: 351   13796 thread_pool::executor_type::
352   on_work_finished() const noexcept 352   on_work_finished() const noexcept
353   { 353   {
HITCBC 354   3798 pool_->impl_->on_work_finished(); 354   13796 pool_->impl_->on_work_finished();
HITCBC 355   3798 } 355   13796 }
356   356  
357   void 357   void
HITCBC 358   19258 thread_pool::executor_type:: 358   33604 thread_pool::executor_type::
359   post(continuation& c) const 359   post(continuation& c) const
360   { 360   {
HITCBC 361   19258 pool_->impl_->post(c); 361   33604 pool_->impl_->post(c);
HITCBC 362   19256 } 362   33602 }
363   363  
364   std::coroutine_handle<> 364   std::coroutine_handle<>
HITCBC 365   3221 thread_pool::executor_type:: 365   13213 thread_pool::executor_type::
366   dispatch(continuation& c) const 366   dispatch(continuation& c) const
367   { 367   {
HITCBC 368   3221 if(pool_->impl_->running_in_this_thread()) 368   13213 if(pool_->impl_->running_in_this_thread())
HITCBC 369   7 return c.h; 369   7 return c.h;
HITCBC 370   3214 pool_->impl_->post(c); 370   13206 pool_->impl_->post(c);
HITCBC 371   3214 return std::noop_coroutine(); 371   13206 return std::noop_coroutine();
372   } 372   }
373   373  
374   } // capy 374   } // capy
375   } // boost 375   } // boost