LCOV - code coverage report
Current view: top level - /jenkins/workspace/boost-root/boost/corosio - tcp_server.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 80.4 % 138 111
Test Date: 2026-02-04 14:16:13 Functions: 90.7 % 43 39

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

Generated by: LCOV version 2.3