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