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