TLA Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2026 Steve Gerbino
4 : // Copyright (c) 2026 Michael Vandeberg
5 : //
6 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 : //
9 : // Official repository: https://github.com/cppalliance/corosio
10 : //
11 :
12 : #ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
13 : #define BOOST_COROSIO_TCP_ACCEPTOR_HPP
14 :
15 : #include <boost/corosio/detail/config.hpp>
16 : #include <boost/corosio/detail/except.hpp>
17 : #include <boost/corosio/detail/native_handle.hpp>
18 : #include <boost/corosio/detail/op_base.hpp>
19 : #include <boost/corosio/wait_type.hpp>
20 : #include <boost/corosio/io/io_object.hpp>
21 : #include <boost/capy/io_result.hpp>
22 : #include <boost/corosio/endpoint.hpp>
23 : #include <boost/corosio/tcp.hpp>
24 : #include <boost/corosio/tcp_socket.hpp>
25 : #include <boost/capy/ex/executor_ref.hpp>
26 : #include <boost/capy/ex/execution_context.hpp>
27 : #include <boost/capy/ex/io_env.hpp>
28 : #include <boost/capy/concept/executor.hpp>
29 :
30 : #include <system_error>
31 :
32 : #include <concepts>
33 : #include <coroutine>
34 : #include <cstddef>
35 : #include <stop_token>
36 : #include <type_traits>
37 :
38 : namespace boost::corosio {
39 :
40 : /** An asynchronous TCP acceptor for coroutine I/O.
41 :
42 : This class provides asynchronous TCP accept operations that return
43 : awaitable types. The acceptor binds to a local endpoint and listens
44 : for incoming connections.
45 :
46 : Each accept operation participates in the affine awaitable protocol,
47 : ensuring coroutines resume on the correct executor.
48 :
49 : @par Thread Safety
50 : Distinct objects: Safe.@n
51 : Shared objects: Unsafe. An acceptor must not have concurrent accept
52 : operations.
53 :
54 : @par Semantics
55 : Wraps the platform TCP listener. Operations dispatch to
56 : OS accept APIs via the io_context reactor.
57 :
58 : @par Example
59 : @par !example convenience_construction
60 :
61 : @par Example
62 : @par !example fine_grained_setup
63 : */
64 : class BOOST_COROSIO_DECL tcp_acceptor : public io_object
65 : {
66 : struct wait_awaitable
67 : : detail::void_op_base<wait_awaitable>
68 : {
69 : tcp_acceptor& acc_;
70 : wait_type w_;
71 :
72 HIT 28 : wait_awaitable(tcp_acceptor& acc, wait_type w) noexcept
73 28 : : acc_(acc), w_(w) {}
74 :
75 26 : std::coroutine_handle<> dispatch(
76 : std::coroutine_handle<> h, capy::executor_ref ex) const
77 : {
78 26 : return acc_.get().wait(h, ex, w_, token_, &ec_);
79 : }
80 : };
81 :
82 : struct accept_awaitable
83 : {
84 : tcp_acceptor& acc_;
85 : tcp_socket& peer_;
86 : std::stop_token token_;
87 : mutable std::error_code ec_;
88 : mutable io_object::implementation* peer_impl_ = nullptr;
89 :
90 6388 : accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
91 6388 : : acc_(acc)
92 6388 : , peer_(peer)
93 : {
94 6388 : }
95 :
96 6388 : bool await_ready() const noexcept
97 : {
98 : // A pre-set ec_ means the initiator failed before
99 : // dispatch (e.g. a closed object).
100 6388 : return static_cast<bool>(ec_) || token_.stop_requested();
101 : }
102 :
103 6378 : [[nodiscard]] capy::io_result<> await_resume() const noexcept
104 : {
105 6378 : if (token_.stop_requested())
106 66 : return {make_error_code(std::errc::operation_canceled)};
107 :
108 6312 : if (!ec_ && peer_impl_)
109 6283 : peer_.h_.reset(peer_impl_);
110 6312 : return {ec_};
111 : }
112 :
113 6386 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
114 : -> std::coroutine_handle<>
115 : {
116 6386 : token_ = env->stop_token;
117 19158 : return acc_.get().accept(
118 19158 : h, env->executor, token_, &ec_, &peer_impl_);
119 : }
120 : };
121 :
122 : struct accept_value_awaitable
123 : {
124 : tcp_acceptor& acc_;
125 : std::stop_token token_;
126 : mutable std::error_code ec_;
127 : mutable io_object::implementation* peer_impl_ = nullptr;
128 :
129 31 : explicit accept_value_awaitable(tcp_acceptor& acc) noexcept
130 31 : : acc_(acc)
131 : {
132 31 : }
133 :
134 31 : bool await_ready() const noexcept
135 : {
136 : // A pre-set ec_ means the initiator failed before
137 : // dispatch (e.g. a closed object).
138 31 : return static_cast<bool>(ec_) || token_.stop_requested();
139 : }
140 :
141 31 : [[nodiscard]] capy::io_result<tcp_socket> await_resume() noexcept
142 : {
143 : // The peer is built only on success: error paths must not
144 : // touch acc_.context(), which a moved-from acceptor lacks.
145 31 : if (token_.stop_requested())
146 MIS 0 : return {make_error_code(std::errc::operation_canceled),
147 0 : tcp_socket()};
148 :
149 HIT 31 : if (ec_ || !peer_impl_)
150 4 : return {ec_, tcp_socket()};
151 :
152 27 : tcp_socket peer(acc_.context());
153 27 : peer.h_.reset(peer_impl_);
154 27 : return {ec_, std::move(peer)};
155 27 : }
156 :
157 27 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
158 : -> std::coroutine_handle<>
159 : {
160 27 : token_ = env->stop_token;
161 81 : return acc_.get().accept(
162 81 : h, env->executor, token_, &ec_, &peer_impl_);
163 : }
164 : };
165 :
166 : public:
167 : /** Destructor.
168 :
169 : Closes the acceptor if open, cancelling any pending operations.
170 : */
171 : ~tcp_acceptor() override;
172 :
173 : /** Construct an acceptor from an execution context.
174 :
175 : @param ctx The execution context that will own this acceptor.
176 : */
177 : explicit tcp_acceptor(capy::execution_context& ctx);
178 :
179 : /** Convenience constructor: open + configure + bind + listen.
180 :
181 : Creates a fully-bound listening acceptor in a single
182 : expression, throwing the codes the piecewise `open()` +
183 : `set_option()` + `bind()` + `listen()` path reports. The
184 : address family is deduced from @p ep.
185 :
186 : Before binding, the constructor configures address reuse so
187 : a server can rebind its port immediately after a restart:
188 : `SO_REUSEADDR` on POSIX, `SO_EXCLUSIVEADDRUSE` on Windows
189 : ( where `SO_REUSEADDR` instead grants other sockets
190 : bind-over rights ). A second listener on an occupied
191 : endpoint therefore throws `errc::address_in_use` on every
192 : platform.
193 :
194 : @param ctx The execution context that will own this acceptor.
195 : @param ep The local endpoint to bind to.
196 : @param backlog The maximum pending connection queue length.
197 :
198 : @throws std::system_error on open, configuration, bind, or
199 : listen failure.
200 : */
201 : tcp_acceptor(capy::execution_context& ctx, endpoint ep, int backlog = 128);
202 :
203 : /** Construct an acceptor from an executor.
204 :
205 : The acceptor is associated with the executor's context.
206 :
207 : @param ex The executor whose context will own the acceptor.
208 : */
209 : template<class Ex>
210 : requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
211 : capy::Executor<Ex>
212 1 : explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
213 : {
214 1 : }
215 :
216 : /** Convenience constructor from an executor.
217 :
218 : @param ex The executor whose context will own the acceptor.
219 : @param ep The local endpoint to bind to.
220 : @param backlog The maximum pending connection queue length.
221 :
222 : @throws std::system_error on open, configuration, bind, or
223 : listen failure.
224 : */
225 : template<class Ex>
226 : requires capy::Executor<Ex>
227 : tcp_acceptor(Ex const& ex, endpoint ep, int backlog = 128)
228 : : tcp_acceptor(ex.context(), ep, backlog)
229 : {
230 : }
231 :
232 : /** Move constructor.
233 :
234 : Transfers ownership of the acceptor resources.
235 :
236 : @param other The acceptor to move from.
237 :
238 : @pre No awaitables returned by @p other's methods exist.
239 : @pre The execution context associated with @p other must
240 : outlive this acceptor.
241 : */
242 9 : tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
243 :
244 : /** Move assignment operator.
245 :
246 : Closes any existing acceptor and transfers ownership.
247 :
248 : @param other The acceptor to move from.
249 :
250 : @pre No awaitables returned by either `*this` or @p other's
251 : methods exist.
252 : @pre The execution context associated with @p other must
253 : outlive this acceptor.
254 :
255 : @return Reference to this acceptor.
256 : */
257 3 : tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
258 : {
259 3 : if (this != &other)
260 : {
261 3 : close();
262 3 : h_ = std::move(other.h_);
263 : }
264 3 : return *this;
265 : }
266 :
267 : tcp_acceptor(tcp_acceptor const&) = delete;
268 : tcp_acceptor& operator=(tcp_acceptor const&) = delete;
269 :
270 : /** Create the acceptor socket without binding or listening.
271 :
272 : Creates a TCP socket with dual-stack enabled for IPv6.
273 : Does not set SO_REUSEADDR — call `set_option` explicitly
274 : if needed.
275 :
276 : If the acceptor is already open, this function is a no-op.
277 :
278 : Failures such as descriptor exhaustion are normal runtime
279 : conditions and are reported through the returned error code.
280 :
281 : @param proto The protocol (IPv4 or IPv6). Defaults to
282 : `tcp::v4()`.
283 :
284 : @par Example
285 : @par !example open
286 :
287 : @see bind, listen
288 :
289 : @return The error code, empty on success.
290 : */
291 : [[nodiscard]] std::error_code open(tcp proto = tcp::v4()) noexcept;
292 :
293 : /** Bind to a local endpoint.
294 :
295 : The acceptor must be open. Binds the socket to @p ep and
296 : caches the resolved local endpoint (useful when port 0 is
297 : used to request an ephemeral port).
298 :
299 : @param ep The local endpoint to bind to.
300 :
301 : @return An error code indicating success or the reason for
302 : failure.
303 :
304 : @par Error Conditions
305 : @li `errc::address_in_use`: The endpoint is already in use.
306 : @li `errc::address_not_available`: The address is not available
307 : on any local interface.
308 : @li `errc::permission_denied`: Insufficient privileges to bind
309 : to the endpoint (e.g., privileged port).
310 :
311 : A closed acceptor reports `errc::bad_file_descriptor`.
312 : */
313 : [[nodiscard]] std::error_code bind(endpoint ep) noexcept;
314 :
315 : /** Start listening for incoming connections.
316 :
317 : The acceptor must be open and bound. Registers the acceptor
318 : with the platform reactor.
319 :
320 : @param backlog The maximum length of the queue of pending
321 : connections. Defaults to 128.
322 :
323 : @return An error code indicating success or the reason for
324 : failure.
325 :
326 : A closed acceptor reports `errc::bad_file_descriptor`.
327 : */
328 : [[nodiscard]] std::error_code listen(int backlog = 128) noexcept;
329 :
330 : /** Close the acceptor.
331 :
332 : Releases acceptor resources. Any pending operations complete
333 : with `errc::operation_canceled`.
334 : */
335 : void close() noexcept;
336 :
337 : /** Check if the acceptor is listening.
338 :
339 : @return `true` if the acceptor is open and listening.
340 : */
341 10609 : bool is_open() const noexcept
342 : {
343 10609 : return h_ && get().is_open();
344 : }
345 :
346 : /** Initiate an asynchronous accept operation.
347 :
348 : Accepts an incoming connection and initializes the provided
349 : socket with the new connection. The acceptor must be listening
350 : before calling this function.
351 :
352 : The operation supports cancellation via `std::stop_token` through
353 : the affine awaitable protocol. If the associated stop token is
354 : triggered, the operation completes immediately with
355 : `errc::operation_canceled`.
356 :
357 : @param peer The socket to receive the accepted connection. Any
358 : existing connection on this socket will be closed.
359 :
360 : @return An awaitable that completes with `io_result<>`.
361 : Returns success on successful accept, or an error code on
362 : failure including:
363 : - operation_canceled: Cancelled via stop_token or cancel().
364 : Check `ec == cond::canceled` for portable comparison.
365 :
366 : A closed acceptor completes with `errc::bad_file_descriptor`.
367 :
368 : @par Preconditions
369 : The peer socket must be associated with the same execution context.
370 :
371 : Both this acceptor and @p peer must outlive the returned
372 : awaitable.
373 :
374 : @par Example
375 : @par !example accept_into_a_reused_socket
376 :
377 : @see accept()
378 : */
379 6388 : [[nodiscard]] auto accept(tcp_socket& peer)
380 : {
381 6388 : accept_awaitable aw(*this, peer);
382 6388 : if (!is_open())
383 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
384 6388 : return aw;
385 : }
386 :
387 : /** Initiate an asynchronous accept operation, returning the peer.
388 :
389 : Accepts an incoming connection and returns a newly constructed
390 : socket for it, associated with this acceptor's execution context.
391 : The acceptor must be listening before calling this function.
392 :
393 : The caller does not pre-construct the peer socket; the returned
394 : socket shares this acceptor's execution context.
395 :
396 : The operation supports cancellation via `std::stop_token` through
397 : the affine awaitable protocol. If the associated stop token is
398 : triggered, the operation completes immediately with
399 : `errc::operation_canceled`.
400 :
401 : @return An awaitable that completes with `io_result<tcp_socket>`.
402 : On success the payload is the connected peer socket; on failure
403 : (including cancellation) the error code is set and the payload
404 : socket is unconnected. Errors include:
405 : - operation_canceled: Cancelled via stop_token or cancel().
406 : Check `ec == cond::canceled` for portable comparison.
407 :
408 : A closed acceptor completes with `errc::bad_file_descriptor`.
409 : On failure the returned socket is default-constructed and
410 : may only be destroyed or assigned.
411 :
412 : @par Preconditions
413 : This acceptor must outlive the returned awaitable.
414 :
415 : @par Example
416 : @par !example accept_returning_a_new_socket
417 :
418 : @see accept(tcp_socket&)
419 : */
420 31 : [[nodiscard]] auto accept()
421 : {
422 31 : accept_value_awaitable aw(*this);
423 31 : if (!is_open())
424 4 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
425 31 : return aw;
426 : }
427 :
428 : /** Wait for an incoming connection or readiness condition.
429 :
430 : Suspends until the listen socket is ready in the
431 : requested direction, or an error condition is reported.
432 : For `wait_type::read`, completion signals that a
433 : subsequent @ref accept will succeed without blocking; a
434 : connection already queued when the wait begins completes
435 : it immediately. No connection is consumed.
436 :
437 : @note `wait_type::write` is not usable on an acceptor:
438 : writability carries no meaning for a listening socket, so
439 : the wait fails with `errc::operation_not_supported` on
440 : every backend.
441 :
442 : @param w The wait direction.
443 :
444 : @return An awaitable that completes with `io_result<>`.
445 :
446 : A closed acceptor completes with `errc::bad_file_descriptor`.
447 :
448 : @par Preconditions
449 : This acceptor must outlive the returned awaitable.
450 : */
451 28 : [[nodiscard]] auto wait(wait_type w)
452 : {
453 28 : wait_awaitable aw(*this, w);
454 28 : if (!is_open())
455 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
456 28 : return aw;
457 : }
458 :
459 : /** Cancel any pending asynchronous operations.
460 :
461 : All outstanding operations complete with `errc::operation_canceled`.
462 : Check `ec == cond::canceled` for portable comparison.
463 : */
464 : void cancel() noexcept;
465 :
466 : /** Get the native socket handle.
467 :
468 : Returns the underlying platform-specific socket descriptor.
469 : On POSIX systems this is an `int` file descriptor.
470 : On Windows this is a `SOCKET` handle.
471 :
472 : @return The native socket handle, or -1/INVALID_SOCKET if not open.
473 :
474 : @par Preconditions
475 : None. May be called on closed acceptors.
476 : */
477 : native_handle_type native_handle() const noexcept;
478 :
479 : /** Assign an existing native socket to this acceptor.
480 :
481 : Adopts a listening socket created outside the library —
482 : received from a service manager, inherited, or made natively —
483 : and registers it with the backend. The socket must be a
484 : listening stream socket in the `AF_INET` or `AF_INET6` family.
485 : Adoption never alters the descriptor's flags or options: on
486 : POSIX the fd must already be non-blocking, and on Windows the
487 : socket must be overlapped-capable.
488 :
489 : Adoption does not verify listen state; @ref accept reports the
490 : error if the socket is not listening.
491 :
492 : If this object is already open, pending operations complete
493 : with `errc::operation_canceled` and the held socket is
494 : closed before the new one is adopted.
495 :
496 : @par Exception Safety
497 : Strong guarantee on validation failure: the object is
498 : unchanged. If backend registration fails, the object either
499 : retains its previous socket or is left closed, depending on
500 : the backend. In all failure cases the caller retains
501 : ownership of `fd`.
502 :
503 : @param fd The native socket to adopt. On success the object
504 : owns it and will close it.
505 :
506 : @return The error code, empty on success. Validation and
507 : registration failures are normal runtime conditions when
508 : adopting foreign descriptors.
509 : */
510 : [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
511 :
512 : /** Release ownership of the native socket handle.
513 :
514 : Deregisters the socket from the backend and cancels pending
515 : operations without closing the descriptor. The caller takes
516 : ownership of the returned handle.
517 :
518 : @return The native handle.
519 :
520 : @throws std::system_error `errc::bad_file_descriptor` if the
521 : acceptor is not open.
522 :
523 : @post is_open() == false
524 : */
525 : native_handle_type release();
526 :
527 : /** Get the local endpoint of the acceptor.
528 :
529 : Returns the local address and port to which the acceptor is bound.
530 : This is useful when binding to port 0 (ephemeral port) to discover
531 : the OS-assigned port number. The endpoint is cached when bind()
532 : is called.
533 :
534 : @return The local endpoint, or a default endpoint (0.0.0.0:0) if
535 : the acceptor is not open.
536 :
537 : @par Thread Safety
538 : The cached endpoint value is set during bind() and cleared
539 : during close(). This function may be called concurrently with
540 : accept operations, but must not be called concurrently with
541 : bind() or close().
542 : */
543 : endpoint local_endpoint() const noexcept;
544 :
545 : /** Set a socket option on the acceptor.
546 :
547 : Applies a type-safe socket option to the underlying listening
548 : socket. The socket must be open (via `open()` or `listen()`).
549 : This is useful for setting options between `open()` and
550 : `listen()`, such as `socket_option::reuse_port`.
551 :
552 : @par Example
553 : @par !example set_option
554 :
555 : @param opt The option to set.
556 :
557 : @throws std::system_error `errc::bad_file_descriptor` if the
558 : acceptor is not open; otherwise thrown on failure.
559 : */
560 : template<class Option>
561 595 : void set_option(Option const& opt)
562 : {
563 595 : if (!is_open())
564 2 : detail::throw_system_error(
565 4 : make_error_code(std::errc::bad_file_descriptor),
566 : "tcp_acceptor::set_option");
567 593 : std::error_code ec = get().set_option(
568 : Option::level(), Option::name(), opt.data(), opt.size());
569 593 : if (ec)
570 8 : detail::throw_system_error(ec, "tcp_acceptor::set_option");
571 585 : }
572 :
573 : /** Get a socket option from the acceptor.
574 :
575 : Retrieves the current value of a type-safe socket option.
576 :
577 : @par Example
578 : @par !example get_option
579 :
580 : @return The current option value.
581 :
582 : @throws std::system_error `errc::bad_file_descriptor` if the
583 : acceptor is not open; otherwise thrown on failure.
584 : */
585 : template<class Option>
586 23 : Option get_option() const
587 : {
588 23 : if (!is_open())
589 2 : detail::throw_system_error(
590 4 : make_error_code(std::errc::bad_file_descriptor),
591 : "tcp_acceptor::get_option");
592 21 : Option opt{};
593 21 : std::size_t sz = opt.size();
594 : std::error_code ec =
595 21 : get().get_option(Option::level(), Option::name(), opt.data(), &sz);
596 21 : if (ec)
597 8 : detail::throw_system_error(ec, "tcp_acceptor::get_option");
598 13 : opt.resize(sz);
599 13 : return opt;
600 : }
601 :
602 : /** Define backend hooks for TCP acceptor operations.
603 :
604 : Platform backends derive from this to implement
605 : accept, endpoint query, open-state checks, cancellation,
606 : and socket-option management.
607 : */
608 : struct implementation : io_object::implementation
609 : {
610 : /// Initiate an asynchronous accept operation.
611 : virtual std::coroutine_handle<> accept(
612 : std::coroutine_handle<>,
613 : capy::executor_ref,
614 : std::stop_token,
615 : std::error_code*,
616 : io_object::implementation**) = 0;
617 :
618 : /** Initiate an asynchronous wait for acceptor readiness.
619 :
620 : Completes when the listen socket becomes ready for
621 : the specified direction (typically `wait_type::read`
622 : for an incoming connection), or an error condition is
623 : reported. No connection is consumed.
624 : */
625 : virtual std::coroutine_handle<> wait(
626 : std::coroutine_handle<> h,
627 : capy::executor_ref ex,
628 : wait_type w,
629 : std::stop_token token,
630 : std::error_code* ec) = 0;
631 :
632 : /// Returns the cached local endpoint.
633 : virtual endpoint local_endpoint() const noexcept = 0;
634 :
635 : /// Return true if the acceptor has a kernel resource open.
636 : virtual bool is_open() const noexcept = 0;
637 :
638 : /// Return the native handle, or the platform sentinel if closed.
639 : virtual native_handle_type native_handle() const noexcept = 0;
640 :
641 : /// Release and return the native handle without closing.
642 : virtual native_handle_type release_socket() noexcept = 0;
643 :
644 : /** Cancel any pending asynchronous operations.
645 :
646 : All outstanding operations complete with operation_canceled error.
647 : */
648 : virtual void cancel() noexcept = 0;
649 :
650 : /** Set a socket option.
651 :
652 : @param level The protocol level.
653 : @param optname The option name.
654 : @param data Pointer to the option value.
655 : @param size Size of the option value in bytes.
656 : @return Error code on failure, empty on success.
657 : */
658 : virtual std::error_code set_option(
659 : int level,
660 : int optname,
661 : void const* data,
662 : std::size_t size) noexcept = 0;
663 :
664 : /** Get a socket option.
665 :
666 : @param level The protocol level.
667 : @param optname The option name.
668 : @param data Pointer to receive the option value.
669 : @param size On entry, the size of the buffer. On exit,
670 : the size of the option value.
671 : @return Error code on failure, empty on success.
672 : */
673 : virtual std::error_code
674 : get_option(int level, int optname, void* data, std::size_t* size)
675 : const noexcept = 0;
676 : };
677 :
678 : protected:
679 33 : explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
680 :
681 : /// Transfer accepted peer impl to the peer socket.
682 : static void
683 15 : reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
684 : {
685 15 : if (impl)
686 15 : peer.h_.reset(impl);
687 15 : }
688 :
689 : private:
690 18228 : inline implementation& get() const noexcept
691 : {
692 18228 : return *static_cast<implementation*>(h_.get());
693 : }
694 : };
695 :
696 : } // namespace boost::corosio
697 :
698 : #endif
|