94.29% Lines (66/70) 100.00% Functions (18/18)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
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_NATIVE_NATIVE_LOCAL_STREAM_ACCEPTOR_HPP 10   #ifndef BOOST_COROSIO_NATIVE_NATIVE_LOCAL_STREAM_ACCEPTOR_HPP
11   #define BOOST_COROSIO_NATIVE_NATIVE_LOCAL_STREAM_ACCEPTOR_HPP 11   #define BOOST_COROSIO_NATIVE_NATIVE_LOCAL_STREAM_ACCEPTOR_HPP
12   12  
13   #include <boost/corosio/local_stream_acceptor.hpp> 13   #include <boost/corosio/local_stream_acceptor.hpp>
14   #include <boost/corosio/native/native_local_stream_socket.hpp> 14   #include <boost/corosio/native/native_local_stream_socket.hpp>
15   #include <boost/corosio/backend.hpp> 15   #include <boost/corosio/backend.hpp>
16   16  
17   #ifndef BOOST_COROSIO_MRDOCS 17   #ifndef BOOST_COROSIO_MRDOCS
18   #if BOOST_COROSIO_HAS_EPOLL 18   #if BOOST_COROSIO_HAS_EPOLL
19   #include <boost/corosio/native/detail/epoll/epoll_types.hpp> 19   #include <boost/corosio/native/detail/epoll/epoll_types.hpp>
20   #endif 20   #endif
21   21  
22   #if BOOST_COROSIO_HAS_SELECT 22   #if BOOST_COROSIO_HAS_SELECT
23   #include <boost/corosio/native/detail/select/select_types.hpp> 23   #include <boost/corosio/native/detail/select/select_types.hpp>
24   #endif 24   #endif
25   25  
26   #if BOOST_COROSIO_HAS_KQUEUE 26   #if BOOST_COROSIO_HAS_KQUEUE
27   #include <boost/corosio/native/detail/kqueue/kqueue_types.hpp> 27   #include <boost/corosio/native/detail/kqueue/kqueue_types.hpp>
28   #endif 28   #endif
29   29  
30   #if BOOST_COROSIO_HAS_IO_URING 30   #if BOOST_COROSIO_HAS_IO_URING
31   #include <boost/corosio/native/detail/io_uring/io_uring_types.hpp> 31   #include <boost/corosio/native/detail/io_uring/io_uring_types.hpp>
32   #endif 32   #endif
33   33  
34   #if BOOST_COROSIO_HAS_IOCP 34   #if BOOST_COROSIO_HAS_IOCP
35   #include <boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp> 35   #include <boost/corosio/native/detail/iocp/win_local_stream_acceptor_service.hpp>
36   #endif 36   #endif
37   #endif // !BOOST_COROSIO_MRDOCS 37   #endif // !BOOST_COROSIO_MRDOCS
38   38  
39   namespace boost::corosio { 39   namespace boost::corosio {
40   40  
41   /** An asynchronous Unix stream acceptor with devirtualized accept. 41   /** An asynchronous Unix stream acceptor with devirtualized accept.
42   42  
43   This class template inherits from @ref local_stream_acceptor 43   This class template inherits from @ref local_stream_acceptor
44   and shadows both `accept` overloads (the peer-reference form 44   and shadows both `accept` overloads (the peer-reference form
45   and the move-return form) with versions that call the backend 45   and the move-return form) with versions that call the backend
46   implementation directly, allowing the compiler to inline 46   implementation directly, allowing the compiler to inline
47   through the entire call chain. The move-return form yields a 47   through the entire call chain. The move-return form yields a
48   @ref native_local_stream_socket so subsequent I/O on the peer 48   @ref native_local_stream_socket so subsequent I/O on the peer
49   is also devirtualized. 49   is also devirtualized.
50   50  
51   Non-async operations (`listen`, `close`, `cancel`) remain 51   Non-async operations (`listen`, `close`, `cancel`) remain
52   unchanged and dispatch through the compiled library. 52   unchanged and dispatch through the compiled library.
53   53  
54   A `native_local_stream_acceptor` IS-A `local_stream_acceptor` 54   A `native_local_stream_acceptor` IS-A `local_stream_acceptor`
55   and can be passed to any function expecting 55   and can be passed to any function expecting
56   `local_stream_acceptor&`. 56   `local_stream_acceptor&`.
57   57  
58   @tparam Backend A backend tag value (e.g., `epoll`). 58   @tparam Backend A backend tag value (e.g., `epoll`).
59   59  
60   @par Thread Safety 60   @par Thread Safety
61   Same as @ref local_stream_acceptor. 61   Same as @ref local_stream_acceptor.
62   62  
63   @see local_stream_acceptor, epoll_t, iocp_t 63   @see local_stream_acceptor, epoll_t, iocp_t
64   */ 64   */
65   template<auto Backend> 65   template<auto Backend>
66   class native_local_stream_acceptor : public local_stream_acceptor 66   class native_local_stream_acceptor : public local_stream_acceptor
67   { 67   {
68   using backend_type = decltype(Backend); 68   using backend_type = decltype(Backend);
69   using impl_type = typename backend_type::local_stream_acceptor_type; 69   using impl_type = typename backend_type::local_stream_acceptor_type;
70   using service_type = 70   using service_type =
71   typename backend_type::local_stream_acceptor_service_type; 71   typename backend_type::local_stream_acceptor_service_type;
72   72  
HITCBC 73   8 impl_type& get_impl() noexcept 73   10 impl_type& get_impl() noexcept
74   { 74   {
HITCBC 75   8 return *static_cast<impl_type*>(h_.get()); 75   10 return *static_cast<impl_type*>(h_.get());
76   } 76   }
77   77  
78   struct native_wait_awaitable 78   struct native_wait_awaitable
79   { 79   {
80   native_local_stream_acceptor& acc_; 80   native_local_stream_acceptor& acc_;
81   wait_type w_; 81   wait_type w_;
82   std::stop_token token_; 82   std::stop_token token_;
83   mutable std::error_code ec_; 83   mutable std::error_code ec_;
84   84  
HITCBC 85   2 native_wait_awaitable( 85   4 native_wait_awaitable(
86   native_local_stream_acceptor& acc, wait_type w) noexcept 86   native_local_stream_acceptor& acc, wait_type w) noexcept
HITCBC 87   2 : acc_(acc) 87   4 : acc_(acc)
HITCBC 88   2 , w_(w) 88   4 , w_(w)
89   { 89   {
HITCBC 90   2 } 90   4 }
91   91  
HITCBC 92   2 bool await_ready() const noexcept 92   4 bool await_ready() const noexcept
93   { 93   {
ECB 94 - 2 return token_.stop_requested(); 94 + // A pre-set ec_ means the initiator failed before
  95 + // dispatch (e.g. a closed object).
HITGNC   96 + 4 return static_cast<bool>(ec_) || token_.stop_requested();
95   } 97   }
96   98  
HITCBC 97   2 [[nodiscard]] capy::io_result<> await_resume() const noexcept 99   4 [[nodiscard]] capy::io_result<> await_resume() const noexcept
98   { 100   {
HITCBC 99   2 if (token_.stop_requested()) 101   4 if (token_.stop_requested())
MISUBC 100   return {make_error_code(std::errc::operation_canceled)}; 102   return {make_error_code(std::errc::operation_canceled)};
HITCBC 101   2 return {ec_}; 103   4 return {ec_};
102   } 104   }
103   105  
HITCBC 104   2 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 106   4 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
105   -> std::coroutine_handle<> 107   -> std::coroutine_handle<>
106   { 108   {
HITCBC 107   2 token_ = env->stop_token; 109   4 token_ = env->stop_token;
HITCBC 108   6 return acc_.get_impl().wait( 110   12 return acc_.get_impl().wait(
HITCBC 109   6 h, env->executor, w_, token_, &ec_); 111   12 h, env->executor, w_, token_, &ec_);
110   } 112   }
111   }; 113   };
112   114  
113   struct native_accept_awaitable 115   struct native_accept_awaitable
114   { 116   {
115   native_local_stream_acceptor& acc_; 117   native_local_stream_acceptor& acc_;
116   local_stream_socket& peer_; 118   local_stream_socket& peer_;
117   std::stop_token token_; 119   std::stop_token token_;
118   mutable std::error_code ec_; 120   mutable std::error_code ec_;
119   mutable io_object::implementation* peer_impl_ = nullptr; 121   mutable io_object::implementation* peer_impl_ = nullptr;
120   122  
HITCBC 121   4 native_accept_awaitable( 123   6 native_accept_awaitable(
122   native_local_stream_acceptor& acc, 124   native_local_stream_acceptor& acc,
123   local_stream_socket& peer) noexcept 125   local_stream_socket& peer) noexcept
HITCBC 124   4 : acc_(acc) 126   6 : acc_(acc)
HITCBC 125   4 , peer_(peer) 127   6 , peer_(peer)
126   { 128   {
HITCBC 127   4 } 129   6 }
128   130  
HITCBC 129   4 bool await_ready() const noexcept 131   6 bool await_ready() const noexcept
130   { 132   {
ECB 131 - 4 return token_.stop_requested(); 133 + // A pre-set ec_ means the initiator failed before
  134 + // dispatch (e.g. a closed object).
HITGNC   135 + 6 return static_cast<bool>(ec_) || token_.stop_requested();
132   } 136   }
133   137  
HITCBC 134   4 [[nodiscard]] capy::io_result<> await_resume() const noexcept 138   6 [[nodiscard]] capy::io_result<> await_resume() const noexcept
135   { 139   {
HITCBC 136   4 if (token_.stop_requested()) 140   6 if (token_.stop_requested())
MISUBC 137   return {make_error_code(std::errc::operation_canceled)}; 141   return {make_error_code(std::errc::operation_canceled)};
HITCBC 138   4 if (!ec_) 142   6 if (!ec_)
HITCBC 139   4 acc_.reset_peer_impl(peer_, peer_impl_); 143   4 acc_.reset_peer_impl(peer_, peer_impl_);
HITCBC 140   4 return {ec_}; 144   6 return {ec_};
141   } 145   }
142   146  
HITCBC 143   4 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 147   4 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
144   -> std::coroutine_handle<> 148   -> std::coroutine_handle<>
145   { 149   {
HITCBC 146   4 token_ = env->stop_token; 150   4 token_ = env->stop_token;
HITCBC 147   12 return acc_.get_impl().accept( 151   12 return acc_.get_impl().accept(
HITCBC 148   12 h, env->executor, token_, &ec_, &peer_impl_); 152   12 h, env->executor, token_, &ec_, &peer_impl_);
149   } 153   }
150   }; 154   };
151   155  
152   struct native_move_accept_awaitable 156   struct native_move_accept_awaitable
153   { 157   {
154   native_local_stream_acceptor& acc_; 158   native_local_stream_acceptor& acc_;
155   std::stop_token token_; 159   std::stop_token token_;
156   mutable std::error_code ec_; 160   mutable std::error_code ec_;
157   mutable io_object::implementation* peer_impl_ = nullptr; 161   mutable io_object::implementation* peer_impl_ = nullptr;
158   162  
HITCBC 159   2 explicit native_move_accept_awaitable( 163   4 explicit native_move_accept_awaitable(
160   native_local_stream_acceptor& acc) noexcept 164   native_local_stream_acceptor& acc) noexcept
HITCBC 161   2 : acc_(acc) 165   4 : acc_(acc)
162   { 166   {
HITCBC 163   2 } 167   4 }
164   168  
HITCBC 165   2 bool await_ready() const noexcept 169   4 bool await_ready() const noexcept
166   { 170   {
ECB 167 - 2 return token_.stop_requested(); 171 + // A pre-set ec_ means the initiator failed before
  172 + // dispatch (e.g. a closed object).
HITGNC   173 + 4 return static_cast<bool>(ec_) || token_.stop_requested();
168   } 174   }
169   175  
170   [[nodiscard]] capy::io_result<native_local_stream_socket<Backend>> 176   [[nodiscard]] capy::io_result<native_local_stream_socket<Backend>>
HITCBC 171   2 await_resume() const noexcept 177   4 await_resume() const noexcept
172   { 178   {
HITCBC 173   2 if (token_.stop_requested()) 179   4 if (token_.stop_requested())
174   return { 180   return {
MISUBC 175   make_error_code(std::errc::operation_canceled), 181   make_error_code(std::errc::operation_canceled),
MISUBC 176   native_local_stream_socket<Backend>(acc_.context())}; 182   native_local_stream_socket<Backend>(acc_.context())};
HITCBC 177   2 if (ec_ || !peer_impl_) 183   4 if (ec_ || !peer_impl_)
178   return { 184   return {
HITGBC 179   ec_, 185   2 ec_,
HITGBC 180   native_local_stream_socket<Backend>(acc_.context())}; 186   2 native_local_stream_socket<Backend>(acc_.context())};
181   187  
HITCBC 182   2 native_local_stream_socket<Backend> peer(acc_.context()); 188   2 native_local_stream_socket<Backend> peer(acc_.context());
HITCBC 183   2 acc_.reset_peer_impl(peer, peer_impl_); 189   2 acc_.reset_peer_impl(peer, peer_impl_);
HITCBC 184   2 return {ec_, std::move(peer)}; 190   2 return {ec_, std::move(peer)};
HITCBC 185   2 } 191   2 }
186   192  
HITCBC 187   2 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env) 193   2 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
188   -> std::coroutine_handle<> 194   -> std::coroutine_handle<>
189   { 195   {
HITCBC 190   2 token_ = env->stop_token; 196   2 token_ = env->stop_token;
HITCBC 191   6 return acc_.get_impl().accept( 197   6 return acc_.get_impl().accept(
HITCBC 192   6 h, env->executor, token_, &ec_, &peer_impl_); 198   6 h, env->executor, token_, &ec_, &peer_impl_);
193   } 199   }
194   }; 200   };
195   201  
196   public: 202   public:
197   /** Construct a native acceptor from an execution context. 203   /** Construct a native acceptor from an execution context.
198   204  
199   @param ctx The execution context that will own this acceptor. 205   @param ctx The execution context that will own this acceptor.
200   */ 206   */
HITCBC 201   12 explicit native_local_stream_acceptor(capy::execution_context& ctx) 207   16 explicit native_local_stream_acceptor(capy::execution_context& ctx)
HITCBC 202   12 : local_stream_acceptor(create_handle<service_type>(ctx), ctx) 208   16 : local_stream_acceptor(create_handle<service_type>(ctx), ctx)
203   { 209   {
HITCBC 204   12 } 210   16 }
205   211  
206   /** Construct a native acceptor from an executor. 212   /** Construct a native acceptor from an executor.
207   213  
208   @param ex The executor whose context will own the acceptor. 214   @param ex The executor whose context will own the acceptor.
209   */ 215   */
210   template<class Ex> 216   template<class Ex>
211   requires(!std::same_as< 217   requires(!std::same_as<
212   std::remove_cvref_t<Ex>, 218   std::remove_cvref_t<Ex>,
213   native_local_stream_acceptor>) && 219   native_local_stream_acceptor>) &&
214   capy::Executor<Ex> 220   capy::Executor<Ex>
215   explicit native_local_stream_acceptor(Ex const& ex) 221   explicit native_local_stream_acceptor(Ex const& ex)
216   : native_local_stream_acceptor(ex.context()) 222   : native_local_stream_acceptor(ex.context())
217   { 223   {
218   } 224   }
219   225  
220   /// Move construct. 226   /// Move construct.
HITGIC 221   native_local_stream_acceptor(native_local_stream_acceptor&&) noexcept = 227   2 native_local_stream_acceptor(native_local_stream_acceptor&&) noexcept =
222   default; 228   default;
223   229  
224   /// Move assign. 230   /// Move assign.
225   native_local_stream_acceptor& 231   native_local_stream_acceptor&
226   operator=(native_local_stream_acceptor&&) noexcept = default; 232   operator=(native_local_stream_acceptor&&) noexcept = default;
227   233  
228   native_local_stream_acceptor(native_local_stream_acceptor const&) = delete; 234   native_local_stream_acceptor(native_local_stream_acceptor const&) = delete;
229   native_local_stream_acceptor& 235   native_local_stream_acceptor&
230   operator=(native_local_stream_acceptor const&) = delete; 236   operator=(native_local_stream_acceptor const&) = delete;
231   237  
232   /** Asynchronously accept an incoming connection. 238   /** Asynchronously accept an incoming connection.
233   239  
234   Calls the backend implementation directly, bypassing virtual 240   Calls the backend implementation directly, bypassing virtual
235   dispatch. Otherwise identical to @ref local_stream_acceptor::accept. 241   dispatch. Otherwise identical to @ref local_stream_acceptor::accept.
236   242  
237   @param peer The socket to receive the accepted connection. 243   @param peer The socket to receive the accepted connection.
238   244  
239   @return An awaitable yielding `io_result<>`. 245   @return An awaitable yielding `io_result<>`.
240   246  
241 - @throws std::logic_error if the acceptor is not listening. 247 + A closed acceptor reports `errc::bad_file_descriptor`.
242   248  
243   Both this acceptor and @p peer must outlive the returned 249   Both this acceptor and @p peer must outlive the returned
244   awaitable. 250   awaitable.
245   */ 251   */
HITCBC 246 - 6 auto accept(local_stream_socket& peer) 252 + 6 [[nodiscard]] auto accept(local_stream_socket& peer)
247   { 253   {
HITGNC   254 + 6 native_accept_awaitable aw(*this, peer);
HITCBC 248   6 if (!is_open()) 255   6 if (!is_open())
HITCBC 249 - 2 detail::throw_logic_error("accept: acceptor not listening"); 256 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 250 - 4 return native_accept_awaitable(*this, peer); 257 + 6 return aw;
251   } 258   }
252   259  
253   /** Asynchronously accept an incoming connection, returning the peer. 260   /** Asynchronously accept an incoming connection, returning the peer.
254   261  
255   Calls the backend implementation directly, bypassing virtual 262   Calls the backend implementation directly, bypassing virtual
256   dispatch. The accepted peer is returned as a 263   dispatch. The accepted peer is returned as a
257   @ref native_local_stream_socket so that subsequent I/O on it 264   @ref native_local_stream_socket so that subsequent I/O on it
258   is also devirtualized. 265   is also devirtualized.
259   266  
260   @return An awaitable yielding 267   @return An awaitable yielding
261   `io_result<native_local_stream_socket<Backend>>`. 268   `io_result<native_local_stream_socket<Backend>>`.
262   269  
263 - @throws std::logic_error if the acceptor is not listening. 270 + A closed acceptor reports `errc::bad_file_descriptor`.
  271 +
  272 + @throws std::logic_error If the acceptor has been moved from.
264   273  
265   This acceptor must outlive the returned awaitable. 274   This acceptor must outlive the returned awaitable.
266   */ 275   */
HITCBC 267 - 4 auto accept() 276 + 6 [[nodiscard]] auto accept()
268   { 277   {
  278 + // The awaitable builds the peer from context(), which a
  279 + // moved-from acceptor no longer has.
HITGNC   280 + 6 if (!h_)
HITGNC   281 + 2 detail::throw_logic_error("accept: acceptor moved-from");
HITGNC   282 + 4 native_move_accept_awaitable aw(*this);
HITCBC 269   4 if (!is_open()) 283   4 if (!is_open())
HITCBC 270 - 2 detail::throw_logic_error("accept: acceptor not listening"); 284 + 2 aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
HITCBC 271 - 2 return native_move_accept_awaitable(*this); 285 + 4 return aw;
272   } 286   }
273   287  
274   /** Asynchronously wait for the acceptor to be ready. 288   /** Asynchronously wait for the acceptor to be ready.
275   289  
276   Calls the backend implementation directly, bypassing virtual 290   Calls the backend implementation directly, bypassing virtual
277   dispatch. Otherwise identical to @ref local_stream_acceptor::wait. 291   dispatch. Otherwise identical to @ref local_stream_acceptor::wait.
278   292  
279   @param w The wait direction (typically `wait_type::read`). 293   @param w The wait direction (typically `wait_type::read`).
280   294  
281   @return An awaitable yielding `io_result<>`. 295   @return An awaitable yielding `io_result<>`.
282   */ 296   */
HITCBC 283   2 [[nodiscard]] auto wait(wait_type w) 297   4 [[nodiscard]] auto wait(wait_type w)
284   { 298   {
HITCBC 285   2 return native_wait_awaitable(*this, w); 299   4 return native_wait_awaitable(*this, w);
286   } 300   }
287   }; 301   };
288   302  
289   } // namespace boost::corosio 303   } // namespace boost::corosio
290   304  
291   #endif // BOOST_COROSIO_NATIVE_NATIVE_LOCAL_STREAM_ACCEPTOR_HPP 305   #endif // BOOST_COROSIO_NATIVE_NATIVE_LOCAL_STREAM_ACCEPTOR_HPP