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