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