include/boost/corosio/tcp_acceptor.hpp

88.9% Lines (72/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 8x 100.0% 100.0% boost::corosio::tcp_acceptor::wait_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :94 8x 100.0% 80.0% boost::corosio::tcp_acceptor::accept_awaitable::accept_awaitable(boost::corosio::tcp_acceptor&, boost::corosio::tcp_socket&) :109 6803x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_ready() const :115 6803x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_resume() const :120 6803x 100.0% 92.0% boost::corosio::tcp_acceptor::accept_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :130 6803x 100.0% 82.0% boost::corosio::tcp_acceptor::accept_value_awaitable::accept_value_awaitable(boost::corosio::tcp_acceptor&) :147 2x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_value_awaitable::await_ready() const :153 2x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_value_awaitable::await_resume() :158 2x 71.4% 67.0% boost::corosio::tcp_acceptor::accept_value_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :169 2x 100.0% 82.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::tcp_acceptor&&) :242 4x 100.0% 100.0% boost::corosio::tcp_acceptor::operator=(boost::corosio::tcp_acceptor&&) :257 2x 100.0% 100.0% boost::corosio::tcp_acceptor::is_open() const :343 8452x 100.0% 100.0% boost::corosio::tcp_acceptor::accept(boost::corosio::tcp_socket&) :386 6803x 75.0% 80.0% boost::corosio::tcp_acceptor::accept() :428 2x 75.0% 80.0% boost::corosio::tcp_acceptor::wait(boost::corosio::wait_type) :451 8x 75.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 2x 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 12x 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 217x 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 2x 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 2x 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 2x 80.0% 88.0% boost::corosio::socket_option::reuse_port boost::corosio::tcp_acceptor::get_option<boost::corosio::socket_option::reuse_port>() const :529 2x 80.0% 88.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::io_object::handle) :614 20x 100.0% 100.0% boost::corosio::tcp_acceptor::reset_peer_impl(boost::corosio::tcp_socket&, boost::corosio::io_object::implementation*) :618 10x 100.0% 100.0% boost::corosio::tcp_acceptor::get() const :625 15711x 100.0% 100.0%
Line TLA Hits 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/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 8x wait_awaitable(tcp_acceptor& acc, wait_type w) noexcept
92 8x : acc_(acc), w_(w) {}
93
94 8x std::coroutine_handle<> dispatch(
95 std::coroutine_handle<> h, capy::executor_ref ex) const
96 {
97 8x 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 6803x accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
110 6803x : acc_(acc)
111 6803x , peer_(peer)
112 {
113 6803x }
114
115 6803x bool await_ready() const noexcept
116 {
117 6803x return token_.stop_requested();
118 }
119
120 6803x capy::io_result<> await_resume() const noexcept
121 {
122 6803x if (token_.stop_requested())
123 12x return {make_error_code(std::errc::operation_canceled)};
124
125 6791x if (!ec_ && peer_impl_)
126 6785x peer_.h_.reset(peer_impl_);
127 6791x return {ec_};
128 }
129
130 6803x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
131 -> std::coroutine_handle<>
132 {
133 6803x token_ = env->stop_token;
134 20409x return acc_.get().accept(
135 20409x 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 2x explicit accept_value_awaitable(tcp_acceptor& acc)
148 2x : acc_(acc)
149 2x , peer_(acc.context())
150 {
151 2x }
152
153 2x bool await_ready() const noexcept
154 {
155 2x return token_.stop_requested();
156 }
157
158 2x capy::io_result<tcp_socket> await_resume() noexcept
159 {
160 2x if (token_.stop_requested())
161 return {make_error_code(std::errc::operation_canceled),
162 std::move(peer_)};
163
164 2x if (!ec_ && peer_impl_)
165 2x peer_.h_.reset(peer_impl_);
166 2x return {ec_, std::move(peer_)};
167 }
168
169 2x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
170 -> std::coroutine_handle<>
171 {
172 2x token_ = env->stop_token;
173 6x return acc_.get().accept(
174 6x 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 4x 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 2x tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
258 {
259 2x if (this != &other)
260 {
261 2x close();
262 2x h_ = std::move(other.h_);
263 }
264 2x 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 8452x bool is_open() const noexcept
344 {
345 8452x 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 6803x auto accept(tcp_socket& peer)
387 {
388 6803x if (!is_open())
389 detail::throw_logic_error("accept: acceptor not listening");
390 6803x 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 2x auto accept()
429 {
430 2x if (!is_open())
431 detail::throw_logic_error("accept: acceptor not listening");
432 2x 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 8x [[nodiscard]] auto wait(wait_type w)
452 {
453 8x if (!is_open())
454 detail::throw_logic_error("wait: acceptor not listening");
455 8x 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 listen()
470 is called.
471
472 @return The local endpoint, or a default endpoint (0.0.0.0:0) if
473 the acceptor is not listening.
474
475 @par Thread Safety
476 The cached endpoint value is set during listen() and cleared
477 during close(). This function may be called concurrently with
478 accept operations, but must not be called concurrently with
479 listen() 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 235x void set_option(Option const& opt)
505 {
506 235x if (!is_open())
507 detail::throw_logic_error("set_option: acceptor not open");
508 235x std::error_code ec = get().set_option(
509 Option::level(), Option::name(), opt.data(), opt.size());
510 235x if (ec)
511 detail::throw_system_error(ec, "tcp_acceptor::set_option");
512 235x }
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 4x Option get_option() const
530 {
531 4x if (!is_open())
532 detail::throw_logic_error("get_option: acceptor not open");
533 4x Option opt{};
534 4x std::size_t sz = opt.size();
535 std::error_code ec =
536 4x get().get_option(Option::level(), Option::name(), opt.data(), &sz);
537 4x if (ec)
538 detail::throw_system_error(ec, "tcp_acceptor::get_option");
539 4x opt.resize(sz);
540 4x 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 20x 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 10x reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
619 {
620 10x if (impl)
621 10x peer.h_.reset(impl);
622 10x }
623
624 private:
625 15711x inline implementation& get() const noexcept
626 {
627 15711x return *static_cast<implementation*>(h_.get());
628 }
629 };
630
631 } // namespace boost::corosio
632
633 #endif
634