include/boost/corosio/tcp_acceptor.hpp

88.1% Lines (59/67) 100.0% List of functions (25/25) 56.8% Branches (25/44)
tcp_acceptor.hpp
f(x) Functions (25)
Function Calls Lines Branches Blocks
boost::corosio::tcp_acceptor::wait_awaitable::wait_awaitable(boost::corosio::tcp_acceptor::wait_awaitable&&) :85 16x 100.0% 100.0% boost::corosio::tcp_acceptor::wait_awaitable::~wait_awaitable() :85 32x 100.0% 100.0% boost::corosio::tcp_acceptor::wait_awaitable::wait_awaitable(boost::corosio::tcp_acceptor&, boost::corosio::wait_type) :91 16x 100.0% 100.0% boost::corosio::tcp_acceptor::wait_awaitable::dispatch(std::__1::coroutine_handle<void>, boost::capy::executor_ref) const :94 8x 66.7% 50.0% 50.0% boost::corosio::tcp_acceptor::accept_awaitable::accept_awaitable(boost::corosio::tcp_acceptor::accept_awaitable&&) :101 11428x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::~accept_awaitable() :101 22856x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::accept_awaitable(boost::corosio::tcp_acceptor&, boost::corosio::tcp_socket&) :109 11428x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_ready() const :115 5714x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_resume() const :120 5714x 100.0% 70.0% 90.0% boost::corosio::tcp_acceptor::accept_awaitable::await_suspend(std::__1::coroutine_handle<void>, boost::capy::io_env const*) :130 5714x 80.0% 50.0% 50.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::tcp_acceptor&&) :203 6x 100.0% 100.0% boost::corosio::tcp_acceptor::operator=(boost::corosio::tcp_acceptor&&) :218 2x 100.0% 50.0% 80.0% boost::corosio::tcp_acceptor::is_open() const :304 12195x 100.0% 100.0% 100.0% boost::corosio::tcp_acceptor::accept(boost::corosio::tcp_socket&) :345 5714x 75.0% 50.0% 66.0% boost::corosio::tcp_acceptor::wait(boost::corosio::wait_type) :368 8x 75.0% 50.0% 66.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::native_socket_option::boolean<65535, 4>>(boost::corosio::native_socket_option::boolean<65535, 4> const&) :421 10x 75.0% 50.0% 60.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::native_socket_option::boolean<65535, 512>>(boost::corosio::native_socket_option::boolean<65535, 512> const&) :421 2x 75.0% 25.0% 60.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::reuse_address>(boost::corosio::socket_option::reuse_address const&) :421 909x 75.0% 50.0% 60.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::reuse_port>(boost::corosio::socket_option::reuse_port const&) :421 2x 75.0% 33.3% 60.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::v6_only>(boost::corosio::socket_option::v6_only const&) :421 2x 100.0% 60.0% boost::corosio::native_socket_option::boolean<65535, 512> boost::corosio::tcp_acceptor::get_option<boost::corosio::native_socket_option::boolean<65535, 512>>() const :446 2x 80.0% 50.0% 60.0% boost::corosio::socket_option::reuse_port boost::corosio::tcp_acceptor::get_option<boost::corosio::socket_option::reuse_port>() const :446 2x 80.0% 50.0% 60.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::io_object::handle) :531 22x 100.0% 100.0% boost::corosio::tcp_acceptor::reset_peer_impl(boost::corosio::tcp_socket&, boost::corosio::io_object::implementation*) :535 12x 100.0% 50.0% 100.0% boost::corosio::tcp_acceptor::get() const :542 19743x 100.0% 100.0%
Line Branch 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 16x wait_awaitable(tcp_acceptor& acc, wait_type w) noexcept
92 16x : acc_(acc), w_(w) {}
93
94 8x std::coroutine_handle<> dispatch(
95 std::coroutine_handle<> h, capy::executor_ref ex) const
96 {
97
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
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 5714x mutable io_object::implementation* peer_impl_ = nullptr;
108
109 17142x accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
110 5714x : acc_(acc)
111 5714x , peer_(peer)
112 5714x {
113 11428x }
114
115 5714x bool await_ready() const noexcept
116 {
117 5714x return token_.stop_requested();
118 }
119
120 5714x capy::io_result<> await_resume() const noexcept
121 {
122
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 5702 times.
5714x if (token_.stop_requested())
123
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12x return {make_error_code(std::errc::operation_canceled)};
124
125
3/4
✓ Branch 0 taken 5696 times.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5696 times.
5702x if (!ec_ && peer_impl_)
126 5696x peer_.h_.reset(peer_impl_);
127
1/2
✓ Branch 0 taken 5702 times.
✗ Branch 1 not taken.
5702x return {ec_};
128 5714x }
129
130 5714x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
131 -> std::coroutine_handle<>
132 {
133 5714x token_ = env->stop_token;
134
2/4
✓ Branch 0 taken 5714 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5714 times.
✗ Branch 3 not taken.
11428x return acc_.get().accept(
135 5714x h, env->executor, token_, &ec_, &peer_impl_);
136 }
137 };
138
139 public:
140 /** Destructor.
141
142 Closes the acceptor if open, cancelling any pending operations.
143 */
144 ~tcp_acceptor() override;
145
146 /** Construct an acceptor from an execution context.
147
148 @param ctx The execution context that will own this acceptor.
149 */
150 explicit tcp_acceptor(capy::execution_context& ctx);
151
152 /** Convenience constructor: open + SO_REUSEADDR + bind + listen.
153
154 Creates a fully-bound listening acceptor in a single
155 expression. The address family is deduced from @p ep.
156
157 @param ctx The execution context that will own this acceptor.
158 @param ep The local endpoint to bind to.
159 @param backlog The maximum pending connection queue length.
160
161 @throws std::system_error on bind or listen failure.
162 */
163 tcp_acceptor(capy::execution_context& ctx, endpoint ep, int backlog = 128);
164
165 /** Construct an acceptor from an executor.
166
167 The acceptor is associated with the executor's context.
168
169 @param ex The executor whose context will own the acceptor.
170 */
171 template<class Ex>
172 requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
173 capy::Executor<Ex>
174 explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
175 {
176 }
177
178 /** Convenience constructor from an executor.
179
180 @param ex The executor whose context will own the acceptor.
181 @param ep The local endpoint to bind to.
182 @param backlog The maximum pending connection queue length.
183
184 @throws std::system_error on bind or listen failure.
185 */
186 template<class Ex>
187 requires capy::Executor<Ex>
188 tcp_acceptor(Ex const& ex, endpoint ep, int backlog = 128)
189 : tcp_acceptor(ex.context(), ep, backlog)
190 {
191 }
192
193 /** Move constructor.
194
195 Transfers ownership of the acceptor resources.
196
197 @param other The acceptor to move from.
198
199 @pre No awaitables returned by @p other's methods exist.
200 @pre The execution context associated with @p other must
201 outlive this acceptor.
202 */
203 6x tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
204
205 /** Move assignment operator.
206
207 Closes any existing acceptor and transfers ownership.
208
209 @param other The acceptor to move from.
210
211 @pre No awaitables returned by either `*this` or @p other's
212 methods exist.
213 @pre The execution context associated with @p other must
214 outlive this acceptor.
215
216 @return Reference to this acceptor.
217 */
218 2x tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
219 {
220
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2x if (this != &other)
221 {
222
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x close();
223 2x h_ = std::move(other.h_);
224 2x }
225 2x return *this;
226 }
227
228 tcp_acceptor(tcp_acceptor const&) = delete;
229 tcp_acceptor& operator=(tcp_acceptor const&) = delete;
230
231 /** Create the acceptor socket without binding or listening.
232
233 Creates a TCP socket with dual-stack enabled for IPv6.
234 Does not set SO_REUSEADDR — call `set_option` explicitly
235 if needed.
236
237 If the acceptor is already open, this function is a no-op.
238
239 @param proto The protocol (IPv4 or IPv6). Defaults to
240 `tcp::v4()`.
241
242 @throws std::system_error on failure.
243
244 @par Example
245 @code
246 acc.open( tcp::v6() );
247 acc.set_option( socket_option::reuse_address( true ) );
248 acc.bind( endpoint( ipv6_address::any(), 8080 ) );
249 acc.listen();
250 @endcode
251
252 @see bind, listen
253 */
254 void open(tcp proto = tcp::v4());
255
256 /** Bind to a local endpoint.
257
258 The acceptor must be open. Binds the socket to @p ep and
259 caches the resolved local endpoint (useful when port 0 is
260 used to request an ephemeral port).
261
262 @param ep The local endpoint to bind to.
263
264 @return An error code indicating success or the reason for
265 failure.
266
267 @par Error Conditions
268 @li `errc::address_in_use`: The endpoint is already in use.
269 @li `errc::address_not_available`: The address is not available
270 on any local interface.
271 @li `errc::permission_denied`: Insufficient privileges to bind
272 to the endpoint (e.g., privileged port).
273
274 @throws std::logic_error if the acceptor is not open.
275 */
276 [[nodiscard]] std::error_code bind(endpoint ep);
277
278 /** Start listening for incoming connections.
279
280 The acceptor must be open and bound. Registers the acceptor
281 with the platform reactor.
282
283 @param backlog The maximum length of the queue of pending
284 connections. Defaults to 128.
285
286 @return An error code indicating success or the reason for
287 failure.
288
289 @throws std::logic_error if the acceptor is not open.
290 */
291 [[nodiscard]] std::error_code listen(int backlog = 128);
292
293 /** Close the acceptor.
294
295 Releases acceptor resources. Any pending operations complete
296 with `errc::operation_canceled`.
297 */
298 void close();
299
300 /** Check if the acceptor is listening.
301
302 @return `true` if the acceptor is open and listening.
303 */
304 12195x bool is_open() const noexcept
305 {
306
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 12185 times.
12195x return h_ && get().is_open();
307 }
308
309 /** Initiate an asynchronous accept operation.
310
311 Accepts an incoming connection and initializes the provided
312 socket with the new connection. The acceptor must be listening
313 before calling this function.
314
315 The operation supports cancellation via `std::stop_token` through
316 the affine awaitable protocol. If the associated stop token is
317 triggered, the operation completes immediately with
318 `errc::operation_canceled`.
319
320 @param peer The socket to receive the accepted connection. Any
321 existing connection on this socket will be closed.
322
323 @return An awaitable that completes with `io_result<>`.
324 Returns success on successful accept, or an error code on
325 failure including:
326 - operation_canceled: Cancelled via stop_token or cancel().
327 Check `ec == cond::canceled` for portable comparison.
328
329 @par Preconditions
330 The acceptor must be listening (`is_open() == true`).
331 The peer socket must be associated with the same execution context.
332
333 Both this acceptor and @p peer must outlive the returned
334 awaitable.
335
336 @par Example
337 @code
338 tcp_socket peer(ioc);
339 auto [ec] = co_await acc.accept(peer);
340 if (!ec) {
341 // Use peer socket
342 }
343 @endcode
344 */
345 5714x auto accept(tcp_socket& peer)
346 {
347
1/2
✓ Branch 0 taken 5714 times.
✗ Branch 1 not taken.
5714x if (!is_open())
348 detail::throw_logic_error("accept: acceptor not listening");
349 5714x return accept_awaitable(*this, peer);
350 }
351
352 /** Wait for an incoming connection or readiness condition.
353
354 Suspends until the listen socket is ready in the
355 requested direction, or an error condition is reported.
356 For `wait_type::read`, completion signals that a
357 subsequent @ref accept will succeed without blocking.
358 No connection is consumed.
359
360 @param w The wait direction.
361
362 @return An awaitable that completes with `io_result<>`.
363
364 @par Preconditions
365 The acceptor must be listening. This acceptor must
366 outlive the returned awaitable.
367 */
368 8x [[nodiscard]] auto wait(wait_type w)
369 {
370
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8x if (!is_open())
371 detail::throw_logic_error("wait: acceptor not listening");
372 8x return wait_awaitable(*this, w);
373 }
374
375 /** Cancel any pending asynchronous operations.
376
377 All outstanding operations complete with `errc::operation_canceled`.
378 Check `ec == cond::canceled` for portable comparison.
379 */
380 void cancel();
381
382 /** Get the local endpoint of the acceptor.
383
384 Returns the local address and port to which the acceptor is bound.
385 This is useful when binding to port 0 (ephemeral port) to discover
386 the OS-assigned port number. The endpoint is cached when listen()
387 is called.
388
389 @return The local endpoint, or a default endpoint (0.0.0.0:0) if
390 the acceptor is not listening.
391
392 @par Thread Safety
393 The cached endpoint value is set during listen() and cleared
394 during close(). This function may be called concurrently with
395 accept operations, but must not be called concurrently with
396 listen() or close().
397 */
398 endpoint local_endpoint() const noexcept;
399
400 /** Set a socket option on the acceptor.
401
402 Applies a type-safe socket option to the underlying listening
403 socket. The socket must be open (via `open()` or `listen()`).
404 This is useful for setting options between `open()` and
405 `listen()`, such as `socket_option::reuse_port`.
406
407 @par Example
408 @code
409 acc.open( tcp::v6() );
410 acc.set_option( socket_option::reuse_port( true ) );
411 acc.bind( endpoint( ipv6_address::any(), 8080 ) );
412 acc.listen();
413 @endcode
414
415 @param opt The option to set.
416
417 @throws std::logic_error if the acceptor is not open.
418 @throws std::system_error on failure.
419 */
420 template<class Option>
421 925x void set_option(Option const& opt)
422 {
423
3/6
✓ Branch 0 taken 919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
925x if (!is_open())
424 detail::throw_logic_error("set_option: acceptor not open");
425 1850x std::error_code ec = get().set_option(
426 925x Option::level(), Option::name(), opt.data(), opt.size());
427
3/6
✓ Branch 0 taken 919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
925x if (ec)
428 detail::throw_system_error(ec, "tcp_acceptor::set_option");
429 925x }
430
431 /** Get a socket option from the acceptor.
432
433 Retrieves the current value of a type-safe socket option.
434
435 @par Example
436 @code
437 auto opt = acc.get_option<socket_option::reuse_address>();
438 @endcode
439
440 @return The current option value.
441
442 @throws std::logic_error if the acceptor is not open.
443 @throws std::system_error on failure.
444 */
445 template<class Option>
446 4x Option get_option() const
447 {
448
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4x if (!is_open())
449 detail::throw_logic_error("get_option: acceptor not open");
450 4x Option opt{};
451 4x std::size_t sz = opt.size();
452 std::error_code ec =
453 4x get().get_option(Option::level(), Option::name(), opt.data(), &sz);
454
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4x if (ec)
455 detail::throw_system_error(ec, "tcp_acceptor::get_option");
456 4x opt.resize(sz);
457 4x return opt;
458 }
459
460 /** Define backend hooks for TCP acceptor operations.
461
462 Platform backends derive from this to implement
463 accept, endpoint query, open-state checks, cancellation,
464 and socket-option management.
465 */
466 struct implementation : io_object::implementation
467 {
468 /// Initiate an asynchronous accept operation.
469 virtual std::coroutine_handle<> accept(
470 std::coroutine_handle<>,
471 capy::executor_ref,
472 std::stop_token,
473 std::error_code*,
474 io_object::implementation**) = 0;
475
476 /** Initiate an asynchronous wait for acceptor readiness.
477
478 Completes when the listen socket becomes ready for
479 the specified direction (typically `wait_type::read`
480 for an incoming connection), or an error condition is
481 reported. No connection is consumed.
482 */
483 virtual std::coroutine_handle<> wait(
484 std::coroutine_handle<> h,
485 capy::executor_ref ex,
486 wait_type w,
487 std::stop_token token,
488 std::error_code* ec) = 0;
489
490 /// Returns the cached local endpoint.
491 virtual endpoint local_endpoint() const noexcept = 0;
492
493 /// Return true if the acceptor has a kernel resource open.
494 virtual bool is_open() const noexcept = 0;
495
496 /** Cancel any pending asynchronous operations.
497
498 All outstanding operations complete with operation_canceled error.
499 */
500 virtual void cancel() noexcept = 0;
501
502 /** Set a socket option.
503
504 @param level The protocol level.
505 @param optname The option name.
506 @param data Pointer to the option value.
507 @param size Size of the option value in bytes.
508 @return Error code on failure, empty on success.
509 */
510 virtual std::error_code set_option(
511 int level,
512 int optname,
513 void const* data,
514 std::size_t size) noexcept = 0;
515
516 /** Get a socket option.
517
518 @param level The protocol level.
519 @param optname The option name.
520 @param data Pointer to receive the option value.
521 @param size On entry, the size of the buffer. On exit,
522 the size of the option value.
523 @return Error code on failure, empty on success.
524 */
525 virtual std::error_code
526 get_option(int level, int optname, void* data, std::size_t* size)
527 const noexcept = 0;
528 };
529
530 protected:
531 22x explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
532
533 /// Transfer accepted peer impl to the peer socket.
534 static void
535 12x reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
536 {
537
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12x if (impl)
538 12x peer.h_.reset(impl);
539 12x }
540
541 private:
542 19743x inline implementation& get() const noexcept
543 {
544 19743x return *static_cast<implementation*>(h_.get());
545 }
546 };
547
548 } // namespace boost::corosio
549
550 #endif
551