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