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