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   22056 void push(continuation* c) noexcept 72   22835 void push(continuation* c) noexcept
73   { 73   {
HITCBC 74   22056 c->reserved = nullptr; 74   22835 c->reserved = nullptr;
HITCBC 75   22056 if(tail_) 75   22835 if(tail_)
HITCBC 76   2714 tail_->reserved = c; 76   4887 tail_->reserved = c;
77   else 77   else
HITCBC 78   19342 head_ = c; 78   17948 head_ = c;
HITCBC 79   22056 tail_ = c; 79   22835 tail_ = c;
HITCBC 80   22056 } 80   22835 }
81   81  
HITCBC 82   24934 continuation* pop() noexcept 82   25699 continuation* pop() noexcept
83   { 83   {
HITCBC 84   24934 if(!head_) 84   25699 if(!head_)
HITCBC 85   2878 return nullptr; 85   2864 return nullptr;
HITCBC 86   22056 continuation* c = head_; 86   22835 continuation* c = head_;
HITCBC 87   22056 head_ = static_cast<continuation*>(head_->reserved); 87   22835 head_ = static_cast<continuation*>(head_->reserved);
HITCBC 88   22056 if(!head_) 88   22835 if(!head_)
HITCBC 89   19342 tail_ = nullptr; 89   17948 tail_ = nullptr;
HITCBC 90   22056 return c; 90   22835 return c;
91   } 91   }
92   92  
HITCBC 93   41158 bool empty() const noexcept 93   40822 bool empty() const noexcept
94   { 94   {
HITCBC 95   41158 return head_ == nullptr; 95   40822 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   1439 ~impl() = default; 110   1432 ~impl() = default;
111   111  
112   bool 112   bool
HITCBC 113   3217 running_in_this_thread() const noexcept 113   3210 running_in_this_thread() const noexcept
114   { 114   {
HITCBC 115   3217 return current_.get() == this; 115   3210 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   2878 drain_abandoned() noexcept 122   2864 drain_abandoned() noexcept
123   { 123   {
124   for(;;) 124   for(;;)
125   { 125   {
126   continuation* c; 126   continuation* c;
127   { 127   {
HITCBC 128   3529 std::lock_guard<std::mutex> lock(mutex_); 128   3395 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 129   3529 c = pop(); 129   3395 c = pop();
HITCBC 130   3529 } 130   3395 }
HITCBC 131   3529 if(!c) 131   3395 if(!c)
HITCBC 132   2878 return; 132   2864 return;
HITCBC 133   651 if(c->h && c->h != std::noop_coroutine()) 133   531 if(c->h && c->h != std::noop_coroutine())
HITCBC 134   318 capy::abandon(*c); 134   278 capy::abandon(*c);
HITCBC 135   651 } 135   531 }
136   } 136   }
137   137  
HITCBC 138   1439 impl(std::size_t num_threads, std::string_view thread_name_prefix) 138   1432 impl(std::size_t num_threads, std::string_view thread_name_prefix)
HITCBC 139   1439 : num_threads_(num_threads) 139   1432 : num_threads_(num_threads)
140   { 140   {
HITCBC 141   1439 if(num_threads_ == 0) 141   1432 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   1439 auto n = thread_name_prefix.copy(thread_name_prefix_, 12); 146   1432 auto n = thread_name_prefix.copy(thread_name_prefix_, 12);
HITCBC 147   1439 thread_name_prefix_[n] = '\0'; 147   1432 thread_name_prefix_[n] = '\0';
HITCBC 148   1439 } 148   1432 }
149   149  
150   void 150   void
HITCBC 151   22058 post(continuation& c) 151   22837 post(continuation& c)
152   { 152   {
HITCBC 153   22058 if(!started_.load(std::memory_order_acquire)) 153   22837 if(!started_.load(std::memory_order_acquire))
154   { 154   {
155   { 155   {
HITCBC 156   609 std::lock_guard<std::mutex> lock(mutex_); 156   597 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 157   609 if(stop_) 157   597 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   609 } 164   597 }
HITCBC 165   389 ensure_started(); 165   377 ensure_started();
166   } 166   }
HITCBC 167   21836 std::lock_guard<std::mutex> lock(mutex_); 167   22615 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 168   21836 push(&c); 168   22615 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   21836 work_cv_.notify_one(); 171   22615 work_cv_.notify_one();
HITCBC 172   21836 } 172   22615 }
173   173  
174   void 174   void
HITCBC 175   4097 on_work_started() noexcept 175   4090 on_work_started() noexcept
176   { 176   {
HITCBC 177   4097 outstanding_work_.fetch_add(1, std::memory_order_acq_rel); 177   4090 outstanding_work_.fetch_add(1, std::memory_order_acq_rel);
HITCBC 178   4097 } 178   4090 }
179   179  
180   void 180   void
HITCBC 181   3797 on_work_finished() noexcept 181   3790 on_work_finished() noexcept
182   { 182   {
HITCBC 183   3797 auto n = outstanding_work_.load(std::memory_order_relaxed); 183   3790 auto n = outstanding_work_.load(std::memory_order_relaxed);
HITCBC 184   3805 while(n > 1) 184   3796 while(n > 1)
185   { 185   {
HITCBC 186   2560 if(outstanding_work_.compare_exchange_weak( 186   2702 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   1272 return; 188   1345 return;
189   } 189   }
HITCBC 190   2525 std::lock_guard<std::mutex> lock(mutex_); 190   2445 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 191   2525 outstanding_work_.fetch_sub(1, std::memory_order_acq_rel); 191   2445 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   2525 if(outstanding_work_.load( 193   2445 if(outstanding_work_.load(
HITCBC 194   2525 std::memory_order_acquire) == 0 && joined_ && !stop_) 194   2445 std::memory_order_acquire) == 0 && joined_ && !stop_)
195   { 195   {
HITCBC 196   703 stop_ = true; 196   705 stop_ = true;
HITCBC 197   703 done_cv_.notify_all(); 197   705 done_cv_.notify_all();
HITCBC 198   703 work_cv_.notify_all(); 198   705 work_cv_.notify_all();
199   } 199   }
HITCBC 200   2525 } 200   2445 }
201   201  
202   void 202   void
HITCBC 203   2739 join() noexcept 203   2725 join() noexcept
204   { 204   {
205   { 205   {
HITCBC 206   2739 std::unique_lock<std::mutex> lock(mutex_); 206   2725 std::unique_lock<std::mutex> lock(mutex_);
HITCBC 207   2739 if(joined_) 207   2725 if(joined_)
HITCBC 208   1300 return; 208   1293 return;
HITCBC 209   1439 joined_ = true; 209   1432 joined_ = true;
210   210  
HITCBC 211   1439 if(outstanding_work_.load( 211   1432 if(outstanding_work_.load(
HITCBC 212   1439 std::memory_order_acquire) == 0) 212   1432 std::memory_order_acquire) == 0)
213   { 213   {
HITCBC 214   380 stop_ = true; 214   371 stop_ = true;
HITCBC 215   380 work_cv_.notify_all(); 215   371 work_cv_.notify_all();
216   } 216   }
217   else 217   else
218   { 218   {
HITCBC 219   1059 done_cv_.wait(lock, [this]{ 219   1061 done_cv_.wait(lock, [this]{
HITCBC 220   2063 return stop_; 220   2067 return stop_;
221   }); 221   });
222   } 222   }
HITCBC 223   2739 } 223   2725 }
224   224  
HITCBC 225   1945 for(auto& t : threads_) 225   1931 for(auto& t : threads_)
HITCBC 226   506 if(t.joinable()) 226   499 if(t.joinable())
HITCBC 227   506 t.join(); 227   499 t.join();
228   } 228   }
229   229  
230   void 230   void
HITCBC 231   1742 stop() noexcept 231   1735 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   1742 std::lock_guard<std::mutex> lock(mutex_); 235   1735 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 236   1742 stop_ = true; 236   1735 stop_ = true;
HITCBC 237   1742 work_cv_.notify_all(); 237   1735 work_cv_.notify_all();
HITCBC 238   1742 done_cv_.notify_all(); 238   1735 done_cv_.notify_all();
HITCBC 239   1742 } 239   1735 }
240   240  
241   private: 241   private:
242   void 242   void
HITCBC 243   389 ensure_started() 243   377 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   389 std::lock_guard<std::mutex> lock(mutex_); 248   377 std::lock_guard<std::mutex> lock(mutex_);
HITCBC 249   389 if(started_.load(std::memory_order_relaxed) || stop_) 249   377 if(started_.load(std::memory_order_relaxed) || stop_)
MISLBC 250   5 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   384 threads_.reserve(num_threads_); 253   377 threads_.reserve(num_threads_);
HITCBC 254   890 for(std::size_t i = threads_.size(); i < num_threads_; ++i) 254   876 for(std::size_t i = threads_.size(); i < num_threads_; ++i)
HITCBC 255   1014 threads_.emplace_back([this, i]{ run(i); }); 255   1000 threads_.emplace_back([this, i]{ run(i); });
HITCBC 256   382 started_.store(true, std::memory_order_release); 256   375 started_.store(true, std::memory_order_release);
HITCBC 257   389 } 257   377 }
258   258  
259   void 259   void
HITCBC 260   506 run(std::size_t index) 260   499 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   506 std::snprintf(name, sizeof(name), "%s%zu", thread_name_prefix_, index); 264   499 std::snprintf(name, sizeof(name), "%s%zu", thread_name_prefix_, index);
HITCBC 265   506 set_current_thread_name(name); 265   499 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   506 scoped_pool(impl const* p) noexcept { current_.set(p); } 271   499 scoped_pool(impl const* p) noexcept { current_.set(p); }
HITCBC 272   506 ~scoped_pool() noexcept { current_.set(nullptr); } 272   499 ~scoped_pool() noexcept { current_.set(nullptr); }
HITCBC 273   506 } guard(this); 273   499 } guard(this);
274   274  
275   for(;;) 275   for(;;)
276   { 276   {
HITCBC 277   21911 continuation* c = nullptr; 277   22803 continuation* c = nullptr;
278   { 278   {
HITCBC 279   21911 std::unique_lock<std::mutex> lock(mutex_); 279   22803 std::unique_lock<std::mutex> lock(mutex_);
HITCBC 280   21911 work_cv_.wait(lock, [this]{ 280   22803 work_cv_.wait(lock, [this]{
HITCBC 281   60806 return !empty() || 281   59241 return !empty() ||
HITCBC 282   60806 stop_; 282   59241 stop_;
283   }); 283   });
HITCBC 284   21911 if(stop_) 284   22803 if(stop_)
HITCBC 285   1012 return; 285   998 return;
HITCBC 286   21405 c = pop(); 286   22304 c = pop();
HITCBC 287   21911 } 287   22803 }
HITCBC 288   21405 if(c) 288   22304 if(c)
HITCBC 289   21405 safe_resume(c->h); 289   22304 safe_resume(c->h);
HITCBC 290   21405 } 290   22304 }
HITCBC 291   506 } 291   499 }
292   }; 292   };
293   293  
294   //------------------------------------------------------------------------------ 294   //------------------------------------------------------------------------------
295   295  
HITCBC 296   1439 thread_pool:: 296   1432 thread_pool::
297   ~thread_pool() 297   ~thread_pool()
298   { 298   {
HITCBC 299   1439 impl_->stop(); 299   1432 impl_->stop();
HITCBC 300   1439 impl_->join(); 300   1432 impl_->join();
HITCBC 301   1439 shutdown(); 301   1432 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   1439 impl_->drain_abandoned(); 304   1432 impl_->drain_abandoned();
HITCBC 305   1439 destroy(); 305   1432 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   1439 impl_->drain_abandoned(); 308   1432 impl_->drain_abandoned();
HITCBC 309   1439 delete impl_; 309   1432 delete impl_;
HITCBC 310   1439 } 310   1432 }
311   311  
HITCBC 312   1439 thread_pool:: 312   1432 thread_pool::
HITCBC 313   1439 thread_pool(std::size_t num_threads, std::string_view thread_name_prefix) 313   1432 thread_pool(std::size_t num_threads, std::string_view thread_name_prefix)
HITCBC 314   1439 : impl_(new impl(num_threads, thread_name_prefix)) 314   1432 : impl_(new impl(num_threads, thread_name_prefix))
315   { 315   {
HITCBC 316   1439 this->set_frame_allocator(std::allocator<void>{}); 316   1432 this->set_frame_allocator(std::allocator<void>{});
HITCBC 317   1439 } 317   1432 }
318   318  
319   void 319   void
HITCBC 320   1300 thread_pool:: 320   1293 thread_pool::
321   join() noexcept 321   join() noexcept
322   { 322   {
HITCBC 323   1300 impl_->join(); 323   1293 impl_->join();
HITCBC 324   1300 } 324   1293 }
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   15358 thread_pool:: 336   15351 thread_pool::
337   get_executor() const noexcept 337   get_executor() const noexcept
338   { 338   {
HITCBC 339   15358 return executor_type( 339   15351 return executor_type(
HITCBC 340   15358 const_cast<thread_pool&>(*this)); 340   15351 const_cast<thread_pool&>(*this));
341   } 341   }
342   342  
343   void 343   void
HITCBC 344   4097 thread_pool::executor_type:: 344   4090 thread_pool::executor_type::
345   on_work_started() const noexcept 345   on_work_started() const noexcept
346   { 346   {
HITCBC 347   4097 pool_->impl_->on_work_started(); 347   4090 pool_->impl_->on_work_started();
HITCBC 348   4097 } 348   4090 }
349   349  
350   void 350   void
HITCBC 351   3797 thread_pool::executor_type:: 351   3790 thread_pool::executor_type::
352   on_work_finished() const noexcept 352   on_work_finished() const noexcept
353   { 353   {
HITCBC 354   3797 pool_->impl_->on_work_finished(); 354   3790 pool_->impl_->on_work_finished();
HITCBC 355   3797 } 355   3790 }
356   356  
357   void 357   void
HITCBC 358   18845 thread_pool::executor_type:: 358   19631 thread_pool::executor_type::
359   post(continuation& c) const 359   post(continuation& c) const
360   { 360   {
HITCBC 361   18845 pool_->impl_->post(c); 361   19631 pool_->impl_->post(c);
HITCBC 362   18843 } 362   19629 }
363   363  
364   std::coroutine_handle<> 364   std::coroutine_handle<>
HITCBC 365   3217 thread_pool::executor_type:: 365   3210 thread_pool::executor_type::
366   dispatch(continuation& c) const 366   dispatch(continuation& c) const
367   { 367   {
HITCBC 368   3217 if(pool_->impl_->running_in_this_thread()) 368   3210 if(pool_->impl_->running_in_this_thread())
HITCBC 369   4 return c.h; 369   4 return c.h;
HITCBC 370   3213 pool_->impl_->post(c); 370   3206 pool_->impl_->post(c);
HITCBC 371   3213 return std::noop_coroutine(); 371   3206 return std::noop_coroutine();
372   } 372   }
373   373  
374   } // capy 374   } // capy
375   } // boost 375   } // boost