LCOV - code coverage report
Current view: top level - /jenkins/workspace/boost-root/boost/corosio - tcp_acceptor.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 94.7 % 38 36
Test Date: 2026-02-04 16:37:34 Functions: 100.0 % 9 9

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

Generated by: LCOV version 2.3