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.hpp>
20 : #include <boost/corosio/tcp_socket.hpp>
21 : #include <boost/capy/ex/executor_ref.hpp>
22 : #include <boost/capy/ex/execution_context.hpp>
23 : #include <boost/capy/ex/io_env.hpp>
24 : #include <boost/capy/concept/executor.hpp>
25 :
26 : #include <system_error>
27 :
28 : #include <concepts>
29 : #include <coroutine>
30 : #include <cstddef>
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 : // Convenience constructor: open + SO_REUSEADDR + bind + listen
57 : io_context ioc;
58 : tcp_acceptor acc( ioc, endpoint( 8080 ) );
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 : @par Example
69 : @code
70 : // Fine-grained setup
71 : tcp_acceptor acc( ioc );
72 : acc.open( tcp::v6() );
73 : acc.set_option( socket_option::reuse_address( true ) );
74 : acc.set_option( socket_option::v6_only( true ) );
75 : if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) )
76 : return ec;
77 : if ( auto ec = acc.listen() )
78 : return ec;
79 : @endcode
80 : */
81 : class BOOST_COROSIO_DECL tcp_acceptor : public io_object
82 : {
83 : struct accept_awaitable
84 : {
85 : tcp_acceptor& acc_;
86 : tcp_socket& peer_;
87 : std::stop_token token_;
88 : mutable std::error_code ec_;
89 : mutable io_object::implementation* peer_impl_ = nullptr;
90 :
91 HIT 8236 : accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
92 8236 : : acc_(acc)
93 8236 : , peer_(peer)
94 : {
95 8236 : }
96 :
97 8236 : bool await_ready() const noexcept
98 : {
99 8236 : return token_.stop_requested();
100 : }
101 :
102 8236 : capy::io_result<> await_resume() const noexcept
103 : {
104 8236 : if (token_.stop_requested())
105 6 : return {make_error_code(std::errc::operation_canceled)};
106 :
107 8230 : if (!ec_ && peer_impl_)
108 8224 : peer_.h_.reset(peer_impl_);
109 8230 : return {ec_};
110 : }
111 :
112 8236 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
113 : -> std::coroutine_handle<>
114 : {
115 8236 : token_ = env->stop_token;
116 24708 : return acc_.get().accept(
117 24708 : h, env->executor, token_, &ec_, &peer_impl_);
118 : }
119 : };
120 :
121 : public:
122 : /** Destructor.
123 :
124 : Closes the acceptor if open, cancelling any pending operations.
125 : */
126 : ~tcp_acceptor() override;
127 :
128 : /** Construct an acceptor from an execution context.
129 :
130 : @param ctx The execution context that will own this acceptor.
131 : */
132 : explicit tcp_acceptor(capy::execution_context& ctx);
133 :
134 : /** Convenience constructor: open + SO_REUSEADDR + bind + listen.
135 :
136 : Creates a fully-bound listening acceptor in a single
137 : expression. The address family is deduced from @p ep.
138 :
139 : @param ctx The execution context that will own this acceptor.
140 : @param ep The local endpoint to bind to.
141 : @param backlog The maximum pending connection queue length.
142 :
143 : @throws std::system_error on bind or listen failure.
144 : */
145 : tcp_acceptor(capy::execution_context& ctx, endpoint ep, int backlog = 128);
146 :
147 : /** Construct an acceptor from an executor.
148 :
149 : The acceptor is associated with the executor's context.
150 :
151 : @param ex The executor whose context will own the acceptor.
152 : */
153 : template<class Ex>
154 : requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
155 : capy::Executor<Ex>
156 : explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
157 : {
158 : }
159 :
160 : /** Convenience constructor from an executor.
161 :
162 : @param ex The executor whose context will own the acceptor.
163 : @param ep The local endpoint to bind to.
164 : @param backlog The maximum pending connection queue length.
165 :
166 : @throws std::system_error on bind or listen failure.
167 : */
168 : template<class Ex>
169 : requires capy::Executor<Ex>
170 : tcp_acceptor(Ex const& ex, endpoint ep, int backlog = 128)
171 : : tcp_acceptor(ex.context(), ep, backlog)
172 : {
173 : }
174 :
175 : /** Move constructor.
176 :
177 : Transfers ownership of the acceptor resources.
178 :
179 : @param other The acceptor to move from.
180 :
181 : @pre No awaitables returned by @p other's methods exist.
182 : @pre The execution context associated with @p other must
183 : outlive this acceptor.
184 : */
185 2 : tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
186 :
187 : /** Move assignment operator.
188 :
189 : Closes any existing acceptor and transfers ownership.
190 :
191 : @param other The acceptor to move from.
192 :
193 : @pre No awaitables returned by either `*this` or @p other's
194 : methods exist.
195 : @pre The execution context associated with @p other must
196 : outlive this acceptor.
197 :
198 : @return Reference to this acceptor.
199 : */
200 2 : tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
201 : {
202 2 : if (this != &other)
203 : {
204 2 : close();
205 2 : h_ = std::move(other.h_);
206 : }
207 2 : return *this;
208 : }
209 :
210 : tcp_acceptor(tcp_acceptor const&) = delete;
211 : tcp_acceptor& operator=(tcp_acceptor const&) = delete;
212 :
213 : /** Create the acceptor socket without binding or listening.
214 :
215 : Creates a TCP socket with dual-stack enabled for IPv6.
216 : Does not set SO_REUSEADDR — call `set_option` explicitly
217 : if needed.
218 :
219 : If the acceptor is already open, this function is a no-op.
220 :
221 : @param proto The protocol (IPv4 or IPv6). Defaults to
222 : `tcp::v4()`.
223 :
224 : @throws std::system_error on failure.
225 :
226 : @par Example
227 : @code
228 : acc.open( tcp::v6() );
229 : acc.set_option( socket_option::reuse_address( true ) );
230 : acc.bind( endpoint( ipv6_address::any(), 8080 ) );
231 : acc.listen();
232 : @endcode
233 :
234 : @see bind, listen
235 : */
236 : void open(tcp proto = tcp::v4());
237 :
238 : /** Bind to a local endpoint.
239 :
240 : The acceptor must be open. Binds the socket to @p ep and
241 : caches the resolved local endpoint (useful when port 0 is
242 : used to request an ephemeral port).
243 :
244 : @param ep The local endpoint to bind to.
245 :
246 : @return An error code indicating success or the reason for
247 : failure.
248 :
249 : @par Error Conditions
250 : @li `errc::address_in_use`: The endpoint is already in use.
251 : @li `errc::address_not_available`: The address is not available
252 : on any local interface.
253 : @li `errc::permission_denied`: Insufficient privileges to bind
254 : to the endpoint (e.g., privileged port).
255 :
256 : @throws std::logic_error if the acceptor is not open.
257 : */
258 : [[nodiscard]] std::error_code bind(endpoint ep);
259 :
260 : /** Start listening for incoming connections.
261 :
262 : The acceptor must be open and bound. Registers the acceptor
263 : with the platform reactor.
264 :
265 : @param backlog The maximum length of the queue of pending
266 : connections. Defaults to 128.
267 :
268 : @return An error code indicating success or the reason for
269 : failure.
270 :
271 : @throws std::logic_error if the acceptor is not open.
272 : */
273 : [[nodiscard]] std::error_code listen(int backlog = 128);
274 :
275 : /** Close the acceptor.
276 :
277 : Releases acceptor resources. Any pending operations complete
278 : with `errc::operation_canceled`.
279 : */
280 : void close();
281 :
282 : /** Check if the acceptor is listening.
283 :
284 : @return `true` if the acceptor is open and listening.
285 : */
286 9232 : bool is_open() const noexcept
287 : {
288 9232 : return h_ && get().is_open();
289 : }
290 :
291 : /** Initiate an asynchronous accept operation.
292 :
293 : Accepts an incoming connection and initializes the provided
294 : socket with the new connection. The acceptor must be listening
295 : before calling this function.
296 :
297 : The operation supports cancellation via `std::stop_token` through
298 : the affine awaitable protocol. If the associated stop token is
299 : triggered, the operation completes immediately with
300 : `errc::operation_canceled`.
301 :
302 : @param peer The socket to receive the accepted connection. Any
303 : existing connection on this socket will be closed.
304 :
305 : @return An awaitable that completes with `io_result<>`.
306 : Returns success on successful accept, or an error code on
307 : failure including:
308 : - operation_canceled: Cancelled via stop_token or cancel().
309 : Check `ec == cond::canceled` for portable comparison.
310 :
311 : @par Preconditions
312 : The acceptor must be listening (`is_open() == true`).
313 : The peer socket must be associated with the same execution context.
314 :
315 : Both this acceptor and @p peer must outlive the returned
316 : awaitable.
317 :
318 : @par Example
319 : @code
320 : tcp_socket peer(ioc);
321 : auto [ec] = co_await acc.accept(peer);
322 : if (!ec) {
323 : // Use peer socket
324 : }
325 : @endcode
326 : */
327 8236 : auto accept(tcp_socket& peer)
328 : {
329 8236 : if (!is_open())
330 MIS 0 : detail::throw_logic_error("accept: acceptor not listening");
331 HIT 8236 : return accept_awaitable(*this, peer);
332 : }
333 :
334 : /** Cancel any pending asynchronous operations.
335 :
336 : All outstanding operations complete with `errc::operation_canceled`.
337 : Check `ec == cond::canceled` for portable comparison.
338 : */
339 : void cancel();
340 :
341 : /** Get the local endpoint of the acceptor.
342 :
343 : Returns the local address and port to which the acceptor is bound.
344 : This is useful when binding to port 0 (ephemeral port) to discover
345 : the OS-assigned port number. The endpoint is cached when listen()
346 : is called.
347 :
348 : @return The local endpoint, or a default endpoint (0.0.0.0:0) if
349 : the acceptor is not listening.
350 :
351 : @par Thread Safety
352 : The cached endpoint value is set during listen() and cleared
353 : during close(). This function may be called concurrently with
354 : accept operations, but must not be called concurrently with
355 : listen() or close().
356 : */
357 : endpoint local_endpoint() const noexcept;
358 :
359 : /** Set a socket option on the acceptor.
360 :
361 : Applies a type-safe socket option to the underlying listening
362 : socket. The socket must be open (via `open()` or `listen()`).
363 : This is useful for setting options between `open()` and
364 : `listen()`, such as `socket_option::reuse_port`.
365 :
366 : @par Example
367 : @code
368 : acc.open( tcp::v6() );
369 : acc.set_option( socket_option::reuse_port( true ) );
370 : acc.bind( endpoint( ipv6_address::any(), 8080 ) );
371 : acc.listen();
372 : @endcode
373 :
374 : @param opt The option to set.
375 :
376 : @throws std::logic_error if the acceptor is not open.
377 : @throws std::system_error on failure.
378 : */
379 : template<class Option>
380 137 : void set_option(Option const& opt)
381 : {
382 137 : if (!is_open())
383 MIS 0 : detail::throw_logic_error("set_option: acceptor not open");
384 HIT 137 : std::error_code ec = get().set_option(
385 : Option::level(), Option::name(), opt.data(), opt.size());
386 137 : if (ec)
387 MIS 0 : detail::throw_system_error(ec, "tcp_acceptor::set_option");
388 HIT 137 : }
389 :
390 : /** Get a socket option from the acceptor.
391 :
392 : Retrieves the current value of a type-safe socket option.
393 :
394 : @par Example
395 : @code
396 : auto opt = acc.get_option<socket_option::reuse_address>();
397 : @endcode
398 :
399 : @return The current option value.
400 :
401 : @throws std::logic_error if the acceptor is not open.
402 : @throws std::system_error on failure.
403 : */
404 : template<class Option>
405 : Option get_option() const
406 : {
407 : if (!is_open())
408 : detail::throw_logic_error("get_option: acceptor not open");
409 : Option opt{};
410 : std::size_t sz = opt.size();
411 : std::error_code ec =
412 : get().get_option(Option::level(), Option::name(), opt.data(), &sz);
413 : if (ec)
414 : detail::throw_system_error(ec, "tcp_acceptor::get_option");
415 : opt.resize(sz);
416 : return opt;
417 : }
418 :
419 : struct implementation : io_object::implementation
420 : {
421 : virtual std::coroutine_handle<> accept(
422 : std::coroutine_handle<>,
423 : capy::executor_ref,
424 : std::stop_token,
425 : std::error_code*,
426 : io_object::implementation**) = 0;
427 :
428 : /// Returns the cached local endpoint.
429 : virtual endpoint local_endpoint() const noexcept = 0;
430 :
431 : /// Return true if the acceptor has a kernel resource open.
432 : virtual bool is_open() const noexcept = 0;
433 :
434 : /** Cancel any pending asynchronous operations.
435 :
436 : All outstanding operations complete with operation_canceled error.
437 : */
438 : virtual void cancel() noexcept = 0;
439 :
440 : /** Set a socket option.
441 :
442 : @param level The protocol level.
443 : @param optname The option name.
444 : @param data Pointer to the option value.
445 : @param size Size of the option value in bytes.
446 : @return Error code on failure, empty on success.
447 : */
448 : virtual std::error_code set_option(
449 : int level,
450 : int optname,
451 : void const* data,
452 : std::size_t size) noexcept = 0;
453 :
454 : /** Get a socket option.
455 :
456 : @param level The protocol level.
457 : @param optname The option name.
458 : @param data Pointer to receive the option value.
459 : @param size On entry, the size of the buffer. On exit,
460 : the size of the option value.
461 : @return Error code on failure, empty on success.
462 : */
463 : virtual std::error_code
464 : get_option(int level, int optname, void* data, std::size_t* size)
465 : const noexcept = 0;
466 : };
467 :
468 : protected:
469 4 : explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
470 :
471 : /// Transfer accepted peer impl to the peer socket.
472 : static void
473 4 : reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
474 : {
475 4 : if (impl)
476 4 : peer.h_.reset(impl);
477 4 : }
478 :
479 : private:
480 17725 : inline implementation& get() const noexcept
481 : {
482 17725 : return *static_cast<implementation*>(h_.get());
483 : }
484 : };
485 :
486 : } // namespace boost::corosio
487 :
488 : #endif
|