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