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