92.50% Lines (74/80) 100.00% Functions (19/19)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 4   // Distributed under the Boost Software License, Version 1.0. (See accompanying
5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP 10   #ifndef BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP
11   #define BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP 11   #define BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP
12   12  
13   #include <boost/corosio/detail/config.hpp> 13   #include <boost/corosio/detail/config.hpp>
14   #include <boost/corosio/detail/except.hpp> 14   #include <boost/corosio/detail/except.hpp>
15   #include <boost/corosio/detail/op_base.hpp> 15   #include <boost/corosio/detail/op_base.hpp>
16   #include <boost/corosio/wait_type.hpp> 16   #include <boost/corosio/wait_type.hpp>
17   #include <boost/corosio/io/io_object.hpp> 17   #include <boost/corosio/io/io_object.hpp>
18   #include <boost/capy/io_result.hpp> 18   #include <boost/capy/io_result.hpp>
19   #include <boost/corosio/local_endpoint.hpp> 19   #include <boost/corosio/local_endpoint.hpp>
20   #include <boost/corosio/local_stream.hpp> 20   #include <boost/corosio/local_stream.hpp>
21   #include <boost/corosio/local_stream_socket.hpp> 21   #include <boost/corosio/local_stream_socket.hpp>
22   #include <boost/capy/ex/executor_ref.hpp> 22   #include <boost/capy/ex/executor_ref.hpp>
23   #include <boost/capy/ex/execution_context.hpp> 23   #include <boost/capy/ex/execution_context.hpp>
24   #include <boost/capy/ex/io_env.hpp> 24   #include <boost/capy/ex/io_env.hpp>
25   #include <boost/capy/concept/executor.hpp> 25   #include <boost/capy/concept/executor.hpp>
26   26  
27   #include <system_error> 27   #include <system_error>
28   28  
29   #include <cassert> 29   #include <cassert>
30   #include <concepts> 30   #include <concepts>
31   #include <coroutine> 31   #include <coroutine>
32   #include <cstddef> 32   #include <cstddef>
33   #include <stop_token> 33   #include <stop_token>
34   #include <type_traits> 34   #include <type_traits>
35   35  
36   namespace boost::corosio { 36   namespace boost::corosio {
37   37  
38   /** Options for @ref local_stream_acceptor::bind(). 38   /** Options for @ref local_stream_acceptor::bind().
39   39  
40   Controls filesystem cleanup behavior before binding 40   Controls filesystem cleanup behavior before binding
41   to a Unix domain socket path. 41   to a Unix domain socket path.
42   */ 42   */
43   enum class bind_option 43   enum class bind_option
44   { 44   {
45   none, 45   none,
46   /// Unlink the socket path before binding (ignored for abstract paths). 46   /// Unlink the socket path before binding (ignored for abstract paths).
47   unlink_existing 47   unlink_existing
48   }; 48   };
49   49  
50   /** An asynchronous Unix domain stream acceptor for coroutine I/O. 50   /** An asynchronous Unix domain stream acceptor for coroutine I/O.
51   51  
52   This class provides asynchronous Unix domain stream accept 52   This class provides asynchronous Unix domain stream accept
53   operations that return awaitable types. The acceptor binds 53   operations that return awaitable types. The acceptor binds
54   to a local endpoint (filesystem path or abstract name) and 54   to a local endpoint (filesystem path or abstract name) and
55   listens for incoming connections. 55   listens for incoming connections.
56   56  
57   The library does NOT automatically unlink the socket path 57   The library does NOT automatically unlink the socket path
58   on close. Callers are responsible for removing the socket 58   on close. Callers are responsible for removing the socket
59   file before bind (via @ref bind_option::unlink_existing) or 59   file before bind (via @ref bind_option::unlink_existing) or
60   after close. 60   after close.
61   61  
62   @par Thread Safety 62   @par Thread Safety
63   Distinct objects: Safe.@n 63   Distinct objects: Safe.@n
64   Shared objects: Unsafe. An acceptor must not have concurrent 64   Shared objects: Unsafe. An acceptor must not have concurrent
65   accept operations. 65   accept operations.
66   66  
67   @par Example 67   @par Example
68   @code 68   @code
69   io_context ioc; 69   io_context ioc;
70   local_stream_acceptor acc(ioc); 70   local_stream_acceptor acc(ioc);
71   acc.open(); 71   acc.open();
72   acc.bind(local_endpoint("/tmp/my.sock"), 72   acc.bind(local_endpoint("/tmp/my.sock"),
73   bind_option::unlink_existing); 73   bind_option::unlink_existing);
74   acc.listen(); 74   acc.listen();
75   auto [ec, peer] = co_await acc.accept(); 75   auto [ec, peer] = co_await acc.accept();
76   @endcode 76   @endcode
77   */ 77   */
78   class BOOST_COROSIO_DECL local_stream_acceptor : public io_object 78   class BOOST_COROSIO_DECL local_stream_acceptor : public io_object
79   { 79   {
80   struct wait_awaitable 80   struct wait_awaitable
81   : detail::void_op_base<wait_awaitable> 81   : detail::void_op_base<wait_awaitable>
82   { 82   {
83   local_stream_acceptor& acc_; 83   local_stream_acceptor& acc_;
84   wait_type w_; 84   wait_type w_;
85   85  
HITCBC 86   4 wait_awaitable(local_stream_acceptor& acc, wait_type w) noexcept 86   4 wait_awaitable(local_stream_acceptor& acc, wait_type w) noexcept
HITCBC 87   4 : acc_(acc), w_(w) {} 87   4 : acc_(acc), w_(w) {}
88   88  
HITCBC 89   4 std::coroutine_handle<> dispatch( 89   4 std::coroutine_handle<> dispatch(
90   std::coroutine_handle<> h, capy::executor_ref ex) const 90   std::coroutine_handle<> h, capy::executor_ref ex) const
91   { 91   {
HITCBC 92   4 return acc_.get().wait(h, ex, w_, token_, &ec_); 92   4 return acc_.get().wait(h, ex, w_, token_, &ec_);
93   } 93   }
94   }; 94   };
95   95  
96   struct move_accept_awaitable 96   struct move_accept_awaitable
97   { 97   {
98   local_stream_acceptor& acc_; 98   local_stream_acceptor& acc_;
99   std::stop_token token_; 99   std::stop_token token_;
100   mutable std::error_code ec_; 100   mutable std::error_code ec_;
101   mutable io_object::implementation* peer_impl_ = nullptr; 101   mutable io_object::implementation* peer_impl_ = nullptr;
102   102  
HITCBC 103   2 explicit move_accept_awaitable( 103   2 explicit move_accept_awaitable(
104   local_stream_acceptor& acc) noexcept 104   local_stream_acceptor& acc) noexcept
HITCBC 105   2 : acc_(acc) 105   2 : acc_(acc)
106   { 106   {
HITCBC 107   2 } 107   2 }
108   108  
HITCBC 109   2 bool await_ready() const noexcept 109   2 bool await_ready() const noexcept
110   { 110   {
HITCBC 111   2 return token_.stop_requested(); 111   2 return token_.stop_requested();
112   } 112   }
113   113  
HITCBC 114   2 [[nodiscard]] capy::io_result<local_stream_socket> await_resume() const noexcept 114   2 [[nodiscard]] capy::io_result<local_stream_socket> await_resume() const noexcept
115   { 115   {
HITCBC 116   2 if (token_.stop_requested()) 116   2 if (token_.stop_requested())
MISUBC 117   return {make_error_code(std::errc::operation_canceled), 117   return {make_error_code(std::errc::operation_canceled),
MISUBC 118   local_stream_socket()}; 118   local_stream_socket()};
119   119  
HITCBC 120   2 if (ec_ || !peer_impl_) 120   2 if (ec_ || !peer_impl_)
MISUBC 121   return {ec_, local_stream_socket()}; 121   return {ec_, local_stream_socket()};
122   122  
HITCBC 123   2 local_stream_socket peer(acc_.ctx_); 123   2 local_stream_socket peer(acc_.ctx_);
HITCBC 124   2 reset_peer_impl(peer, peer_impl_); 124   2 reset_peer_impl(peer, peer_impl_);
HITCBC 125   2 return {ec_, std::move(peer)}; 125   2 return {ec_, std::move(peer)};
HITCBC 126   2 } 126   2 }
127   127  
HITCBC 128   2 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 128   2 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
129   -> std::coroutine_handle<> 129   -> std::coroutine_handle<>
130   { 130   {
HITCBC 131   2 token_ = env->stop_token; 131   2 token_ = env->stop_token;
HITCBC 132   6 return acc_.get().accept( 132   6 return acc_.get().accept(
HITCBC 133   6 h, env->executor, token_, &ec_, &peer_impl_); 133   6 h, env->executor, token_, &ec_, &peer_impl_);
134   } 134   }
135   }; 135   };
136   136  
137   struct accept_awaitable 137   struct accept_awaitable
138   { 138   {
139   local_stream_acceptor& acc_; 139   local_stream_acceptor& acc_;
140   local_stream_socket& peer_; 140   local_stream_socket& peer_;
141   std::stop_token token_; 141   std::stop_token token_;
142   mutable std::error_code ec_; 142   mutable std::error_code ec_;
143   mutable io_object::implementation* peer_impl_ = nullptr; 143   mutable io_object::implementation* peer_impl_ = nullptr;
144   144  
HITCBC 145   21 accept_awaitable( 145   27 accept_awaitable(
146   local_stream_acceptor& acc, local_stream_socket& peer) noexcept 146   local_stream_acceptor& acc, local_stream_socket& peer) noexcept
HITCBC 147   21 : acc_(acc) 147   27 : acc_(acc)
HITCBC 148   21 , peer_(peer) 148   27 , peer_(peer)
149   { 149   {
HITCBC 150   21 } 150   27 }
151   151  
HITCBC 152   21 bool await_ready() const noexcept 152   27 bool await_ready() const noexcept
153   { 153   {
HITCBC 154   21 return token_.stop_requested(); 154   27 return token_.stop_requested();
155   } 155   }
156   156  
HITCBC 157   19 [[nodiscard]] capy::io_result<> await_resume() const noexcept 157   25 [[nodiscard]] capy::io_result<> await_resume() const noexcept
158   { 158   {
HITCBC 159   19 if (token_.stop_requested()) 159   25 if (token_.stop_requested())
HITCBC 160   4 return {make_error_code(std::errc::operation_canceled)}; 160   4 return {make_error_code(std::errc::operation_canceled)};
161   161  
HITCBC 162   15 if (!ec_ && peer_impl_) 162   21 if (!ec_ && peer_impl_)
HITCBC 163   11 peer_.h_.reset(peer_impl_); 163   17 peer_.h_.reset(peer_impl_);
HITCBC 164   15 return {ec_}; 164   21 return {ec_};
165   } 165   }
166   166  
HITCBC 167   21 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 167   27 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
168   -> std::coroutine_handle<> 168   -> std::coroutine_handle<>
169   { 169   {
HITCBC 170   21 token_ = env->stop_token; 170   27 token_ = env->stop_token;
HITCBC 171   63 return acc_.get().accept( 171   81 return acc_.get().accept(
HITCBC 172   63 h, env->executor, token_, &ec_, &peer_impl_); 172   81 h, env->executor, token_, &ec_, &peer_impl_);
173   } 173   }
174   }; 174   };
175   175  
176   public: 176   public:
177   /** Destructor. 177   /** Destructor.
178   178  
179   Closes the acceptor if open, cancelling any pending operations. 179   Closes the acceptor if open, cancelling any pending operations.
180   */ 180   */
181   ~local_stream_acceptor() override; 181   ~local_stream_acceptor() override;
182   182  
183   /** Construct an acceptor from an execution context. 183   /** Construct an acceptor from an execution context.
184   184  
185   @param ctx The execution context that will own this acceptor. 185   @param ctx The execution context that will own this acceptor.
186   */ 186   */
187   explicit local_stream_acceptor(capy::execution_context& ctx); 187   explicit local_stream_acceptor(capy::execution_context& ctx);
188   188  
189   /** Construct an acceptor from an executor. 189   /** Construct an acceptor from an executor.
190   190  
191   The acceptor is associated with the executor's context. 191   The acceptor is associated with the executor's context.
192   192  
193   @param ex The executor whose context will own the acceptor. 193   @param ex The executor whose context will own the acceptor.
194   194  
195   @tparam Ex A type satisfying @ref capy::Executor. Must not 195   @tparam Ex A type satisfying @ref capy::Executor. Must not
196   be `local_stream_acceptor` itself (disables implicit 196   be `local_stream_acceptor` itself (disables implicit
197   conversion from move). 197   conversion from move).
198   */ 198   */
199   template<class Ex> 199   template<class Ex>
200   requires(!std::same_as<std::remove_cvref_t<Ex>, local_stream_acceptor>) && 200   requires(!std::same_as<std::remove_cvref_t<Ex>, local_stream_acceptor>) &&
201   capy::Executor<Ex> 201   capy::Executor<Ex>
202   explicit local_stream_acceptor(Ex const& ex) : local_stream_acceptor(ex.context()) 202   explicit local_stream_acceptor(Ex const& ex) : local_stream_acceptor(ex.context())
203   { 203   {
204   } 204   }
205   205  
206   /** Move constructor. 206   /** Move constructor.
207   207  
208   Transfers ownership of the acceptor resources. 208   Transfers ownership of the acceptor resources.
209   209  
210   @param other The acceptor to move from. 210   @param other The acceptor to move from.
211   211  
212   @pre No awaitables returned by @p other's methods exist. 212   @pre No awaitables returned by @p other's methods exist.
213   @pre The execution context associated with @p other must 213   @pre The execution context associated with @p other must
214   outlive this acceptor. 214   outlive this acceptor.
215   */ 215   */
216   local_stream_acceptor(local_stream_acceptor&& other) noexcept 216   local_stream_acceptor(local_stream_acceptor&& other) noexcept
217   : local_stream_acceptor(other.ctx_, std::move(other)) 217   : local_stream_acceptor(other.ctx_, std::move(other))
218   { 218   {
219   } 219   }
220   220  
221   /** Move assignment operator. 221   /** Move assignment operator.
222   222  
223   Closes any existing acceptor and transfers ownership. 223   Closes any existing acceptor and transfers ownership.
224   Both acceptors must share the same execution context. 224   Both acceptors must share the same execution context.
225   225  
226   @param other The acceptor to move from. 226   @param other The acceptor to move from.
227   227  
228   @return Reference to this acceptor. 228   @return Reference to this acceptor.
229   229  
230   @pre `&ctx_ == &other.ctx_` (same execution context). 230   @pre `&ctx_ == &other.ctx_` (same execution context).
231   @pre No awaitables returned by either `*this` or @p other's 231   @pre No awaitables returned by either `*this` or @p other's
232   methods exist. 232   methods exist.
233   */ 233   */
234   local_stream_acceptor& operator=(local_stream_acceptor&& other) noexcept 234   local_stream_acceptor& operator=(local_stream_acceptor&& other) noexcept
235   { 235   {
236   assert(&ctx_ == &other.ctx_ && 236   assert(&ctx_ == &other.ctx_ &&
237   "move-assign requires the same execution_context"); 237   "move-assign requires the same execution_context");
238   if (this != &other) 238   if (this != &other)
239   { 239   {
240   close(); 240   close();
241   io_object::operator=(std::move(other)); 241   io_object::operator=(std::move(other));
242   } 242   }
243   return *this; 243   return *this;
244   } 244   }
245   245  
246   local_stream_acceptor(local_stream_acceptor const&) = delete; 246   local_stream_acceptor(local_stream_acceptor const&) = delete;
247   local_stream_acceptor& operator=(local_stream_acceptor const&) = delete; 247   local_stream_acceptor& operator=(local_stream_acceptor const&) = delete;
248   248  
249   /** Create the acceptor socket. 249   /** Create the acceptor socket.
250   250  
251   @param proto The protocol. Defaults to local_stream{}. 251   @param proto The protocol. Defaults to local_stream{}.
252   252  
253   @throws std::system_error on failure. 253   @throws std::system_error on failure.
254   */ 254   */
255   void open(local_stream proto = {}); 255   void open(local_stream proto = {});
256   256  
257   /** Bind to a local endpoint. 257   /** Bind to a local endpoint.
258   258  
259   @param ep The local endpoint (path) to bind to. 259   @param ep The local endpoint (path) to bind to.
260   @param opt Bind options. Pass bind_option::unlink_existing 260   @param opt Bind options. Pass bind_option::unlink_existing
261   to unlink the socket path before binding (ignored for 261   to unlink the socket path before binding (ignored for
262   abstract sockets and empty endpoints). 262   abstract sockets and empty endpoints).
263   263  
264   @return An error code on failure, empty on success. 264   @return An error code on failure, empty on success.
265   265  
266   @throws std::logic_error if the acceptor is not open. 266   @throws std::logic_error if the acceptor is not open.
267   */ 267   */
268   [[nodiscard]] std::error_code 268   [[nodiscard]] std::error_code
269   bind(corosio::local_endpoint ep, 269   bind(corosio::local_endpoint ep,
270   bind_option opt = bind_option::none); 270   bind_option opt = bind_option::none);
271   271  
272   /** Start listening for incoming connections. 272   /** Start listening for incoming connections.
273   273  
274   @param backlog The maximum pending connection queue length. 274   @param backlog The maximum pending connection queue length.
275   275  
276   @return An error code on failure, empty on success. 276   @return An error code on failure, empty on success.
277   277  
278   @throws std::logic_error if the acceptor is not open. 278   @throws std::logic_error if the acceptor is not open.
279   */ 279   */
280   [[nodiscard]] std::error_code listen(int backlog = 128); 280   [[nodiscard]] std::error_code listen(int backlog = 128);
281   281  
282   /** Close the acceptor. 282   /** Close the acceptor.
283   283  
284   Cancels any pending accept operations and releases the 284   Cancels any pending accept operations and releases the
285   underlying socket. Has no effect if the acceptor is not 285   underlying socket. Has no effect if the acceptor is not
286   open. 286   open.
287   287  
288   @post is_open() == false 288   @post is_open() == false
289   */ 289   */
290   void close(); 290   void close();
291   291  
292   /// Check if the acceptor has an open socket handle. 292   /// Check if the acceptor has an open socket handle.
HITCBC 293   277 bool is_open() const noexcept 293   365 bool is_open() const noexcept
294   { 294   {
HITCBC 295   277 return h_ && get().is_open(); 295   365 return h_ && get().is_open();
296   } 296   }
297   297  
298   /** Initiate an asynchronous accept into an existing socket. 298   /** Initiate an asynchronous accept into an existing socket.
299   299  
300   Completes when a new connection is available. On success 300   Completes when a new connection is available. On success
301   @p peer is reset to the accepted connection. Only one 301   @p peer is reset to the accepted connection. Only one
302   accept may be in flight at a time. 302   accept may be in flight at a time.
303   303  
304   @param peer The socket to receive the accepted connection. 304   @param peer The socket to receive the accepted connection.
305   305  
306   @par Cancellation 306   @par Cancellation
307   Supports cancellation via stop_token or cancel(). 307   Supports cancellation via stop_token or cancel().
308   On cancellation, yields `capy::cond::canceled` and 308   On cancellation, yields `capy::cond::canceled` and
309   @p peer is not modified. 309   @p peer is not modified.
310   310  
311   @return An awaitable that completes with io_result<>. 311   @return An awaitable that completes with io_result<>.
312   312  
313   @throws std::logic_error if the acceptor is not open. 313   @throws std::logic_error if the acceptor is not open.
314   */ 314   */
HITCBC 315   23 auto accept(local_stream_socket& peer) 315   29 auto accept(local_stream_socket& peer)
316   { 316   {
HITCBC 317   23 if (!is_open()) 317   29 if (!is_open())
HITCBC 318   2 detail::throw_logic_error("accept: acceptor not listening"); 318   2 detail::throw_logic_error("accept: acceptor not listening");
HITCBC 319   21 return accept_awaitable(*this, peer); 319   27 return accept_awaitable(*this, peer);
320   } 320   }
321   321  
322   /** Wait for an incoming connection or readiness condition. 322   /** Wait for an incoming connection or readiness condition.
323   323  
324   Suspends until the listen socket is ready in the 324   Suspends until the listen socket is ready in the
325   requested direction. For `wait_type::read`, completion 325   requested direction. For `wait_type::read`, completion
326   signals that a subsequent @ref accept will succeed 326   signals that a subsequent @ref accept will succeed
327 - without blocking. No connection is consumed. 327 + without blocking; a connection already queued when the
  328 + wait begins completes it immediately. No connection is
  329 + consumed.
  330 +
  331 + @note `wait_type::write` is not usable on an acceptor:
  332 + writability carries no meaning for a listening socket, so
  333 + the wait fails with `errc::operation_not_supported` on
  334 + every backend.
328   335  
329   @param w The wait direction. 336   @param w The wait direction.
330   337  
331   @return An awaitable that completes with `io_result<>`. 338   @return An awaitable that completes with `io_result<>`.
332   339  
333   @par Preconditions 340   @par Preconditions
334   The acceptor must be listening. 341   The acceptor must be listening.
335   */ 342   */
HITCBC 336   4 [[nodiscard]] auto wait(wait_type w) 343   4 [[nodiscard]] auto wait(wait_type w)
337   { 344   {
HITCBC 338   4 if (!is_open()) 345   4 if (!is_open())
MISUBC 339   detail::throw_logic_error("wait: acceptor not listening"); 346   detail::throw_logic_error("wait: acceptor not listening");
HITCBC 340   4 return wait_awaitable(*this, w); 347   4 return wait_awaitable(*this, w);
341   } 348   }
342   349  
343   /** Initiate an asynchronous accept, returning the socket. 350   /** Initiate an asynchronous accept, returning the socket.
344   351  
345   Completes when a new connection is available. Only one 352   Completes when a new connection is available. Only one
346   accept may be in flight at a time. 353   accept may be in flight at a time.
347   354  
348   @par Cancellation 355   @par Cancellation
349   Supports cancellation via stop_token or cancel(). 356   Supports cancellation via stop_token or cancel().
350   On cancellation, yields `capy::cond::canceled` with 357   On cancellation, yields `capy::cond::canceled` with
351   a default-constructed socket. 358   a default-constructed socket.
352   359  
353   @return An awaitable that completes with 360   @return An awaitable that completes with
354   io_result<local_stream_socket>. 361   io_result<local_stream_socket>.
355   362  
356   @throws std::logic_error if the acceptor is not open. 363   @throws std::logic_error if the acceptor is not open.
357   */ 364   */
HITCBC 358   4 auto accept() 365   4 auto accept()
359   { 366   {
HITCBC 360   4 if (!is_open()) 367   4 if (!is_open())
HITCBC 361   2 detail::throw_logic_error("accept: acceptor not listening"); 368   2 detail::throw_logic_error("accept: acceptor not listening");
HITCBC 362   2 return move_accept_awaitable(*this); 369   2 return move_accept_awaitable(*this);
363   } 370   }
364   371  
365   /** Cancel pending asynchronous accept operations. 372   /** Cancel pending asynchronous accept operations.
366   373  
367   Outstanding accept operations complete with 374   Outstanding accept operations complete with
368   @c capy::cond::canceled. Safe to call when no 375   @c capy::cond::canceled. Safe to call when no
369   operations are pending (no-op). 376   operations are pending (no-op).
370   */ 377   */
371   void cancel(); 378   void cancel();
372   379  
373   /** Release ownership of the native socket handle. 380   /** Release ownership of the native socket handle.
374   381  
375   Deregisters the acceptor from the reactor and cancels 382   Deregisters the acceptor from the reactor and cancels
376   pending operations without closing the descriptor. The 383   pending operations without closing the descriptor. The
377   caller takes ownership of the returned handle. 384   caller takes ownership of the returned handle.
378   385  
379   @return The native handle. 386   @return The native handle.
380   387  
381   @throws std::logic_error if the acceptor is not open. 388   @throws std::logic_error if the acceptor is not open.
382   389  
383   @post is_open() == false 390   @post is_open() == false
384   */ 391   */
385   native_handle_type release(); 392   native_handle_type release();
386   393  
  394 + /** Get the native socket handle.
  395 +
  396 + @return The native socket handle, or -1/INVALID_SOCKET if not
  397 + open.
  398 +
  399 + @par Preconditions
  400 + None. May be called on closed acceptors.
  401 + */
  402 + native_handle_type native_handle() const noexcept;
  403 +
  404 + /** Assign an existing native socket to this acceptor.
  405 +
  406 + Adopts a listening socket created outside the library —
  407 + received from a service manager, inherited, or made natively —
  408 + and registers it with the backend. The socket must be a
  409 + listening stream socket in the local IPC family. Adoption
  410 + never alters the descriptor's flags or options: on POSIX the
  411 + fd must already be non-blocking, and on Windows the socket
  412 + must be overlapped-capable.
  413 +
  414 + Adoption does not verify listen state; @ref accept reports the
  415 + error if the socket is not listening.
  416 +
  417 + If this object is already open, pending operations complete
  418 + with `errc::operation_canceled` and the held socket is closed
  419 + before the new one is adopted.
  420 +
  421 + @par Exception Safety
  422 + Strong guarantee on validation failure: the object is
  423 + unchanged. If backend registration fails, the object either
  424 + retains its previous socket or is left closed, depending on
  425 + the backend. In all failure cases the caller retains
  426 + ownership of `fd`.
  427 +
  428 + @param fd The native socket to adopt. On success the object
  429 + owns it and will close it.
  430 +
  431 + @throws std::system_error On validation or registration
  432 + failure.
  433 + */
  434 + void assign(native_handle_type fd);
  435 +
387   /** Return the local endpoint the acceptor is bound to. 436   /** Return the local endpoint the acceptor is bound to.
388   437  
389   Returns a default-constructed (empty) endpoint if the 438   Returns a default-constructed (empty) endpoint if the
390   acceptor is not open or not yet bound. Safe to call in 439   acceptor is not open or not yet bound. Safe to call in
391   any state. 440   any state.
392   */ 441   */
393   corosio::local_endpoint local_endpoint() const noexcept; 442   corosio::local_endpoint local_endpoint() const noexcept;
394   443  
395   /** Set a socket option on the acceptor. 444   /** Set a socket option on the acceptor.
396   445  
397   Applies a type-safe socket option to the underlying socket. 446   Applies a type-safe socket option to the underlying socket.
398   The option type encodes the protocol level and option name. 447   The option type encodes the protocol level and option name.
399   448  
400   @param opt The option to set. 449   @param opt The option to set.
401   450  
402   @tparam Option A socket option type providing static 451   @tparam Option A socket option type providing static
403   `level()` and `name()` members, and `data()` / `size()` 452   `level()` and `name()` members, and `data()` / `size()`
404   accessors. 453   accessors.
405   454  
406   @throws std::logic_error if the acceptor is not open. 455   @throws std::logic_error if the acceptor is not open.
407   @throws std::system_error on failure. 456   @throws std::system_error on failure.
408   */ 457   */
409   template<class Option> 458   template<class Option>
HITCBC 410   4 void set_option(Option const& opt) 459   4 void set_option(Option const& opt)
411   { 460   {
HITCBC 412   4 if (!is_open()) 461   4 if (!is_open())
HITCBC 413   2 detail::throw_logic_error("set_option: acceptor not open"); 462   2 detail::throw_logic_error("set_option: acceptor not open");
HITCBC 414   2 std::error_code ec = get().set_option( 463   2 std::error_code ec = get().set_option(
415   Option::level(), Option::name(), opt.data(), opt.size()); 464   Option::level(), Option::name(), opt.data(), opt.size());
HITCBC 416   2 if (ec) 465   2 if (ec)
MISUBC 417   detail::throw_system_error(ec, "local_stream_acceptor::set_option"); 466   detail::throw_system_error(ec, "local_stream_acceptor::set_option");
HITCBC 418   2 } 467   2 }
419   468  
420   /** Get a socket option from the acceptor. 469   /** Get a socket option from the acceptor.
421   470  
422   Retrieves the current value of a type-safe socket option. 471   Retrieves the current value of a type-safe socket option.
423   472  
424   @return The current option value. 473   @return The current option value.
425   474  
426   @tparam Option A socket option type providing static 475   @tparam Option A socket option type providing static
427   `level()` and `name()` members, and `data()` / `size()` 476   `level()` and `name()` members, and `data()` / `size()`
428   / `resize()` members. 477   / `resize()` members.
429   478  
430   @throws std::logic_error if the acceptor is not open. 479   @throws std::logic_error if the acceptor is not open.
431   @throws std::system_error on failure. 480   @throws std::system_error on failure.
432   */ 481   */
433   template<class Option> 482   template<class Option>
HITCBC 434   4 Option get_option() const 483   4 Option get_option() const
435   { 484   {
HITCBC 436   4 if (!is_open()) 485   4 if (!is_open())
HITCBC 437   2 detail::throw_logic_error("get_option: acceptor not open"); 486   2 detail::throw_logic_error("get_option: acceptor not open");
HITCBC 438   2 Option opt{}; 487   2 Option opt{};
HITCBC 439   2 std::size_t sz = opt.size(); 488   2 std::size_t sz = opt.size();
440   std::error_code ec = 489   std::error_code ec =
HITCBC 441   2 get().get_option(Option::level(), Option::name(), opt.data(), &sz); 490   2 get().get_option(Option::level(), Option::name(), opt.data(), &sz);
HITCBC 442   2 if (ec) 491   2 if (ec)
MISUBC 443   detail::throw_system_error(ec, "local_stream_acceptor::get_option"); 492   detail::throw_system_error(ec, "local_stream_acceptor::get_option");
HITCBC 444   2 opt.resize(sz); 493   2 opt.resize(sz);
HITCBC 445   2 return opt; 494   2 return opt;
446   } 495   }
447   496  
448   /** Backend hooks for local stream acceptor operations. 497   /** Backend hooks for local stream acceptor operations.
449   498  
450   Platform backends derive from this to implement 499   Platform backends derive from this to implement
451   accept, option, and lifecycle management. 500   accept, option, and lifecycle management.
452   */ 501   */
453   struct implementation : io_object::implementation 502   struct implementation : io_object::implementation
454   { 503   {
455   /** Initiate an asynchronous accept. 504   /** Initiate an asynchronous accept.
456   505  
457   On completion the backend sets @p *ec and, on 506   On completion the backend sets @p *ec and, on
458   success, stores a pointer to the new socket 507   success, stores a pointer to the new socket
459   implementation in @p *impl_out. 508   implementation in @p *impl_out.
460   509  
461   @param h Coroutine handle to resume. 510   @param h Coroutine handle to resume.
462   @param ex Executor for dispatching the completion. 511   @param ex Executor for dispatching the completion.
463   @param token Stop token for cancellation. 512   @param token Stop token for cancellation.
464   @param ec Output error code. 513   @param ec Output error code.
465   @param impl_out Output pointer for the accepted socket. 514   @param impl_out Output pointer for the accepted socket.
466   @return Coroutine handle to resume immediately. 515   @return Coroutine handle to resume immediately.
467   */ 516   */
468   virtual std::coroutine_handle<> accept( 517   virtual std::coroutine_handle<> accept(
469   std::coroutine_handle<>, 518   std::coroutine_handle<>,
470   capy::executor_ref, 519   capy::executor_ref,
471   std::stop_token, 520   std::stop_token,
472   std::error_code*, 521   std::error_code*,
473   io_object::implementation**) = 0; 522   io_object::implementation**) = 0;
474   523  
475   /** Initiate an asynchronous wait for acceptor readiness. 524   /** Initiate an asynchronous wait for acceptor readiness.
476   525  
477   Completes when the listen socket becomes ready for 526   Completes when the listen socket becomes ready for
478   the specified direction. No connection is consumed. 527   the specified direction. No connection is consumed.
479   */ 528   */
480   virtual std::coroutine_handle<> wait( 529   virtual std::coroutine_handle<> wait(
481   std::coroutine_handle<> h, 530   std::coroutine_handle<> h,
482   capy::executor_ref ex, 531   capy::executor_ref ex,
483   wait_type w, 532   wait_type w,
484   std::stop_token token, 533   std::stop_token token,
485   std::error_code* ec) = 0; 534   std::error_code* ec) = 0;
486   535  
487   /// Return the cached local endpoint. 536   /// Return the cached local endpoint.
488   virtual corosio::local_endpoint local_endpoint() const noexcept = 0; 537   virtual corosio::local_endpoint local_endpoint() const noexcept = 0;
489   538  
490   /// Return whether the underlying socket is open. 539   /// Return whether the underlying socket is open.
491   virtual bool is_open() const noexcept = 0; 540   virtual bool is_open() const noexcept = 0;
  541 +
  542 + /// Return the native handle, or the platform sentinel if closed.
  543 + virtual native_handle_type native_handle() const noexcept = 0;
492   544  
493   /// Release and return the native handle without closing. 545   /// Release and return the native handle without closing.
494   virtual native_handle_type release_socket() noexcept = 0; 546   virtual native_handle_type release_socket() noexcept = 0;
495   547  
496   /// Cancel pending accept operations. 548   /// Cancel pending accept operations.
497   virtual void cancel() noexcept = 0; 549   virtual void cancel() noexcept = 0;
498   550  
499   /// Set a raw socket option. 551   /// Set a raw socket option.
500   virtual std::error_code set_option( 552   virtual std::error_code set_option(
501   int level, 553   int level,
502   int optname, 554   int optname,
503   void const* data, 555   void const* data,
504   std::size_t size) noexcept = 0; 556   std::size_t size) noexcept = 0;
505   557  
506   /// Get a raw socket option. 558   /// Get a raw socket option.
507   virtual std::error_code 559   virtual std::error_code
508   get_option(int level, int optname, void* data, std::size_t* size) 560   get_option(int level, int optname, void* data, std::size_t* size)
509   const noexcept = 0; 561   const noexcept = 0;
510   }; 562   };
511   563  
512   protected: 564   protected:
HITCBC 513   12 local_stream_acceptor(handle h, capy::execution_context& ctx) noexcept 565   12 local_stream_acceptor(handle h, capy::execution_context& ctx) noexcept
HITCBC 514   12 : io_object(std::move(h)) 566   12 : io_object(std::move(h))
HITCBC 515   12 , ctx_(ctx) 567   12 , ctx_(ctx)
516   { 568   {
HITCBC 517   12 } 569   12 }
518   570  
519   local_stream_acceptor( 571   local_stream_acceptor(
520   capy::execution_context& ctx, local_stream_acceptor&& other) noexcept 572   capy::execution_context& ctx, local_stream_acceptor&& other) noexcept
521   : io_object(std::move(other)) 573   : io_object(std::move(other))
522   , ctx_(ctx) 574   , ctx_(ctx)
523   { 575   {
524   } 576   }
525   577  
HITCBC 526   8 static void reset_peer_impl( 578   8 static void reset_peer_impl(
527   local_stream_socket& peer, io_object::implementation* impl) noexcept 579   local_stream_socket& peer, io_object::implementation* impl) noexcept
528   { 580   {
HITCBC 529   8 if (impl) 581   8 if (impl)
HITCBC 530   8 peer.h_.reset(impl); 582   8 peer.h_.reset(impl);
HITCBC 531   8 } 583   8 }
532   584  
533   private: 585   private:
534   capy::execution_context& ctx_; 586   capy::execution_context& ctx_;
535   587  
HITCBC 536   316 inline implementation& get() const noexcept 588   430 inline implementation& get() const noexcept
537   { 589   {
HITCBC 538   316 return *static_cast<implementation*>(h_.get()); 590   430 return *static_cast<implementation*>(h_.get());
539   } 591   }
540   }; 592   };
541   593  
542   } // namespace boost::corosio 594   } // namespace boost::corosio
543   595  
544   #endif // BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP 596   #endif // BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP