97.78% Lines (88/90) 100.00% Functions (22/22)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) 2   // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3   // Copyright (c) 2026 Steve Gerbino 3   // Copyright (c) 2026 Steve Gerbino
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
9   // 9   //
10   10  
11   #ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP 11   #ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
12   #define BOOST_COROSIO_TCP_ACCEPTOR_HPP 12   #define BOOST_COROSIO_TCP_ACCEPTOR_HPP
13   13  
14   #include <boost/corosio/detail/config.hpp> 14   #include <boost/corosio/detail/config.hpp>
15   #include <boost/corosio/detail/except.hpp> 15   #include <boost/corosio/detail/except.hpp>
16   #include <boost/corosio/detail/native_handle.hpp> 16   #include <boost/corosio/detail/native_handle.hpp>
17   #include <boost/corosio/detail/op_base.hpp> 17   #include <boost/corosio/detail/op_base.hpp>
18   #include <boost/corosio/wait_type.hpp> 18   #include <boost/corosio/wait_type.hpp>
19   #include <boost/corosio/io/io_object.hpp> 19   #include <boost/corosio/io/io_object.hpp>
20   #include <boost/capy/io_result.hpp> 20   #include <boost/capy/io_result.hpp>
21   #include <boost/corosio/endpoint.hpp> 21   #include <boost/corosio/endpoint.hpp>
22   #include <boost/corosio/tcp.hpp> 22   #include <boost/corosio/tcp.hpp>
23   #include <boost/corosio/tcp_socket.hpp> 23   #include <boost/corosio/tcp_socket.hpp>
24   #include <boost/capy/ex/executor_ref.hpp> 24   #include <boost/capy/ex/executor_ref.hpp>
25   #include <boost/capy/ex/execution_context.hpp> 25   #include <boost/capy/ex/execution_context.hpp>
26   #include <boost/capy/ex/io_env.hpp> 26   #include <boost/capy/ex/io_env.hpp>
27   #include <boost/capy/concept/executor.hpp> 27   #include <boost/capy/concept/executor.hpp>
28   28  
29   #include <system_error> 29   #include <system_error>
30   30  
31   #include <concepts> 31   #include <concepts>
32   #include <coroutine> 32   #include <coroutine>
33   #include <cstddef> 33   #include <cstddef>
34   #include <stop_token> 34   #include <stop_token>
35   #include <type_traits> 35   #include <type_traits>
36   36  
37   namespace boost::corosio { 37   namespace boost::corosio {
38   38  
39   /** An asynchronous TCP acceptor for coroutine I/O. 39   /** An asynchronous TCP acceptor for coroutine I/O.
40   40  
41   This class provides asynchronous TCP accept operations that return 41   This class provides asynchronous TCP accept operations that return
42   awaitable types. The acceptor binds to a local endpoint and listens 42   awaitable types. The acceptor binds to a local endpoint and listens
43   for incoming connections. 43   for incoming connections.
44   44  
45   Each accept operation participates in the affine awaitable protocol, 45   Each accept operation participates in the affine awaitable protocol,
46   ensuring coroutines resume on the correct executor. 46   ensuring coroutines resume on the correct executor.
47   47  
48   @par Thread Safety 48   @par Thread Safety
49   Distinct objects: Safe.@n 49   Distinct objects: Safe.@n
50   Shared objects: Unsafe. An acceptor must not have concurrent accept 50   Shared objects: Unsafe. An acceptor must not have concurrent accept
51   operations. 51   operations.
52   52  
53   @par Semantics 53   @par Semantics
54   Wraps the platform TCP listener. Operations dispatch to 54   Wraps the platform TCP listener. Operations dispatch to
55   OS accept APIs via the io_context reactor. 55   OS accept APIs via the io_context reactor.
56   56  
57   @par Example 57   @par Example
58   @code 58   @code
59 - // Convenience constructor: open + SO_REUSEADDR + bind + listen 59 + // Convenience constructor: open + configure + bind + listen
60   io_context ioc; 60   io_context ioc;
61   tcp_acceptor acc( ioc, endpoint( 8080 ) ); 61   tcp_acceptor acc( ioc, endpoint( 8080 ) );
62   62  
63   tcp_socket peer( ioc ); 63   tcp_socket peer( ioc );
64   auto [ec] = co_await acc.accept( peer ); 64   auto [ec] = co_await acc.accept( peer );
65   if ( !ec ) { 65   if ( !ec ) {
66   // peer is now a connected socket 66   // peer is now a connected socket
67   auto [ec2, n] = co_await peer.read_some( buf ); 67   auto [ec2, n] = co_await peer.read_some( buf );
68   } 68   }
69   @endcode 69   @endcode
70   70  
71   @par Example 71   @par Example
72   @code 72   @code
73   // Fine-grained setup 73   // Fine-grained setup
74   tcp_acceptor acc( ioc ); 74   tcp_acceptor acc( ioc );
75 - acc.open( tcp::v6() ); 75 + if ( auto ec = acc.open( tcp::v6() ) )
  76 + return ec;
76   acc.set_option( socket_option::reuse_address( true ) ); 77   acc.set_option( socket_option::reuse_address( true ) );
77   acc.set_option( socket_option::v6_only( true ) ); 78   acc.set_option( socket_option::v6_only( true ) );
78   if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) ) 79   if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) )
79   return ec; 80   return ec;
80   if ( auto ec = acc.listen() ) 81   if ( auto ec = acc.listen() )
81   return ec; 82   return ec;
82   @endcode 83   @endcode
83   */ 84   */
84   class BOOST_COROSIO_DECL tcp_acceptor : public io_object 85   class BOOST_COROSIO_DECL tcp_acceptor : public io_object
85   { 86   {
86   struct wait_awaitable 87   struct wait_awaitable
87   : detail::void_op_base<wait_awaitable> 88   : detail::void_op_base<wait_awaitable>
88   { 89   {
89   tcp_acceptor& acc_; 90   tcp_acceptor& acc_;
90   wait_type w_; 91   wait_type w_;
91   92  
HITCBC 92   15 wait_awaitable(tcp_acceptor& acc, wait_type w) noexcept 93   19 wait_awaitable(tcp_acceptor& acc, wait_type w) noexcept
HITCBC 93   15 : acc_(acc), w_(w) {} 94   19 : acc_(acc), w_(w) {}
94   95  
HITCBC 95   15 std::coroutine_handle<> dispatch( 96   17 std::coroutine_handle<> dispatch(
96   std::coroutine_handle<> h, capy::executor_ref ex) const 97   std::coroutine_handle<> h, capy::executor_ref ex) const
97   { 98   {
HITCBC 98   15 return acc_.get().wait(h, ex, w_, token_, &ec_); 99   17 return acc_.get().wait(h, ex, w_, token_, &ec_);
99   } 100   }
100   }; 101   };
101   102  
102   struct accept_awaitable 103   struct accept_awaitable
103   { 104   {
104   tcp_acceptor& acc_; 105   tcp_acceptor& acc_;
105   tcp_socket& peer_; 106   tcp_socket& peer_;
106   std::stop_token token_; 107   std::stop_token token_;
107   mutable std::error_code ec_; 108   mutable std::error_code ec_;
108   mutable io_object::implementation* peer_impl_ = nullptr; 109   mutable io_object::implementation* peer_impl_ = nullptr;
109   110  
HITCBC 110   5718 accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept 111   7002 accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
HITCBC 111   5718 : acc_(acc) 112   7002 : acc_(acc)
HITCBC 112   5718 , peer_(peer) 113   7002 , peer_(peer)
113   { 114   {
HITCBC 114   5718 } 115   7002 }
115   116  
HITCBC 116   5718 bool await_ready() const noexcept 117   7002 bool await_ready() const noexcept
117   { 118   {
ECB 118 - 5718 return token_.stop_requested(); 119 + // A pre-set ec_ means the initiator failed before
  120 + // dispatch (e.g. a closed object).
HITGNC   121 + 7002 return static_cast<bool>(ec_) || token_.stop_requested();
119   } 122   }
120   123  
HITCBC 121   5716 [[nodiscard]] capy::io_result<> await_resume() const noexcept 124   7000 [[nodiscard]] capy::io_result<> await_resume() const noexcept
122   { 125   {
HITCBC 123   5716 if (token_.stop_requested()) 126   7000 if (token_.stop_requested())
HITCBC 124   27 return {make_error_code(std::errc::operation_canceled)}; 127   27 return {make_error_code(std::errc::operation_canceled)};
125   128  
HITCBC 126   5689 if (!ec_ && peer_impl_) 129   6973 if (!ec_ && peer_impl_)
HITCBC 127   5680 peer_.h_.reset(peer_impl_); 130   6962 peer_.h_.reset(peer_impl_);
HITCBC 128   5689 return {ec_}; 131   6973 return {ec_};
129   } 132   }
130   133  
HITCBC 131   5718 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 134   7000 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
132   -> std::coroutine_handle<> 135   -> std::coroutine_handle<>
133   { 136   {
HITCBC 134   5718 token_ = env->stop_token; 137   7000 token_ = env->stop_token;
HITCBC 135   17154 return acc_.get().accept( 138   21000 return acc_.get().accept(
HITCBC 136   17154 h, env->executor, token_, &ec_, &peer_impl_); 139   21000 h, env->executor, token_, &ec_, &peer_impl_);
137   } 140   }
138   }; 141   };
139   142  
140   struct accept_value_awaitable 143   struct accept_value_awaitable
141   { 144   {
142 - tcp_socket peer_;  
143   tcp_acceptor& acc_; 145   tcp_acceptor& acc_;
144   std::stop_token token_; 146   std::stop_token token_;
145   mutable std::error_code ec_; 147   mutable std::error_code ec_;
146   mutable io_object::implementation* peer_impl_ = nullptr; 148   mutable io_object::implementation* peer_impl_ = nullptr;
147   149  
HITCBC 148 - 27 explicit accept_value_awaitable(tcp_acceptor& acc) 150 + 31 explicit accept_value_awaitable(tcp_acceptor& acc) noexcept
DCB 149 - 27 , peer_(acc.context())  
HITCBC 150   27 : acc_(acc) 151   31 : acc_(acc)
151   { 152   {
HITCBC 152   27 } 153   31 }
153   154  
HITCBC 154   27 bool await_ready() const noexcept 155   31 bool await_ready() const noexcept
155   { 156   {
ECB 156 - 27 return token_.stop_requested(); 157 + // A pre-set ec_ means the initiator failed before
  158 + // dispatch (e.g. a closed object).
HITGNC   159 + 31 return static_cast<bool>(ec_) || token_.stop_requested();
157   } 160   }
158   161  
HITCBC 159   27 [[nodiscard]] capy::io_result<tcp_socket> await_resume() noexcept 162   31 [[nodiscard]] capy::io_result<tcp_socket> await_resume() noexcept
160   { 163   {
  164 + // The peer is built only on success: error paths must not
  165 + // touch acc_.context(), which a moved-from acceptor lacks.
HITCBC 161   27 if (token_.stop_requested()) 166   31 if (token_.stop_requested())
MISUBC 162   return {make_error_code(std::errc::operation_canceled), 167   return {make_error_code(std::errc::operation_canceled),
MISUBC 163 - std::move(peer_)}; 168 + tcp_socket()};
164   169  
HITCBC 165 - 27 if (!ec_ && peer_impl_) 170 + 31 if (ec_ || !peer_impl_)
HITCBC 166 - 27 peer_.h_.reset(peer_impl_); 171 + 4 return {ec_, tcp_socket()};
ECB 167 - 27 return {ec_, std::move(peer_)}; 172 +
HITGNC   173 + 27 tcp_socket peer(acc_.context());
HITGNC   174 + 27 peer.h_.reset(peer_impl_);
HITGNC   175 + 27 return {ec_, std::move(peer)};
HITGIC 168   } 176   27 }
169   177  
HITCBC 170   27 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 178   27 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
171   -> std::coroutine_handle<> 179   -> std::coroutine_handle<>
172   { 180   {
HITCBC 173   27 token_ = env->stop_token; 181   27 token_ = env->stop_token;
HITCBC 174   81 return acc_.get().accept( 182   81 return acc_.get().accept(
HITCBC 175   81 h, env->executor, token_, &ec_, &peer_impl_); 183   81 h, env->executor, token_, &ec_, &peer_impl_);
176   } 184   }
177   }; 185   };
178   186  
179   public: 187   public:
180   /** Destructor. 188   /** Destructor.
181   189  
182   Closes the acceptor if open, cancelling any pending operations. 190   Closes the acceptor if open, cancelling any pending operations.
183   */ 191   */
184   ~tcp_acceptor() override; 192   ~tcp_acceptor() override;
185   193  
186   /** Construct an acceptor from an execution context. 194   /** Construct an acceptor from an execution context.
187   195  
188   @param ctx The execution context that will own this acceptor. 196   @param ctx The execution context that will own this acceptor.
189   */ 197   */
190   explicit tcp_acceptor(capy::execution_context& ctx); 198   explicit tcp_acceptor(capy::execution_context& ctx);
191   199  
192 - /** Convenience constructor: open + SO_REUSEADDR + bind + listen. 200 + /** Convenience constructor: open + configure + bind + listen.
193   201  
194   Creates a fully-bound listening acceptor in a single 202   Creates a fully-bound listening acceptor in a single
195 - expression. The address family is deduced from @p ep. 203 + expression, throwing the codes the piecewise `open()` +
  204 + `set_option()` + `bind()` + `listen()` path reports. The
  205 + address family is deduced from @p ep.
  206 +
  207 + Before binding, the constructor configures address reuse so
  208 + a server can rebind its port immediately after a restart:
  209 + `SO_REUSEADDR` on POSIX, `SO_EXCLUSIVEADDRUSE` on Windows
  210 + ( where `SO_REUSEADDR` instead grants other sockets
  211 + bind-over rights ). A second listener on an occupied
  212 + endpoint therefore throws `errc::address_in_use` on every
  213 + platform.
196   214  
197   @param ctx The execution context that will own this acceptor. 215   @param ctx The execution context that will own this acceptor.
198   @param ep The local endpoint to bind to. 216   @param ep The local endpoint to bind to.
199   @param backlog The maximum pending connection queue length. 217   @param backlog The maximum pending connection queue length.
200   218  
201 - @throws std::system_error on bind or listen failure. 219 + @throws std::system_error on open, configuration, bind, or
  220 + listen failure.
202   */ 221   */
203   tcp_acceptor(capy::execution_context& ctx, endpoint ep, int backlog = 128); 222   tcp_acceptor(capy::execution_context& ctx, endpoint ep, int backlog = 128);
204   223  
205   /** Construct an acceptor from an executor. 224   /** Construct an acceptor from an executor.
206   225  
207   The acceptor is associated with the executor's context. 226   The acceptor is associated with the executor's context.
208   227  
209   @param ex The executor whose context will own the acceptor. 228   @param ex The executor whose context will own the acceptor.
210   */ 229   */
211   template<class Ex> 230   template<class Ex>
212   requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) && 231   requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
213   capy::Executor<Ex> 232   capy::Executor<Ex>
HITCBC 214   1 explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context()) 233   1 explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
215   { 234   {
HITCBC 216   1 } 235   1 }
217   236  
218   /** Convenience constructor from an executor. 237   /** Convenience constructor from an executor.
219   238  
220   @param ex The executor whose context will own the acceptor. 239   @param ex The executor whose context will own the acceptor.
221   @param ep The local endpoint to bind to. 240   @param ep The local endpoint to bind to.
222   @param backlog The maximum pending connection queue length. 241   @param backlog The maximum pending connection queue length.
223   242  
224 - @throws std::system_error on bind or listen failure. 243 + @throws std::system_error on open, configuration, bind, or
  244 + listen failure.
225   */ 245   */
226   template<class Ex> 246   template<class Ex>
227   requires capy::Executor<Ex> 247   requires capy::Executor<Ex>
228   tcp_acceptor(Ex const& ex, endpoint ep, int backlog = 128) 248   tcp_acceptor(Ex const& ex, endpoint ep, int backlog = 128)
229   : tcp_acceptor(ex.context(), ep, backlog) 249   : tcp_acceptor(ex.context(), ep, backlog)
230   { 250   {
231   } 251   }
232   252  
233   /** Move constructor. 253   /** Move constructor.
234   254  
235   Transfers ownership of the acceptor resources. 255   Transfers ownership of the acceptor resources.
236   256  
237   @param other The acceptor to move from. 257   @param other The acceptor to move from.
238   258  
239   @pre No awaitables returned by @p other's methods exist. 259   @pre No awaitables returned by @p other's methods exist.
240   @pre The execution context associated with @p other must 260   @pre The execution context associated with @p other must
241   outlive this acceptor. 261   outlive this acceptor.
242   */ 262   */
HITCBC 243   5 tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {} 263   9 tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
244   264  
245   /** Move assignment operator. 265   /** Move assignment operator.
246   266  
247   Closes any existing acceptor and transfers ownership. 267   Closes any existing acceptor and transfers ownership.
248   268  
249   @param other The acceptor to move from. 269   @param other The acceptor to move from.
250   270  
251   @pre No awaitables returned by either `*this` or @p other's 271   @pre No awaitables returned by either `*this` or @p other's
252   methods exist. 272   methods exist.
253   @pre The execution context associated with @p other must 273   @pre The execution context associated with @p other must
254   outlive this acceptor. 274   outlive this acceptor.
255   275  
256   @return Reference to this acceptor. 276   @return Reference to this acceptor.
257   */ 277   */
HITCBC 258   3 tcp_acceptor& operator=(tcp_acceptor&& other) noexcept 278   3 tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
259   { 279   {
HITCBC 260   3 if (this != &other) 280   3 if (this != &other)
261   { 281   {
HITCBC 262   3 close(); 282   3 close();
HITCBC 263   3 h_ = std::move(other.h_); 283   3 h_ = std::move(other.h_);
264   } 284   }
HITCBC 265   3 return *this; 285   3 return *this;
266   } 286   }
267   287  
268   tcp_acceptor(tcp_acceptor const&) = delete; 288   tcp_acceptor(tcp_acceptor const&) = delete;
269   tcp_acceptor& operator=(tcp_acceptor const&) = delete; 289   tcp_acceptor& operator=(tcp_acceptor const&) = delete;
270   290  
271   /** Create the acceptor socket without binding or listening. 291   /** Create the acceptor socket without binding or listening.
272   292  
273   Creates a TCP socket with dual-stack enabled for IPv6. 293   Creates a TCP socket with dual-stack enabled for IPv6.
274   Does not set SO_REUSEADDR — call `set_option` explicitly 294   Does not set SO_REUSEADDR — call `set_option` explicitly
275   if needed. 295   if needed.
276   296  
277   If the acceptor is already open, this function is a no-op. 297   If the acceptor is already open, this function is a no-op.
278   298  
  299 + Failures such as descriptor exhaustion are normal runtime
  300 + conditions and are reported through the returned error code.
  301 +
279   @param proto The protocol (IPv4 or IPv6). Defaults to 302   @param proto The protocol (IPv4 or IPv6). Defaults to
280   `tcp::v4()`. 303   `tcp::v4()`.
281 - @throws std::system_error on failure.  
282 -  
283   304  
284   @par Example 305   @par Example
285   @code 306   @code
286 - acc.open( tcp::v6() ); 307 + if (auto ec = acc.open( tcp::v6() ))
  308 + return; // report the error
287   acc.set_option( socket_option::reuse_address( true ) ); 309   acc.set_option( socket_option::reuse_address( true ) );
288 - acc.bind( endpoint( ipv6_address::any(), 8080 ) ); 310 + if (auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ))
289 - acc.listen(); 311 + return;
  312 + if (auto ec = acc.listen())
  313 + return;
290   @endcode 314   @endcode
291   315  
292   @see bind, listen 316   @see bind, listen
  317 +
  318 + @return The error code, empty on success.
293   */ 319   */
294 - void open(tcp proto = tcp::v4()); 320 + [[nodiscard]] std::error_code open(tcp proto = tcp::v4()) noexcept;
295   321  
296   /** Bind to a local endpoint. 322   /** Bind to a local endpoint.
297   323  
298   The acceptor must be open. Binds the socket to @p ep and 324   The acceptor must be open. Binds the socket to @p ep and
299   caches the resolved local endpoint (useful when port 0 is 325   caches the resolved local endpoint (useful when port 0 is
300   used to request an ephemeral port). 326   used to request an ephemeral port).
301   327  
302   @param ep The local endpoint to bind to. 328   @param ep The local endpoint to bind to.
303   329  
304   @return An error code indicating success or the reason for 330   @return An error code indicating success or the reason for
305   failure. 331   failure.
306   332  
307   @par Error Conditions 333   @par Error Conditions
308   @li `errc::address_in_use`: The endpoint is already in use. 334   @li `errc::address_in_use`: The endpoint is already in use.
309   @li `errc::address_not_available`: The address is not available 335   @li `errc::address_not_available`: The address is not available
310   on any local interface. 336   on any local interface.
311   @li `errc::permission_denied`: Insufficient privileges to bind 337   @li `errc::permission_denied`: Insufficient privileges to bind
312   to the endpoint (e.g., privileged port). 338   to the endpoint (e.g., privileged port).
313   339  
314 - @throws std::logic_error if the acceptor is not open. 340 + A closed acceptor reports `errc::bad_file_descriptor`.
315   */ 341   */
316 - [[nodiscard]] std::error_code bind(endpoint ep); 342 + [[nodiscard]] std::error_code bind(endpoint ep) noexcept;
317   343  
318   /** Start listening for incoming connections. 344   /** Start listening for incoming connections.
319   345  
320   The acceptor must be open and bound. Registers the acceptor 346   The acceptor must be open and bound. Registers the acceptor
321   with the platform reactor. 347   with the platform reactor.
322   348  
323   @param backlog The maximum length of the queue of pending 349   @param backlog The maximum length of the queue of pending
324   connections. Defaults to 128. 350   connections. Defaults to 128.
325   351  
326   @return An error code indicating success or the reason for 352   @return An error code indicating success or the reason for
327   failure. 353   failure.
328   354  
329 - @throws std::logic_error if the acceptor is not open. 355 + A closed acceptor reports `errc::bad_file_descriptor`.
330   */ 356   */
331 - [[nodiscard]] std::error_code listen(int backlog = 128); 357 + [[nodiscard]] std::error_code listen(int backlog = 128) noexcept;
332   358  
333   /** Close the acceptor. 359   /** Close the acceptor.
334   360  
335   Releases acceptor resources. Any pending operations complete 361   Releases acceptor resources. Any pending operations complete
336   with `errc::operation_canceled`. 362   with `errc::operation_canceled`.
337   */ 363   */
338 - void close(); 364 + void close() noexcept;
339   365  
340   /** Check if the acceptor is listening. 366   /** Check if the acceptor is listening.
341   367  
342   @return `true` if the acceptor is open and listening. 368   @return `true` if the acceptor is open and listening.
343   */ 369   */
HITCBC 344   8584 bool is_open() const noexcept 370   9926 bool is_open() const noexcept
345   { 371   {
HITCBC 346   8584 return h_ && get().is_open(); 372   9926 return h_ && get().is_open();
347   } 373   }
348   374  
349   /** Initiate an asynchronous accept operation. 375   /** Initiate an asynchronous accept operation.
350   376  
351   Accepts an incoming connection and initializes the provided 377   Accepts an incoming connection and initializes the provided
352   socket with the new connection. The acceptor must be listening 378   socket with the new connection. The acceptor must be listening
353   before calling this function. 379   before calling this function.
354   380  
355   The operation supports cancellation via `std::stop_token` through 381   The operation supports cancellation via `std::stop_token` through
356   the affine awaitable protocol. If the associated stop token is 382   the affine awaitable protocol. If the associated stop token is
357   triggered, the operation completes immediately with 383   triggered, the operation completes immediately with
358   `errc::operation_canceled`. 384   `errc::operation_canceled`.
359   385  
360   @param peer The socket to receive the accepted connection. Any 386   @param peer The socket to receive the accepted connection. Any
361   existing connection on this socket will be closed. 387   existing connection on this socket will be closed.
362   388  
363   @return An awaitable that completes with `io_result<>`. 389   @return An awaitable that completes with `io_result<>`.
364   Returns success on successful accept, or an error code on 390   Returns success on successful accept, or an error code on
365   failure including: 391   failure including:
366   - operation_canceled: Cancelled via stop_token or cancel(). 392   - operation_canceled: Cancelled via stop_token or cancel().
367   Check `ec == cond::canceled` for portable comparison. 393   Check `ec == cond::canceled` for portable comparison.
368   394  
  395 + A closed acceptor completes with `errc::bad_file_descriptor`.
  396 +
369 - The acceptor must be listening (`is_open() == true`).  
370   @par Preconditions 397   @par Preconditions
371   The peer socket must be associated with the same execution context. 398   The peer socket must be associated with the same execution context.
372   399  
373   Both this acceptor and @p peer must outlive the returned 400   Both this acceptor and @p peer must outlive the returned
374   awaitable. 401   awaitable.
375   402  
376   @par Example 403   @par Example
377   @code 404   @code
378   tcp_socket peer(ioc); 405   tcp_socket peer(ioc);
379   auto [ec] = co_await acc.accept(peer); 406   auto [ec] = co_await acc.accept(peer);
380 - if (!ec) { 407 + if (ec)
381 - // Use peer socket 408 + co_return;
382 - } 409 + auto [wec, n] = co_await peer.write_some(buffer);
383   @endcode 410   @endcode
384   411  
385   @see accept() 412   @see accept()
386   */ 413   */
HITCBC 387 - 5720 auto accept(tcp_socket& peer) 414 + 7002 [[nodiscard]] auto accept(tcp_socket& peer)
388   { 415   {
HITGNC   416 + 7002 accept_awaitable aw(*this, peer);
HITCBC 389   5720 if (!is_open()) 417   7002 if (!is_open())
HITCBC 390 - 2 detail::throw_logic_error("accept: acceptor not listening"); 418 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 391 - 5718 return accept_awaitable(*this, peer); 419 + 7002 return aw;
392   } 420   }
393   421  
394   /** Initiate an asynchronous accept operation, returning the peer. 422   /** Initiate an asynchronous accept operation, returning the peer.
395   423  
396   Accepts an incoming connection and returns a newly constructed 424   Accepts an incoming connection and returns a newly constructed
397   socket for it, associated with this acceptor's execution context. 425   socket for it, associated with this acceptor's execution context.
398   The acceptor must be listening before calling this function. 426   The acceptor must be listening before calling this function.
399   427  
400   The caller does not pre-construct the peer socket; the returned 428   The caller does not pre-construct the peer socket; the returned
401   socket shares this acceptor's execution context. 429   socket shares this acceptor's execution context.
402   430  
403   The operation supports cancellation via `std::stop_token` through 431   The operation supports cancellation via `std::stop_token` through
404   the affine awaitable protocol. If the associated stop token is 432   the affine awaitable protocol. If the associated stop token is
405   triggered, the operation completes immediately with 433   triggered, the operation completes immediately with
406   `errc::operation_canceled`. 434   `errc::operation_canceled`.
407   435  
408   @return An awaitable that completes with `io_result<tcp_socket>`. 436   @return An awaitable that completes with `io_result<tcp_socket>`.
409   On success the payload is the connected peer socket; on failure 437   On success the payload is the connected peer socket; on failure
410   (including cancellation) the error code is set and the payload 438   (including cancellation) the error code is set and the payload
411   socket is unconnected. Errors include: 439   socket is unconnected. Errors include:
412   - operation_canceled: Cancelled via stop_token or cancel(). 440   - operation_canceled: Cancelled via stop_token or cancel().
413   Check `ec == cond::canceled` for portable comparison. 441   Check `ec == cond::canceled` for portable comparison.
414   442  
  443 + A closed acceptor completes with `errc::bad_file_descriptor`.
  444 + On failure the returned socket is default-constructed and
  445 + may only be destroyed or assigned.
  446 +
415   @par Preconditions 447   @par Preconditions
416 - The acceptor must be listening (`is_open() == true`). This acceptor 448 + This acceptor must outlive the returned awaitable.
417 - must outlive the returned awaitable.  
418   449  
419   @par Example 450   @par Example
420   @code 451   @code
421   auto [ec, peer] = co_await acc.accept(); 452   auto [ec, peer] = co_await acc.accept();
422 - if (!ec) { 453 + if (ec)
423 - // peer is a connected socket 454 + co_return;
424 - } 455 + auto [wec, n] = co_await peer.write_some(buffer);
425   @endcode 456   @endcode
426   457  
427   @see accept(tcp_socket&) 458   @see accept(tcp_socket&)
428   */ 459   */
HITCBC 429 - 29 auto accept() 460 + 31 [[nodiscard]] auto accept()
430   { 461   {
HITGNC   462 + 31 accept_value_awaitable aw(*this);
HITCBC 431   29 if (!is_open()) 463   31 if (!is_open())
HITCBC 432 - 2 detail::throw_logic_error("accept: acceptor not listening"); 464 + 4 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 433 - 27 return accept_value_awaitable(*this); 465 + 31 return aw;
434   } 466   }
435   467  
436   /** Wait for an incoming connection or readiness condition. 468   /** Wait for an incoming connection or readiness condition.
437   469  
438   Suspends until the listen socket is ready in the 470   Suspends until the listen socket is ready in the
439   requested direction, or an error condition is reported. 471   requested direction, or an error condition is reported.
440   For `wait_type::read`, completion signals that a 472   For `wait_type::read`, completion signals that a
441   subsequent @ref accept will succeed without blocking; a 473   subsequent @ref accept will succeed without blocking; a
442   connection already queued when the wait begins completes 474   connection already queued when the wait begins completes
443   it immediately. No connection is consumed. 475   it immediately. No connection is consumed.
444   476  
445   @note `wait_type::write` is not usable on an acceptor: 477   @note `wait_type::write` is not usable on an acceptor:
446   writability carries no meaning for a listening socket, so 478   writability carries no meaning for a listening socket, so
447   the wait fails with `errc::operation_not_supported` on 479   the wait fails with `errc::operation_not_supported` on
448   every backend. 480   every backend.
449   481  
450   @param w The wait direction. 482   @param w The wait direction.
451   483  
452   @return An awaitable that completes with `io_result<>`. 484   @return An awaitable that completes with `io_result<>`.
453   485  
  486 + A closed acceptor completes with `errc::bad_file_descriptor`.
  487 +
454   @par Preconditions 488   @par Preconditions
455 - The acceptor must be listening. This acceptor must 489 + This acceptor must outlive the returned awaitable.
456 - outlive the returned awaitable.  
457   */ 490   */
HITCBC 458   17 [[nodiscard]] auto wait(wait_type w) 491   19 [[nodiscard]] auto wait(wait_type w)
459   { 492   {
HITGNC   493 + 19 wait_awaitable aw(*this, w);
HITCBC 460   17 if (!is_open()) 494   19 if (!is_open())
HITCBC 461 - 2 detail::throw_logic_error("wait: acceptor not listening"); 495 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 462 - 15 return wait_awaitable(*this, w); 496 + 19 return aw;
463   } 497   }
464   498  
465   /** Cancel any pending asynchronous operations. 499   /** Cancel any pending asynchronous operations.
466   500  
467   All outstanding operations complete with `errc::operation_canceled`. 501   All outstanding operations complete with `errc::operation_canceled`.
468   Check `ec == cond::canceled` for portable comparison. 502   Check `ec == cond::canceled` for portable comparison.
469   */ 503   */
470 - void cancel(); 504 + void cancel() noexcept;
471   505  
472   /** Get the native socket handle. 506   /** Get the native socket handle.
473   507  
474   Returns the underlying platform-specific socket descriptor. 508   Returns the underlying platform-specific socket descriptor.
475   On POSIX systems this is an `int` file descriptor. 509   On POSIX systems this is an `int` file descriptor.
476   On Windows this is a `SOCKET` handle. 510   On Windows this is a `SOCKET` handle.
477   511  
478   @return The native socket handle, or -1/INVALID_SOCKET if not open. 512   @return The native socket handle, or -1/INVALID_SOCKET if not open.
479   513  
480   @par Preconditions 514   @par Preconditions
481   None. May be called on closed acceptors. 515   None. May be called on closed acceptors.
482   */ 516   */
483   native_handle_type native_handle() const noexcept; 517   native_handle_type native_handle() const noexcept;
484   518  
485   /** Assign an existing native socket to this acceptor. 519   /** Assign an existing native socket to this acceptor.
486   520  
487   Adopts a listening socket created outside the library — 521   Adopts a listening socket created outside the library —
488   received from a service manager, inherited, or made natively — 522   received from a service manager, inherited, or made natively —
489   and registers it with the backend. The socket must be a 523   and registers it with the backend. The socket must be a
490   listening stream socket in the `AF_INET` or `AF_INET6` family. 524   listening stream socket in the `AF_INET` or `AF_INET6` family.
491   Adoption never alters the descriptor's flags or options: on 525   Adoption never alters the descriptor's flags or options: on
492   POSIX the fd must already be non-blocking, and on Windows the 526   POSIX the fd must already be non-blocking, and on Windows the
493   socket must be overlapped-capable. 527   socket must be overlapped-capable.
494   528  
495   Adoption does not verify listen state; @ref accept reports the 529   Adoption does not verify listen state; @ref accept reports the
496   error if the socket is not listening. 530   error if the socket is not listening.
497   531  
498   If this object is already open, pending operations complete 532   If this object is already open, pending operations complete
499   with `errc::operation_canceled` and the held socket is 533   with `errc::operation_canceled` and the held socket is
500   closed before the new one is adopted. 534   closed before the new one is adopted.
501   535  
502   @par Exception Safety 536   @par Exception Safety
503   Strong guarantee on validation failure: the object is 537   Strong guarantee on validation failure: the object is
504   unchanged. If backend registration fails, the object either 538   unchanged. If backend registration fails, the object either
505   retains its previous socket or is left closed, depending on 539   retains its previous socket or is left closed, depending on
506   the backend. In all failure cases the caller retains 540   the backend. In all failure cases the caller retains
507   ownership of `fd`. 541   ownership of `fd`.
508   542  
509   @param fd The native socket to adopt. On success the object 543   @param fd The native socket to adopt. On success the object
510   owns it and will close it. 544   owns it and will close it.
511   545  
512 - @throws std::system_error On validation or registration 546 + @return The error code, empty on success. Validation and
513 - failure. 547 + registration failures are normal runtime conditions when
  548 + adopting foreign descriptors.
514   */ 549   */
515 - void assign(native_handle_type fd); 550 + [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
516   551  
517   /** Release ownership of the native socket handle. 552   /** Release ownership of the native socket handle.
518   553  
519   Deregisters the socket from the backend and cancels pending 554   Deregisters the socket from the backend and cancels pending
520   operations without closing the descriptor. The caller takes 555   operations without closing the descriptor. The caller takes
521   ownership of the returned handle. 556   ownership of the returned handle.
522   557  
523   @return The native handle. 558   @return The native handle.
524   559  
525 - @throws std::logic_error if the acceptor is not open. 560 + @throws std::system_error `errc::bad_file_descriptor` if the
  561 + acceptor is not open.
526   562  
527   @post is_open() == false 563   @post is_open() == false
528   */ 564   */
529   native_handle_type release(); 565   native_handle_type release();
530   566  
531   /** Get the local endpoint of the acceptor. 567   /** Get the local endpoint of the acceptor.
532   568  
533   Returns the local address and port to which the acceptor is bound. 569   Returns the local address and port to which the acceptor is bound.
534   This is useful when binding to port 0 (ephemeral port) to discover 570   This is useful when binding to port 0 (ephemeral port) to discover
535   the OS-assigned port number. The endpoint is cached when bind() 571   the OS-assigned port number. The endpoint is cached when bind()
536   is called. 572   is called.
537   573  
538   @return The local endpoint, or a default endpoint (0.0.0.0:0) if 574   @return The local endpoint, or a default endpoint (0.0.0.0:0) if
539   the acceptor is not open. 575   the acceptor is not open.
540   576  
541   @par Thread Safety 577   @par Thread Safety
542   The cached endpoint value is set during bind() and cleared 578   The cached endpoint value is set during bind() and cleared
543   during close(). This function may be called concurrently with 579   during close(). This function may be called concurrently with
544   accept operations, but must not be called concurrently with 580   accept operations, but must not be called concurrently with
545   bind() or close(). 581   bind() or close().
546   */ 582   */
547   endpoint local_endpoint() const noexcept; 583   endpoint local_endpoint() const noexcept;
548   584  
549   /** Set a socket option on the acceptor. 585   /** Set a socket option on the acceptor.
550   586  
551   Applies a type-safe socket option to the underlying listening 587   Applies a type-safe socket option to the underlying listening
552   socket. The socket must be open (via `open()` or `listen()`). 588   socket. The socket must be open (via `open()` or `listen()`).
553   This is useful for setting options between `open()` and 589   This is useful for setting options between `open()` and
554   `listen()`, such as `socket_option::reuse_port`. 590   `listen()`, such as `socket_option::reuse_port`.
555   591  
556   @par Example 592   @par Example
557   @code 593   @code
558 - acc.open( tcp::v6() ); 594 + if ( auto ec = acc.open( tcp::v6() ) )
  595 + return ec;
559   acc.set_option( socket_option::reuse_port( true ) ); 596   acc.set_option( socket_option::reuse_port( true ) );
560 - acc.bind( endpoint( ipv6_address::any(), 8080 ) ); 597 + if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) )
561 - acc.listen(); 598 + return ec;
  599 + if ( auto ec = acc.listen() )
  600 + return ec;
562   @endcode 601   @endcode
563   602  
564   @param opt The option to set. 603   @param opt The option to set.
565   604  
566 - @throws std::logic_error if the acceptor is not open. 605 + @throws std::system_error `errc::bad_file_descriptor` if the
567 - @throws std::system_error on failure. 606 + acceptor is not open; otherwise thrown on failure.
568   */ 607   */
569   template<class Option> 608   template<class Option>
HITCBC 570   400 void set_option(Option const& opt) 609   404 void set_option(Option const& opt)
571   { 610   {
HITCBC 572   400 if (!is_open()) 611   404 if (!is_open())
HITCBC 573 - 2 detail::throw_logic_error("set_option: acceptor not open"); 612 + 2 detail::throw_system_error(
HITGNC   613 + 4 make_error_code(std::errc::bad_file_descriptor),
  614 + "tcp_acceptor::set_option");
HITCBC 574   398 std::error_code ec = get().set_option( 615   402 std::error_code ec = get().set_option(
575   Option::level(), Option::name(), opt.data(), opt.size()); 616   Option::level(), Option::name(), opt.data(), opt.size());
HITCBC 576   398 if (ec) 617   402 if (ec)
HITGBC 577   detail::throw_system_error(ec, "tcp_acceptor::set_option"); 618   2 detail::throw_system_error(ec, "tcp_acceptor::set_option");
HITCBC 578   398 } 619   400 }
579   620  
580   /** Get a socket option from the acceptor. 621   /** Get a socket option from the acceptor.
581   622  
582   Retrieves the current value of a type-safe socket option. 623   Retrieves the current value of a type-safe socket option.
583   624  
584   @par Example 625   @par Example
585   @code 626   @code
586   auto opt = acc.get_option<socket_option::reuse_address>(); 627   auto opt = acc.get_option<socket_option::reuse_address>();
587   @endcode 628   @endcode
588   629  
589   @return The current option value. 630   @return The current option value.
590   631  
591 - @throws std::logic_error if the acceptor is not open. 632 + @throws std::system_error `errc::bad_file_descriptor` if the
592 - @throws std::system_error on failure. 633 + acceptor is not open; otherwise thrown on failure.
593   */ 634   */
594   template<class Option> 635   template<class Option>
HITCBC 595   8 Option get_option() const 636   10 Option get_option() const
596   { 637   {
HITCBC 597   8 if (!is_open()) 638   10 if (!is_open())
HITCBC 598 - 2 detail::throw_logic_error("get_option: acceptor not open"); 639 + 2 detail::throw_system_error(
HITGNC   640 + 4 make_error_code(std::errc::bad_file_descriptor),
  641 + "tcp_acceptor::get_option");
HITCBC 599   6 Option opt{}; 642   8 Option opt{};
HITCBC 600   6 std::size_t sz = opt.size(); 643   8 std::size_t sz = opt.size();
601   std::error_code ec = 644   std::error_code ec =
HITCBC 602   6 get().get_option(Option::level(), Option::name(), opt.data(), &sz); 645   8 get().get_option(Option::level(), Option::name(), opt.data(), &sz);
HITCBC 603   6 if (ec) 646   8 if (ec)
HITGBC 604   detail::throw_system_error(ec, "tcp_acceptor::get_option"); 647   2 detail::throw_system_error(ec, "tcp_acceptor::get_option");
HITCBC 605   6 opt.resize(sz); 648   6 opt.resize(sz);
HITCBC 606   6 return opt; 649   6 return opt;
607   } 650   }
608   651  
609   /** Define backend hooks for TCP acceptor operations. 652   /** Define backend hooks for TCP acceptor operations.
610   653  
611   Platform backends derive from this to implement 654   Platform backends derive from this to implement
612   accept, endpoint query, open-state checks, cancellation, 655   accept, endpoint query, open-state checks, cancellation,
613   and socket-option management. 656   and socket-option management.
614   */ 657   */
615   struct implementation : io_object::implementation 658   struct implementation : io_object::implementation
616   { 659   {
617   /// Initiate an asynchronous accept operation. 660   /// Initiate an asynchronous accept operation.
618   virtual std::coroutine_handle<> accept( 661   virtual std::coroutine_handle<> accept(
619   std::coroutine_handle<>, 662   std::coroutine_handle<>,
620   capy::executor_ref, 663   capy::executor_ref,
621   std::stop_token, 664   std::stop_token,
622   std::error_code*, 665   std::error_code*,
623   io_object::implementation**) = 0; 666   io_object::implementation**) = 0;
624   667  
625   /** Initiate an asynchronous wait for acceptor readiness. 668   /** Initiate an asynchronous wait for acceptor readiness.
626   669  
627   Completes when the listen socket becomes ready for 670   Completes when the listen socket becomes ready for
628   the specified direction (typically `wait_type::read` 671   the specified direction (typically `wait_type::read`
629   for an incoming connection), or an error condition is 672   for an incoming connection), or an error condition is
630   reported. No connection is consumed. 673   reported. No connection is consumed.
631   */ 674   */
632   virtual std::coroutine_handle<> wait( 675   virtual std::coroutine_handle<> wait(
633   std::coroutine_handle<> h, 676   std::coroutine_handle<> h,
634   capy::executor_ref ex, 677   capy::executor_ref ex,
635   wait_type w, 678   wait_type w,
636   std::stop_token token, 679   std::stop_token token,
637   std::error_code* ec) = 0; 680   std::error_code* ec) = 0;
638   681  
639   /// Returns the cached local endpoint. 682   /// Returns the cached local endpoint.
640   virtual endpoint local_endpoint() const noexcept = 0; 683   virtual endpoint local_endpoint() const noexcept = 0;
641   684  
642   /// Return true if the acceptor has a kernel resource open. 685   /// Return true if the acceptor has a kernel resource open.
643   virtual bool is_open() const noexcept = 0; 686   virtual bool is_open() const noexcept = 0;
644   687  
645   /// Return the native handle, or the platform sentinel if closed. 688   /// Return the native handle, or the platform sentinel if closed.
646   virtual native_handle_type native_handle() const noexcept = 0; 689   virtual native_handle_type native_handle() const noexcept = 0;
647   690  
648   /// Release and return the native handle without closing. 691   /// Release and return the native handle without closing.
649   virtual native_handle_type release_socket() noexcept = 0; 692   virtual native_handle_type release_socket() noexcept = 0;
650   693  
651   /** Cancel any pending asynchronous operations. 694   /** Cancel any pending asynchronous operations.
652   695  
653   All outstanding operations complete with operation_canceled error. 696   All outstanding operations complete with operation_canceled error.
654   */ 697   */
655   virtual void cancel() noexcept = 0; 698   virtual void cancel() noexcept = 0;
656   699  
657   /** Set a socket option. 700   /** Set a socket option.
658   701  
659   @param level The protocol level. 702   @param level The protocol level.
660   @param optname The option name. 703   @param optname The option name.
661   @param data Pointer to the option value. 704   @param data Pointer to the option value.
662   @param size Size of the option value in bytes. 705   @param size Size of the option value in bytes.
663   @return Error code on failure, empty on success. 706   @return Error code on failure, empty on success.
664   */ 707   */
665   virtual std::error_code set_option( 708   virtual std::error_code set_option(
666   int level, 709   int level,
667   int optname, 710   int optname,
668   void const* data, 711   void const* data,
669   std::size_t size) noexcept = 0; 712   std::size_t size) noexcept = 0;
670   713  
671   /** Get a socket option. 714   /** Get a socket option.
672   715  
673   @param level The protocol level. 716   @param level The protocol level.
674   @param optname The option name. 717   @param optname The option name.
675   @param data Pointer to receive the option value. 718   @param data Pointer to receive the option value.
676   @param size On entry, the size of the buffer. On exit, 719   @param size On entry, the size of the buffer. On exit,
677   the size of the option value. 720   the size of the option value.
678   @return Error code on failure, empty on success. 721   @return Error code on failure, empty on success.
679   */ 722   */
680   virtual std::error_code 723   virtual std::error_code
681   get_option(int level, int optname, void* data, std::size_t* size) 724   get_option(int level, int optname, void* data, std::size_t* size)
682   const noexcept = 0; 725   const noexcept = 0;
683   }; 726   };
684   727  
685   protected: 728   protected:
HITCBC 686   21 explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {} 729   27 explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
687   730  
688   /// Transfer accepted peer impl to the peer socket. 731   /// Transfer accepted peer impl to the peer socket.
689   static void 732   static void
HITCBC 690   11 reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept 733   11 reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
691   { 734   {
HITCBC 692   11 if (impl) 735   11 if (impl)
HITCBC 693   11 peer.h_.reset(impl); 736   11 peer.h_.reset(impl);
HITCBC 694   11 } 737   11 }
695   738  
696   private: 739   private:
HITCBC 697   15136 inline implementation& get() const noexcept 740   17766 inline implementation& get() const noexcept
698   { 741   {
HITCBC 699   15136 return *static_cast<implementation*>(h_.get()); 742   17766 return *static_cast<implementation*>(h_.get());
700   } 743   }
701   }; 744   };
702   745  
703   } // namespace boost::corosio 746   } // namespace boost::corosio
704   747  
705   #endif 748   #endif