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 7262 : accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
80 7262 : : acc_(acc)
81 7262 : , peer_(peer)
82 : {
83 7262 : }
84 :
85 7262 : bool await_ready() const noexcept
86 : {
87 7262 : return token_.stop_requested();
88 : }
89 :
90 7262 : capy::io_result<> await_resume() const noexcept
91 : {
92 7262 : if (token_.stop_requested())
93 6 : return {make_error_code(std::errc::operation_canceled)};
94 :
95 7256 : if (!ec_ && peer_impl_)
96 7250 : peer_.h_.reset(peer_impl_);
97 7256 : return {ec_};
98 : }
99 :
100 7262 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
101 : -> std::coroutine_handle<>
102 : {
103 7262 : token_ = env->stop_token;
104 21786 : return acc_.get().accept(
105 21786 : 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 : @pre No awaitables returned by @p other's methods exist.
142 : @pre The execution context associated with @p other must
143 : outlive this acceptor.
144 : */
145 2 : tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
146 :
147 : /** Move assignment operator.
148 :
149 : Closes any existing acceptor and transfers ownership.
150 :
151 : @param other The acceptor to move from.
152 :
153 : @pre No awaitables returned by either `*this` or @p other's
154 : methods exist.
155 : @pre The execution context associated with @p other must
156 : outlive this acceptor.
157 :
158 : @return Reference to this acceptor.
159 : */
160 2 : tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
161 : {
162 2 : if (this != &other)
163 : {
164 2 : close();
165 2 : h_ = std::move(other.h_);
166 : }
167 2 : return *this;
168 : }
169 :
170 : tcp_acceptor(tcp_acceptor const&) = delete;
171 : tcp_acceptor& operator=(tcp_acceptor const&) = delete;
172 :
173 : /** Open, bind, and listen on an endpoint.
174 :
175 : Creates an IPv4 TCP socket, binds it to the specified endpoint,
176 : and begins listening for incoming connections. This must be
177 : called before initiating accept operations.
178 :
179 : @param ep The local endpoint to bind to. Use `endpoint(port)` to
180 : bind to all interfaces on a specific port.
181 :
182 : @param backlog The maximum length of the queue of pending
183 : connections. Defaults to 128.
184 :
185 : @return An error code indicating success or the reason for failure.
186 : A default-constructed error code indicates success.
187 :
188 : @par Error Conditions
189 : @li `errc::address_in_use`: The endpoint is already in use.
190 : @li `errc::address_not_available`: The address is not available
191 : on any local interface.
192 : @li `errc::permission_denied`: Insufficient privileges to bind
193 : to the endpoint (e.g., privileged port).
194 : @li `errc::operation_not_supported`: The acceptor service is
195 : unavailable in the context (POSIX only).
196 :
197 : @throws Nothing.
198 : */
199 : [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
200 :
201 : /** Close the acceptor.
202 :
203 : Releases acceptor resources. Any pending operations complete
204 : with `errc::operation_canceled`.
205 : */
206 : void close();
207 :
208 : /** Check if the acceptor is listening.
209 :
210 : @return `true` if the acceptor is open and listening.
211 : */
212 7707 : bool is_open() const noexcept
213 : {
214 7707 : return h_ && get().is_open();
215 : }
216 :
217 : /** Initiate an asynchronous accept operation.
218 :
219 : Accepts an incoming connection and initializes the provided
220 : socket with the new connection. The acceptor must be listening
221 : before calling this function.
222 :
223 : The operation supports cancellation via `std::stop_token` through
224 : the affine awaitable protocol. If the associated stop token is
225 : triggered, the operation completes immediately with
226 : `errc::operation_canceled`.
227 :
228 : @param peer The socket to receive the accepted connection. Any
229 : existing connection on this socket will be closed.
230 :
231 : @return An awaitable that completes with `io_result<>`.
232 : Returns success on successful accept, or an error code on
233 : failure including:
234 : - operation_canceled: Cancelled via stop_token or cancel().
235 : Check `ec == cond::canceled` for portable comparison.
236 :
237 : @par Preconditions
238 : The acceptor must be listening (`is_open() == true`).
239 : The peer socket must be associated with the same execution context.
240 :
241 : Both this acceptor and @p peer must outlive the returned
242 : awaitable.
243 :
244 : @par Example
245 : @code
246 : tcp_socket peer(ioc);
247 : auto [ec] = co_await acc.accept(peer);
248 : if (!ec) {
249 : // Use peer socket
250 : }
251 : @endcode
252 : */
253 7262 : auto accept(tcp_socket& peer)
254 : {
255 7262 : if (!is_open())
256 MIS 0 : detail::throw_logic_error("accept: acceptor not listening");
257 HIT 7262 : return accept_awaitable(*this, peer);
258 : }
259 :
260 : /** Cancel any pending asynchronous operations.
261 :
262 : All outstanding operations complete with `errc::operation_canceled`.
263 : Check `ec == cond::canceled` for portable comparison.
264 : */
265 : void cancel();
266 :
267 : /** Get the local endpoint of the acceptor.
268 :
269 : Returns the local address and port to which the acceptor is bound.
270 : This is useful when binding to port 0 (ephemeral port) to discover
271 : the OS-assigned port number. The endpoint is cached when listen()
272 : is called.
273 :
274 : @return The local endpoint, or a default endpoint (0.0.0.0:0) if
275 : the acceptor is not listening.
276 :
277 : @par Thread Safety
278 : The cached endpoint value is set during listen() and cleared
279 : during close(). This function may be called concurrently with
280 : accept operations, but must not be called concurrently with
281 : listen() or close().
282 : */
283 : endpoint local_endpoint() const noexcept;
284 :
285 : struct implementation : io_object::implementation
286 : {
287 : virtual std::coroutine_handle<> accept(
288 : std::coroutine_handle<>,
289 : capy::executor_ref,
290 : std::stop_token,
291 : std::error_code*,
292 : io_object::implementation**) = 0;
293 :
294 : /// Returns the cached local endpoint.
295 : virtual endpoint local_endpoint() const noexcept = 0;
296 :
297 : /// Return true if the acceptor has a kernel resource open.
298 : virtual bool is_open() const noexcept = 0;
299 :
300 : /** Cancel any pending asynchronous operations.
301 :
302 : All outstanding operations complete with operation_canceled error.
303 : */
304 : virtual void cancel() noexcept = 0;
305 : };
306 :
307 : protected:
308 4 : explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
309 :
310 : /// Transfer accepted peer impl to the peer socket.
311 : static void
312 4 : reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
313 : {
314 4 : if (impl)
315 4 : peer.h_.reset(impl);
316 4 : }
317 :
318 : private:
319 15053 : inline implementation& get() const noexcept
320 : {
321 15053 : return *static_cast<implementation*>(h_.get());
322 : }
323 : };
324 :
325 : } // namespace boost::corosio
326 :
327 : #endif
|