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