include/boost/corosio/tcp_acceptor.hpp

92.6% Lines (75/81) 100.0% List of functions (26/26)
tcp_acceptor.hpp
f(x) Functions (26)
Function Calls Lines Blocks
boost::corosio::tcp_acceptor::wait_awaitable::wait_awaitable(boost::corosio::tcp_acceptor&, boost::corosio::wait_type) :91 9x 100.0% 100.0% boost::corosio::tcp_acceptor::wait_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :94 9x 100.0% 80.0% boost::corosio::tcp_acceptor::accept_awaitable::accept_awaitable(boost::corosio::tcp_acceptor&, boost::corosio::tcp_socket&) :109 14499x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_ready() const :115 14499x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_resume() const :120 14497x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :130 14499x 100.0% 82.0% boost::corosio::tcp_acceptor::accept_value_awaitable::accept_value_awaitable(boost::corosio::tcp_acceptor&) :147 3x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_value_awaitable::await_ready() const :153 3x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_value_awaitable::await_resume() :158 3x 71.4% 67.0% boost::corosio::tcp_acceptor::accept_value_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :169 3x 100.0% 82.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::tcp_acceptor&&) :242 6x 100.0% 100.0% boost::corosio::tcp_acceptor::operator=(boost::corosio::tcp_acceptor&&) :257 3x 100.0% 100.0% boost::corosio::tcp_acceptor::is_open() const :343 24513x 100.0% 100.0% boost::corosio::tcp_acceptor::accept(boost::corosio::tcp_socket&) :386 14502x 100.0% 100.0% boost::corosio::tcp_acceptor::accept() :428 6x 100.0% 100.0% boost::corosio::tcp_acceptor::wait(boost::corosio::wait_type) :451 12x 100.0% 80.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::native_socket_option::boolean<1, 15> >(boost::corosio::native_socket_option::boolean<1, 15> const&) :504 3x 71.4% 86.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::native_socket_option::boolean<1, 2> >(boost::corosio::native_socket_option::boolean<1, 2> const&) :504 18x 71.4% 86.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::reuse_address>(boost::corosio::socket_option::reuse_address const&) :504 1409x 71.4% 86.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::reuse_port>(boost::corosio::socket_option::reuse_port const&) :504 3x 71.4% 86.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::v6_only>(boost::corosio::socket_option::v6_only const&) :504 3x 71.4% 86.0% boost::corosio::native_socket_option::boolean<1, 15> boost::corosio::tcp_acceptor::get_option<boost::corosio::native_socket_option::boolean<1, 15> >() const :529 3x 80.0% 88.0% boost::corosio::socket_option::reuse_port boost::corosio::tcp_acceptor::get_option<boost::corosio::socket_option::reuse_port>() const :529 3x 80.0% 88.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::io_object::handle) :614 30x 100.0% 100.0% boost::corosio::tcp_acceptor::reset_peer_impl(boost::corosio::tcp_socket&, boost::corosio::io_object::implementation*) :618 15x 100.0% 100.0% boost::corosio::tcp_acceptor::get() const :625 41839x 100.0% 100.0%
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
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/detail/op_base.hpp>
17 #include <boost/corosio/wait_type.hpp>
18 #include <boost/corosio/io/io_object.hpp>
19 #include <boost/capy/io_result.hpp>
20 #include <boost/corosio/endpoint.hpp>
21 #include <boost/corosio/tcp.hpp>
22 #include <boost/corosio/tcp_socket.hpp>
23 #include <boost/capy/ex/executor_ref.hpp>
24 #include <boost/capy/ex/execution_context.hpp>
25 #include <boost/capy/ex/io_env.hpp>
26 #include <boost/capy/concept/executor.hpp>
27
28 #include <system_error>
29
30 #include <concepts>
31 #include <coroutine>
32 #include <cstddef>
33 #include <stop_token>
34 #include <type_traits>
35
36 namespace boost::corosio {
37
38 /** An asynchronous TCP acceptor for coroutine I/O.
39
40 This class provides asynchronous TCP accept operations that return
41 awaitable types. The acceptor binds to a local endpoint and listens
42 for incoming connections.
43
44 Each accept operation participates in the affine awaitable protocol,
45 ensuring coroutines resume on the correct executor.
46
47 @par Thread Safety
48 Distinct objects: Safe.@n
49 Shared objects: Unsafe. An acceptor must not have concurrent accept
50 operations.
51
52 @par Semantics
53 Wraps the platform TCP listener. Operations dispatch to
54 OS accept APIs via the io_context reactor.
55
56 @par Example
57 @code
58 // Convenience constructor: open + SO_REUSEADDR + bind + listen
59 io_context ioc;
60 tcp_acceptor acc( ioc, endpoint( 8080 ) );
61
62 tcp_socket peer( ioc );
63 auto [ec] = co_await acc.accept( peer );
64 if ( !ec ) {
65 // peer is now a connected socket
66 auto [ec2, n] = co_await peer.read_some( buf );
67 }
68 @endcode
69
70 @par Example
71 @code
72 // Fine-grained setup
73 tcp_acceptor acc( ioc );
74 acc.open( tcp::v6() );
75 acc.set_option( socket_option::reuse_address( true ) );
76 acc.set_option( socket_option::v6_only( true ) );
77 if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) )
78 return ec;
79 if ( auto ec = acc.listen() )
80 return ec;
81 @endcode
82 */
83 class BOOST_COROSIO_DECL tcp_acceptor : public io_object
84 {
85 struct wait_awaitable
86 : detail::void_op_base<wait_awaitable>
87 {
88 tcp_acceptor& acc_;
89 wait_type w_;
90
91 9x wait_awaitable(tcp_acceptor& acc, wait_type w) noexcept
92 9x : acc_(acc), w_(w) {}
93
94 9x std::coroutine_handle<> dispatch(
95 std::coroutine_handle<> h, capy::executor_ref ex) const
96 {
97 9x return acc_.get().wait(h, ex, w_, token_, &ec_);
98 }
99 };
100
101 struct accept_awaitable
102 {
103 tcp_acceptor& acc_;
104 tcp_socket& peer_;
105 std::stop_token token_;
106 mutable std::error_code ec_;
107 mutable io_object::implementation* peer_impl_ = nullptr;
108
109 14499x accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
110 14499x : acc_(acc)
111 14499x , peer_(peer)
112 {
113 14499x }
114
115 14499x bool await_ready() const noexcept
116 {
117 14499x return token_.stop_requested();
118 }
119
120 14497x capy::io_result<> await_resume() const noexcept
121 {
122 14497x if (token_.stop_requested())
123 38x return {make_error_code(std::errc::operation_canceled)};
124
125 14459x if (!ec_ && peer_impl_)
126 14447x peer_.h_.reset(peer_impl_);
127 14459x return {ec_};
128 }
129
130 14499x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
131 -> std::coroutine_handle<>
132 {
133 14499x token_ = env->stop_token;
134 43497x return acc_.get().accept(
135 43497x h, env->executor, token_, &ec_, &peer_impl_);
136 }
137 };
138
139 struct accept_value_awaitable
140 {
141 tcp_acceptor& acc_;
142 tcp_socket peer_;
143 std::stop_token token_;
144 mutable std::error_code ec_;
145 mutable io_object::implementation* peer_impl_ = nullptr;
146
147 3x explicit accept_value_awaitable(tcp_acceptor& acc)
148 3x : acc_(acc)
149 3x , peer_(acc.context())
150 {
151 3x }
152
153 3x bool await_ready() const noexcept
154 {
155 3x return token_.stop_requested();
156 }
157
158 3x capy::io_result<tcp_socket> await_resume() noexcept
159 {
160 3x if (token_.stop_requested())
161 return {make_error_code(std::errc::operation_canceled),
162 std::move(peer_)};
163
164 3x if (!ec_ && peer_impl_)
165 3x peer_.h_.reset(peer_impl_);
166 3x return {ec_, std::move(peer_)};
167 }
168
169 3x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
170 -> std::coroutine_handle<>
171 {
172 3x token_ = env->stop_token;
173 9x return acc_.get().accept(
174 9x h, env->executor, token_, &ec_, &peer_impl_);
175 }
176 };
177
178 public:
179 /** Destructor.
180
181 Closes the acceptor if open, cancelling any pending operations.
182 */
183 ~tcp_acceptor() override;
184
185 /** Construct an acceptor from an execution context.
186
187 @param ctx The execution context that will own this acceptor.
188 */
189 explicit tcp_acceptor(capy::execution_context& ctx);
190
191 /** Convenience constructor: open + SO_REUSEADDR + bind + listen.
192
193 Creates a fully-bound listening acceptor in a single
194 expression. The address family is deduced from @p ep.
195
196 @param ctx The execution context that will own this acceptor.
197 @param ep The local endpoint to bind to.
198 @param backlog The maximum pending connection queue length.
199
200 @throws std::system_error on bind or listen failure.
201 */
202 tcp_acceptor(capy::execution_context& ctx, endpoint ep, int backlog = 128);
203
204 /** Construct an acceptor from an executor.
205
206 The acceptor is associated with the executor's context.
207
208 @param ex The executor whose context will own the acceptor.
209 */
210 template<class Ex>
211 requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
212 capy::Executor<Ex>
213 explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
214 {
215 }
216
217 /** Convenience constructor from an executor.
218
219 @param ex The executor whose context will own the acceptor.
220 @param ep The local endpoint to bind to.
221 @param backlog The maximum pending connection queue length.
222
223 @throws std::system_error on bind or 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 6x 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 3x tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
258 {
259 3x if (this != &other)
260 {
261 3x close();
262 3x h_ = std::move(other.h_);
263 }
264 3x 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 @param proto The protocol (IPv4 or IPv6). Defaults to
279 `tcp::v4()`.
280
281 @throws std::system_error on failure.
282
283 @par Example
284 @code
285 acc.open( tcp::v6() );
286 acc.set_option( socket_option::reuse_address( true ) );
287 acc.bind( endpoint( ipv6_address::any(), 8080 ) );
288 acc.listen();
289 @endcode
290
291 @see bind, listen
292 */
293 void open(tcp proto = tcp::v4());
294
295 /** Bind to a local endpoint.
296
297 The acceptor must be open. Binds the socket to @p ep and
298 caches the resolved local endpoint (useful when port 0 is
299 used to request an ephemeral port).
300
301 @param ep The local endpoint to bind to.
302
303 @return An error code indicating success or the reason for
304 failure.
305
306 @par Error Conditions
307 @li `errc::address_in_use`: The endpoint is already in use.
308 @li `errc::address_not_available`: The address is not available
309 on any local interface.
310 @li `errc::permission_denied`: Insufficient privileges to bind
311 to the endpoint (e.g., privileged port).
312
313 @throws std::logic_error if the acceptor is not open.
314 */
315 [[nodiscard]] std::error_code bind(endpoint ep);
316
317 /** Start listening for incoming connections.
318
319 The acceptor must be open and bound. Registers the acceptor
320 with the platform reactor.
321
322 @param backlog The maximum length of the queue of pending
323 connections. Defaults to 128.
324
325 @return An error code indicating success or the reason for
326 failure.
327
328 @throws std::logic_error if the acceptor is not open.
329 */
330 [[nodiscard]] std::error_code listen(int backlog = 128);
331
332 /** Close the acceptor.
333
334 Releases acceptor resources. Any pending operations complete
335 with `errc::operation_canceled`.
336 */
337 void close();
338
339 /** Check if the acceptor is listening.
340
341 @return `true` if the acceptor is open and listening.
342 */
343 24513x bool is_open() const noexcept
344 {
345 24513x return h_ && get().is_open();
346 }
347
348 /** Initiate an asynchronous accept operation.
349
350 Accepts an incoming connection and initializes the provided
351 socket with the new connection. The acceptor must be listening
352 before calling this function.
353
354 The operation supports cancellation via `std::stop_token` through
355 the affine awaitable protocol. If the associated stop token is
356 triggered, the operation completes immediately with
357 `errc::operation_canceled`.
358
359 @param peer The socket to receive the accepted connection. Any
360 existing connection on this socket will be closed.
361
362 @return An awaitable that completes with `io_result<>`.
363 Returns success on successful accept, or an error code on
364 failure including:
365 - operation_canceled: Cancelled via stop_token or cancel().
366 Check `ec == cond::canceled` for portable comparison.
367
368 @par Preconditions
369 The acceptor must be listening (`is_open() == true`).
370 The peer socket must be associated with the same execution context.
371
372 Both this acceptor and @p peer must outlive the returned
373 awaitable.
374
375 @par Example
376 @code
377 tcp_socket peer(ioc);
378 auto [ec] = co_await acc.accept(peer);
379 if (!ec) {
380 // Use peer socket
381 }
382 @endcode
383
384 @see accept()
385 */
386 14502x auto accept(tcp_socket& peer)
387 {
388 14502x if (!is_open())
389 3x detail::throw_logic_error("accept: acceptor not listening");
390 14499x return accept_awaitable(*this, peer);
391 }
392
393 /** Initiate an asynchronous accept operation, returning the peer.
394
395 Accepts an incoming connection and returns a newly constructed
396 socket for it, associated with this acceptor's execution context.
397 The acceptor must be listening before calling this function.
398
399 The caller does not pre-construct the peer socket; the returned
400 socket shares this acceptor's execution context.
401
402 The operation supports cancellation via `std::stop_token` through
403 the affine awaitable protocol. If the associated stop token is
404 triggered, the operation completes immediately with
405 `errc::operation_canceled`.
406
407 @return An awaitable that completes with `io_result<tcp_socket>`.
408 On success the payload is the connected peer socket; on failure
409 (including cancellation) the error code is set and the payload
410 socket is unconnected. Errors include:
411 - operation_canceled: Cancelled via stop_token or cancel().
412 Check `ec == cond::canceled` for portable comparison.
413
414 @par Preconditions
415 The acceptor must be listening (`is_open() == true`). This acceptor
416 must outlive the returned awaitable.
417
418 @par Example
419 @code
420 auto [ec, peer] = co_await acc.accept();
421 if (!ec) {
422 // peer is a connected socket
423 }
424 @endcode
425
426 @see accept(tcp_socket&)
427 */
428 6x auto accept()
429 {
430 6x if (!is_open())
431 3x detail::throw_logic_error("accept: acceptor not listening");
432 3x return accept_value_awaitable(*this);
433 }
434
435 /** Wait for an incoming connection or readiness condition.
436
437 Suspends until the listen socket is ready in the
438 requested direction, or an error condition is reported.
439 For `wait_type::read`, completion signals that a
440 subsequent @ref accept will succeed without blocking.
441 No connection is consumed.
442
443 @param w The wait direction.
444
445 @return An awaitable that completes with `io_result<>`.
446
447 @par Preconditions
448 The acceptor must be listening. This acceptor must
449 outlive the returned awaitable.
450 */
451 12x [[nodiscard]] auto wait(wait_type w)
452 {
453 12x if (!is_open())
454 3x detail::throw_logic_error("wait: acceptor not listening");
455 9x return wait_awaitable(*this, w);
456 }
457
458 /** Cancel any pending asynchronous operations.
459
460 All outstanding operations complete with `errc::operation_canceled`.
461 Check `ec == cond::canceled` for portable comparison.
462 */
463 void cancel();
464
465 /** Get the local endpoint of the acceptor.
466
467 Returns the local address and port to which the acceptor is bound.
468 This is useful when binding to port 0 (ephemeral port) to discover
469 the OS-assigned port number. The endpoint is cached when bind()
470 is called.
471
472 @return The local endpoint, or a default endpoint (0.0.0.0:0) if
473 the acceptor is not open.
474
475 @par Thread Safety
476 The cached endpoint value is set during bind() and cleared
477 during close(). This function may be called concurrently with
478 accept operations, but must not be called concurrently with
479 bind() or close().
480 */
481 endpoint local_endpoint() const noexcept;
482
483 /** Set a socket option on the acceptor.
484
485 Applies a type-safe socket option to the underlying listening
486 socket. The socket must be open (via `open()` or `listen()`).
487 This is useful for setting options between `open()` and
488 `listen()`, such as `socket_option::reuse_port`.
489
490 @par Example
491 @code
492 acc.open( tcp::v6() );
493 acc.set_option( socket_option::reuse_port( true ) );
494 acc.bind( endpoint( ipv6_address::any(), 8080 ) );
495 acc.listen();
496 @endcode
497
498 @param opt The option to set.
499
500 @throws std::logic_error if the acceptor is not open.
501 @throws std::system_error on failure.
502 */
503 template<class Option>
504 1436x void set_option(Option const& opt)
505 {
506 1436x if (!is_open())
507 detail::throw_logic_error("set_option: acceptor not open");
508 1436x std::error_code ec = get().set_option(
509 Option::level(), Option::name(), opt.data(), opt.size());
510 1436x if (ec)
511 detail::throw_system_error(ec, "tcp_acceptor::set_option");
512 1436x }
513
514 /** Get a socket option from the acceptor.
515
516 Retrieves the current value of a type-safe socket option.
517
518 @par Example
519 @code
520 auto opt = acc.get_option<socket_option::reuse_address>();
521 @endcode
522
523 @return The current option value.
524
525 @throws std::logic_error if the acceptor is not open.
526 @throws std::system_error on failure.
527 */
528 template<class Option>
529 6x Option get_option() const
530 {
531 6x if (!is_open())
532 detail::throw_logic_error("get_option: acceptor not open");
533 6x Option opt{};
534 6x std::size_t sz = opt.size();
535 std::error_code ec =
536 6x get().get_option(Option::level(), Option::name(), opt.data(), &sz);
537 6x if (ec)
538 detail::throw_system_error(ec, "tcp_acceptor::get_option");
539 6x opt.resize(sz);
540 6x return opt;
541 }
542
543 /** Define backend hooks for TCP acceptor operations.
544
545 Platform backends derive from this to implement
546 accept, endpoint query, open-state checks, cancellation,
547 and socket-option management.
548 */
549 struct implementation : io_object::implementation
550 {
551 /// Initiate an asynchronous accept operation.
552 virtual std::coroutine_handle<> accept(
553 std::coroutine_handle<>,
554 capy::executor_ref,
555 std::stop_token,
556 std::error_code*,
557 io_object::implementation**) = 0;
558
559 /** Initiate an asynchronous wait for acceptor readiness.
560
561 Completes when the listen socket becomes ready for
562 the specified direction (typically `wait_type::read`
563 for an incoming connection), or an error condition is
564 reported. No connection is consumed.
565 */
566 virtual std::coroutine_handle<> wait(
567 std::coroutine_handle<> h,
568 capy::executor_ref ex,
569 wait_type w,
570 std::stop_token token,
571 std::error_code* ec) = 0;
572
573 /// Returns the cached local endpoint.
574 virtual endpoint local_endpoint() const noexcept = 0;
575
576 /// Return true if the acceptor has a kernel resource open.
577 virtual bool is_open() const noexcept = 0;
578
579 /** Cancel any pending asynchronous operations.
580
581 All outstanding operations complete with operation_canceled error.
582 */
583 virtual void cancel() noexcept = 0;
584
585 /** Set a socket option.
586
587 @param level The protocol level.
588 @param optname The option name.
589 @param data Pointer to the option value.
590 @param size Size of the option value in bytes.
591 @return Error code on failure, empty on success.
592 */
593 virtual std::error_code set_option(
594 int level,
595 int optname,
596 void const* data,
597 std::size_t size) noexcept = 0;
598
599 /** Get a socket option.
600
601 @param level The protocol level.
602 @param optname The option name.
603 @param data Pointer to receive the option value.
604 @param size On entry, the size of the buffer. On exit,
605 the size of the option value.
606 @return Error code on failure, empty on success.
607 */
608 virtual std::error_code
609 get_option(int level, int optname, void* data, std::size_t* size)
610 const noexcept = 0;
611 };
612
613 protected:
614 30x explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
615
616 /// Transfer accepted peer impl to the peer socket.
617 static void
618 15x reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
619 {
620 15x if (impl)
621 15x peer.h_.reset(impl);
622 15x }
623
624 private:
625 41839x inline implementation& get() const noexcept
626 {
627 41839x return *static_cast<implementation*>(h_.get());
628 }
629 };
630
631 } // namespace boost::corosio
632
633 #endif
634