include/boost/corosio/tcp_acceptor.hpp

92.0% Lines (46/50) 100.0% List of functions (16/16)
tcp_acceptor.hpp
f(x) Functions (16)
Function Calls Lines Blocks
boost::corosio::tcp_acceptor::wait_awaitable::wait_awaitable(boost::corosio::tcp_acceptor&, boost::corosio::wait_type) :91 2x 100.0% 100.0% boost::corosio::tcp_acceptor::wait_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :94 2x 100.0% 80.0% boost::corosio::tcp_acceptor::accept_awaitable::accept_awaitable(boost::corosio::tcp_acceptor&, boost::corosio::tcp_socket&) :109 8424x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_ready() const :115 8424x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_resume() const :120 8424x 100.0% 92.0% boost::corosio::tcp_acceptor::accept_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :130 8424x 100.0% 82.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::tcp_acceptor&&) :203 2x 100.0% 100.0% boost::corosio::tcp_acceptor::operator=(boost::corosio::tcp_acceptor&&) :218 2x 100.0% 100.0% boost::corosio::tcp_acceptor::is_open() const :304 9650x 100.0% 100.0% boost::corosio::tcp_acceptor::accept(boost::corosio::tcp_socket&) :345 8424x 75.0% 80.0% boost::corosio::tcp_acceptor::wait(boost::corosio::wait_type) :368 2x 75.0% 80.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::reuse_address>(boost::corosio::socket_option::reuse_address const&) :421 169x 71.4% 86.0% void boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::v6_only>(boost::corosio::socket_option::v6_only const&) :421 2x 71.4% 86.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::io_object::handle) :531 4x 100.0% 100.0% boost::corosio::tcp_acceptor::reset_peer_impl(boost::corosio::tcp_socket&, boost::corosio::io_object::implementation*) :535 4x 100.0% 100.0% boost::corosio::tcp_acceptor::get() const :542 18407x 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 2x wait_awaitable(tcp_acceptor& acc, wait_type w) noexcept
92 2x : acc_(acc), w_(w) {}
93
94 2x std::coroutine_handle<> dispatch(
95 std::coroutine_handle<> h, capy::executor_ref ex) const
96 {
97 2x 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 8424x accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
110 8424x : acc_(acc)
111 8424x , peer_(peer)
112 {
113 8424x }
114
115 8424x bool await_ready() const noexcept
116 {
117 8424x return token_.stop_requested();
118 }
119
120 8424x capy::io_result<> await_resume() const noexcept
121 {
122 8424x if (token_.stop_requested())
123 6x return {make_error_code(std::errc::operation_canceled)};
124
125 8418x if (!ec_ && peer_impl_)
126 8412x peer_.h_.reset(peer_impl_);
127 8418x return {ec_};
128 }
129
130 8424x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
131 -> std::coroutine_handle<>
132 {
133 8424x token_ = env->stop_token;
134 25272x return acc_.get().accept(
135 25272x 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 2x 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 2x if (this != &other)
221 {
222 2x close();
223 2x h_ = std::move(other.h_);
224 }
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 9650x bool is_open() const noexcept
305 {
306 9650x 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 8424x auto accept(tcp_socket& peer)
346 {
347 8424x if (!is_open())
348 detail::throw_logic_error("accept: acceptor not listening");
349 8424x 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 2x [[nodiscard]] auto wait(wait_type w)
369 {
370 2x if (!is_open())
371 detail::throw_logic_error("wait: acceptor not listening");
372 2x 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 171x void set_option(Option const& opt)
422 {
423 171x if (!is_open())
424 detail::throw_logic_error("set_option: acceptor not open");
425 171x std::error_code ec = get().set_option(
426 Option::level(), Option::name(), opt.data(), opt.size());
427 171x if (ec)
428 detail::throw_system_error(ec, "tcp_acceptor::set_option");
429 171x }
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 Option get_option() const
447 {
448 if (!is_open())
449 detail::throw_logic_error("get_option: acceptor not open");
450 Option opt{};
451 std::size_t sz = opt.size();
452 std::error_code ec =
453 get().get_option(Option::level(), Option::name(), opt.data(), &sz);
454 if (ec)
455 detail::throw_system_error(ec, "tcp_acceptor::get_option");
456 opt.resize(sz);
457 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 4x 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 4x reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
536 {
537 4x if (impl)
538 4x peer.h_.reset(impl);
539 4x }
540
541 private:
542 18407x inline implementation& get() const noexcept
543 {
544 18407x return *static_cast<implementation*>(h_.get());
545 }
546 };
547
548 } // namespace boost::corosio
549
550 #endif
551