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 8145 : accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
79 8145 : : acc_(acc)
80 8145 : , peer_(peer)
81 : {
82 8145 : }
83 :
84 8145 : bool await_ready() const noexcept
85 : {
86 8145 : return token_.stop_requested();
87 : }
88 :
89 8145 : capy::io_result<> await_resume() const noexcept
90 : {
91 8145 : if (token_.stop_requested())
92 6 : return {make_error_code(std::errc::operation_canceled)};
93 :
94 8139 : if (!ec_ && peer_impl_)
95 8133 : peer_.h_.reset(peer_impl_);
96 8139 : return {ec_};
97 : }
98 :
99 8145 : auto await_suspend(
100 : std::coroutine_handle<> h,
101 : capy::io_env const* env) -> std::coroutine_handle<>
102 : {
103 8145 : token_ = env->stop_token;
104 8145 : return acc_.get().accept(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();
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)
131 : : 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
142 2 : : io_object(std::move(other))
143 : {
144 2 : }
145 :
146 : /** Move assignment operator.
147 :
148 : Closes any existing acceptor and transfers ownership.
149 : The source and destination must share the same execution context.
150 :
151 : @param other The acceptor to move from.
152 :
153 : @return Reference to this acceptor.
154 :
155 : @throws std::logic_error if the acceptors have different execution contexts.
156 : */
157 2 : tcp_acceptor& operator=(tcp_acceptor&& other)
158 : {
159 2 : if (this != &other)
160 : {
161 2 : if (&context() != &other.context())
162 0 : detail::throw_logic_error(
163 : "cannot move tcp_acceptor across execution contexts");
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 8570 : bool is_open() const noexcept
213 : {
214 8570 : 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 : @par Example
242 : @code
243 : tcp_socket peer(ioc);
244 : auto [ec] = co_await acc.accept(peer);
245 : if (!ec) {
246 : // Use peer socket
247 : }
248 : @endcode
249 : */
250 8145 : auto accept(tcp_socket& peer)
251 : {
252 8145 : if (!is_open())
253 0 : detail::throw_logic_error("accept: acceptor not listening");
254 8145 : return accept_awaitable(*this, peer);
255 : }
256 :
257 : /** Cancel any pending asynchronous operations.
258 :
259 : All outstanding operations complete with `errc::operation_canceled`.
260 : Check `ec == cond::canceled` for portable comparison.
261 : */
262 : void cancel();
263 :
264 : /** Get the local endpoint of the acceptor.
265 :
266 : Returns the local address and port to which the acceptor is bound.
267 : This is useful when binding to port 0 (ephemeral port) to discover
268 : the OS-assigned port number. The endpoint is cached when listen()
269 : is called.
270 :
271 : @return The local endpoint, or a default endpoint (0.0.0.0:0) if
272 : the acceptor is not listening.
273 :
274 : @par Thread Safety
275 : The cached endpoint value is set during listen() and cleared
276 : during close(). This function may be called concurrently with
277 : accept operations, but must not be called concurrently with
278 : listen() or close().
279 : */
280 : endpoint local_endpoint() const noexcept;
281 :
282 : struct implementation : io_object::implementation
283 : {
284 : virtual std::coroutine_handle<> accept(
285 : std::coroutine_handle<>,
286 : capy::executor_ref,
287 : std::stop_token,
288 : std::error_code*,
289 : io_object::implementation**) = 0;
290 :
291 : /// Returns the cached local endpoint.
292 : virtual endpoint local_endpoint() const noexcept = 0;
293 :
294 : /// Return true if the acceptor has a kernel resource open.
295 : virtual bool is_open() const noexcept = 0;
296 :
297 : /** Cancel any pending asynchronous operations.
298 :
299 : All outstanding operations complete with operation_canceled error.
300 : */
301 : virtual void cancel() noexcept = 0;
302 : };
303 :
304 : private:
305 16795 : inline implementation& get() const noexcept
306 : {
307 16795 : return *static_cast<implementation*>(h_.get());
308 : }
309 : };
310 :
311 : } // namespace boost::corosio
312 :
313 : #endif
|