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