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   24412 void push(continuation* c) noexcept 72   21967 void push(continuation* c) noexcept
73   { 73   {
HITCBC 74   24412 c->reserved = nullptr; 74   21967 c->reserved = nullptr;
HITCBC 75   24412 if(tail_) 75   21967 if(tail_)
HITCBC 76   2698 tail_->reserved = c; 76   2157 tail_->reserved = c;
77   else 77   else
HITCBC 78   21714 head_ = c; 78   19810 head_ = c;
HITCBC 79   24412 tail_ = c; 79   21967 tail_ = c;
HITCBC 80   24412 } 80   21967 }
81   81  
HITCBC 82   27270 continuation* pop() noexcept 82   24817 continuation* pop() noexcept
83   { 83   {
HITCBC 84   27270 if(!head_) 84   24817 if(!head_)
HITCBC 85   2858 return nullptr; 85   2850 return nullptr;
HITCBC 86   24412 continuation* c = head_; 86   21967 continuation* c = head_;
HITCBC 87   24412 head_ = static_cast<continuation*>(head_->reserved); 87   21967 head_ = static_cast<continuation*>(head_->reserved);
HITCBC 88   24412 if(!head_) 88   21967 if(!head_)
HITCBC 89   21714 tail_ = nullptr; 89   19810 tail_ = nullptr;
HITCBC 90   24412 return c; 90   21967 return c;
91   } 91   }
92   92  
HITCBC 93   46156 bool empty() const noexcept 93   41711 bool empty() const noexcept
94   { 94   {
HITCBC 95   46156 return head_ == nullptr; 95   41711 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   1429 ~impl() = default; 110   1425 ~impl() = default;
111   111  
112   bool 112   bool
HITCBC 113   3212 running_in_this_thread() const noexcept 113   3206 running_in_this_thread() const noexcept
114   { 114   {
HITCBC 115   3212 return current_.get() == this; 115   3206 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   2858 drain_abandoned() noexcept 122   2850 drain_abandoned() noexcept
123   { 123   {
124   for(;;) 124   for(;;)
125   { 125   {
126   continuation* c; 126   continuation* c;
127   { 127   {
HITCBC 128   3395 std::lock_guard<std::mutex> lock(mutex_); 128   3383 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 129   3395 c = pop(); 129   3383 c = pop();
HITCBC 130   3395 } 130   3383 }
HITCBC 131   3395 if(!c) 131   3383 if(!c)
HITCBC 132   2858 return; 132   2850 return;
HITCBC 133   537 if(c->h && c->h != std::noop_coroutine()) 133   533 if(c->h && c->h != std::noop_coroutine())
HITCBC 134   286 capy::abandon(*c); 134   244 capy::abandon(*c);
HITCBC 135   537 } 135   533 }
136   } 136   }
137   137  
HITCBC 138   1429 impl(std::size_t num_threads, std::string_view thread_name_prefix) 138   1425 impl(std::size_t num_threads, std::string_view thread_name_prefix)
HITCBC 139   1429 : num_threads_(num_threads) 139   1425 : num_threads_(num_threads)
140   { 140   {
HITCBC 141   1429 if(num_threads_ == 0) 141   1425 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   1429 auto n = thread_name_prefix.copy(thread_name_prefix_, 12); 146   1425 auto n = thread_name_prefix.copy(thread_name_prefix_, 12);
HITCBC 147   1429 thread_name_prefix_[n] = '\0'; 147   1425 thread_name_prefix_[n] = '\0';
HITCBC 148   1429 } 148   1425 }
149   149  
150   void 150   void
HITCBC 151   24412 post(continuation& c) 151   21969 post(continuation& c)
152   { 152   {
HITCBC 153   24412 if(!started_.load(std::memory_order_acquire)) 153   21969 if(!started_.load(std::memory_order_acquire))
154   { 154   {
155   { 155   {
HITCBC 156   593 std::lock_guard<std::mutex> lock(mutex_); 156   590 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 157   593 if(stop_) 157   590 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   593 } 164   590 }
HITCBC 165   373 ensure_started(); 165   370 ensure_started();
166   } 166   }
HITCBC 167   24192 std::lock_guard<std::mutex> lock(mutex_); 167   21747 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 168   24192 push(&c); 168   21747 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   24192 work_cv_.notify_one(); 171   21747 work_cv_.notify_one();
HITCBC 172   24192 } 172   21747 }
173   173  
174   void 174   void
HITCBC 175   4089 on_work_started() noexcept 175   4083 on_work_started() noexcept
176   { 176   {
HITCBC 177   4089 outstanding_work_.fetch_add(1, std::memory_order_acq_rel); 177   4083 outstanding_work_.fetch_add(1, std::memory_order_acq_rel);
HITCBC 178   4089 } 178   4083 }
179   179  
180   void 180   void
HITCBC 181   3789 on_work_finished() noexcept 181   3783 on_work_finished() noexcept
182   { 182   {
HITCBC 183   3789 auto n = outstanding_work_.load(std::memory_order_relaxed); 183   3783 auto n = outstanding_work_.load(std::memory_order_relaxed);
HITCBC 184   3799 while(n > 1) 184   3791 while(n > 1)
185   { 185   {
HITCBC 186   912 if(outstanding_work_.compare_exchange_weak( 186   2540 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   446 return; 188   1262 return;
189   } 189   }
HITCBC 190   3343 std::lock_guard<std::mutex> lock(mutex_); 190   2521 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 191   3343 outstanding_work_.fetch_sub(1, std::memory_order_acq_rel); 191   2521 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   3343 if(outstanding_work_.load( 193   2521 if(outstanding_work_.load(
HITCBC 194   3343 std::memory_order_acquire) == 0 && joined_ && !stop_) 194   2521 std::memory_order_acquire) == 0 && joined_ && !stop_)
195   { 195   {
HITCBC 196   700 stop_ = true; 196   683 stop_ = true;
HITCBC 197   700 done_cv_.notify_all(); 197   683 done_cv_.notify_all();
HITCBC 198   700 work_cv_.notify_all(); 198   683 work_cv_.notify_all();
199   } 199   }
HITCBC 200   3343 } 200   2521 }
201   201  
202   void 202   void
HITCBC 203   2715 join() noexcept 203   2707 join() noexcept
204   { 204   {
205   { 205   {
HITCBC 206   2715 std::unique_lock<std::mutex> lock(mutex_); 206   2707 std::unique_lock<std::mutex> lock(mutex_);
HITCBC 207   2715 if(joined_) 207   2707 if(joined_)
HITCBC 208   1286 return; 208   1282 return;
HITCBC 209   1429 joined_ = true; 209   1425 joined_ = true;
210   210  
HITCBC 211   1429 if(outstanding_work_.load( 211   1425 if(outstanding_work_.load(
HITCBC 212   1429 std::memory_order_acquire) == 0) 212   1425 std::memory_order_acquire) == 0)
213   { 213   {
HITCBC 214   374 stop_ = true; 214   386 stop_ = true;
HITCBC 215   374 work_cv_.notify_all(); 215   386 work_cv_.notify_all();
216   } 216   }
217   else 217   else
218   { 218   {
HITCBC 219   1055 done_cv_.wait(lock, [this]{ 219   1039 done_cv_.wait(lock, [this]{
HITCBC 220   2056 return stop_; 220   2023 return stop_;
221   }); 221   });
222   } 222   }
HITCBC 223   2715 } 223   2707 }
224   224  
HITCBC 225   1926 for(auto& t : threads_) 225   1920 for(auto& t : threads_)
HITCBC 226   497 if(t.joinable()) 226   495 if(t.joinable())
HITCBC 227   497 t.join(); 227   495 t.join();
228   } 228   }
229   229  
230   void 230   void
HITCBC 231   1732 stop() noexcept 231   1728 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   1732 std::lock_guard<std::mutex> lock(mutex_); 235   1728 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 236   1732 stop_ = true; 236   1728 stop_ = true;
HITCBC 237   1732 work_cv_.notify_all(); 237   1728 work_cv_.notify_all();
HITCBC 238   1732 done_cv_.notify_all(); 238   1728 done_cv_.notify_all();
HITCBC 239   1732 } 239   1728 }
240   240  
241   private: 241   private:
242   void 242   void
HITCBC 243   373 ensure_started() 243   370 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   373 std::lock_guard<std::mutex> lock(mutex_); 248   370 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 249   373 if(started_.load(std::memory_order_relaxed) || stop_) 249   370 if(started_.load(std::memory_order_relaxed) || stop_)
MISLBC 250   1 return; 250   ✗ return;
  251 + // Start at the threads already running: if an earlier call
  252 + // threw partway, this one adds only the missing ones.
HITCBC 251   372 threads_.reserve(num_threads_); 253   370 threads_.reserve(num_threads_);
HITCBC 252 - 869 for(std::size_t i = 0; i < num_threads_; ++i) 254 + 865 for(std::size_t i = threads_.size(); i < num_threads_; ++i)
HITCBC 253   994 threads_.emplace_back([this, i]{ run(i); }); 255   992 threads_.emplace_back([this, i]{ run(i); });
HITCBC 254   372 started_.store(true, std::memory_order_release); 256   368 started_.store(true, std::memory_order_release);
HITCBC 255   373 } 257   370 }
256   258  
257   void 259   void
HITCBC 258   497 run(std::size_t index) 260   495 run(std::size_t index)
259   { 261   {
260   // Build name; set_current_thread_name truncates to platform limits. 262   // Build name; set_current_thread_name truncates to platform limits.
261   char name[16]; 263   char name[16];
HITCBC 262   497 std::snprintf(name, sizeof(name), "%s%zu", thread_name_prefix_, index); 264   495 std::snprintf(name, sizeof(name), "%s%zu", thread_name_prefix_, index);
HITCBC 263   497 set_current_thread_name(name); 265   495 set_current_thread_name(name);
264   266  
265   // Mark this thread as a worker of this pool so dispatch() 267   // Mark this thread as a worker of this pool so dispatch()
266   // can symmetric-transfer when called from within pool work. 268   // can symmetric-transfer when called from within pool work.
267   struct scoped_pool 269   struct scoped_pool
268   { 270   {
HITCBC 269   497 scoped_pool(impl const* p) noexcept { current_.set(p); } 271   495 scoped_pool(impl const* p) noexcept { current_.set(p); }
HITCBC 270   497 ~scoped_pool() noexcept { current_.set(nullptr); } 272   495 ~scoped_pool() noexcept { current_.set(nullptr); }
HITCBC 271   497 } guard(this); 273   495 } guard(this);
272   274  
273   for(;;) 275   for(;;)
274   { 276   {
HITCBC 275   24372 continuation* c = nullptr; 277   21929 continuation* c = nullptr;
276   { 278   {
HITCBC 277   24372 std::unique_lock<std::mutex> lock(mutex_); 279   21929 std::unique_lock<std::mutex> lock(mutex_);
HITCBC 278   24372 work_cv_.wait(lock, [this]{ 280   21929 work_cv_.wait(lock, [this]{
HITCBC 279   68344 return !empty() || 281   61885 return !empty() ||
HITCBC 280   68344 stop_; 282   61885 stop_;
281   }); 283   });
HITCBC 282   24372 if(stop_) 284   21929 if(stop_)
HITCBC 283   994 return; 285   990 return;
HITCBC 284   23875 c = pop(); 286   21434 c = pop();
HITCBC 285   24372 } 287   21929 }
HITCBC 286   23875 if(c) 288   21434 if(c)
HITCBC 287   23875 safe_resume(c->h); 289   21434 safe_resume(c->h);
HITCBC 288   23875 } 290   21434 }
HITCBC 289   497 } 291   495 }
290   }; 292   };
291   293  
292   //------------------------------------------------------------------------------ 294   //------------------------------------------------------------------------------
293   295  
HITCBC 294   1429 thread_pool:: 296   1425 thread_pool::
295   ~thread_pool() 297   ~thread_pool()
296   { 298   {
HITCBC 297   1429 impl_->stop(); 299   1425 impl_->stop();
HITCBC 298   1429 impl_->join(); 300   1425 impl_->join();
HITCBC 299   1429 shutdown(); 301   1425 shutdown();
300   // 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
301   // create, which is shut down as it is created. 303   // create, which is shut down as it is created.
HITCBC 302   1429 impl_->drain_abandoned(); 304   1425 impl_->drain_abandoned();
HITCBC 303   1429 destroy(); 305   1425 destroy();
304   // Services give up their work in shutdown(), but anything a 306   // Services give up their work in shutdown(), but anything a
305   // destructor posts anyway is still destroyed. 307   // destructor posts anyway is still destroyed.
HITCBC 306   1429 impl_->drain_abandoned(); 308   1425 impl_->drain_abandoned();
HITCBC 307   1429 delete impl_; 309   1425 delete impl_;
HITCBC 308   1429 } 310   1425 }
309   311  
HITCBC 310   1429 thread_pool:: 312   1425 thread_pool::
HITCBC 311   1429 thread_pool(std::size_t num_threads, std::string_view thread_name_prefix) 313   1425 thread_pool(std::size_t num_threads, std::string_view thread_name_prefix)
HITCBC 312   1429 : impl_(new impl(num_threads, thread_name_prefix)) 314   1425 : impl_(new impl(num_threads, thread_name_prefix))
313   { 315   {
HITCBC 314   1429 this->set_frame_allocator(std::allocator<void>{}); 316   1425 this->set_frame_allocator(std::allocator<void>{});
HITCBC 315   1429 } 317   1425 }
316   318  
317   void 319   void
HITCBC 318   1286 thread_pool:: 320   1282 thread_pool::
319   join() noexcept 321   join() noexcept
320   { 322   {
HITCBC 321   1286 impl_->join(); 323   1282 impl_->join();
HITCBC 322   1286 } 324   1282 }
323   325  
324   void 326   void
HITCBC 325   303 thread_pool:: 327   303 thread_pool::
326   stop() noexcept 328   stop() noexcept
327   { 329   {
HITCBC 328   303 impl_->stop(); 330   303 impl_->stop();
HITCBC 329   303 } 331   303 }
330   332  
331   //------------------------------------------------------------------------------ 333   //------------------------------------------------------------------------------
332   334  
333   thread_pool::executor_type 335   thread_pool::executor_type
HITCBC 334   15346 thread_pool:: 336   15344 thread_pool::
335   get_executor() const noexcept 337   get_executor() const noexcept
336   { 338   {
HITCBC 337   15346 return executor_type( 339   15344 return executor_type(
HITCBC 338   15346 const_cast<thread_pool&>(*this)); 340   15344 const_cast<thread_pool&>(*this));
339   } 341   }
340   342  
341   void 343   void
HITCBC 342   4089 thread_pool::executor_type:: 344   4083 thread_pool::executor_type::
343   on_work_started() const noexcept 345   on_work_started() const noexcept
344   { 346   {
HITCBC 345   4089 pool_->impl_->on_work_started(); 347   4083 pool_->impl_->on_work_started();
HITCBC 346   4089 } 348   4083 }
347   349  
348   void 350   void
HITCBC 349   3789 thread_pool::executor_type:: 351   3783 thread_pool::executor_type::
350   on_work_finished() const noexcept 352   on_work_finished() const noexcept
351   { 353   {
HITCBC 352   3789 pool_->impl_->on_work_finished(); 354   3783 pool_->impl_->on_work_finished();
HITCBC 353   3789 } 355   3783 }
354   356  
355   void 357   void
HITCBC 356   21207 thread_pool::executor_type:: 358   18770 thread_pool::executor_type::
357   post(continuation& c) const 359   post(continuation& c) const
358   { 360   {
HITCBC 359   21207 pool_->impl_->post(c); 361   18770 pool_->impl_->post(c);
HITCBC 360   21207 } 362   18768 }
361   363  
362   std::coroutine_handle<> 364   std::coroutine_handle<>
HITCBC 363   3212 thread_pool::executor_type:: 365   3206 thread_pool::executor_type::
364   dispatch(continuation& c) const 366   dispatch(continuation& c) const
365   { 367   {
HITCBC 366   3212 if(pool_->impl_->running_in_this_thread()) 368   3206 if(pool_->impl_->running_in_this_thread())
HITCBC 367   7 return c.h; 369   7 return c.h;
HITCBC 368   3205 pool_->impl_->post(c); 370   3199 pool_->impl_->post(c);
HITCBC 369   3205 return std::noop_coroutine(); 371   3199 return std::noop_coroutine();
370   } 372   }
371   373  
372   } // capy 374   } // capy
373   } // boost 375   } // boost