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