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  
    srv.bind(endpoint{address_v4::any(), 8080});
74  
    srv.start();
74  
    srv.start();
75  
    ioc.run();  // Blocks until all work completes
75  
    ioc.run();  // Blocks until all work completes
76  
    @endcode
76  
    @endcode
77  

77  

78  
    @par Graceful Shutdown
78  
    @par Graceful Shutdown
79  
    To shut down gracefully, call @ref stop then drain the io_context:
79  
    To shut down gracefully, call @ref stop then drain the io_context:
80  
    @code
80  
    @code
81  
    // From a signal handler or timer callback:
81  
    // From a signal handler or timer callback:
82  
    srv.stop();
82  
    srv.stop();
83  

83  

84  
    // ioc.run() returns after pending work drains.
84  
    // ioc.run() returns after pending work drains.
85  
    // Then from the thread that called ioc.run():
85  
    // Then from the thread that called ioc.run():
86  
    srv.join();  // Wait for accept loops to finish
86  
    srv.join();  // Wait for accept loops to finish
87  
    @endcode
87  
    @endcode
88  

88  

89  
    @par Restart After Stop
89  
    @par Restart After Stop
90  
    The server can be restarted after a complete shutdown cycle.
90  
    The server can be restarted after a complete shutdown cycle.
91  
    You must drain the io_context and call @ref join before restarting:
91  
    You must drain the io_context and call @ref join before restarting:
92  
    @code
92  
    @code
93  
    srv.start();
93  
    srv.start();
94  
    ioc.run_for( 10s );   // Run for a while
94  
    ioc.run_for( 10s );   // Run for a while
95  
    srv.stop();           // Signal shutdown
95  
    srv.stop();           // Signal shutdown
96  
    ioc.run();            // REQUIRED: drain pending completions
96  
    ioc.run();            // REQUIRED: drain pending completions
97  
    srv.join();           // REQUIRED: wait for accept loops
97  
    srv.join();           // REQUIRED: wait for accept loops
98  

98  

99  
    // Now safe to restart
99  
    // Now safe to restart
100  
    srv.start();
100  
    srv.start();
101  
    ioc.run();
101  
    ioc.run();
102  
    @endcode
102  
    @endcode
103  

103  

104  
    @par WARNING: What NOT to Do
104  
    @par WARNING: What NOT to Do
105  
    - Do NOT call @ref join from inside a worker coroutine (deadlock).
105  
    - Do NOT call @ref join from inside a worker coroutine (deadlock).
106  
    - Do NOT call @ref join from a thread running `ioc.run()` (deadlock).
106  
    - 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.
107  
    - 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.
108  
    - Do NOT call `ioc.stop()` for graceful shutdown; use @ref stop instead.
109  

109  

110  
    @par Example
110  
    @par Example
111  
    @code
111  
    @code
112  
    class my_worker : public tcp_server::worker_base
112  
    class my_worker : public tcp_server::worker_base
113  
    {
113  
    {
114  
        corosio::tcp_socket sock_;
114  
        corosio::tcp_socket sock_;
115  
        capy::any_executor ex_;
115  
        capy::any_executor ex_;
116  
    public:
116  
    public:
117  
        my_worker(io_context& ctx)
117  
        my_worker(io_context& ctx)
118  
            : sock_(ctx)
118  
            : sock_(ctx)
119  
            , ex_(ctx.get_executor())
119  
            , ex_(ctx.get_executor())
120  
        {
120  
        {
121  
        }
121  
        }
122  

122  

123  
        corosio::tcp_socket& socket() override { return sock_; }
123  
        corosio::tcp_socket& socket() override { return sock_; }
124  

124  

125  
        void run(launcher launch) override
125  
        void run(launcher launch) override
126  
        {
126  
        {
127  
            launch(ex_, [](corosio::tcp_socket* sock) -> capy::task<>
127  
            launch(ex_, [](corosio::tcp_socket* sock) -> capy::task<>
128  
            {
128  
            {
129  
                // handle connection using sock
129  
                // handle connection using sock
130  
                co_return;
130  
                co_return;
131  
            }(&sock_));
131  
            }(&sock_));
132  
        }
132  
        }
133  
    };
133  
    };
134  

134  

135  
    auto make_workers(io_context& ctx, int n)
135  
    auto make_workers(io_context& ctx, int n)
136  
    {
136  
    {
137  
        std::vector<std::unique_ptr<tcp_server::worker_base>> v;
137  
        std::vector<std::unique_ptr<tcp_server::worker_base>> v;
138  
        v.reserve(n);
138  
        v.reserve(n);
139  
        for(int i = 0; i < n; ++i)
139  
        for(int i = 0; i < n; ++i)
140  
            v.push_back(std::make_unique<my_worker>(ctx));
140  
            v.push_back(std::make_unique<my_worker>(ctx));
141  
        return v;
141  
        return v;
142  
    }
142  
    }
143  

143  

144  
    io_context ioc;
144  
    io_context ioc;
145  
    tcp_server srv(ioc, ioc.get_executor());
145  
    tcp_server srv(ioc, ioc.get_executor());
146  
    srv.set_workers(make_workers(ioc, 100));
146  
    srv.set_workers(make_workers(ioc, 100));
147  
    @endcode
147  
    @endcode
148  

148  

149  
    @see worker_base, set_workers, launcher
149  
    @see worker_base, set_workers, launcher
150  
*/
150  
*/
151 -
class BOOST_COROSIO_DECL
151 +
class BOOST_COROSIO_DECL tcp_server
152 -
    tcp_server
 
153  
{
152  
{
154  
public:
153  
public:
155 -
    class worker_base;  ///< Abstract base for connection handlers.
154 +
    class worker_base; ///< Abstract base for connection handlers.
156 -
    class launcher;     ///< Move-only handle to launch worker coroutines.
155 +
    class launcher;    ///< Move-only handle to launch worker coroutines.
157  

156  

158  
private:
157  
private:
159  
    struct waiter
158  
    struct waiter
160  
    {
159  
    {
161  
        waiter* next;
160  
        waiter* next;
162  
        std::coroutine_handle<> h;
161  
        std::coroutine_handle<> h;
163  
        worker_base* w;
162  
        worker_base* w;
164  
    };
163  
    };
165  

164  

166  
    struct impl;
165  
    struct impl;
167  

166  

168  
    static impl* make_impl(capy::execution_context& ctx);
167  
    static impl* make_impl(capy::execution_context& ctx);
169  

168  

170  
    impl* impl_;
169  
    impl* impl_;
171  
    capy::any_executor ex_;
170  
    capy::any_executor ex_;
172  
    waiter* waiters_ = nullptr;
171  
    waiter* waiters_ = nullptr;
173 -
    worker_base* idle_head_ = nullptr;    // Forward list: available workers
172 +
    worker_base* idle_head_ = nullptr; // Forward list: available workers
174 -
    worker_base* active_head_ = nullptr;  // Doubly linked: workers handling connections
173 +
    worker_base* active_head_ =
175 -
    worker_base* active_tail_ = nullptr;  // Tail for O(1) push_back
174 +
        nullptr; // Doubly linked: workers handling connections
176 -
    std::size_t active_accepts_ = 0;      // Number of active do_accept coroutines
175 +
    worker_base* active_tail_ = nullptr; // Tail for O(1) push_back
177 -
    std::shared_ptr<void> storage_;       // Owns the worker container (type-erased)
176 +
    std::size_t active_accepts_ = 0; // Number of active do_accept coroutines
 
177 +
    std::shared_ptr<void> storage_;  // Owns the worker container (type-erased)
178  
    bool running_ = false;
178  
    bool running_ = false;
179  

179  

180  
    // Idle list (forward/singly linked) - push front, pop front
180  
    // Idle list (forward/singly linked) - push front, pop front
181  
    void idle_push(worker_base* w) noexcept
181  
    void idle_push(worker_base* w) noexcept
182  
    {
182  
    {
183  
        w->next_ = idle_head_;
183  
        w->next_ = idle_head_;
184  
        idle_head_ = w;
184  
        idle_head_ = w;
185  
    }
185  
    }
186  

186  

187  
    worker_base* idle_pop() noexcept
187  
    worker_base* idle_pop() noexcept
188  
    {
188  
    {
189  
        auto* w = idle_head_;
189  
        auto* w = idle_head_;
190 -
        if(w) idle_head_ = w->next_;
190 +
        if (w)
 
191 +
            idle_head_ = w->next_;
191  
        return w;
192  
        return w;
192  
    }
193  
    }
193  

194  

194 -
    bool idle_empty() const noexcept { return idle_head_ == nullptr; }
195 +
    bool idle_empty() const noexcept
 
196 +
    {
 
197 +
        return idle_head_ == nullptr;
 
198 +
    }
195  

199  

196  
    // Active list (doubly linked) - push back, remove anywhere
200  
    // Active list (doubly linked) - push back, remove anywhere
197  
    void active_push(worker_base* w) noexcept
201  
    void active_push(worker_base* w) noexcept
198  
    {
202  
    {
199  
        w->next_ = nullptr;
203  
        w->next_ = nullptr;
200  
        w->prev_ = active_tail_;
204  
        w->prev_ = active_tail_;
201 -
        if(active_tail_)
205 +
        if (active_tail_)
202  
            active_tail_->next_ = w;
206  
            active_tail_->next_ = w;
203  
        else
207  
        else
204  
            active_head_ = w;
208  
            active_head_ = w;
205  
        active_tail_ = w;
209  
        active_tail_ = w;
206  
    }
210  
    }
207  

211  

208  
    void active_remove(worker_base* w) noexcept
212  
    void active_remove(worker_base* w) noexcept
209  
    {
213  
    {
210  
        // Skip if not in active list (e.g., after failed accept)
214  
        // Skip if not in active list (e.g., after failed accept)
211 -
        if(w != active_head_ && w->prev_ == nullptr)
215 +
        if (w != active_head_ && w->prev_ == nullptr)
212  
            return;
216  
            return;
213 -
        if(w->prev_)
217 +
        if (w->prev_)
214  
            w->prev_->next_ = w->next_;
218  
            w->prev_->next_ = w->next_;
215  
        else
219  
        else
216  
            active_head_ = w->next_;
220  
            active_head_ = w->next_;
217 -
        if(w->next_)
221 +
        if (w->next_)
218  
            w->next_->prev_ = w->prev_;
222  
            w->next_->prev_ = w->prev_;
219  
        else
223  
        else
220  
            active_tail_ = w->prev_;
224  
            active_tail_ = w->prev_;
221 -
        w->prev_ = nullptr;  // Mark as not in active list
225 +
        w->prev_ = nullptr; // Mark as not in active list
222  
    }
226  
    }
223  

227  

224  
    template<capy::Executor Ex>
228  
    template<capy::Executor Ex>
225  
    struct launch_wrapper
229  
    struct launch_wrapper
226  
    {
230  
    {
227  
        struct promise_type
231  
        struct promise_type
228  
        {
232  
        {
229 -
            Ex ex;  // Executor stored directly in frame (outlives child tasks)
233 +
            Ex ex; // Executor stored directly in frame (outlives child tasks)
230  
            capy::io_env env_;
234  
            capy::io_env env_;
231  

235  

232  
            // For regular coroutines: first arg is executor, second is stop token
236  
            // For regular coroutines: first arg is executor, second is stop token
233  
            template<class E, class S, class... Args>
237  
            template<class E, class S, class... Args>
234  
                requires capy::Executor<std::decay_t<E>>
238  
                requires capy::Executor<std::decay_t<E>>
235  
            promise_type(E e, S s, Args&&...)
239  
            promise_type(E e, S s, Args&&...)
236  
                : ex(std::move(e))
240  
                : ex(std::move(e))
237 -
                , env_{capy::executor_ref(ex), std::move(s),
241 +
                , env_{
238 -
                       capy::get_current_frame_allocator()}
242 +
                      capy::executor_ref(ex), std::move(s),
 
243 +
                      capy::get_current_frame_allocator()}
239  
            {
244  
            {
240  
            }
245  
            }
241  

246  

242  
            // For lambda coroutines: first arg is closure, second is executor, third is stop token
247  
            // For lambda coroutines: first arg is closure, second is executor, third is stop token
243  
            template<class Closure, class E, class S, class... Args>
248  
            template<class Closure, class E, class S, class... Args>
244 -
                requires (!capy::Executor<std::decay_t<Closure>> && 
249 +
                requires(!capy::Executor<std::decay_t<Closure>> &&
245 -
                          capy::Executor<std::decay_t<E>>)
250 +
                         capy::Executor<std::decay_t<E>>)
246  
            promise_type(Closure&&, E e, S s, Args&&...)
251  
            promise_type(Closure&&, E e, S s, Args&&...)
247  
                : ex(std::move(e))
252  
                : ex(std::move(e))
248 -
                , env_{capy::executor_ref(ex), std::move(s),
253 +
                , env_{
249 -
                       capy::get_current_frame_allocator()}
254 +
                      capy::executor_ref(ex), std::move(s),
 
255 +
                      capy::get_current_frame_allocator()}
250  
            {
256  
            {
251  
            }
257  
            }
252  

258  

253 -
            launch_wrapper get_return_object() noexcept {
259 +
            launch_wrapper get_return_object() noexcept
254 -
                return {std::coroutine_handle<promise_type>::from_promise(*this)};
260 +
            {
 
261 +
                return {
 
262 +
                    std::coroutine_handle<promise_type>::from_promise(*this)};
 
263 +
            }
 
264 +
            std::suspend_always initial_suspend() noexcept
 
265 +
            {
 
266 +
                return {};
 
267 +
            }
 
268 +
            std::suspend_never final_suspend() noexcept
 
269 +
            {
 
270 +
                return {};
255 -
            std::suspend_always initial_suspend() noexcept { return {}; }
 
256 -
            std::suspend_never final_suspend() noexcept { return {}; }
 
257  
            }
271  
            }
258  
            void return_void() noexcept {}
272  
            void return_void() noexcept {}
259 -
            void unhandled_exception() { std::terminate(); }
273 +
            void unhandled_exception()
 
274 +
            {
 
275 +
                std::terminate();
 
276 +
            }
260  

277  

261  
            // Inject io_env for IoAwaitable
278  
            // Inject io_env for IoAwaitable
262  
            template<capy::IoAwaitable Awaitable>
279  
            template<capy::IoAwaitable Awaitable>
263  
            auto await_transform(Awaitable&& a)
280  
            auto await_transform(Awaitable&& a)
264  
            {
281  
            {
265  
                using AwaitableT = std::decay_t<Awaitable>;
282  
                using AwaitableT = std::decay_t<Awaitable>;
266  
                struct adapter
283  
                struct adapter
267  
                {
284  
                {
268  
                    AwaitableT aw;
285  
                    AwaitableT aw;
269  
                    capy::io_env const* env;
286  
                    capy::io_env const* env;
270  

287  

271 -
                    bool await_ready() { return aw.await_ready(); }
288 +
                    bool await_ready()
272 -
                    decltype(auto) await_resume() { return aw.await_resume(); }
289 +
                    {
 
290 +
                        return aw.await_ready();
 
291 +
                    }
 
292 +
                    decltype(auto) await_resume()
 
293 +
                    {
 
294 +
                        return aw.await_resume();
 
295 +
                    }
273  

296  

274  
                    auto await_suspend(std::coroutine_handle<promise_type> h)
297  
                    auto await_suspend(std::coroutine_handle<promise_type> h)
275  
                    {
298  
                    {
276  
                        return aw.await_suspend(h, env);
299  
                        return aw.await_suspend(h, env);
277  
                    }
300  
                    }
278  
                };
301  
                };
279  
                return adapter{std::forward<Awaitable>(a), &env_};
302  
                return adapter{std::forward<Awaitable>(a), &env_};
280  
            }
303  
            }
281  
        };
304  
        };
282  

305  

283  
        std::coroutine_handle<promise_type> h;
306  
        std::coroutine_handle<promise_type> h;
284  

307  

285  
        launch_wrapper(std::coroutine_handle<promise_type> handle) noexcept
308  
        launch_wrapper(std::coroutine_handle<promise_type> handle) noexcept
286  
            : h(handle)
309  
            : h(handle)
287  
        {
310  
        {
288  
        }
311  
        }
289  

312  

290  
        ~launch_wrapper()
313  
        ~launch_wrapper()
291  
        {
314  
        {
292 -
            if(h)
315 +
            if (h)
293  
                h.destroy();
316  
                h.destroy();
294  
        }
317  
        }
295  

318  

296  
        launch_wrapper(launch_wrapper&& o) noexcept
319  
        launch_wrapper(launch_wrapper&& o) noexcept
297  
            : h(std::exchange(o.h, nullptr))
320  
            : h(std::exchange(o.h, nullptr))
298  
        {
321  
        {
299  
        }
322  
        }
300  

323  

301  
        launch_wrapper(launch_wrapper const&) = delete;
324  
        launch_wrapper(launch_wrapper const&) = delete;
302  
        launch_wrapper& operator=(launch_wrapper const&) = delete;
325  
        launch_wrapper& operator=(launch_wrapper const&) = delete;
303  
        launch_wrapper& operator=(launch_wrapper&&) = delete;
326  
        launch_wrapper& operator=(launch_wrapper&&) = delete;
304  
    };
327  
    };
305  

328  

306  
    // Named functor to avoid incomplete lambda type in coroutine promise
329  
    // Named functor to avoid incomplete lambda type in coroutine promise
307  
    template<class Executor>
330  
    template<class Executor>
308  
    struct launch_coro
331  
    struct launch_coro
309  
    {
332  
    {
310  
        launch_wrapper<Executor> operator()(
333  
        launch_wrapper<Executor> operator()(
311  
            Executor,
334  
            Executor,
312  
            std::stop_token,
335  
            std::stop_token,
313  
            tcp_server* self,
336  
            tcp_server* self,
314  
            capy::task<void> t,
337  
            capy::task<void> t,
315  
            worker_base* wp)
338  
            worker_base* wp)
316  
        {
339  
        {
317  
            // Executor and stop token stored in promise via constructor
340  
            // Executor and stop token stored in promise via constructor
318  
            co_await std::move(t);
341  
            co_await std::move(t);
319  
            co_await self->push(*wp); // worker goes back to idle list
342  
            co_await self->push(*wp); // worker goes back to idle list
320  
        }
343  
        }
321  
    };
344  
    };
322  

345  

323  
    class push_awaitable
346  
    class push_awaitable
324  
    {
347  
    {
325  
        tcp_server& self_;
348  
        tcp_server& self_;
326  
        worker_base& w_;
349  
        worker_base& w_;
327  

350  

328  
    public:
351  
    public:
329 -
        push_awaitable(
352 +
        push_awaitable(tcp_server& self, worker_base& w) noexcept
330 -
            tcp_server& self,
 
331 -
            worker_base& w) noexcept
 
332  
            : self_(self)
353  
            : self_(self)
333  
            , w_(w)
354  
            , w_(w)
334  
        {
355  
        {
335  
        }
356  
        }
336  

357  

337  
        bool await_ready() const noexcept
358  
        bool await_ready() const noexcept
338  
        {
359  
        {
339  
            return false;
360  
            return false;
340  
        }
361  
        }
341  

362  

342  
        std::coroutine_handle<>
363  
        std::coroutine_handle<>
343 -
        await_suspend(
364 +
        await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept
344 -
            std::coroutine_handle<> h,
 
345 -
            capy::io_env const*) noexcept
 
346  
        {
365  
        {
347  
            // Symmetric transfer to server's executor
366  
            // Symmetric transfer to server's executor
348  
            return self_.ex_.dispatch(h);
367  
            return self_.ex_.dispatch(h);
349  
        }
368  
        }
350  

369  

351  
        void await_resume() noexcept
370  
        void await_resume() noexcept
352  
        {
371  
        {
353  
            // Running on server executor - safe to modify lists
372  
            // Running on server executor - safe to modify lists
354  
            // Remove from active (if present), then wake waiter or add to idle
373  
            // Remove from active (if present), then wake waiter or add to idle
355  
            self_.active_remove(&w_);
374  
            self_.active_remove(&w_);
356 -
            if(self_.waiters_)
375 +
            if (self_.waiters_)
357  
            {
376  
            {
358  
                auto* wait = self_.waiters_;
377  
                auto* wait = self_.waiters_;
359  
                self_.waiters_ = wait->next;
378  
                self_.waiters_ = wait->next;
360  
                wait->w = &w_;
379  
                wait->w = &w_;
361  
                self_.ex_.post(wait->h);
380  
                self_.ex_.post(wait->h);
362  
            }
381  
            }
363  
            else
382  
            else
364  
            {
383  
            {
365  
                self_.idle_push(&w_);
384  
                self_.idle_push(&w_);
366  
            }
385  
            }
367  
        }
386  
        }
368  
    };
387  
    };
369  

388  

370  
    class pop_awaitable
389  
    class pop_awaitable
371  
    {
390  
    {
372  
        tcp_server& self_;
391  
        tcp_server& self_;
373  
        waiter wait_;
392  
        waiter wait_;
374  

393  

375  
    public:
394  
    public:
376 -
        pop_awaitable(tcp_server& self) noexcept
395 +
        pop_awaitable(tcp_server& self) noexcept : self_(self), wait_{} {}
377 -
            : self_(self)
 
378 -
            , wait_{}
 
379 -
        {
 
380 -
        }
 
381  

396  

382  
        bool await_ready() const noexcept
397  
        bool await_ready() const noexcept
383  
        {
398  
        {
384  
            return !self_.idle_empty();
399  
            return !self_.idle_empty();
385  
        }
400  
        }
386  

401  

387  
        bool
402  
        bool
388 -
        await_suspend(
403 +
        await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept
389 -
            std::coroutine_handle<> h,
 
390 -
            capy::io_env const*) noexcept
 
391  
        {
404  
        {
392  
            // Running on server executor (do_accept runs there)
405  
            // Running on server executor (do_accept runs there)
393  
            wait_.h = h;
406  
            wait_.h = h;
394  
            wait_.w = nullptr;
407  
            wait_.w = nullptr;
395  
            wait_.next = self_.waiters_;
408  
            wait_.next = self_.waiters_;
396  
            self_.waiters_ = &wait_;
409  
            self_.waiters_ = &wait_;
397  
            return true;
410  
            return true;
398  
        }
411  
        }
399  

412  

400  
        worker_base& await_resume() noexcept
413  
        worker_base& await_resume() noexcept
401  
        {
414  
        {
402  
            // Running on server executor
415  
            // Running on server executor
403 -
            if(wait_.w)
416 +
            if (wait_.w)
404 -
                return *wait_.w;  // Woken by push_awaitable
417 +
                return *wait_.w; // Woken by push_awaitable
405  
            return *self_.idle_pop();
418  
            return *self_.idle_pop();
406  
        }
419  
        }
407  
    };
420  
    };
408  

421  

409  
    push_awaitable push(worker_base& w)
422  
    push_awaitable push(worker_base& w)
410  
    {
423  
    {
411  
        return push_awaitable{*this, w};
424  
        return push_awaitable{*this, w};
412  
    }
425  
    }
413  

426  

414  
    // Synchronous version for destructor/guard paths
427  
    // Synchronous version for destructor/guard paths
415  
    // Must be called from server executor context
428  
    // Must be called from server executor context
416  
    void push_sync(worker_base& w) noexcept
429  
    void push_sync(worker_base& w) noexcept
417  
    {
430  
    {
418  
        active_remove(&w);
431  
        active_remove(&w);
419 -
        if(waiters_)
432 +
        if (waiters_)
420  
        {
433  
        {
421  
            auto* wait = waiters_;
434  
            auto* wait = waiters_;
422  
            waiters_ = wait->next;
435  
            waiters_ = wait->next;
423  
            wait->w = &w;
436  
            wait->w = &w;
424  
            ex_.post(wait->h);
437  
            ex_.post(wait->h);
425  
        }
438  
        }
426  
        else
439  
        else
427  
        {
440  
        {
428  
            idle_push(&w);
441  
            idle_push(&w);
429  
        }
442  
        }
430  
    }
443  
    }
431  

444  

432  
    pop_awaitable pop()
445  
    pop_awaitable pop()
433  
    {
446  
    {
434  
        return pop_awaitable{*this};
447  
        return pop_awaitable{*this};
435  
    }
448  
    }
436  

449  

437  
    capy::task<void> do_accept(tcp_acceptor& acc);
450  
    capy::task<void> do_accept(tcp_acceptor& acc);
438  

451  

439  
public:
452  
public:
440  
    /** Abstract base class for connection handlers.
453  
    /** Abstract base class for connection handlers.
441  

454  

442  
        Derive from this class to implement custom connection handling.
455  
        Derive from this class to implement custom connection handling.
443  
        Each worker owns a socket and is reused across multiple
456  
        Each worker owns a socket and is reused across multiple
444  
        connections to avoid per-connection allocation.
457  
        connections to avoid per-connection allocation.
445  

458  

446  
        @see tcp_server, launcher
459  
        @see tcp_server, launcher
447  
    */
460  
    */
448 -
    class BOOST_COROSIO_DECL
461 +
    class BOOST_COROSIO_DECL worker_base
449 -
        worker_base
 
450  
    {
462  
    {
451  
        // Ordered largest to smallest for optimal packing
463  
        // Ordered largest to smallest for optimal packing
452 -
        std::stop_source stop_;        // ~16 bytes
464 +
        std::stop_source stop_;       // ~16 bytes
453 -
        worker_base* next_ = nullptr;  // 8 bytes - used by idle and active lists
465 +
        worker_base* next_ = nullptr; // 8 bytes - used by idle and active lists
454 -
        worker_base* prev_ = nullptr;  // 8 bytes - used only by active list
466 +
        worker_base* prev_ = nullptr; // 8 bytes - used only by active list
455  

467  

456  
        friend class tcp_server;
468  
        friend class tcp_server;
457  

469  

458  
    public:
470  
    public:
459  
        /// Destroy the worker.
471  
        /// Destroy the worker.
460  
        virtual ~worker_base() = default;
472  
        virtual ~worker_base() = default;
461  

473  

462  
        /** Handle an accepted connection.
474  
        /** Handle an accepted connection.
463  

475  

464  
            Called when this worker is dispatched to handle a new
476  
            Called when this worker is dispatched to handle a new
465  
            connection. The implementation must invoke the launcher
477  
            connection. The implementation must invoke the launcher
466  
            exactly once to start the handling coroutine.
478  
            exactly once to start the handling coroutine.
467  

479  

468  
            @param launch Handle to launch the connection coroutine.
480  
            @param launch Handle to launch the connection coroutine.
469  
        */
481  
        */
470  
        virtual void run(launcher launch) = 0;
482  
        virtual void run(launcher launch) = 0;
471  

483  

472  
        /// Return the socket used for connections.
484  
        /// Return the socket used for connections.
473  
        virtual corosio::tcp_socket& socket() = 0;
485  
        virtual corosio::tcp_socket& socket() = 0;
474  
    };
486  
    };
475  

487  

476  
    /** Move-only handle to launch a worker coroutine.
488  
    /** Move-only handle to launch a worker coroutine.
477  

489  

478  
        Passed to @ref worker_base::run to start the connection-handling
490  
        Passed to @ref worker_base::run to start the connection-handling
479  
        coroutine. The launcher ensures the worker returns to the idle
491  
        coroutine. The launcher ensures the worker returns to the idle
480  
        pool when the coroutine completes or if launching fails.
492  
        pool when the coroutine completes or if launching fails.
481  

493  

482  
        The launcher must be invoked exactly once via `operator()`.
494  
        The launcher must be invoked exactly once via `operator()`.
483  
        If destroyed without invoking, the worker is returned to the
495  
        If destroyed without invoking, the worker is returned to the
484  
        idle pool automatically.
496  
        idle pool automatically.
485  

497  

486  
        @see worker_base::run
498  
        @see worker_base::run
487  
    */
499  
    */
488 -
    class BOOST_COROSIO_DECL
500 +
    class BOOST_COROSIO_DECL launcher
489 -
        launcher
 
490  
    {
501  
    {
491  
        tcp_server* srv_;
502  
        tcp_server* srv_;
492  
        worker_base* w_;
503  
        worker_base* w_;
493  

504  

494  
        friend class tcp_server;
505  
        friend class tcp_server;
495  

506  

496 -
        launcher(tcp_server& srv, worker_base& w) noexcept
507 +
        launcher(tcp_server& srv, worker_base& w) noexcept : srv_(&srv), w_(&w)
497 -
            : srv_(&srv)
 
498 -
            , w_(&w)
 
499  
        {
508  
        {
500  
        }
509  
        }
501  

510  

502  
    public:
511  
    public:
503  
        /// Return the worker to the pool if not launched.
512  
        /// Return the worker to the pool if not launched.
504  
        ~launcher()
513  
        ~launcher()
505  
        {
514  
        {
506 -
            if(w_)
515 +
            if (w_)
507  
                srv_->push_sync(*w_);
516  
                srv_->push_sync(*w_);
508  
        }
517  
        }
509  

518  

510  
        launcher(launcher&& o) noexcept
519  
        launcher(launcher&& o) noexcept
511  
            : srv_(o.srv_)
520  
            : srv_(o.srv_)
512  
            , w_(std::exchange(o.w_, nullptr))
521  
            , w_(std::exchange(o.w_, nullptr))
513  
        {
522  
        {
514  
        }
523  
        }
515  
        launcher(launcher const&) = delete;
524  
        launcher(launcher const&) = delete;
516  
        launcher& operator=(launcher const&) = delete;
525  
        launcher& operator=(launcher const&) = delete;
517  
        launcher& operator=(launcher&&) = delete;
526  
        launcher& operator=(launcher&&) = delete;
518  

527  

519  
        /** Launch the connection-handling coroutine.
528  
        /** Launch the connection-handling coroutine.
520  

529  

521  
            Starts the given coroutine on the specified executor. When
530  
            Starts the given coroutine on the specified executor. When
522  
            the coroutine completes, the worker is automatically returned
531  
            the coroutine completes, the worker is automatically returned
523  
            to the idle pool.
532  
            to the idle pool.
524  

533  

525  
            @param ex The executor to run the coroutine on.
534  
            @param ex The executor to run the coroutine on.
526  
            @param task The coroutine to execute.
535  
            @param task The coroutine to execute.
527  

536  

528  
            @throws std::logic_error If this launcher was already invoked.
537  
            @throws std::logic_error If this launcher was already invoked.
529  
        */
538  
        */
530  
        template<class Executor>
539  
        template<class Executor>
531  
        void operator()(Executor const& ex, capy::task<void> task)
540  
        void operator()(Executor const& ex, capy::task<void> task)
532  
        {
541  
        {
533 -
            if(! w_)
542 +
            if (!w_)
534  
                detail::throw_logic_error(); // launcher already invoked
543  
                detail::throw_logic_error(); // launcher already invoked
535  

544  

536  
            auto* w = std::exchange(w_, nullptr);
545  
            auto* w = std::exchange(w_, nullptr);
537  

546  

538  
            // Worker is being dispatched - add to active list
547  
            // Worker is being dispatched - add to active list
539  
            srv_->active_push(w);
548  
            srv_->active_push(w);
540  

549  

541  
            // Return worker to pool if coroutine setup throws
550  
            // Return worker to pool if coroutine setup throws
542 -
            struct guard_t {
551 +
            struct guard_t
 
552 +
            {
543  
                tcp_server* srv;
553  
                tcp_server* srv;
544  
                worker_base* w;
554  
                worker_base* w;
545 -
                ~guard_t() { if(w) srv->push_sync(*w); }
555 +
                ~guard_t()
 
556 +
                {
 
557 +
                    if (w)
 
558 +
                        srv->push_sync(*w);
 
559 +
                }
546  
            } guard{srv_, w};
560  
            } guard{srv_, w};
547  

561  

548  
            // Reset worker's stop source for this connection
562  
            // Reset worker's stop source for this connection
549  
            w->stop_ = {};
563  
            w->stop_ = {};
550  
            auto st = w->stop_.get_token();
564  
            auto st = w->stop_.get_token();
551  

565  

552 -
            auto wrapper = launch_coro<Executor>{}(
566 +
            auto wrapper =
553 -
                ex, st, srv_, std::move(task), w);
567 +
                launch_coro<Executor>{}(ex, st, srv_, std::move(task), w);
554  

568  

555  
            // Executor and stop token stored in promise via constructor
569  
            // Executor and stop token stored in promise via constructor
556  
            ex.post(std::exchange(wrapper.h, nullptr)); // Release before post
570  
            ex.post(std::exchange(wrapper.h, nullptr)); // Release before post
557  
            guard.w = nullptr; // Success - dismiss guard
571  
            guard.w = nullptr; // Success - dismiss guard
558  
        }
572  
        }
559  
    };
573  
    };
560  

574  

561  
    /** Construct a TCP server.
575  
    /** Construct a TCP server.
562  

576  

563  
        @tparam Ctx Execution context type satisfying ExecutionContext.
577  
        @tparam Ctx Execution context type satisfying ExecutionContext.
564  
        @tparam Ex Executor type satisfying Executor.
578  
        @tparam Ex Executor type satisfying Executor.
565  

579  

566  
        @param ctx The execution context for socket operations.
580  
        @param ctx The execution context for socket operations.
567  
        @param ex The executor for dispatching coroutines.
581  
        @param ex The executor for dispatching coroutines.
568  

582  

569  
        @par Example
583  
        @par Example
570  
        @code
584  
        @code
571  
        tcp_server srv(ctx, ctx.get_executor());
585  
        tcp_server srv(ctx, ctx.get_executor());
572  
        srv.set_workers(make_workers(ctx, 100));
586  
        srv.set_workers(make_workers(ctx, 100));
573  
        srv.bind(endpoint{...});
587  
        srv.bind(endpoint{...});
574  
        srv.start();
588  
        srv.start();
575  
        @endcode
589  
        @endcode
576  
    */
590  
    */
577 -
    template<
591 +
    template<capy::ExecutionContext Ctx, capy::Executor Ex>
578 -
        capy::ExecutionContext Ctx,
592 +
    tcp_server(Ctx& ctx, Ex ex) : impl_(make_impl(ctx))
579 -
        capy::Executor Ex>
593 +
                                , ex_(std::move(ex))
580 -
    tcp_server(Ctx& ctx, Ex ex)
 
581 -
        : impl_(make_impl(ctx))
 
582 -
        , ex_(std::move(ex))
 
583  
    {
594  
    {
584  
    }
595  
    }
585  

596  

586  
public:
597  
public:
587  
    ~tcp_server();
598  
    ~tcp_server();
588  
    tcp_server(tcp_server const&) = delete;
599  
    tcp_server(tcp_server const&) = delete;
589  
    tcp_server& operator=(tcp_server const&) = delete;
600  
    tcp_server& operator=(tcp_server const&) = delete;
590  
    tcp_server(tcp_server&& o) noexcept;
601  
    tcp_server(tcp_server&& o) noexcept;
591  
    tcp_server& operator=(tcp_server&& o) noexcept;
602  
    tcp_server& operator=(tcp_server&& o) noexcept;
592  

603  

593  
    /** Bind to a local endpoint.
604  
    /** Bind to a local endpoint.
594  

605  

595  
        Creates an acceptor listening on the specified endpoint.
606  
        Creates an acceptor listening on the specified endpoint.
596  
        Multiple endpoints can be bound by calling this method
607  
        Multiple endpoints can be bound by calling this method
597  
        multiple times before @ref start.
608  
        multiple times before @ref start.
598  

609  

599  
        @param ep The local endpoint to bind to.
610  
        @param ep The local endpoint to bind to.
600  

611  

601  
        @return The error code if binding fails.
612  
        @return The error code if binding fails.
602  
    */
613  
    */
603 -
    std::error_code
614 +
    std::error_code bind(endpoint ep);
604 -
    bind(endpoint ep);
 
605  

615  

606  
    /** Set the worker pool.
616  
    /** Set the worker pool.
607  

617  

608  
        Replaces any existing workers with the given range. Any
618  
        Replaces any existing workers with the given range. Any
609  
        previous workers are released and the idle/active lists
619  
        previous workers are released and the idle/active lists
610  
        are cleared before populating with new workers.
620  
        are cleared before populating with new workers.
611  

621  

612  
        @tparam Range Forward range of pointer-like objects to worker_base.
622  
        @tparam Range Forward range of pointer-like objects to worker_base.
613  

623  

614  
        @param workers Range of workers to manage. Each element must
624  
        @param workers Range of workers to manage. Each element must
615  
            support `std::to_address()` yielding `worker_base*`.
625  
            support `std::to_address()` yielding `worker_base*`.
616  

626  

617  
        @par Example
627  
        @par Example
618  
        @code
628  
        @code
619  
        std::vector<std::unique_ptr<my_worker>> workers;
629  
        std::vector<std::unique_ptr<my_worker>> workers;
620  
        for(int i = 0; i < 100; ++i)
630  
        for(int i = 0; i < 100; ++i)
621  
            workers.push_back(std::make_unique<my_worker>(ctx));
631  
            workers.push_back(std::make_unique<my_worker>(ctx));
622  
        srv.set_workers(std::move(workers));
632  
        srv.set_workers(std::move(workers));
623  
        @endcode
633  
        @endcode
624  
    */
634  
    */
625  
    template<std::ranges::forward_range Range>
635  
    template<std::ranges::forward_range Range>
626  
        requires std::convertible_to<
636  
        requires std::convertible_to<
627  
            decltype(std::to_address(
637  
            decltype(std::to_address(
628  
                std::declval<std::ranges::range_value_t<Range>&>())),
638  
                std::declval<std::ranges::range_value_t<Range>&>())),
629  
            worker_base*>
639  
            worker_base*>
630 -
    void
640 +
    void set_workers(Range&& workers)
631 -
    set_workers(Range&& workers)
 
632  
    {
641  
    {
633  
        // Clear existing state
642  
        // Clear existing state
634  
        storage_.reset();
643  
        storage_.reset();
635  
        idle_head_ = nullptr;
644  
        idle_head_ = nullptr;
636  
        active_head_ = nullptr;
645  
        active_head_ = nullptr;
637  
        active_tail_ = nullptr;
646  
        active_tail_ = nullptr;
638  

647  

639  
        // Take ownership and populate idle list
648  
        // Take ownership and populate idle list
640  
        using StorageType = std::decay_t<Range>;
649  
        using StorageType = std::decay_t<Range>;
641  
        auto* p = new StorageType(std::forward<Range>(workers));
650  
        auto* p = new StorageType(std::forward<Range>(workers));
642 -
        storage_ = std::shared_ptr<void>(p, [](void* ptr) {
651 +
        storage_ = std::shared_ptr<void>(
643 -
            delete static_cast<StorageType*>(ptr);
652 +
            p, [](void* ptr) { delete static_cast<StorageType*>(ptr); });
644 -
        });
653 +
        for (auto&& elem : *static_cast<StorageType*>(p))
645 -
        for(auto&& elem : *static_cast<StorageType*>(p))
 
646  
            idle_push(std::to_address(elem));
654  
            idle_push(std::to_address(elem));
647  
    }
655  
    }
648  

656  

649  
    /** Start accepting connections.
657  
    /** Start accepting connections.
650  

658  

651  
        Launches accept loops for all bound endpoints. Incoming
659  
        Launches accept loops for all bound endpoints. Incoming
652  
        connections are dispatched to idle workers from the pool.
660  
        connections are dispatched to idle workers from the pool.
653  
        
661  
        
654  
        Calling `start()` on an already-running server has no effect.
662  
        Calling `start()` on an already-running server has no effect.
655  

663  

656  
        @par Preconditions
664  
        @par Preconditions
657  
        - At least one endpoint bound via @ref bind.
665  
        - At least one endpoint bound via @ref bind.
658  
        - Workers provided to the constructor.
666  
        - Workers provided to the constructor.
659  
        - If restarting, @ref join must have completed first.
667  
        - If restarting, @ref join must have completed first.
660  

668  

661  
        @par Effects
669  
        @par Effects
662  
        Creates one accept coroutine per bound endpoint. Each coroutine
670  
        Creates one accept coroutine per bound endpoint. Each coroutine
663  
        runs on the server's executor, waiting for connections and
671  
        runs on the server's executor, waiting for connections and
664  
        dispatching them to idle workers.
672  
        dispatching them to idle workers.
665  

673  

666  
        @par Restart Sequence
674  
        @par Restart Sequence
667  
        To restart after stopping, complete the full shutdown cycle:
675  
        To restart after stopping, complete the full shutdown cycle:
668  
        @code
676  
        @code
669  
        srv.start();
677  
        srv.start();
670  
        ioc.run_for( 1s );
678  
        ioc.run_for( 1s );
671  
        srv.stop();       // 1. Signal shutdown
679  
        srv.stop();       // 1. Signal shutdown
672  
        ioc.run();        // 2. Drain remaining completions
680  
        ioc.run();        // 2. Drain remaining completions
673  
        srv.join();       // 3. Wait for accept loops
681  
        srv.join();       // 3. Wait for accept loops
674  

682  

675  
        // Now safe to restart
683  
        // Now safe to restart
676  
        srv.start();
684  
        srv.start();
677  
        ioc.run();
685  
        ioc.run();
678  
        @endcode
686  
        @endcode
679  

687  

680  
        @par Thread Safety
688  
        @par Thread Safety
681  
        Not thread safe.
689  
        Not thread safe.
682  
        
690  
        
683  
        @throws std::logic_error If a previous session has not been
691  
        @throws std::logic_error If a previous session has not been
684  
            joined (accept loops still active).
692  
            joined (accept loops still active).
685  
    */
693  
    */
686  
    void start();
694  
    void start();
687  

695  

688  
    /** Stop accepting connections.
696  
    /** Stop accepting connections.
689  

697  

690  
        Signals all listening ports to stop accepting new connections
698  
        Signals all listening ports to stop accepting new connections
691  
        and requests cancellation of active workers via their stop tokens.
699  
        and requests cancellation of active workers via their stop tokens.
692  
        
700  
        
693  
        This function returns immediately; it does not wait for workers
701  
        This function returns immediately; it does not wait for workers
694  
        to finish. Pending I/O operations complete asynchronously.
702  
        to finish. Pending I/O operations complete asynchronously.
695  

703  

696  
        Calling `stop()` on a non-running server has no effect.
704  
        Calling `stop()` on a non-running server has no effect.
697  

705  

698  
        @par Effects
706  
        @par Effects
699  
        - Closes all acceptors (pending accepts complete with error).
707  
        - Closes all acceptors (pending accepts complete with error).
700  
        - Requests stop on each active worker's stop token.
708  
        - Requests stop on each active worker's stop token.
701  
        - Workers observing their stop token should exit promptly.
709  
        - Workers observing their stop token should exit promptly.
702  

710  

703  
        @par Postconditions
711  
        @par Postconditions
704  
        No new connections will be accepted. Active workers continue
712  
        No new connections will be accepted. Active workers continue
705  
        until they observe their stop token or complete naturally.
713  
        until they observe their stop token or complete naturally.
706  

714  

707  
        @par What Happens Next
715  
        @par What Happens Next
708  
        After calling `stop()`:
716  
        After calling `stop()`:
709  
        1. Let `ioc.run()` return (drains pending completions).
717  
        1. Let `ioc.run()` return (drains pending completions).
710  
        2. Call @ref join to wait for accept loops to finish.
718  
        2. Call @ref join to wait for accept loops to finish.
711  
        3. Only then is it safe to restart or destroy the server.
719  
        3. Only then is it safe to restart or destroy the server.
712  

720  

713  
        @par Thread Safety
721  
        @par Thread Safety
714  
        Not thread safe.
722  
        Not thread safe.
715  

723  

716  
        @see join, start
724  
        @see join, start
717  
    */
725  
    */
718  
    void stop();
726  
    void stop();
719  

727  

720  
    /** Block until all accept loops complete.
728  
    /** Block until all accept loops complete.
721  

729  

722  
        Blocks the calling thread until all accept coroutines launched
730  
        Blocks the calling thread until all accept coroutines launched
723  
        by @ref start have finished executing. This synchronizes the
731  
        by @ref start have finished executing. This synchronizes the
724  
        shutdown sequence, ensuring the server is fully stopped before
732  
        shutdown sequence, ensuring the server is fully stopped before
725  
        restarting or destroying it.
733  
        restarting or destroying it.
726  

734  

727  
        @par Preconditions
735  
        @par Preconditions
728  
        @ref stop has been called and `ioc.run()` has returned.
736  
        @ref stop has been called and `ioc.run()` has returned.
729  

737  

730  
        @par Postconditions
738  
        @par Postconditions
731  
        All accept loops have completed. The server is in the stopped
739  
        All accept loops have completed. The server is in the stopped
732  
        state and may be restarted via @ref start.
740  
        state and may be restarted via @ref start.
733  

741  

734  
        @par Example (Correct Usage)
742  
        @par Example (Correct Usage)
735  
        @code
743  
        @code
736  
        // main thread
744  
        // main thread
737  
        srv.start();
745  
        srv.start();
738  
        ioc.run();      // Blocks until work completes
746  
        ioc.run();      // Blocks until work completes
739  
        srv.join();     // Safe: called after ioc.run() returns
747  
        srv.join();     // Safe: called after ioc.run() returns
740  
        @endcode
748  
        @endcode
741  

749  

742  
        @par WARNING: Deadlock Scenarios
750  
        @par WARNING: Deadlock Scenarios
743  
        Calling `join()` from the wrong context causes deadlock:
751  
        Calling `join()` from the wrong context causes deadlock:
744  

752  

745  
        @code
753  
        @code
746  
        // WRONG: calling join() from inside a worker coroutine
754  
        // WRONG: calling join() from inside a worker coroutine
747  
        void run( launcher launch ) override
755  
        void run( launcher launch ) override
748  
        {
756  
        {
749  
            launch( ex, [this]() -> capy::task<>
757  
            launch( ex, [this]() -> capy::task<>
750  
            {
758  
            {
751  
                srv_.join();  // DEADLOCK: blocks the executor
759  
                srv_.join();  // DEADLOCK: blocks the executor
752  
                co_return;
760  
                co_return;
753  
            }());
761  
            }());
754  
        }
762  
        }
755  

763  

756  
        // WRONG: calling join() while ioc.run() is still active
764  
        // WRONG: calling join() while ioc.run() is still active
757  
        std::thread t( [&]{ ioc.run(); } );
765  
        std::thread t( [&]{ ioc.run(); } );
758  
        srv.stop();
766  
        srv.stop();
759  
        srv.join();  // DEADLOCK: ioc.run() still running in thread t
767  
        srv.join();  // DEADLOCK: ioc.run() still running in thread t
760  
        @endcode
768  
        @endcode
761  

769  

762  
        @par Thread Safety
770  
        @par Thread Safety
763  
        May be called from any thread, but will deadlock if called
771  
        May be called from any thread, but will deadlock if called
764  
        from within the io_context event loop or from a worker coroutine.
772  
        from within the io_context event loop or from a worker coroutine.
765  

773  

766  
        @see stop, start
774  
        @see stop, start
767  
    */
775  
    */
768  
    void join();
776  
    void join();
769  

777  

770  
private:
778  
private:
771  
    capy::task<> do_stop();
779  
    capy::task<> do_stop();
772  
};
780  
};
773  

781  

774  
#ifdef _MSC_VER
782  
#ifdef _MSC_VER
775  
#pragma warning(pop)
783  
#pragma warning(pop)
776  
#endif
784  
#endif
777  

785  

778  
} // namespace boost::corosio
786  
} // namespace boost::corosio
779  

787  

780  
#endif
788  
#endif