LCOV - code coverage report
Current view: top level - include/boost/corosio - tcp_acceptor.hpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 96.7 % 30 29 1
Test Date: 2026-02-16 16:21:08 Functions: 100.0 % 9 9

           TLA  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 HIT        8387 :         accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
      79            8387 :             : acc_(acc)
      80            8387 :             , peer_(peer)
      81                 :         {
      82            8387 :         }
      83                 : 
      84            8387 :         bool await_ready() const noexcept
      85                 :         {
      86            8387 :             return token_.stop_requested();
      87                 :         }
      88                 : 
      89            8387 :         capy::io_result<> await_resume() const noexcept
      90                 :         {
      91            8387 :             if (token_.stop_requested())
      92               6 :                 return {make_error_code(std::errc::operation_canceled)};
      93                 : 
      94            8381 :             if (!ec_ && peer_impl_)
      95            8375 :                 peer_.h_.reset(peer_impl_);
      96            8381 :             return {ec_};
      97                 :         }
      98                 : 
      99            8387 :         auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
     100                 :             -> std::coroutine_handle<>
     101                 :         {
     102            8387 :             token_ = env->stop_token;
     103           25161 :             return acc_.get().accept(
     104           25161 :                 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() override;
     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) : tcp_acceptor(ex.context())
     131                 :     {
     132                 :     }
     133                 : 
     134                 :     /** Move constructor.
     135                 : 
     136                 :         Transfers ownership of the acceptor resources.
     137                 : 
     138                 :         @param other The acceptor to move from.
     139                 :     */
     140               2 :     tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
     141                 : 
     142                 :     /** Move assignment operator.
     143                 : 
     144                 :         Closes any existing acceptor and transfers ownership.
     145                 :         @param other The acceptor to move from.
     146                 : 
     147                 :         @return Reference to this acceptor.
     148                 :     */
     149               2 :     tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
     150                 :     {
     151               2 :         if (this != &other)
     152                 :         {
     153               2 :             close();
     154               2 :             h_ = std::move(other.h_);
     155                 :         }
     156               2 :         return *this;
     157                 :     }
     158                 : 
     159                 :     tcp_acceptor(tcp_acceptor const&) = delete;
     160                 :     tcp_acceptor& operator=(tcp_acceptor const&) = delete;
     161                 : 
     162                 :     /** Open, bind, and listen on an endpoint.
     163                 : 
     164                 :         Creates an IPv4 TCP socket, binds it to the specified endpoint,
     165                 :         and begins listening for incoming connections. This must be
     166                 :         called before initiating accept operations.
     167                 : 
     168                 :         @param ep The local endpoint to bind to. Use `endpoint(port)` to
     169                 :             bind to all interfaces on a specific port.
     170                 : 
     171                 :         @param backlog The maximum length of the queue of pending
     172                 :             connections. Defaults to 128.
     173                 : 
     174                 :         @return An error code indicating success or the reason for failure.
     175                 :             A default-constructed error code indicates success.
     176                 : 
     177                 :         @par Error Conditions
     178                 :         @li `errc::address_in_use`: The endpoint is already in use.
     179                 :         @li `errc::address_not_available`: The address is not available
     180                 :             on any local interface.
     181                 :         @li `errc::permission_denied`: Insufficient privileges to bind
     182                 :             to the endpoint (e.g., privileged port).
     183                 :         @li `errc::operation_not_supported`: The acceptor service is
     184                 :             unavailable in the context (POSIX only).
     185                 : 
     186                 :         @throws Nothing.
     187                 :     */
     188                 :     [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
     189                 : 
     190                 :     /** Close the acceptor.
     191                 : 
     192                 :         Releases acceptor resources. Any pending operations complete
     193                 :         with `errc::operation_canceled`.
     194                 :     */
     195                 :     void close();
     196                 : 
     197                 :     /** Check if the acceptor is listening.
     198                 : 
     199                 :         @return `true` if the acceptor is open and listening.
     200                 :     */
     201            8812 :     bool is_open() const noexcept
     202                 :     {
     203            8812 :         return h_ && get().is_open();
     204                 :     }
     205                 : 
     206                 :     /** Initiate an asynchronous accept operation.
     207                 : 
     208                 :         Accepts an incoming connection and initializes the provided
     209                 :         socket with the new connection. The acceptor must be listening
     210                 :         before calling this function.
     211                 : 
     212                 :         The operation supports cancellation via `std::stop_token` through
     213                 :         the affine awaitable protocol. If the associated stop token is
     214                 :         triggered, the operation completes immediately with
     215                 :         `errc::operation_canceled`.
     216                 : 
     217                 :         @param peer The socket to receive the accepted connection. Any
     218                 :             existing connection on this socket will be closed.
     219                 : 
     220                 :         @return An awaitable that completes with `io_result<>`.
     221                 :             Returns success on successful accept, or an error code on
     222                 :             failure including:
     223                 :             - operation_canceled: Cancelled via stop_token or cancel().
     224                 :                 Check `ec == cond::canceled` for portable comparison.
     225                 : 
     226                 :         @par Preconditions
     227                 :         The acceptor must be listening (`is_open() == true`).
     228                 :         The peer socket must be associated with the same execution context.
     229                 : 
     230                 :         @par Example
     231                 :         @code
     232                 :         tcp_socket peer(ioc);
     233                 :         auto [ec] = co_await acc.accept(peer);
     234                 :         if (!ec) {
     235                 :             // Use peer socket
     236                 :         }
     237                 :         @endcode
     238                 :     */
     239            8387 :     auto accept(tcp_socket& peer)
     240                 :     {
     241            8387 :         if (!is_open())
     242 MIS           0 :             detail::throw_logic_error("accept: acceptor not listening");
     243 HIT        8387 :         return accept_awaitable(*this, peer);
     244                 :     }
     245                 : 
     246                 :     /** Cancel any pending asynchronous operations.
     247                 : 
     248                 :         All outstanding operations complete with `errc::operation_canceled`.
     249                 :         Check `ec == cond::canceled` for portable comparison.
     250                 :     */
     251                 :     void cancel();
     252                 : 
     253                 :     /** Get the local endpoint of the acceptor.
     254                 : 
     255                 :         Returns the local address and port to which the acceptor is bound.
     256                 :         This is useful when binding to port 0 (ephemeral port) to discover
     257                 :         the OS-assigned port number. The endpoint is cached when listen()
     258                 :         is called.
     259                 : 
     260                 :         @return The local endpoint, or a default endpoint (0.0.0.0:0) if
     261                 :             the acceptor is not listening.
     262                 : 
     263                 :         @par Thread Safety
     264                 :         The cached endpoint value is set during listen() and cleared
     265                 :         during close(). This function may be called concurrently with
     266                 :         accept operations, but must not be called concurrently with
     267                 :         listen() or close().
     268                 :     */
     269                 :     endpoint local_endpoint() const noexcept;
     270                 : 
     271                 :     struct implementation : io_object::implementation
     272                 :     {
     273                 :         virtual std::coroutine_handle<> accept(
     274                 :             std::coroutine_handle<>,
     275                 :             capy::executor_ref,
     276                 :             std::stop_token,
     277                 :             std::error_code*,
     278                 :             io_object::implementation**) = 0;
     279                 : 
     280                 :         /// Returns the cached local endpoint.
     281                 :         virtual endpoint local_endpoint() const noexcept = 0;
     282                 : 
     283                 :         /// Return true if the acceptor has a kernel resource open.
     284                 :         virtual bool is_open() const noexcept = 0;
     285                 : 
     286                 :         /** Cancel any pending asynchronous operations.
     287                 : 
     288                 :             All outstanding operations complete with operation_canceled error.
     289                 :         */
     290                 :         virtual void cancel() noexcept = 0;
     291                 :     };
     292                 : 
     293                 : private:
     294           17279 :     inline implementation& get() const noexcept
     295                 :     {
     296           17279 :         return *static_cast<implementation*>(h_.get());
     297                 :     }
     298                 : };
     299                 : 
     300                 : } // namespace boost::corosio
     301                 : 
     302                 : #endif
        

Generated by: LCOV version 2.3