LCOV - code coverage report
Current view: top level - include/boost/corosio - tcp_acceptor.hpp (source / functions) Coverage Total Hit
Test: coverage_remapped.info Lines: 93.9 % 33 31
Test Date: 2026-02-15 04:10:46 Functions: 100.0 % 9 9

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.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_ACCEPTOR_HPP
      11              : #define BOOST_COROSIO_TCP_ACCEPTOR_HPP
      12              : 
      13              : #include <boost/corosio/detail/config.hpp>
      14              : #include <boost/corosio/detail/except.hpp>
      15              : #include <boost/corosio/io_object.hpp>
      16              : #include <boost/capy/io_result.hpp>
      17              : #include <boost/corosio/endpoint.hpp>
      18              : #include <boost/corosio/tcp_socket.hpp>
      19              : #include <boost/capy/ex/executor_ref.hpp>
      20              : #include <boost/capy/ex/execution_context.hpp>
      21              : #include <boost/capy/ex/io_env.hpp>
      22              : #include <boost/capy/concept/executor.hpp>
      23              : 
      24              : #include <system_error>
      25              : 
      26              : #include <concepts>
      27              : #include <coroutine>
      28              : #include <cstddef>
      29              : #include <memory>
      30              : #include <stop_token>
      31              : #include <type_traits>
      32              : 
      33              : namespace boost::corosio {
      34              : 
      35              : /** An asynchronous TCP acceptor for coroutine I/O.
      36              : 
      37              :     This class provides asynchronous TCP accept operations that return
      38              :     awaitable types. The acceptor binds to a local endpoint and listens
      39              :     for incoming connections.
      40              : 
      41              :     Each accept operation participates in the affine awaitable protocol,
      42              :     ensuring coroutines resume on the correct executor.
      43              : 
      44              :     @par Thread Safety
      45              :     Distinct objects: Safe.@n
      46              :     Shared objects: Unsafe. An acceptor must not have concurrent accept
      47              :     operations.
      48              : 
      49              :     @par Semantics
      50              :     Wraps the platform TCP listener. Operations dispatch to
      51              :     OS accept APIs via the io_context reactor.
      52              : 
      53              :     @par Example
      54              :     @code
      55              :     io_context ioc;
      56              :     tcp_acceptor acc(ioc);
      57              :     if (auto ec = acc.listen(endpoint(8080)))  // Bind to port 8080
      58              :         return ec;
      59              : 
      60              :     tcp_socket peer(ioc);
      61              :     auto [ec] = co_await acc.accept(peer);
      62              :     if (!ec) {
      63              :         // peer is now a connected socket
      64              :         auto [ec2, n] = co_await peer.read_some(buf);
      65              :     }
      66              :     @endcode
      67              : */
      68              : class BOOST_COROSIO_DECL tcp_acceptor : public io_object
      69              : {
      70              :     struct accept_awaitable
      71              :     {
      72              :         tcp_acceptor& acc_;
      73              :         tcp_socket& peer_;
      74              :         std::stop_token token_;
      75              :         mutable std::error_code ec_;
      76              :         mutable io_object::implementation* peer_impl_ = nullptr;
      77              : 
      78         8145 :         accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
      79         8145 :             : acc_(acc)
      80         8145 :             , peer_(peer)
      81              :         {
      82         8145 :         }
      83              : 
      84         8145 :         bool await_ready() const noexcept
      85              :         {
      86         8145 :             return token_.stop_requested();
      87              :         }
      88              : 
      89         8145 :         capy::io_result<> await_resume() const noexcept
      90              :         {
      91         8145 :             if (token_.stop_requested())
      92            6 :                 return {make_error_code(std::errc::operation_canceled)};
      93              :             
      94         8139 :             if (!ec_ && peer_impl_)
      95         8133 :                 peer_.h_.reset(peer_impl_);
      96         8139 :             return {ec_};
      97              :         }
      98              : 
      99         8145 :         auto await_suspend(
     100              :             std::coroutine_handle<> h,
     101              :             capy::io_env const* env) -> std::coroutine_handle<>
     102              :         {
     103         8145 :             token_ = env->stop_token;
     104         8145 :             return acc_.get().accept(h, env->executor, token_, &ec_, &peer_impl_);
     105              :         }
     106              :     };
     107              : 
     108              : public:
     109              :     /** Destructor.
     110              : 
     111              :         Closes the acceptor if open, cancelling any pending operations.
     112              :     */
     113              :     ~tcp_acceptor();
     114              : 
     115              :     /** Construct an acceptor from an execution context.
     116              : 
     117              :         @param ctx The execution context that will own this acceptor.
     118              :     */
     119              :     explicit tcp_acceptor(capy::execution_context& ctx);
     120              : 
     121              :     /** Construct an acceptor from an executor.
     122              : 
     123              :         The acceptor is associated with the executor's context.
     124              : 
     125              :         @param ex The executor whose context will own the acceptor.
     126              :     */
     127              :     template<class Ex>
     128              :         requires (!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
     129              :                  capy::Executor<Ex>
     130              :     explicit tcp_acceptor(Ex const& ex)
     131              :         : tcp_acceptor(ex.context())
     132              :     {
     133              :     }
     134              : 
     135              :     /** Move constructor.
     136              : 
     137              :         Transfers ownership of the acceptor resources.
     138              : 
     139              :         @param other The acceptor to move from.
     140              :     */
     141            2 :     tcp_acceptor(tcp_acceptor&& other) noexcept
     142            2 :         : io_object(std::move(other))
     143              :     {
     144            2 :     }
     145              : 
     146              :     /** Move assignment operator.
     147              : 
     148              :         Closes any existing acceptor and transfers ownership.
     149              :         The source and destination must share the same execution context.
     150              : 
     151              :         @param other The acceptor to move from.
     152              : 
     153              :         @return Reference to this acceptor.
     154              : 
     155              :         @throws std::logic_error if the acceptors have different execution contexts.
     156              :     */
     157            2 :     tcp_acceptor& operator=(tcp_acceptor&& other)
     158              :     {
     159            2 :         if (this != &other)
     160              :         {
     161            2 :             if (&context() != &other.context())
     162            0 :                 detail::throw_logic_error(
     163              :                     "cannot move tcp_acceptor across execution contexts");
     164            2 :             close();
     165            2 :             h_ = std::move(other.h_);
     166              :         }
     167            2 :         return *this;
     168              :     }
     169              : 
     170              :     tcp_acceptor(tcp_acceptor const&) = delete;
     171              :     tcp_acceptor& operator=(tcp_acceptor const&) = delete;
     172              : 
     173              :     /** Open, bind, and listen on an endpoint.
     174              : 
     175              :         Creates an IPv4 TCP socket, binds it to the specified endpoint,
     176              :         and begins listening for incoming connections. This must be
     177              :         called before initiating accept operations.
     178              : 
     179              :         @param ep The local endpoint to bind to. Use `endpoint(port)` to
     180              :             bind to all interfaces on a specific port.
     181              : 
     182              :         @param backlog The maximum length of the queue of pending
     183              :             connections. Defaults to 128.
     184              : 
     185              :         @return An error code indicating success or the reason for failure.
     186              :             A default-constructed error code indicates success.
     187              : 
     188              :         @par Error Conditions
     189              :         @li `errc::address_in_use`: The endpoint is already in use.
     190              :         @li `errc::address_not_available`: The address is not available
     191              :             on any local interface.
     192              :         @li `errc::permission_denied`: Insufficient privileges to bind
     193              :             to the endpoint (e.g., privileged port).
     194              :         @li `errc::operation_not_supported`: The acceptor service is
     195              :             unavailable in the context (POSIX only).
     196              : 
     197              :         @throws Nothing.
     198              :     */
     199              :     [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
     200              : 
     201              :     /** Close the acceptor.
     202              : 
     203              :         Releases acceptor resources. Any pending operations complete
     204              :         with `errc::operation_canceled`.
     205              :     */
     206              :     void close();
     207              : 
     208              :     /** Check if the acceptor is listening.
     209              : 
     210              :         @return `true` if the acceptor is open and listening.
     211              :     */
     212         8570 :     bool is_open() const noexcept
     213              :     {
     214         8570 :         return h_ && get().is_open();
     215              :     }
     216              : 
     217              :     /** Initiate an asynchronous accept operation.
     218              : 
     219              :         Accepts an incoming connection and initializes the provided
     220              :         socket with the new connection. The acceptor must be listening
     221              :         before calling this function.
     222              : 
     223              :         The operation supports cancellation via `std::stop_token` through
     224              :         the affine awaitable protocol. If the associated stop token is
     225              :         triggered, the operation completes immediately with
     226              :         `errc::operation_canceled`.
     227              : 
     228              :         @param peer The socket to receive the accepted connection. Any
     229              :             existing connection on this socket will be closed.
     230              : 
     231              :         @return An awaitable that completes with `io_result<>`.
     232              :             Returns success on successful accept, or an error code on
     233              :             failure including:
     234              :             - operation_canceled: Cancelled via stop_token or cancel().
     235              :                 Check `ec == cond::canceled` for portable comparison.
     236              : 
     237              :         @par Preconditions
     238              :         The acceptor must be listening (`is_open() == true`).
     239              :         The peer socket must be associated with the same execution context.
     240              : 
     241              :         @par Example
     242              :         @code
     243              :         tcp_socket peer(ioc);
     244              :         auto [ec] = co_await acc.accept(peer);
     245              :         if (!ec) {
     246              :             // Use peer socket
     247              :         }
     248              :         @endcode
     249              :     */
     250         8145 :     auto accept(tcp_socket& peer)
     251              :     {
     252         8145 :         if (!is_open())
     253            0 :             detail::throw_logic_error("accept: acceptor not listening");
     254         8145 :         return accept_awaitable(*this, peer);
     255              :     }
     256              : 
     257              :     /** Cancel any pending asynchronous operations.
     258              : 
     259              :         All outstanding operations complete with `errc::operation_canceled`.
     260              :         Check `ec == cond::canceled` for portable comparison.
     261              :     */
     262              :     void cancel();
     263              : 
     264              :     /** Get the local endpoint of the acceptor.
     265              : 
     266              :         Returns the local address and port to which the acceptor is bound.
     267              :         This is useful when binding to port 0 (ephemeral port) to discover
     268              :         the OS-assigned port number. The endpoint is cached when listen()
     269              :         is called.
     270              : 
     271              :         @return The local endpoint, or a default endpoint (0.0.0.0:0) if
     272              :             the acceptor is not listening.
     273              : 
     274              :         @par Thread Safety
     275              :         The cached endpoint value is set during listen() and cleared
     276              :         during close(). This function may be called concurrently with
     277              :         accept operations, but must not be called concurrently with
     278              :         listen() or close().
     279              :     */
     280              :     endpoint local_endpoint() const noexcept;
     281              : 
     282              :     struct implementation : io_object::implementation
     283              :     {
     284              :         virtual std::coroutine_handle<> accept(
     285              :             std::coroutine_handle<>,
     286              :             capy::executor_ref,
     287              :             std::stop_token,
     288              :             std::error_code*,
     289              :             io_object::implementation**) = 0;
     290              : 
     291              :         /// Returns the cached local endpoint.
     292              :         virtual endpoint local_endpoint() const noexcept = 0;
     293              : 
     294              :         /// Return true if the acceptor has a kernel resource open.
     295              :         virtual bool is_open() const noexcept = 0;
     296              : 
     297              :         /** Cancel any pending asynchronous operations.
     298              : 
     299              :             All outstanding operations complete with operation_canceled error.
     300              :         */
     301              :         virtual void cancel() noexcept = 0;
     302              :     };
     303              : 
     304              : private:
     305        16795 :     inline implementation& get() const noexcept
     306              :     {
     307        16795 :         return *static_cast<implementation*>(h_.get());
     308              :     }
     309              : };
     310              : 
     311              : } // namespace boost::corosio
     312              : 
     313              : #endif
        

Generated by: LCOV version 2.3