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