97.97% Lines (145/148) 97.30% Functions (36/37)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2026 Vinnie Falco (vinnie.falco@gmail.com)
  3 + // Copyright (c) 2026 Michael Vandeberg
3   // 4   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // 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)
6   // 7   //
7   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
8   // 9   //
9   10  
10   #ifndef BOOST_COROSIO_TCP_SERVER_HPP 11   #ifndef BOOST_COROSIO_TCP_SERVER_HPP
11   #define BOOST_COROSIO_TCP_SERVER_HPP 12   #define BOOST_COROSIO_TCP_SERVER_HPP
12   13  
13   #include <boost/corosio/detail/config.hpp> 14   #include <boost/corosio/detail/config.hpp>
14   #include <boost/corosio/detail/except.hpp> 15   #include <boost/corosio/detail/except.hpp>
15   #include <boost/corosio/tcp_acceptor.hpp> 16   #include <boost/corosio/tcp_acceptor.hpp>
16   #include <boost/corosio/tcp_socket.hpp> 17   #include <boost/corosio/tcp_socket.hpp>
17   #include <boost/corosio/io_context.hpp> 18   #include <boost/corosio/io_context.hpp>
18   #include <boost/corosio/endpoint.hpp> 19   #include <boost/corosio/endpoint.hpp>
19   #include <boost/capy/task.hpp> 20   #include <boost/capy/task.hpp>
20   #include <boost/capy/concept/execution_context.hpp> 21   #include <boost/capy/concept/execution_context.hpp>
21   #include <boost/capy/concept/io_awaitable.hpp> 22   #include <boost/capy/concept/io_awaitable.hpp>
22   #include <boost/capy/concept/executor.hpp> 23   #include <boost/capy/concept/executor.hpp>
23   #include <boost/capy/ex/any_executor.hpp> 24   #include <boost/capy/ex/any_executor.hpp>
24   #include <boost/capy/ex/frame_allocator.hpp> 25   #include <boost/capy/ex/frame_allocator.hpp>
25   #include <boost/capy/ex/io_env.hpp> 26   #include <boost/capy/ex/io_env.hpp>
26   #include <boost/capy/ex/run_async.hpp> 27   #include <boost/capy/ex/run_async.hpp>
27   28  
28   #include <coroutine> 29   #include <coroutine>
29   #include <memory> 30   #include <memory>
30   #include <ranges> 31   #include <ranges>
31   #include <vector> 32   #include <vector>
32   33  
33   namespace boost::corosio { 34   namespace boost::corosio {
34   35  
35   #ifdef _MSC_VER 36   #ifdef _MSC_VER
36   #pragma warning(push) 37   #pragma warning(push)
37   #pragma warning(disable : 4251) // class needs to have dll-interface 38   #pragma warning(disable : 4251) // class needs to have dll-interface
38   #endif 39   #endif
39   40  
40   /** TCP server with pooled workers. 41   /** TCP server with pooled workers.
41   42  
42   This class manages a pool of reusable worker objects that handle 43   This class manages a pool of reusable worker objects that handle
43   incoming connections. When a connection arrives, an idle worker 44   incoming connections. When a connection arrives, an idle worker
44   is dispatched to handle it. After the connection completes, the 45   is dispatched to handle it. After the connection completes, the
45   worker returns to the pool for reuse, avoiding allocation overhead 46   worker returns to the pool for reuse, avoiding allocation overhead
46   per connection. 47   per connection.
47   48  
48   Workers are set via @ref set_workers as a forward range of 49   Workers are set via @ref set_workers as a forward range of
49   pointer-like objects (e.g., `unique_ptr<worker_base>`). The server 50   pointer-like objects (e.g., `unique_ptr<worker_base>`). The server
50   takes ownership of the container via type erasure. 51   takes ownership of the container via type erasure.
51   52  
52   @par Thread Safety 53   @par Thread Safety
53   Distinct objects: Safe. 54   Distinct objects: Safe.
54   Shared objects: Unsafe. 55   Shared objects: Unsafe.
55   56  
56   @par Lifecycle 57   @par Lifecycle
57   The server operates in three states: 58   The server operates in three states:
58   59  
59   - **Stopped**: Initial state, or after @ref join completes. 60   - **Stopped**: Initial state, or after @ref join completes.
60   - **Running**: After @ref start, actively accepting connections. 61   - **Running**: After @ref start, actively accepting connections.
61   - **Stopping**: After @ref stop, draining active work. 62   - **Stopping**: After @ref stop, draining active work.
62   63  
63   State transitions: 64   State transitions:
64   @code 65   @code
65   [Stopped] --start()--> [Running] --stop()--> [Stopping] --join()--> [Stopped] 66   [Stopped] --start()--> [Running] --stop()--> [Stopping] --join()--> [Stopped]
66   @endcode 67   @endcode
67   68  
68   @par Running the Server 69   @par Running the Server
69 - @code 70 + @par !example running_the_server
70 - io_context ioc;  
71 - tcp_server srv(ioc, ioc.get_executor());  
72 - srv.set_workers(make_workers(ioc, 100));  
73 - if (auto ec = srv.bind(endpoint{ipv4_address::any(), 8080}))  
74 - return;  
75 - srv.start();  
76 - ioc.run(); // Blocks until all work completes  
77 - @endcode  
78   71  
79   @par Graceful Shutdown 72   @par Graceful Shutdown
80   To shut down gracefully, call @ref stop then drain the io_context: 73   To shut down gracefully, call @ref stop then drain the io_context:
81 - @code 74 + @par !example graceful_shutdown
82 - // From a signal handler or timer callback:  
83 - srv.stop();  
84 -  
85 - // ioc.run() returns after pending work drains.  
86 - // Then from the thread that called ioc.run():  
87 - srv.join(); // Wait for accept loops to finish  
88 - @endcode  
89   75  
90   @par Restart After Stop 76   @par Restart After Stop
91   The server can be restarted after a complete shutdown cycle. 77   The server can be restarted after a complete shutdown cycle.
92 - You must drain the io_context and call @ref join before restarting: 78 + You must drain the io_context, call @ref join, and restart the
93 - @code 79 + io_context itself (`ioc.restart()`) before restarting:
94 - srv.start(); 80 + @par !example restart_after_stop
95 - ioc.run_for( 10s ); // Run for a while  
96 - srv.stop(); // Signal shutdown  
97 - ioc.run(); // REQUIRED: drain pending completions  
98 - srv.join(); // REQUIRED: wait for accept loops  
99 -  
100 - // Now safe to restart  
101 - srv.start();  
102 - ioc.run();  
103 - @endcode  
104   81  
105   @par WARNING: What NOT to Do 82   @par WARNING: What NOT to Do
106   - Do NOT call @ref join from inside a worker coroutine (deadlock). 83   - Do NOT call @ref join from inside a worker coroutine (deadlock).
107   - Do NOT call @ref join from a thread running `ioc.run()` (deadlock). 84   - Do NOT call @ref join from a thread running `ioc.run()` (deadlock).
108   - Do NOT call @ref start without completing @ref join after @ref stop. 85   - Do NOT call @ref start without completing @ref join after @ref stop.
109   - Do NOT call `ioc.stop()` for graceful shutdown; use @ref stop instead. 86   - Do NOT call `ioc.stop()` for graceful shutdown; use @ref stop instead.
110   87  
111   @par Example 88   @par Example
112 - @code 89 + @par !example custom_worker
113 - class my_worker : public tcp_server::worker_base  
114 - {  
115 - corosio::tcp_socket sock_;  
116 - capy::any_executor ex_;  
117 - public:  
118 - my_worker(io_context& ctx)  
119 - : sock_(ctx)  
120 - , ex_(ctx.get_executor())  
121 - {  
122 - }  
123 -  
124 - corosio::tcp_socket& socket() override { return sock_; }  
125 -  
126 - void run(launcher launch) override  
127 - {  
128 - launch(ex_, [](corosio::tcp_socket* sock) -> capy::task<>  
129 - {  
130 - // handle connection using sock  
131 - co_return;  
132 - }(&sock_));  
133 - }  
134 - };  
135 -  
136 - auto make_workers(io_context& ctx, int n)  
137 - {  
138 - std::vector<std::unique_ptr<tcp_server::worker_base>> v;  
139 - v.reserve(n);  
140 - for(int i = 0; i < n; ++i)  
141 - v.push_back(std::make_unique<my_worker>(ctx));  
142 - return v;  
143 - }  
144 -  
145 - io_context ioc;  
146 - tcp_server srv(ioc, ioc.get_executor());  
147 - srv.set_workers(make_workers(ioc, 100));  
148 - @endcode  
149   90  
150   @see worker_base, set_workers, launcher 91   @see worker_base, set_workers, launcher
151   */ 92   */
152   class BOOST_COROSIO_DECL tcp_server 93   class BOOST_COROSIO_DECL tcp_server
153   { 94   {
154   public: 95   public:
155   class worker_base; ///< Abstract base for connection handlers. 96   class worker_base; ///< Abstract base for connection handlers.
156   class launcher; ///< Move-only handle to launch worker coroutines. 97   class launcher; ///< Move-only handle to launch worker coroutines.
157   98  
158   private: 99   private:
159   struct waiter 100   struct waiter
160   { 101   {
161   waiter* next; 102   waiter* next;
162   std::coroutine_handle<> h; 103   std::coroutine_handle<> h;
163   capy::continuation cont; 104   capy::continuation cont;
164   worker_base* w; 105   worker_base* w;
165   }; 106   };
166   107  
167   struct impl; 108   struct impl;
168   109  
169   static impl* make_impl(capy::execution_context& ctx); 110   static impl* make_impl(capy::execution_context& ctx);
170   111  
171   impl* impl_; 112   impl* impl_;
172   capy::any_executor ex_; 113   capy::any_executor ex_;
173   waiter* waiters_ = nullptr; 114   waiter* waiters_ = nullptr;
174   worker_base* idle_head_ = nullptr; // Forward list: available workers 115   worker_base* idle_head_ = nullptr; // Forward list: available workers
175   worker_base* active_head_ = 116   worker_base* active_head_ =
176   nullptr; // Doubly linked: workers handling connections 117   nullptr; // Doubly linked: workers handling connections
177   worker_base* active_tail_ = nullptr; // Tail for O(1) push_back 118   worker_base* active_tail_ = nullptr; // Tail for O(1) push_back
178   std::size_t active_accepts_ = 0; // Number of active do_accept coroutines 119   std::size_t active_accepts_ = 0; // Number of active do_accept coroutines
179   std::shared_ptr<void> storage_; // Owns the worker container (type-erased) 120   std::shared_ptr<void> storage_; // Owns the worker container (type-erased)
180   bool running_ = false; 121   bool running_ = false;
181   122  
182   // Idle list (forward/singly linked) - push front, pop front 123   // Idle list (forward/singly linked) - push front, pop front
HITCBC 183   238 void idle_push(worker_base* w) noexcept 124   238 void idle_push(worker_base* w) noexcept
184   { 125   {
HITCBC 185   238 w->next_ = idle_head_; 126   238 w->next_ = idle_head_;
HITCBC 186   238 idle_head_ = w; 127   238 idle_head_ = w;
HITCBC 187   238 } 128   238 }
188   129  
HITCBC 189   75 worker_base* idle_pop() noexcept 130   75 worker_base* idle_pop() noexcept
190   { 131   {
HITCBC 191   75 auto* w = idle_head_; 132   75 auto* w = idle_head_;
HITCBC 192   75 if (w) 133   75 if (w)
HITCBC 193   75 idle_head_ = w->next_; 134   75 idle_head_ = w->next_;
HITCBC 194   75 return w; 135   75 return w;
195   } 136   }
196   137  
HITCBC 197   153 bool idle_empty() const noexcept 138   153 bool idle_empty() const noexcept
198   { 139   {
HITCBC 199   153 return idle_head_ == nullptr; 140   153 return idle_head_ == nullptr;
200   } 141   }
201   142  
202   // Active list (doubly linked) - push back, remove anywhere 143   // Active list (doubly linked) - push back, remove anywhere
HITCBC 203   88 void active_push(worker_base* w) noexcept 144   88 void active_push(worker_base* w) noexcept
204   { 145   {
HITCBC 205   88 w->next_ = nullptr; 146   88 w->next_ = nullptr;
HITCBC 206   88 w->prev_ = active_tail_; 147   88 w->prev_ = active_tail_;
HITCBC 207   88 if (active_tail_) 148   88 if (active_tail_)
HITCBC 208   4 active_tail_->next_ = w; 149   4 active_tail_->next_ = w;
209   else 150   else
HITCBC 210   84 active_head_ = w; 151   84 active_head_ = w;
HITCBC 211   88 active_tail_ = w; 152   88 active_tail_ = w;
HITCBC 212   88 } 153   88 }
213   154  
HITCBC 214   153 void active_remove(worker_base* w) noexcept 155   153 void active_remove(worker_base* w) noexcept
215   { 156   {
216   // Skip if not in active list (e.g., after failed accept) 157   // Skip if not in active list (e.g., after failed accept)
HITCBC 217   153 if (w != active_head_ && w->prev_ == nullptr) 158   153 if (w != active_head_ && w->prev_ == nullptr)
HITCBC 218   65 return; 159   65 return;
HITCBC 219   88 if (w->prev_) 160   88 if (w->prev_)
HITCBC 220   4 w->prev_->next_ = w->next_; 161   4 w->prev_->next_ = w->next_;
221   else 162   else
HITCBC 222   84 active_head_ = w->next_; 163   84 active_head_ = w->next_;
HITCBC 223   88 if (w->next_) 164   88 if (w->next_)
HITCBC 224   2 w->next_->prev_ = w->prev_; 165   2 w->next_->prev_ = w->prev_;
225   else 166   else
HITCBC 226   86 active_tail_ = w->prev_; 167   86 active_tail_ = w->prev_;
HITCBC 227   88 w->prev_ = nullptr; // Mark as not in active list 168   88 w->prev_ = nullptr; // Mark as not in active list
228   } 169   }
229   170  
230   template<capy::Executor Ex> 171   template<capy::Executor Ex>
231   struct launch_wrapper 172   struct launch_wrapper
232   { 173   {
233   struct promise_type 174   struct promise_type
234   { 175   {
235   Ex ex; // Executor stored directly in frame (outlives child tasks) 176   Ex ex; // Executor stored directly in frame (outlives child tasks)
236   capy::io_env env_; 177   capy::io_env env_;
237   178  
238   // For regular coroutines: first arg is executor, second is stop token 179   // For regular coroutines: first arg is executor, second is stop token
239   template<class E, class S, class... Args> 180   template<class E, class S, class... Args>
240   requires capy::Executor<std::decay_t<E>> 181   requires capy::Executor<std::decay_t<E>>
241   promise_type(E e, S s, Args&&...) 182   promise_type(E e, S s, Args&&...)
242   : ex(std::move(e)) 183   : ex(std::move(e))
243   , env_{ 184   , env_{
244   capy::executor_ref(ex), std::move(s), 185   capy::executor_ref(ex), std::move(s),
245   capy::get_current_frame_allocator()} 186   capy::get_current_frame_allocator()}
246   { 187   {
247   } 188   }
248   189  
249   // For lambda coroutines: first arg is closure, second is executor, third is stop token 190   // For lambda coroutines: first arg is closure, second is executor, third is stop token
250   template<class Closure, class E, class S, class... Args> 191   template<class Closure, class E, class S, class... Args>
251   requires(!capy::Executor<std::decay_t<Closure>> && 192   requires(!capy::Executor<std::decay_t<Closure>> &&
252   capy::Executor<std::decay_t<E>>) 193   capy::Executor<std::decay_t<E>>)
HITCBC 253   88 promise_type(Closure&&, E e, S s, Args&&...) 194   88 promise_type(Closure&&, E e, S s, Args&&...)
HITCBC 254   88 : ex(std::move(e)) 195   88 : ex(std::move(e))
HITCBC 255   88 , env_{ 196   88 , env_{
HITCBC 256   88 capy::executor_ref(ex), std::move(s), 197   88 capy::executor_ref(ex), std::move(s),
HITCBC 257   88 capy::get_current_frame_allocator()} 198   88 capy::get_current_frame_allocator()}
258   { 199   {
HITCBC 259   88 } 200   88 }
260   201  
HITCBC 261   88 launch_wrapper get_return_object() noexcept 202   88 launch_wrapper get_return_object() noexcept
262   { 203   {
263   return { 204   return {
HITCBC 264   88 std::coroutine_handle<promise_type>::from_promise(*this)}; 205   88 std::coroutine_handle<promise_type>::from_promise(*this)};
265   } 206   }
HITCBC 266   88 std::suspend_always initial_suspend() noexcept 207   88 std::suspend_always initial_suspend() noexcept
267   { 208   {
HITCBC 268   88 return {}; 209   88 return {};
269   } 210   }
HITCBC 270   88 std::suspend_never final_suspend() noexcept 211   88 std::suspend_never final_suspend() noexcept
271   { 212   {
HITCBC 272   88 return {}; 213   88 return {};
273   } 214   }
HITCBC 274   88 void return_void() noexcept {} 215   88 void return_void() noexcept {}
MISUBC 275   void unhandled_exception() 216   void unhandled_exception()
276   { 217   {
277   // LCOV_EXCL_START: terminating by contract is not a 218   // LCOV_EXCL_START: terminating by contract is not a
278   // coverable outcome. 219   // coverable outcome.
279   std::terminate(); 220   std::terminate();
280   // LCOV_EXCL_STOP 221   // LCOV_EXCL_STOP
281   } 222   }
282   223  
283   // Inject io_env for IoAwaitable 224   // Inject io_env for IoAwaitable
284   template<capy::IoAwaitable Awaitable> 225   template<capy::IoAwaitable Awaitable>
HITCBC 285   176 auto await_transform(Awaitable&& a) 226   176 auto await_transform(Awaitable&& a)
286   { 227   {
287   using AwaitableT = std::decay_t<Awaitable>; 228   using AwaitableT = std::decay_t<Awaitable>;
288   struct adapter 229   struct adapter
289   { 230   {
290   AwaitableT aw; 231   AwaitableT aw;
291   capy::io_env const* env; 232   capy::io_env const* env;
292   233  
HITCBC 293   176 bool await_ready() 234   176 bool await_ready()
294   { 235   {
HITCBC 295   176 return aw.await_ready(); 236   176 return aw.await_ready();
296   } 237   }
HITCBC 297   176 decltype(auto) await_resume() 238   176 decltype(auto) await_resume()
298   { 239   {
HITCBC 299   176 return aw.await_resume(); 240   176 return aw.await_resume();
300   } 241   }
301   242  
HITCBC 302   176 auto await_suspend(std::coroutine_handle<promise_type> h) 243   176 auto await_suspend(std::coroutine_handle<promise_type> h)
303   { 244   {
HITCBC 304   176 return aw.await_suspend(h, env); 245   176 return aw.await_suspend(h, env);
305   } 246   }
306   }; 247   };
HITCBC 307   264 return adapter{std::forward<Awaitable>(a), &env_}; 248   264 return adapter{std::forward<Awaitable>(a), &env_};
HITCBC 308   88 } 249   88 }
309   }; 250   };
310   251  
311   std::coroutine_handle<promise_type> h; 252   std::coroutine_handle<promise_type> h;
312   253  
HITCBC 313   88 launch_wrapper(std::coroutine_handle<promise_type> handle) noexcept 254   88 launch_wrapper(std::coroutine_handle<promise_type> handle) noexcept
HITCBC 314   88 : h(handle) 255   88 : h(handle)
315   { 256   {
HITCBC 316   88 } 257   88 }
317   258  
HITCBC 318   88 ~launch_wrapper() 259   88 ~launch_wrapper()
319   { 260   {
HITCBC 320   88 if (h) 261   88 if (h)
MISUBC 321   h.destroy(); 262   h.destroy();
HITCBC 322   88 } 263   88 }
323   264  
324   launch_wrapper(launch_wrapper&& o) noexcept 265   launch_wrapper(launch_wrapper&& o) noexcept
325   : h(std::exchange(o.h, nullptr)) 266   : h(std::exchange(o.h, nullptr))
326   { 267   {
327   } 268   }
328   269  
329   launch_wrapper(launch_wrapper const&) = delete; 270   launch_wrapper(launch_wrapper const&) = delete;
330   launch_wrapper& operator=(launch_wrapper const&) = delete; 271   launch_wrapper& operator=(launch_wrapper const&) = delete;
331   launch_wrapper& operator=(launch_wrapper&&) = delete; 272   launch_wrapper& operator=(launch_wrapper&&) = delete;
332   }; 273   };
333   274  
334   // Named functor to avoid incomplete lambda type in coroutine promise 275   // Named functor to avoid incomplete lambda type in coroutine promise
335   template<class Executor> 276   template<class Executor>
336   struct launch_coro 277   struct launch_coro
337   { 278   {
HITCBC 338   88 launch_wrapper<Executor> operator()( 279   88 launch_wrapper<Executor> operator()(
339   Executor, 280   Executor,
340   std::stop_token, 281   std::stop_token,
341   tcp_server* self, 282   tcp_server* self,
342   capy::task<void> t, 283   capy::task<void> t,
343   worker_base* wp) 284   worker_base* wp)
344   { 285   {
345   // Executor and stop token stored in promise via constructor 286   // Executor and stop token stored in promise via constructor
346   co_await std::move(t); 287   co_await std::move(t);
347   co_await self->push(*wp); // worker goes back to idle list 288   co_await self->push(*wp); // worker goes back to idle list
HITCBC 348   176 } 289   176 }
349   }; 290   };
350   291  
351   class push_awaitable 292   class push_awaitable
352   { 293   {
353   tcp_server& self_; 294   tcp_server& self_;
354   worker_base& w_; 295   worker_base& w_;
355   capy::continuation cont_; 296   capy::continuation cont_;
356   297  
357   public: 298   public:
HITCBC 358   145 push_awaitable(tcp_server& self, worker_base& w) noexcept 299   145 push_awaitable(tcp_server& self, worker_base& w) noexcept
HITCBC 359   145 : self_(self) 300   145 : self_(self)
HITCBC 360   145 , w_(w) 301   145 , w_(w)
361   { 302   {
HITCBC 362   145 } 303   145 }
363   304  
HITCBC 364   145 bool await_ready() const noexcept 305   145 bool await_ready() const noexcept
365   { 306   {
HITCBC 366   145 return false; 307   145 return false;
367   } 308   }
368   309  
369   std::coroutine_handle<> 310   std::coroutine_handle<>
HITCBC 370   145 await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept 311   145 await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept
371   { 312   {
372   // Symmetric transfer to server's executor 313   // Symmetric transfer to server's executor
HITCBC 373   145 cont_.h = h; 314   145 cont_.h = h;
HITCBC 374   145 return self_.ex_.dispatch(cont_); 315   145 return self_.ex_.dispatch(cont_);
375   } 316   }
376   317  
HITCBC 377   145 void await_resume() noexcept 318   145 void await_resume() noexcept
378   { 319   {
379   // Running on server executor - safe to modify lists 320   // Running on server executor - safe to modify lists
380   // Remove from active (if present), then wake waiter or add to idle 321   // Remove from active (if present), then wake waiter or add to idle
HITCBC 381   145 self_.active_remove(&w_); 322   145 self_.active_remove(&w_);
HITCBC 382   145 if (self_.waiters_) 323   145 if (self_.waiters_)
383   { 324   {
HITCBC 384   76 auto* wait = self_.waiters_; 325   76 auto* wait = self_.waiters_;
HITCBC 385   76 self_.waiters_ = wait->next; 326   76 self_.waiters_ = wait->next;
HITCBC 386   76 wait->w = &w_; 327   76 wait->w = &w_;
HITCBC 387   76 wait->cont.h = wait->h; 328   76 wait->cont.h = wait->h;
HITCBC 388   76 self_.ex_.post(wait->cont); 329   76 self_.ex_.post(wait->cont);
389   } 330   }
390   else 331   else
391   { 332   {
HITCBC 392   69 self_.idle_push(&w_); 333   69 self_.idle_push(&w_);
393   } 334   }
HITCBC 394   145 } 335   145 }
395   }; 336   };
396   337  
397   class pop_awaitable 338   class pop_awaitable
398   { 339   {
399   tcp_server& self_; 340   tcp_server& self_;
400   waiter wait_; 341   waiter wait_;
401   342  
402   public: 343   public:
HITCBC 403   153 pop_awaitable(tcp_server& self) noexcept : self_(self), wait_{} {} 344   153 pop_awaitable(tcp_server& self) noexcept : self_(self), wait_{} {}
404   345  
HITCBC 405   153 bool await_ready() const noexcept 346   153 bool await_ready() const noexcept
406   { 347   {
HITCBC 407   153 return !self_.idle_empty(); 348   153 return !self_.idle_empty();
408   } 349   }
409   350  
410   bool 351   bool
HITCBC 411   78 await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept 352   78 await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept
412   { 353   {
413   // Running on server executor (do_accept runs there) 354   // Running on server executor (do_accept runs there)
HITCBC 414   78 wait_.h = h; 355   78 wait_.h = h;
HITCBC 415   78 wait_.w = nullptr; 356   78 wait_.w = nullptr;
HITCBC 416   78 wait_.next = self_.waiters_; 357   78 wait_.next = self_.waiters_;
HITCBC 417   78 self_.waiters_ = &wait_; 358   78 self_.waiters_ = &wait_;
HITCBC 418   78 return true; 359   78 return true;
419   } 360   }
420   361  
HITCBC 421   153 worker_base& await_resume() noexcept 362   153 worker_base& await_resume() noexcept
422   { 363   {
423   // Running on server executor 364   // Running on server executor
HITCBC 424   153 if (wait_.w) 365   153 if (wait_.w)
HITCBC 425   78 return *wait_.w; // Woken by push_awaitable 366   78 return *wait_.w; // Woken by push_awaitable
HITCBC 426   75 return *self_.idle_pop(); 367   75 return *self_.idle_pop();
427   } 368   }
428   }; 369   };
429   370  
HITCBC 430   145 push_awaitable push(worker_base& w) 371   145 push_awaitable push(worker_base& w)
431   { 372   {
HITCBC 432   145 return push_awaitable{*this, w}; 373   145 return push_awaitable{*this, w};
433   } 374   }
434   375  
435   // Synchronous version for destructor/guard paths 376   // Synchronous version for destructor/guard paths
436   // Must be called from server executor context 377   // Must be called from server executor context
HITCBC 437   8 void push_sync(worker_base& w) noexcept 378   8 void push_sync(worker_base& w) noexcept
438   { 379   {
HITCBC 439   8 active_remove(&w); 380   8 active_remove(&w);
HITCBC 440   8 if (waiters_) 381   8 if (waiters_)
441   { 382   {
HITCBC 442   2 auto* wait = waiters_; 383   2 auto* wait = waiters_;
HITCBC 443   2 waiters_ = wait->next; 384   2 waiters_ = wait->next;
HITCBC 444   2 wait->w = &w; 385   2 wait->w = &w;
HITCBC 445   2 wait->cont.h = wait->h; 386   2 wait->cont.h = wait->h;
HITCBC 446   2 ex_.post(wait->cont); 387   2 ex_.post(wait->cont);
447   } 388   }
448   else 389   else
449   { 390   {
HITCBC 450   6 idle_push(&w); 391   6 idle_push(&w);
451   } 392   }
HITCBC 452   8 } 393   8 }
453   394  
HITCBC 454   153 pop_awaitable pop() 395   153 pop_awaitable pop()
455   { 396   {
HITCBC 456   153 return pop_awaitable{*this}; 397   153 return pop_awaitable{*this};
457   } 398   }
458   399  
459   capy::task<void> do_accept(tcp_acceptor& acc); 400   capy::task<void> do_accept(tcp_acceptor& acc);
460   401  
461   public: 402   public:
462   /** Abstract base class for connection handlers. 403   /** Abstract base class for connection handlers.
463   404  
464   Derive from this class to implement custom connection handling. 405   Derive from this class to implement custom connection handling.
465   Each worker owns a socket and is reused across multiple 406   Each worker owns a socket and is reused across multiple
466   connections to avoid per-connection allocation. 407   connections to avoid per-connection allocation.
467   408  
468   @see tcp_server, launcher 409   @see tcp_server, launcher
469   */ 410   */
470   class BOOST_COROSIO_DECL worker_base 411   class BOOST_COROSIO_DECL worker_base
471   { 412   {
472   // Ordered largest to smallest for optimal packing 413   // Ordered largest to smallest for optimal packing
473   std::stop_source stop_; // ~16 bytes 414   std::stop_source stop_; // ~16 bytes
474   worker_base* next_ = nullptr; // 8 bytes - used by idle and active lists 415   worker_base* next_ = nullptr; // 8 bytes - used by idle and active lists
475   worker_base* prev_ = nullptr; // 8 bytes - used only by active list 416   worker_base* prev_ = nullptr; // 8 bytes - used only by active list
476   417  
477   friend class tcp_server; 418   friend class tcp_server;
478   419  
479   public: 420   public:
480   /// Construct a worker. 421   /// Construct a worker.
481   worker_base(); 422   worker_base();
482   423  
483   /// Destroy the worker. 424   /// Destroy the worker.
484   virtual ~worker_base(); 425   virtual ~worker_base();
485   426  
486   /** Handle an accepted connection. 427   /** Handle an accepted connection.
487   428  
488   Called when this worker is dispatched to handle a new 429   Called when this worker is dispatched to handle a new
489   connection. The implementation must invoke the launcher 430   connection. The implementation must invoke the launcher
490   exactly once to start the handling coroutine. 431   exactly once to start the handling coroutine.
491   432  
492   @param launch Handle to launch the connection coroutine. 433   @param launch Handle to launch the connection coroutine.
493   */ 434   */
494   virtual void run(launcher launch) = 0; 435   virtual void run(launcher launch) = 0;
495   436  
496   /// Return the socket used for connections. 437   /// Return the socket used for connections.
497   virtual corosio::tcp_socket& socket() = 0; 438   virtual corosio::tcp_socket& socket() = 0;
498   }; 439   };
499   440  
500   /** Move-only handle to launch a worker coroutine. 441   /** Move-only handle to launch a worker coroutine.
501   442  
502   Passed to @ref worker_base::run to start the connection-handling 443   Passed to @ref worker_base::run to start the connection-handling
503   coroutine. The launcher ensures the worker returns to the idle 444   coroutine. The launcher ensures the worker returns to the idle
504   pool when the coroutine completes or if launching fails. 445   pool when the coroutine completes or if launching fails.
505   446  
506   The launcher must be invoked exactly once via `operator()`. 447   The launcher must be invoked exactly once via `operator()`.
507   If destroyed without invoking, the worker is returned to the 448   If destroyed without invoking, the worker is returned to the
508   idle pool automatically. 449   idle pool automatically.
509   450  
510   @see worker_base::run 451   @see worker_base::run
511   */ 452   */
512   class BOOST_COROSIO_DECL launcher 453   class BOOST_COROSIO_DECL launcher
513   { 454   {
514   tcp_server* srv_; 455   tcp_server* srv_;
515   worker_base* w_; 456   worker_base* w_;
516   457  
517   friend class tcp_server; 458   friend class tcp_server;
518   459  
HITCBC 519   96 launcher(tcp_server& srv, worker_base& w) noexcept : srv_(&srv), w_(&w) 460   96 launcher(tcp_server& srv, worker_base& w) noexcept : srv_(&srv), w_(&w)
520   { 461   {
HITCBC 521   96 } 462   96 }
522   463  
523   public: 464   public:
524   /// Return the worker to the pool if not launched. 465   /// Return the worker to the pool if not launched.
HITCBC 525   98 ~launcher() 466   98 ~launcher()
526   { 467   {
HITCBC 527   98 if (w_) 468   98 if (w_)
HITCBC 528   8 srv_->push_sync(*w_); 469   8 srv_->push_sync(*w_);
HITCBC 529   98 } 470   98 }
530   471  
HITCBC 531   2 launcher(launcher&& o) noexcept 472   2 launcher(launcher&& o) noexcept
HITCBC 532   2 : srv_(o.srv_) 473   2 : srv_(o.srv_)
HITCBC 533   2 , w_(std::exchange(o.w_, nullptr)) 474   2 , w_(std::exchange(o.w_, nullptr))
534   { 475   {
HITCBC 535   2 } 476   2 }
536   launcher(launcher const&) = delete; 477   launcher(launcher const&) = delete;
537   launcher& operator=(launcher const&) = delete; 478   launcher& operator=(launcher const&) = delete;
538   launcher& operator=(launcher&&) = delete; 479   launcher& operator=(launcher&&) = delete;
539   480  
540   /** Launch the connection-handling coroutine. 481   /** Launch the connection-handling coroutine.
541   482  
542   Starts the given coroutine on the specified executor. When 483   Starts the given coroutine on the specified executor. When
543   the coroutine completes, the worker is automatically returned 484   the coroutine completes, the worker is automatically returned
544   to the idle pool. 485   to the idle pool.
545   486  
546   @param ex The executor to run the coroutine on. 487   @param ex The executor to run the coroutine on.
547   @param task The coroutine to execute. 488   @param task The coroutine to execute.
548   489  
549   @throws std::logic_error If this launcher was already invoked. 490   @throws std::logic_error If this launcher was already invoked.
550   */ 491   */
551   template<class Executor> 492   template<class Executor>
HITCBC 552   90 void operator()(Executor const& ex, capy::task<void> task) 493   90 void operator()(Executor const& ex, capy::task<void> task)
553   { 494   {
HITCBC 554   90 if (!w_) 495   90 if (!w_)
HITCBC 555   2 detail::throw_logic_error(); // launcher already invoked 496   2 detail::throw_logic_error(); // launcher already invoked
556   497  
HITCBC 557   88 auto* w = std::exchange(w_, nullptr); 498   88 auto* w = std::exchange(w_, nullptr);
558   499  
559   // Worker is being dispatched - add to active list 500   // Worker is being dispatched - add to active list
HITCBC 560   88 srv_->active_push(w); 501   88 srv_->active_push(w);
561   502  
562   // Return worker to pool if coroutine setup throws 503   // Return worker to pool if coroutine setup throws
563   struct guard_t 504   struct guard_t
564   { 505   {
565   tcp_server* srv; 506   tcp_server* srv;
566   worker_base* w; 507   worker_base* w;
HITCBC 567   88 ~guard_t() 508   88 ~guard_t()
568   { 509   {
HITCBC 569   88 if (w) 510   88 if (w)
MISUBC 570   srv->push_sync(*w); 511   srv->push_sync(*w);
HITCBC 571   88 } 512   88 }
HITCBC 572   88 } guard{srv_, w}; 513   88 } guard{srv_, w};
573   514  
574   // Reset worker's stop source for this connection 515   // Reset worker's stop source for this connection
HITCBC 575   88 w->stop_ = {}; 516   88 w->stop_ = {};
HITCBC 576   88 auto st = w->stop_.get_token(); 517   88 auto st = w->stop_.get_token();
577   518  
HITCBC 578   88 auto wrapper = 519   88 auto wrapper =
HITCBC 579   88 launch_coro<Executor>{}(ex, st, srv_, std::move(task), w); 520   88 launch_coro<Executor>{}(ex, st, srv_, std::move(task), w);
580   521  
581   // Executor and stop token stored in promise via constructor 522   // Executor and stop token stored in promise via constructor
HITCBC 582   88 ex.post(std::exchange(wrapper.h, nullptr)); // Release before post 523   88 ex.post(std::exchange(wrapper.h, nullptr)); // Release before post
HITCBC 583   88 guard.w = nullptr; // Success - dismiss guard 524   88 guard.w = nullptr; // Success - dismiss guard
HITCBC 584   88 } 525   88 }
585   }; 526   };
586   527  
587   /** Construct a TCP server. 528   /** Construct a TCP server.
588   529  
589   @tparam Ctx Execution context type satisfying ExecutionContext. 530   @tparam Ctx Execution context type satisfying ExecutionContext.
590   @tparam Ex Executor type satisfying Executor. 531   @tparam Ex Executor type satisfying Executor.
591   532  
592   @param ctx The execution context for socket operations. 533   @param ctx The execution context for socket operations.
593   @param ex The executor for dispatching coroutines. 534   @param ex The executor for dispatching coroutines.
594   535  
595   @par Example 536   @par Example
596 - @code 537 + @par !example tcp_server
597 - tcp_server srv(ctx, ctx.get_executor());  
598 - srv.set_workers(make_workers(ctx, 100));  
599 - if (auto ec = srv.bind(endpoint{...}))  
600 - return;  
601 - srv.start();  
602 - @endcode  
603   */ 538   */
604   template<capy::ExecutionContext Ctx, capy::Executor Ex> 539   template<capy::ExecutionContext Ctx, capy::Executor Ex>
HITCBC 605   73 tcp_server(Ctx& ctx, Ex ex) : impl_(make_impl(ctx)) 540   73 tcp_server(Ctx& ctx, Ex ex) : impl_(make_impl(ctx))
HITCBC 606   73 , ex_(std::move(ex)) 541   73 , ex_(std::move(ex))
607   { 542   {
HITCBC 608   73 } 543   73 }
609   544  
610   public: 545   public:
611   /// Destroy the server, stopping all accept loops. 546   /// Destroy the server, stopping all accept loops.
612   ~tcp_server(); 547   ~tcp_server();
613   548  
614   tcp_server(tcp_server const&) = delete; 549   tcp_server(tcp_server const&) = delete;
615   tcp_server& operator=(tcp_server const&) = delete; 550   tcp_server& operator=(tcp_server const&) = delete;
616   551  
617   /** Move construct from another server. 552   /** Move construct from another server.
618   553  
619   @param o The source server. After the move, @p o is 554   @param o The source server. After the move, @p o is
620   in a valid but unspecified state. 555   in a valid but unspecified state.
621   */ 556   */
622   tcp_server(tcp_server&& o) noexcept; 557   tcp_server(tcp_server&& o) noexcept;
623   558  
624   /** Move assign from another server. 559   /** Move assign from another server.
625   560  
626   @param o The source server. After the move, @p o is 561   @param o The source server. After the move, @p o is
627   in a valid but unspecified state. 562   in a valid but unspecified state.
628   563  
629   @return `*this`. 564   @return `*this`.
630   */ 565   */
631   tcp_server& operator=(tcp_server&& o) noexcept; 566   tcp_server& operator=(tcp_server&& o) noexcept;
632   567  
633   /** Bind to a local endpoint. 568   /** Bind to a local endpoint.
634   569  
635   Creates an acceptor listening on the specified endpoint. 570   Creates an acceptor listening on the specified endpoint.
636   Multiple endpoints can be bound by calling this method 571   Multiple endpoints can be bound by calling this method
637   multiple times before @ref start. 572   multiple times before @ref start.
638   573  
639   @param ep The local endpoint to bind to. 574   @param ep The local endpoint to bind to.
640   575  
641   @return The error code if binding fails. 576   @return The error code if binding fails.
642   */ 577   */
643   [[nodiscard]] std::error_code bind(endpoint ep); 578   [[nodiscard]] std::error_code bind(endpoint ep);
644   579  
645   /** Set the worker pool. 580   /** Set the worker pool.
646   581  
647   Replaces any existing workers with the given range. Any 582   Replaces any existing workers with the given range. Any
648   previous workers are released and the idle/active lists 583   previous workers are released and the idle/active lists
649   are cleared before populating with new workers. 584   are cleared before populating with new workers.
650   585  
651   @tparam Range Forward range of pointer-like objects to worker_base. 586   @tparam Range Forward range of pointer-like objects to worker_base.
652   587  
653   @param workers Range of workers to manage. Each element must 588   @param workers Range of workers to manage. Each element must
654   support `std::to_address()` yielding `worker_base*`. 589   support `std::to_address()` yielding `worker_base*`.
655   590  
656   @par Example 591   @par Example
657 - @code 592 + @par !example set_workers
658 - std::vector<std::unique_ptr<my_worker>> workers;  
659 - for(int i = 0; i < 100; ++i)  
660 - workers.push_back(std::make_unique<my_worker>(ctx));  
661 - srv.set_workers(std::move(workers));  
662 - @endcode  
663   */ 593   */
664   template<std::ranges::forward_range Range> 594   template<std::ranges::forward_range Range>
665   requires std::convertible_to< 595   requires std::convertible_to<
666   decltype(std::to_address( 596   decltype(std::to_address(
667   std::declval<std::ranges::range_value_t<Range>&>())), 597   std::declval<std::ranges::range_value_t<Range>&>())),
668   worker_base*> 598   worker_base*>
HITCBC 669   73 void set_workers(Range&& workers) 599   73 void set_workers(Range&& workers)
670   { 600   {
671   // Clear existing state 601   // Clear existing state
HITCBC 672   73 storage_.reset(); 602   73 storage_.reset();
HITCBC 673   73 idle_head_ = nullptr; 603   73 idle_head_ = nullptr;
HITCBC 674   73 active_head_ = nullptr; 604   73 active_head_ = nullptr;
HITCBC 675   73 active_tail_ = nullptr; 605   73 active_tail_ = nullptr;
676   606  
677   // Take ownership and populate idle list 607   // Take ownership and populate idle list
678   using StorageType = std::decay_t<Range>; 608   using StorageType = std::decay_t<Range>;
HITCBC 679   73 auto* p = new StorageType(std::forward<Range>(workers)); 609   73 auto* p = new StorageType(std::forward<Range>(workers));
HITCBC 680   73 storage_ = std::shared_ptr<void>( 610   73 storage_ = std::shared_ptr<void>(
HITCBC 681   73 p, [](void* ptr) { delete static_cast<StorageType*>(ptr); }); 611   73 p, [](void* ptr) { delete static_cast<StorageType*>(ptr); });
HITCBC 682   236 for (auto&& elem : *static_cast<StorageType*>(p)) 612   236 for (auto&& elem : *static_cast<StorageType*>(p))
HITCBC 683   163 idle_push(std::to_address(elem)); 613   163 idle_push(std::to_address(elem));
HITCBC 684   73 } 614   73 }
685   615  
686   /** Start accepting connections. 616   /** Start accepting connections.
687   617  
688   Launches accept loops for all bound endpoints. Incoming 618   Launches accept loops for all bound endpoints. Incoming
689   connections are dispatched to idle workers from the pool. 619   connections are dispatched to idle workers from the pool.
690   620  
691   Calling `start()` on an already-running server has no effect. 621   Calling `start()` on an already-running server has no effect.
692   622  
693   @par Preconditions 623   @par Preconditions
694   - At least one endpoint bound via @ref bind. 624   - At least one endpoint bound via @ref bind.
695   - Workers provided via @ref set_workers. 625   - Workers provided via @ref set_workers.
696 - - If restarting, @ref join must have completed first. 626 + - If restarting, @ref join must have completed first, and the
  627 + io_context must have been restarted (`ioc.restart()`).
697   628  
698   @par Effects 629   @par Effects
699   Creates one accept coroutine per bound endpoint. Each coroutine 630   Creates one accept coroutine per bound endpoint. Each coroutine
700   runs on the server's executor, waiting for connections and 631   runs on the server's executor, waiting for connections and
701   dispatching them to idle workers. 632   dispatching them to idle workers.
702   633  
703   @par Restart Sequence 634   @par Restart Sequence
704   To restart after stopping, complete the full shutdown cycle: 635   To restart after stopping, complete the full shutdown cycle:
705 - @code 636 + @par !example start
706 - srv.start();  
707 - ioc.run_for( 1s );  
708 - srv.stop(); // 1. Signal shutdown  
709 - ioc.run(); // 2. Drain remaining completions  
710 - srv.join(); // 3. Wait for accept loops  
711 -  
712 - // Now safe to restart  
713 - srv.start();  
714 - ioc.run();  
715 - @endcode  
716   637  
717   @par Thread Safety 638   @par Thread Safety
718   Not thread safe. 639   Not thread safe.
719   640  
720   @throws std::logic_error If a previous session has not been 641   @throws std::logic_error If a previous session has not been
721   joined (accept loops still active). 642   joined (accept loops still active).
722   */ 643   */
723   void start(); 644   void start();
724   645  
725   /** Return the local endpoint for the i-th bound port. 646   /** Return the local endpoint for the i-th bound port.
726   647  
727   @param index Zero-based index into the list of bound ports. 648   @param index Zero-based index into the list of bound ports.
728   649  
729   @return The local endpoint, or a default-constructed endpoint 650   @return The local endpoint, or a default-constructed endpoint
730   if @p index is out of range or the acceptor is not open. 651   if @p index is out of range or the acceptor is not open.
731   */ 652   */
732   endpoint local_endpoint(std::size_t index = 0) const noexcept; 653   endpoint local_endpoint(std::size_t index = 0) const noexcept;
733   654  
734   /** Stop accepting connections. 655   /** Stop accepting connections.
735   656  
736   Requests the accept loops' stop token and requests cancellation 657   Requests the accept loops' stop token and requests cancellation
737   of active workers via their stop tokens. The acceptors are not 658   of active workers via their stop tokens. The acceptors are not
738   closed; a suspended accept completes once more before its loop 659   closed; a suspended accept completes once more before its loop
739   observes the stop token and ends. 660   observes the stop token and ends.
740   661  
741   This function returns immediately; it does not wait for workers 662   This function returns immediately; it does not wait for workers
742   to finish. Pending I/O operations complete asynchronously. 663   to finish. Pending I/O operations complete asynchronously.
743   664  
744   Calling `stop()` on a non-running server has no effect. 665   Calling `stop()` on a non-running server has no effect.
745   666  
746   @par Effects 667   @par Effects
747   - Requests stop on the accept loops' stop token. The acceptors 668   - Requests stop on the accept loops' stop token. The acceptors
748   are not closed; a pending accept completes once more before 669   are not closed; a pending accept completes once more before
749   the accept loop ends. 670   the accept loop ends.
750   - Requests stop on each active worker's stop token. 671   - Requests stop on each active worker's stop token.
751   - Workers observing their stop token should exit promptly. 672   - Workers observing their stop token should exit promptly.
752   673  
753   @par Postconditions 674   @par Postconditions
754   No new connections will be accepted. Active workers continue 675   No new connections will be accepted. Active workers continue
755   until they observe their stop token or complete naturally. 676   until they observe their stop token or complete naturally.
756   677  
757   @par What Happens Next 678   @par What Happens Next
758   After calling `stop()`: 679   After calling `stop()`:
759   1. Let `ioc.run()` return (drains pending completions). 680   1. Let `ioc.run()` return (drains pending completions).
760   2. Call @ref join to wait for accept loops to finish. 681   2. Call @ref join to wait for accept loops to finish.
761   3. Only then is it safe to restart or destroy the server. 682   3. Only then is it safe to restart or destroy the server.
762   683  
763   @par Thread Safety 684   @par Thread Safety
764   Not thread safe. 685   Not thread safe.
765   686  
766   @see join, start 687   @see join, start
767   */ 688   */
768   void stop(); 689   void stop();
769   690  
770   /** Block until all accept loops complete. 691   /** Block until all accept loops complete.
771   692  
772   Blocks the calling thread until all accept coroutines launched 693   Blocks the calling thread until all accept coroutines launched
773   by @ref start have finished executing. This synchronizes the 694   by @ref start have finished executing. This synchronizes the
774   shutdown sequence, ensuring the server is fully stopped before 695   shutdown sequence, ensuring the server is fully stopped before
775   restarting or destroying it. 696   restarting or destroying it.
776   697  
777   @par Preconditions 698   @par Preconditions
778   @ref stop has been called and `ioc.run()` has returned. 699   @ref stop has been called and `ioc.run()` has returned.
779   700  
780   @par Postconditions 701   @par Postconditions
781   All accept loops have completed. The server is in the stopped 702   All accept loops have completed. The server is in the stopped
782   state and may be restarted via @ref start. 703   state and may be restarted via @ref start.
783   704  
784   @par Example (Correct Usage) 705   @par Example (Correct Usage)
785 - @code 706 + @par !example correct_usage
786 - // main thread  
787 - srv.start();  
788 - ioc.run(); // Blocks until work completes  
789 - srv.join(); // Safe: called after ioc.run() returns  
790 - @endcode  
791 -  
792 - @par WARNING: Deadlock Scenarios  
793 - Calling `join()` from the wrong context causes deadlock:  
794   707  
795 - @code 708 + @par WARNING: Deadlock Scenario
796 - // WRONG: calling join() from inside a worker coroutine 709 + Calling `join()` from inside a worker coroutine deadlocks:
797 - void run( launcher launch ) override  
798 - {  
799 - launch( ex, [this]() -> capy::task<>  
800 - {  
801 - srv_.join(); // DEADLOCK: blocks the executor  
802 - co_return;  
803 - }());  
804 - }  
805   710  
806 - // WRONG: calling join() while ioc.run() is still active 711 + @par !example deadlock_scenarios
807 - std::thread t( [&]{ ioc.run(); } );  
808 - srv.stop();  
809 - srv.join(); // DEADLOCK: ioc.run() still running in thread t  
810 - @endcode  
811   712  
812   @par Thread Safety 713   @par Thread Safety
813   May be called from any thread, but will deadlock if called 714   May be called from any thread, but will deadlock if called
814   from within the io_context event loop or from a worker coroutine. 715   from within the io_context event loop or from a worker coroutine.
815   716  
816   @see stop, start 717   @see stop, start
817   */ 718   */
818   void join(); 719   void join();
819   720  
820   private: 721   private:
821   capy::task<> do_stop(); 722   capy::task<> do_stop();
822   }; 723   };
823   724  
824   #ifdef _MSC_VER 725   #ifdef _MSC_VER
825   #pragma warning(pop) 726   #pragma warning(pop)
826   #endif 727   #endif
827   728  
828   } // namespace boost::corosio 729   } // namespace boost::corosio
829   730  
830   #endif 731   #endif