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