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