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 8863 : accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
78 8863 : : acc_(acc)
79 8863 : , peer_(peer)
80 : {
81 8863 : }
82 :
83 8863 : bool await_ready() const noexcept
84 : {
85 8863 : return token_.stop_requested();
86 : }
87 :
88 8863 : capy::io_result<> await_resume() const noexcept
89 : {
90 8863 : 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 8857 : if (!ec_ && peer_impl_)
96 : {
97 8851 : peer_.close();
98 8851 : peer_.impl_ = peer_impl_;
99 : }
100 8857 : 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 : return acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
109 : }
110 :
111 : template<typename Ex>
112 8863 : auto await_suspend(
113 : std::coroutine_handle<> h,
114 : Ex const& ex,
115 : std::stop_token token) -> std::coroutine_handle<>
116 : {
117 8863 : token_ = std::move(token);
118 8863 : return acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
119 : }
120 : };
121 :
122 : public:
123 : /** Destructor.
124 :
125 : Closes the acceptor if open, cancelling any pending operations.
126 : */
127 : ~tcp_acceptor();
128 :
129 : /** Construct an acceptor from an execution context.
130 :
131 : @param ctx The execution context that will own this acceptor.
132 : */
133 : explicit tcp_acceptor(capy::execution_context& ctx);
134 :
135 : /** Construct an acceptor from an executor.
136 :
137 : The acceptor is associated with the executor's context.
138 :
139 : @param ex The executor whose context will own the acceptor.
140 : */
141 : template<class Ex>
142 : requires (!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
143 : capy::Executor<Ex>
144 : explicit tcp_acceptor(Ex const& ex)
145 : : tcp_acceptor(ex.context())
146 : {
147 : }
148 :
149 : /** Move constructor.
150 :
151 : Transfers ownership of the acceptor resources.
152 :
153 : @param other The acceptor to move from.
154 : */
155 2 : tcp_acceptor(tcp_acceptor&& other) noexcept
156 2 : : io_object(other.context())
157 : {
158 2 : impl_ = other.impl_;
159 2 : other.impl_ = nullptr;
160 2 : }
161 :
162 : /** Move assignment operator.
163 :
164 : Closes any existing acceptor and transfers ownership.
165 : The source and destination must share the same execution context.
166 :
167 : @param other The acceptor to move from.
168 :
169 : @return Reference to this acceptor.
170 :
171 : @throws std::logic_error if the acceptors have different execution contexts.
172 : */
173 22 : tcp_acceptor& operator=(tcp_acceptor&& other)
174 : {
175 22 : if (this != &other)
176 : {
177 22 : if (ctx_ != other.ctx_)
178 0 : detail::throw_logic_error(
179 : "cannot move tcp_acceptor across execution contexts");
180 22 : close();
181 22 : impl_ = other.impl_;
182 22 : other.impl_ = nullptr;
183 : }
184 22 : return *this;
185 : }
186 :
187 : tcp_acceptor(tcp_acceptor const&) = delete;
188 : tcp_acceptor& operator=(tcp_acceptor const&) = delete;
189 :
190 : /** Open, bind, and listen on an endpoint.
191 :
192 : Creates an IPv4 TCP socket, binds it to the specified endpoint,
193 : and begins listening for incoming connections. This must be
194 : called before initiating accept operations.
195 :
196 : @param ep The local endpoint to bind to. Use `endpoint(port)` to
197 : bind to all interfaces on a specific port.
198 :
199 : @param backlog The maximum length of the queue of pending
200 : connections. Defaults to 128.
201 :
202 : @return An error code indicating success or the reason for failure.
203 : A default-constructed error code indicates success.
204 :
205 : @par Error Conditions
206 : @li `errc::address_in_use`: The endpoint is already in use.
207 : @li `errc::address_not_available`: The address is not available
208 : on any local interface.
209 : @li `errc::permission_denied`: Insufficient privileges to bind
210 : to the endpoint (e.g., privileged port).
211 : @li `errc::operation_not_supported`: The acceptor service is
212 : unavailable in the context (POSIX only).
213 :
214 : @throws Nothing.
215 : */
216 : [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
217 :
218 : /** Close the acceptor.
219 :
220 : Releases acceptor resources. Any pending operations complete
221 : with `errc::operation_canceled`.
222 : */
223 : void close();
224 :
225 : /** Check if the acceptor is listening.
226 :
227 : @return `true` if the acceptor is open and listening.
228 : */
229 25 : bool is_open() const noexcept
230 : {
231 25 : return impl_ != nullptr;
232 : }
233 :
234 : /** Initiate an asynchronous accept operation.
235 :
236 : Accepts an incoming connection and initializes the provided
237 : socket with the new connection. The acceptor must be listening
238 : before calling this function.
239 :
240 : The operation supports cancellation via `std::stop_token` through
241 : the affine awaitable protocol. If the associated stop token is
242 : triggered, the operation completes immediately with
243 : `errc::operation_canceled`.
244 :
245 : @param peer The socket to receive the accepted connection. Any
246 : existing connection on this socket will be closed.
247 :
248 : @return An awaitable that completes with `io_result<>`.
249 : Returns success on successful accept, or an error code on
250 : failure including:
251 : - operation_canceled: Cancelled via stop_token or cancel().
252 : Check `ec == cond::canceled` for portable comparison.
253 :
254 : @par Preconditions
255 : The acceptor must be listening (`is_open() == true`).
256 : The peer socket must be associated with the same execution context.
257 :
258 : @par Example
259 : @code
260 : tcp_socket peer(ioc);
261 : auto [ec] = co_await acc.accept(peer);
262 : if (!ec) {
263 : // Use peer socket
264 : }
265 : @endcode
266 : */
267 8863 : auto accept(tcp_socket& peer)
268 : {
269 8863 : if (!impl_)
270 0 : detail::throw_logic_error("accept: acceptor not listening");
271 8863 : return accept_awaitable(*this, peer);
272 : }
273 :
274 : /** Cancel any pending asynchronous operations.
275 :
276 : All outstanding operations complete with `errc::operation_canceled`.
277 : Check `ec == cond::canceled` for portable comparison.
278 : */
279 : void cancel();
280 :
281 : /** Get the local endpoint of the acceptor.
282 :
283 : Returns the local address and port to which the acceptor is bound.
284 : This is useful when binding to port 0 (ephemeral port) to discover
285 : the OS-assigned port number. The endpoint is cached when listen()
286 : is called.
287 :
288 : @return The local endpoint, or a default endpoint (0.0.0.0:0) if
289 : the acceptor is not listening.
290 :
291 : @par Thread Safety
292 : The cached endpoint value is set during listen() and cleared
293 : during close(). This function may be called concurrently with
294 : accept operations, but must not be called concurrently with
295 : listen() or close().
296 : */
297 : endpoint local_endpoint() const noexcept;
298 :
299 : struct acceptor_impl : io_object_impl
300 : {
301 : virtual std::coroutine_handle<> accept(
302 : std::coroutine_handle<>,
303 : capy::executor_ref,
304 : std::stop_token,
305 : std::error_code*,
306 : io_object_impl**) = 0;
307 :
308 : /// Returns the cached local endpoint.
309 : virtual endpoint local_endpoint() const noexcept = 0;
310 :
311 : /** Cancel any pending asynchronous operations.
312 :
313 : All outstanding operations complete with operation_canceled error.
314 : */
315 : virtual void cancel() noexcept = 0;
316 : };
317 :
318 : private:
319 8881 : inline acceptor_impl& get() const noexcept
320 : {
321 8881 : return *static_cast<acceptor_impl*>(impl_);
322 : }
323 : };
324 :
325 : } // namespace boost::corosio
326 :
327 : #endif
|