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