TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
12 : #define BOOST_COROSIO_TCP_ACCEPTOR_HPP
13 :
14 : #include <boost/corosio/detail/config.hpp>
15 : #include <boost/corosio/detail/except.hpp>
16 : #include <boost/corosio/io/io_object.hpp>
17 : #include <boost/capy/io_result.hpp>
18 : #include <boost/corosio/endpoint.hpp>
19 : #include <boost/corosio/tcp_socket.hpp>
20 : #include <boost/capy/ex/executor_ref.hpp>
21 : #include <boost/capy/ex/execution_context.hpp>
22 : #include <boost/capy/ex/io_env.hpp>
23 : #include <boost/capy/concept/executor.hpp>
24 :
25 : #include <system_error>
26 :
27 : #include <concepts>
28 : #include <coroutine>
29 : #include <cstddef>
30 : #include <memory>
31 : #include <stop_token>
32 : #include <type_traits>
33 :
34 : namespace boost::corosio {
35 :
36 : /** An asynchronous TCP acceptor for coroutine I/O.
37 :
38 : This class provides asynchronous TCP accept operations that return
39 : awaitable types. The acceptor binds to a local endpoint and listens
40 : for incoming connections.
41 :
42 : Each accept operation participates in the affine awaitable protocol,
43 : ensuring coroutines resume on the correct executor.
44 :
45 : @par Thread Safety
46 : Distinct objects: Safe.@n
47 : Shared objects: Unsafe. An acceptor must not have concurrent accept
48 : operations.
49 :
50 : @par Semantics
51 : Wraps the platform TCP listener. Operations dispatch to
52 : OS accept APIs via the io_context reactor.
53 :
54 : @par Example
55 : @code
56 : io_context ioc;
57 : tcp_acceptor acc(ioc);
58 : if (auto ec = acc.listen(endpoint(8080))) // Bind to port 8080
59 : return ec;
60 :
61 : tcp_socket peer(ioc);
62 : auto [ec] = co_await acc.accept(peer);
63 : if (!ec) {
64 : // peer is now a connected socket
65 : auto [ec2, n] = co_await peer.read_some(buf);
66 : }
67 : @endcode
68 : */
69 : class BOOST_COROSIO_DECL tcp_acceptor : public io_object
70 : {
71 : struct accept_awaitable
72 : {
73 : tcp_acceptor& acc_;
74 : tcp_socket& peer_;
75 : std::stop_token token_;
76 : mutable std::error_code ec_;
77 : mutable io_object::implementation* peer_impl_ = nullptr;
78 :
79 HIT 7882 : accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
80 7882 : : acc_(acc)
81 7882 : , peer_(peer)
82 : {
83 7882 : }
84 :
85 7882 : bool await_ready() const noexcept
86 : {
87 7882 : return token_.stop_requested();
88 : }
89 :
90 7882 : capy::io_result<> await_resume() const noexcept
91 : {
92 7882 : if (token_.stop_requested())
93 6 : return {make_error_code(std::errc::operation_canceled)};
94 :
95 7876 : if (!ec_ && peer_impl_)
96 7870 : peer_.h_.reset(peer_impl_);
97 7876 : return {ec_};
98 : }
99 :
100 7882 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
101 : -> std::coroutine_handle<>
102 : {
103 7882 : token_ = env->stop_token;
104 23646 : return acc_.get().accept(
105 23646 : h, env->executor, token_, &ec_, &peer_impl_);
106 : }
107 : };
108 :
109 : public:
110 : /** Destructor.
111 :
112 : Closes the acceptor if open, cancelling any pending operations.
113 : */
114 : ~tcp_acceptor() override;
115 :
116 : /** Construct an acceptor from an execution context.
117 :
118 : @param ctx The execution context that will own this acceptor.
119 : */
120 : explicit tcp_acceptor(capy::execution_context& ctx);
121 :
122 : /** Construct an acceptor from an executor.
123 :
124 : The acceptor is associated with the executor's context.
125 :
126 : @param ex The executor whose context will own the acceptor.
127 : */
128 : template<class Ex>
129 : requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
130 : capy::Executor<Ex>
131 : explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
132 : {
133 : }
134 :
135 : /** Move constructor.
136 :
137 : Transfers ownership of the acceptor resources.
138 :
139 : @param other The acceptor to move from.
140 : */
141 2 : tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
142 :
143 : /** Move assignment operator.
144 :
145 : Closes any existing acceptor and transfers ownership.
146 : @param other The acceptor to move from.
147 :
148 : @return Reference to this acceptor.
149 : */
150 2 : tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
151 : {
152 2 : if (this != &other)
153 : {
154 2 : close();
155 2 : h_ = std::move(other.h_);
156 : }
157 2 : return *this;
158 : }
159 :
160 : tcp_acceptor(tcp_acceptor const&) = delete;
161 : tcp_acceptor& operator=(tcp_acceptor const&) = delete;
162 :
163 : /** Open, bind, and listen on an endpoint.
164 :
165 : Creates an IPv4 TCP socket, binds it to the specified endpoint,
166 : and begins listening for incoming connections. This must be
167 : called before initiating accept operations.
168 :
169 : @param ep The local endpoint to bind to. Use `endpoint(port)` to
170 : bind to all interfaces on a specific port.
171 :
172 : @param backlog The maximum length of the queue of pending
173 : connections. Defaults to 128.
174 :
175 : @return An error code indicating success or the reason for failure.
176 : A default-constructed error code indicates success.
177 :
178 : @par Error Conditions
179 : @li `errc::address_in_use`: The endpoint is already in use.
180 : @li `errc::address_not_available`: The address is not available
181 : on any local interface.
182 : @li `errc::permission_denied`: Insufficient privileges to bind
183 : to the endpoint (e.g., privileged port).
184 : @li `errc::operation_not_supported`: The acceptor service is
185 : unavailable in the context (POSIX only).
186 :
187 : @throws Nothing.
188 : */
189 : [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
190 :
191 : /** Close the acceptor.
192 :
193 : Releases acceptor resources. Any pending operations complete
194 : with `errc::operation_canceled`.
195 : */
196 : void close();
197 :
198 : /** Check if the acceptor is listening.
199 :
200 : @return `true` if the acceptor is open and listening.
201 : */
202 8327 : bool is_open() const noexcept
203 : {
204 8327 : return h_ && get().is_open();
205 : }
206 :
207 : /** Initiate an asynchronous accept operation.
208 :
209 : Accepts an incoming connection and initializes the provided
210 : socket with the new connection. The acceptor must be listening
211 : before calling this function.
212 :
213 : The operation supports cancellation via `std::stop_token` through
214 : the affine awaitable protocol. If the associated stop token is
215 : triggered, the operation completes immediately with
216 : `errc::operation_canceled`.
217 :
218 : @param peer The socket to receive the accepted connection. Any
219 : existing connection on this socket will be closed.
220 :
221 : @return An awaitable that completes with `io_result<>`.
222 : Returns success on successful accept, or an error code on
223 : failure including:
224 : - operation_canceled: Cancelled via stop_token or cancel().
225 : Check `ec == cond::canceled` for portable comparison.
226 :
227 : @par Preconditions
228 : The acceptor must be listening (`is_open() == true`).
229 : The peer socket must be associated with the same execution context.
230 :
231 : @par Example
232 : @code
233 : tcp_socket peer(ioc);
234 : auto [ec] = co_await acc.accept(peer);
235 : if (!ec) {
236 : // Use peer socket
237 : }
238 : @endcode
239 : */
240 7882 : auto accept(tcp_socket& peer)
241 : {
242 7882 : if (!is_open())
243 MIS 0 : detail::throw_logic_error("accept: acceptor not listening");
244 HIT 7882 : return accept_awaitable(*this, peer);
245 : }
246 :
247 : /** Cancel any pending asynchronous operations.
248 :
249 : All outstanding operations complete with `errc::operation_canceled`.
250 : Check `ec == cond::canceled` for portable comparison.
251 : */
252 : void cancel();
253 :
254 : /** Get the local endpoint of the acceptor.
255 :
256 : Returns the local address and port to which the acceptor is bound.
257 : This is useful when binding to port 0 (ephemeral port) to discover
258 : the OS-assigned port number. The endpoint is cached when listen()
259 : is called.
260 :
261 : @return The local endpoint, or a default endpoint (0.0.0.0:0) if
262 : the acceptor is not listening.
263 :
264 : @par Thread Safety
265 : The cached endpoint value is set during listen() and cleared
266 : during close(). This function may be called concurrently with
267 : accept operations, but must not be called concurrently with
268 : listen() or close().
269 : */
270 : endpoint local_endpoint() const noexcept;
271 :
272 : struct implementation : io_object::implementation
273 : {
274 : virtual std::coroutine_handle<> accept(
275 : std::coroutine_handle<>,
276 : capy::executor_ref,
277 : std::stop_token,
278 : std::error_code*,
279 : io_object::implementation**) = 0;
280 :
281 : /// Returns the cached local endpoint.
282 : virtual endpoint local_endpoint() const noexcept = 0;
283 :
284 : /// Return true if the acceptor has a kernel resource open.
285 : virtual bool is_open() const noexcept = 0;
286 :
287 : /** Cancel any pending asynchronous operations.
288 :
289 : All outstanding operations complete with operation_canceled error.
290 : */
291 : virtual void cancel() noexcept = 0;
292 : };
293 :
294 : protected:
295 4 : explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
296 :
297 : /// Transfer accepted peer impl to the peer socket.
298 : static void
299 4 : reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
300 : {
301 4 : if (impl)
302 4 : peer.h_.reset(impl);
303 4 : }
304 :
305 : private:
306 16293 : inline implementation& get() const noexcept
307 : {
308 16293 : return *static_cast<implementation*>(h_.get());
309 : }
310 : };
311 :
312 : } // namespace boost::corosio
313 :
314 : #endif
|