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 tcp_server
151  
class BOOST_COROSIO_DECL tcp_server
152  
{
152  
{
153  
public:
153  
public:
154  
    class worker_base; ///< Abstract base for connection handlers.
154  
    class worker_base; ///< Abstract base for connection handlers.
155  
    class launcher;    ///< Move-only handle to launch worker coroutines.
155  
    class launcher;    ///< Move-only handle to launch worker coroutines.
156  

156  

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

164  

165  
    struct impl;
165  
    struct impl;
166  

166  

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

168  

169  
    impl* impl_;
169  
    impl* impl_;
170  
    capy::any_executor ex_;
170  
    capy::any_executor ex_;
171  
    waiter* waiters_        = nullptr;
171  
    waiter* waiters_        = nullptr;
172  
    worker_base* idle_head_ = nullptr; // Forward list: available workers
172  
    worker_base* idle_head_ = nullptr; // Forward list: available workers
173  
    worker_base* active_head_ =
173  
    worker_base* active_head_ =
174  
        nullptr; // Doubly linked: workers handling connections
174  
        nullptr; // Doubly linked: workers handling connections
175  
    worker_base* active_tail_   = nullptr; // Tail for O(1) push_back
175  
    worker_base* active_tail_   = nullptr; // Tail for O(1) push_back
176  
    std::size_t active_accepts_ = 0; // Number of active do_accept coroutines
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)
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)
190  
        if (w)
191  
            idle_head_ = w->next_;
191  
            idle_head_ = w->next_;
192  
        return w;
192  
        return w;
193  
    }
193  
    }
194  

194  

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

199  

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

211  

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

227  

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

235  

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

246  

247  
            // 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
248  
            template<class Closure, class E, class S, class... Args>
248  
            template<class Closure, class E, class S, class... Args>
249  
                requires(!capy::Executor<std::decay_t<Closure>> &&
249  
                requires(!capy::Executor<std::decay_t<Closure>> &&
250  
                         capy::Executor<std::decay_t<E>>)
250  
                         capy::Executor<std::decay_t<E>>)
251  
            promise_type(Closure&&, E e, S s, Args&&...)
251  
            promise_type(Closure&&, E e, S s, Args&&...)
252  
                : ex(std::move(e))
252  
                : ex(std::move(e))
253  
                , env_{
253  
                , env_{
254  
                      capy::executor_ref(ex), std::move(s),
254  
                      capy::executor_ref(ex), std::move(s),
255  
                      capy::get_current_frame_allocator()}
255  
                      capy::get_current_frame_allocator()}
256  
            {
256  
            {
257  
            }
257  
            }
258  

258  

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

277  

278  
            // Inject io_env for IoAwaitable
278  
            // Inject io_env for IoAwaitable
279  
            template<capy::IoAwaitable Awaitable>
279  
            template<capy::IoAwaitable Awaitable>
280  
            auto await_transform(Awaitable&& a)
280  
            auto await_transform(Awaitable&& a)
281  
            {
281  
            {
282  
                using AwaitableT = std::decay_t<Awaitable>;
282  
                using AwaitableT = std::decay_t<Awaitable>;
283  
                struct adapter
283  
                struct adapter
284  
                {
284  
                {
285  
                    AwaitableT aw;
285  
                    AwaitableT aw;
286  
                    capy::io_env const* env;
286  
                    capy::io_env const* env;
287  

287  

288  
                    bool await_ready()
288  
                    bool await_ready()
289  
                    {
289  
                    {
290  
                        return aw.await_ready();
290  
                        return aw.await_ready();
291  
                    }
291  
                    }
292  
                    decltype(auto) await_resume()
292  
                    decltype(auto) await_resume()
293  
                    {
293  
                    {
294  
                        return aw.await_resume();
294  
                        return aw.await_resume();
295  
                    }
295  
                    }
296  

296  

297  
                    auto await_suspend(std::coroutine_handle<promise_type> h)
297  
                    auto await_suspend(std::coroutine_handle<promise_type> h)
298  
                    {
298  
                    {
299  
                        return aw.await_suspend(h, env);
299  
                        return aw.await_suspend(h, env);
300  
                    }
300  
                    }
301  
                };
301  
                };
302  
                return adapter{std::forward<Awaitable>(a), &env_};
302  
                return adapter{std::forward<Awaitable>(a), &env_};
303  
            }
303  
            }
304  
        };
304  
        };
305  

305  

306  
        std::coroutine_handle<promise_type> h;
306  
        std::coroutine_handle<promise_type> h;
307  

307  

308  
        launch_wrapper(std::coroutine_handle<promise_type> handle) noexcept
308  
        launch_wrapper(std::coroutine_handle<promise_type> handle) noexcept
309  
            : h(handle)
309  
            : h(handle)
310  
        {
310  
        {
311  
        }
311  
        }
312  

312  

313  
        ~launch_wrapper()
313  
        ~launch_wrapper()
314  
        {
314  
        {
315  
            if (h)
315  
            if (h)
316  
                h.destroy();
316  
                h.destroy();
317  
        }
317  
        }
318  

318  

319  
        launch_wrapper(launch_wrapper&& o) noexcept
319  
        launch_wrapper(launch_wrapper&& o) noexcept
320  
            : h(std::exchange(o.h, nullptr))
320  
            : h(std::exchange(o.h, nullptr))
321  
        {
321  
        {
322  
        }
322  
        }
323  

323  

324  
        launch_wrapper(launch_wrapper const&)            = delete;
324  
        launch_wrapper(launch_wrapper const&)            = delete;
325  
        launch_wrapper& operator=(launch_wrapper const&) = delete;
325  
        launch_wrapper& operator=(launch_wrapper const&) = delete;
326  
        launch_wrapper& operator=(launch_wrapper&&)      = delete;
326  
        launch_wrapper& operator=(launch_wrapper&&)      = delete;
327  
    };
327  
    };
328  

328  

329  
    // Named functor to avoid incomplete lambda type in coroutine promise
329  
    // Named functor to avoid incomplete lambda type in coroutine promise
330  
    template<class Executor>
330  
    template<class Executor>
331  
    struct launch_coro
331  
    struct launch_coro
332  
    {
332  
    {
333  
        launch_wrapper<Executor> operator()(
333  
        launch_wrapper<Executor> operator()(
334  
            Executor,
334  
            Executor,
335  
            std::stop_token,
335  
            std::stop_token,
336  
            tcp_server* self,
336  
            tcp_server* self,
337  
            capy::task<void> t,
337  
            capy::task<void> t,
338  
            worker_base* wp)
338  
            worker_base* wp)
339  
        {
339  
        {
340  
            // Executor and stop token stored in promise via constructor
340  
            // Executor and stop token stored in promise via constructor
341  
            co_await std::move(t);
341  
            co_await std::move(t);
342  
            co_await self->push(*wp); // worker goes back to idle list
342  
            co_await self->push(*wp); // worker goes back to idle list
343  
        }
343  
        }
344  
    };
344  
    };
345  

345  

346  
    class push_awaitable
346  
    class push_awaitable
347  
    {
347  
    {
348  
        tcp_server& self_;
348  
        tcp_server& self_;
349  
        worker_base& w_;
349  
        worker_base& w_;
350  

350  

351  
    public:
351  
    public:
352  
        push_awaitable(tcp_server& self, worker_base& w) noexcept
352  
        push_awaitable(tcp_server& self, worker_base& w) noexcept
353  
            : self_(self)
353  
            : self_(self)
354  
            , w_(w)
354  
            , w_(w)
355  
        {
355  
        {
356  
        }
356  
        }
357  

357  

358  
        bool await_ready() const noexcept
358  
        bool await_ready() const noexcept
359  
        {
359  
        {
360  
            return false;
360  
            return false;
361  
        }
361  
        }
362  

362  

363  
        std::coroutine_handle<>
363  
        std::coroutine_handle<>
364  
        await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept
364  
        await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept
365  
        {
365  
        {
366  
            // Symmetric transfer to server's executor
366  
            // Symmetric transfer to server's executor
367  
            return self_.ex_.dispatch(h);
367  
            return self_.ex_.dispatch(h);
368  
        }
368  
        }
369  

369  

370  
        void await_resume() noexcept
370  
        void await_resume() noexcept
371  
        {
371  
        {
372  
            // Running on server executor - safe to modify lists
372  
            // Running on server executor - safe to modify lists
373  
            // 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
374  
            self_.active_remove(&w_);
374  
            self_.active_remove(&w_);
375  
            if (self_.waiters_)
375  
            if (self_.waiters_)
376  
            {
376  
            {
377  
                auto* wait     = self_.waiters_;
377  
                auto* wait     = self_.waiters_;
378  
                self_.waiters_ = wait->next;
378  
                self_.waiters_ = wait->next;
379  
                wait->w        = &w_;
379  
                wait->w        = &w_;
380  
                self_.ex_.post(wait->h);
380  
                self_.ex_.post(wait->h);
381  
            }
381  
            }
382  
            else
382  
            else
383  
            {
383  
            {
384  
                self_.idle_push(&w_);
384  
                self_.idle_push(&w_);
385  
            }
385  
            }
386  
        }
386  
        }
387  
    };
387  
    };
388  

388  

389  
    class pop_awaitable
389  
    class pop_awaitable
390  
    {
390  
    {
391  
        tcp_server& self_;
391  
        tcp_server& self_;
392  
        waiter wait_;
392  
        waiter wait_;
393  

393  

394  
    public:
394  
    public:
395  
        pop_awaitable(tcp_server& self) noexcept : self_(self), wait_{} {}
395  
        pop_awaitable(tcp_server& self) noexcept : self_(self), wait_{} {}
396  

396  

397  
        bool await_ready() const noexcept
397  
        bool await_ready() const noexcept
398  
        {
398  
        {
399  
            return !self_.idle_empty();
399  
            return !self_.idle_empty();
400  
        }
400  
        }
401  

401  

402  
        bool
402  
        bool
403  
        await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept
403  
        await_suspend(std::coroutine_handle<> h, capy::io_env const*) noexcept
404  
        {
404  
        {
405  
            // Running on server executor (do_accept runs there)
405  
            // Running on server executor (do_accept runs there)
406  
            wait_.h        = h;
406  
            wait_.h        = h;
407  
            wait_.w        = nullptr;
407  
            wait_.w        = nullptr;
408  
            wait_.next     = self_.waiters_;
408  
            wait_.next     = self_.waiters_;
409  
            self_.waiters_ = &wait_;
409  
            self_.waiters_ = &wait_;
410  
            return true;
410  
            return true;
411  
        }
411  
        }
412  

412  

413  
        worker_base& await_resume() noexcept
413  
        worker_base& await_resume() noexcept
414  
        {
414  
        {
415  
            // Running on server executor
415  
            // Running on server executor
416  
            if (wait_.w)
416  
            if (wait_.w)
417  
                return *wait_.w; // Woken by push_awaitable
417  
                return *wait_.w; // Woken by push_awaitable
418  
            return *self_.idle_pop();
418  
            return *self_.idle_pop();
419  
        }
419  
        }
420  
    };
420  
    };
421  

421  

422  
    push_awaitable push(worker_base& w)
422  
    push_awaitable push(worker_base& w)
423  
    {
423  
    {
424  
        return push_awaitable{*this, w};
424  
        return push_awaitable{*this, w};
425  
    }
425  
    }
426  

426  

427  
    // Synchronous version for destructor/guard paths
427  
    // Synchronous version for destructor/guard paths
428  
    // Must be called from server executor context
428  
    // Must be called from server executor context
429  
    void push_sync(worker_base& w) noexcept
429  
    void push_sync(worker_base& w) noexcept
430  
    {
430  
    {
431  
        active_remove(&w);
431  
        active_remove(&w);
432  
        if (waiters_)
432  
        if (waiters_)
433  
        {
433  
        {
434  
            auto* wait = waiters_;
434  
            auto* wait = waiters_;
435  
            waiters_   = wait->next;
435  
            waiters_   = wait->next;
436  
            wait->w    = &w;
436  
            wait->w    = &w;
437  
            ex_.post(wait->h);
437  
            ex_.post(wait->h);
438  
        }
438  
        }
439  
        else
439  
        else
440  
        {
440  
        {
441  
            idle_push(&w);
441  
            idle_push(&w);
442  
        }
442  
        }
443  
    }
443  
    }
444  

444  

445  
    pop_awaitable pop()
445  
    pop_awaitable pop()
446  
    {
446  
    {
447  
        return pop_awaitable{*this};
447  
        return pop_awaitable{*this};
448  
    }
448  
    }
449  

449  

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

451  

452  
public:
452  
public:
453  
    /** Abstract base class for connection handlers.
453  
    /** Abstract base class for connection handlers.
454  

454  

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

458  

459  
        @see tcp_server, launcher
459  
        @see tcp_server, launcher
460  
    */
460  
    */
461  
    class BOOST_COROSIO_DECL worker_base
461  
    class BOOST_COROSIO_DECL worker_base
462  
    {
462  
    {
463  
        // Ordered largest to smallest for optimal packing
463  
        // Ordered largest to smallest for optimal packing
464  
        std::stop_source stop_;       // ~16 bytes
464  
        std::stop_source stop_;       // ~16 bytes
465  
        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
466  
        worker_base* prev_ = nullptr; // 8 bytes - used only by active list
466  
        worker_base* prev_ = nullptr; // 8 bytes - used only by active list
467  

467  

468  
        friend class tcp_server;
468  
        friend class tcp_server;
469  

469  

470  
    public:
470  
    public:
471  
        /// Destroy the worker.
471  
        /// Destroy the worker.
472  
        virtual ~worker_base() = default;
472  
        virtual ~worker_base() = default;
473  

473  

474  
        /** Handle an accepted connection.
474  
        /** Handle an accepted connection.
475  

475  

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

479  

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

483  

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

487  

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

489  

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

493  

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

497  

498  
        @see worker_base::run
498  
        @see worker_base::run
499  
    */
499  
    */
500  
    class BOOST_COROSIO_DECL launcher
500  
    class BOOST_COROSIO_DECL launcher
501  
    {
501  
    {
502  
        tcp_server* srv_;
502  
        tcp_server* srv_;
503  
        worker_base* w_;
503  
        worker_base* w_;
504  

504  

505  
        friend class tcp_server;
505  
        friend class tcp_server;
506  

506  

507  
        launcher(tcp_server& srv, worker_base& w) noexcept : srv_(&srv), w_(&w)
507  
        launcher(tcp_server& srv, worker_base& w) noexcept : srv_(&srv), w_(&w)
508  
        {
508  
        {
509  
        }
509  
        }
510  

510  

511  
    public:
511  
    public:
512  
        /// Return the worker to the pool if not launched.
512  
        /// Return the worker to the pool if not launched.
513  
        ~launcher()
513  
        ~launcher()
514  
        {
514  
        {
515  
            if (w_)
515  
            if (w_)
516  
                srv_->push_sync(*w_);
516  
                srv_->push_sync(*w_);
517  
        }
517  
        }
518  

518  

519  
        launcher(launcher&& o) noexcept
519  
        launcher(launcher&& o) noexcept
520  
            : srv_(o.srv_)
520  
            : srv_(o.srv_)
521  
            , w_(std::exchange(o.w_, nullptr))
521  
            , w_(std::exchange(o.w_, nullptr))
522  
        {
522  
        {
523  
        }
523  
        }
524  
        launcher(launcher const&)            = delete;
524  
        launcher(launcher const&)            = delete;
525  
        launcher& operator=(launcher const&) = delete;
525  
        launcher& operator=(launcher const&) = delete;
526  
        launcher& operator=(launcher&&)      = delete;
526  
        launcher& operator=(launcher&&)      = delete;
527  

527  

528  
        /** Launch the connection-handling coroutine.
528  
        /** Launch the connection-handling coroutine.
529  

529  

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

533  

534  
            @param ex The executor to run the coroutine on.
534  
            @param ex The executor to run the coroutine on.
535  
            @param task The coroutine to execute.
535  
            @param task The coroutine to execute.
536  

536  

537  
            @throws std::logic_error If this launcher was already invoked.
537  
            @throws std::logic_error If this launcher was already invoked.
538  
        */
538  
        */
539  
        template<class Executor>
539  
        template<class Executor>
540  
        void operator()(Executor const& ex, capy::task<void> task)
540  
        void operator()(Executor const& ex, capy::task<void> task)
541  
        {
541  
        {
542  
            if (!w_)
542  
            if (!w_)
543  
                detail::throw_logic_error(); // launcher already invoked
543  
                detail::throw_logic_error(); // launcher already invoked
544  

544  

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

546  

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

549  

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

561  

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

565  

566  
            auto wrapper =
566  
            auto wrapper =
567  
                launch_coro<Executor>{}(ex, st, srv_, std::move(task), w);
567  
                launch_coro<Executor>{}(ex, st, srv_, std::move(task), w);
568  

568  

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

574  

575  
    /** Construct a TCP server.
575  
    /** Construct a TCP server.
576  

576  

577  
        @tparam Ctx Execution context type satisfying ExecutionContext.
577  
        @tparam Ctx Execution context type satisfying ExecutionContext.
578  
        @tparam Ex Executor type satisfying Executor.
578  
        @tparam Ex Executor type satisfying Executor.
579  

579  

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

582  

583  
        @par Example
583  
        @par Example
584  
        @code
584  
        @code
585  
        tcp_server srv(ctx, ctx.get_executor());
585  
        tcp_server srv(ctx, ctx.get_executor());
586  
        srv.set_workers(make_workers(ctx, 100));
586  
        srv.set_workers(make_workers(ctx, 100));
587  
        srv.bind(endpoint{...});
587  
        srv.bind(endpoint{...});
588  
        srv.start();
588  
        srv.start();
589  
        @endcode
589  
        @endcode
590  
    */
590  
    */
591  
    template<capy::ExecutionContext Ctx, capy::Executor Ex>
591  
    template<capy::ExecutionContext Ctx, capy::Executor Ex>
592  
    tcp_server(Ctx& ctx, Ex ex) : impl_(make_impl(ctx))
592  
    tcp_server(Ctx& ctx, Ex ex) : impl_(make_impl(ctx))
593  
                                , ex_(std::move(ex))
593  
                                , ex_(std::move(ex))
594  
    {
594  
    {
595  
    }
595  
    }
596  

596  

597  
public:
597  
public:
598  
    ~tcp_server();
598  
    ~tcp_server();
599  
    tcp_server(tcp_server const&)            = delete;
599  
    tcp_server(tcp_server const&)            = delete;
600  
    tcp_server& operator=(tcp_server const&) = delete;
600  
    tcp_server& operator=(tcp_server const&) = delete;
601  
    tcp_server(tcp_server&& o) noexcept;
601  
    tcp_server(tcp_server&& o) noexcept;
602  
    tcp_server& operator=(tcp_server&& o) noexcept;
602  
    tcp_server& operator=(tcp_server&& o) noexcept;
603  

603  

604  
    /** Bind to a local endpoint.
604  
    /** Bind to a local endpoint.
605  

605  

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

609  

610  
        @param ep The local endpoint to bind to.
610  
        @param ep The local endpoint to bind to.
611  

611  

612  
        @return The error code if binding fails.
612  
        @return The error code if binding fails.
613  
    */
613  
    */
614  
    std::error_code bind(endpoint ep);
614  
    std::error_code bind(endpoint ep);
615  

615  

616  
    /** Set the worker pool.
616  
    /** Set the worker pool.
617  

617  

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

621  

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

623  

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

626  

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

647  

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

656  

657  
    /** Start accepting connections.
657  
    /** Start accepting connections.
658  

658  

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

663  

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

668  

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

673  

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

682  

683  
        // Now safe to restart
683  
        // Now safe to restart
684  
        srv.start();
684  
        srv.start();
685  
        ioc.run();
685  
        ioc.run();
686  
        @endcode
686  
        @endcode
687  

687  

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

695  

696  
    /** Stop accepting connections.
696  
    /** Stop accepting connections.
697  

697  

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

703  

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

705  

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

710  

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

714  

715  
        @par What Happens Next
715  
        @par What Happens Next
716  
        After calling `stop()`:
716  
        After calling `stop()`:
717  
        1. Let `ioc.run()` return (drains pending completions).
717  
        1. Let `ioc.run()` return (drains pending completions).
718  
        2. Call @ref join to wait for accept loops to finish.
718  
        2. Call @ref join to wait for accept loops to finish.
719  
        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.
720  

720  

721  
        @par Thread Safety
721  
        @par Thread Safety
722  
        Not thread safe.
722  
        Not thread safe.
723  

723  

724  
        @see join, start
724  
        @see join, start
725  
    */
725  
    */
726  
    void stop();
726  
    void stop();
727  

727  

728  
    /** Block until all accept loops complete.
728  
    /** Block until all accept loops complete.
729  

729  

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

734  

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

737  

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

741  

742  
        @par Example (Correct Usage)
742  
        @par Example (Correct Usage)
743  
        @code
743  
        @code
744  
        // main thread
744  
        // main thread
745  
        srv.start();
745  
        srv.start();
746  
        ioc.run();      // Blocks until work completes
746  
        ioc.run();      // Blocks until work completes
747  
        srv.join();     // Safe: called after ioc.run() returns
747  
        srv.join();     // Safe: called after ioc.run() returns
748  
        @endcode
748  
        @endcode
749  

749  

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

752  

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

763  

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

769  

770  
        @par Thread Safety
770  
        @par Thread Safety
771  
        May be called from any thread, but will deadlock if called
771  
        May be called from any thread, but will deadlock if called
772  
        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.
773  

773  

774  
        @see stop, start
774  
        @see stop, start
775  
    */
775  
    */
776  
    void join();
776  
    void join();
777  

777  

778  
private:
778  
private:
779  
    capy::task<> do_stop();
779  
    capy::task<> do_stop();
780  
};
780  
};
781  

781  

782  
#ifdef _MSC_VER
782  
#ifdef _MSC_VER
783  
#pragma warning(pop)
783  
#pragma warning(pop)
784  
#endif
784  
#endif
785  

785  

786  
} // namespace boost::corosio
786  
} // namespace boost::corosio
787  

787  

788  
#endif
788  
#endif