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