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