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