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