include/boost/corosio/tcp_acceptor.hpp

100.0% Lines (87 / 87) 100.0% Functions (32 / 32)
tcp_acceptor.hpp
f(x) Functions (32)
Function Calls Lines Blocks
boost::corosio::tcp_acceptor::wait_awaitable::wait_awaitable(boost::corosio::tcp_acceptor&, boost::corosio::wait_type) :75 31x 100.0% 100.0% boost::corosio::tcp_acceptor::wait_awaitable::dispatch(boost::capy::continuation&, boost::capy::executor_ref) const :87 27x 100.0% 80.0% boost::corosio::tcp_acceptor::accept_awaitable::accept_awaitable(boost::corosio::tcp_acceptor&, boost::corosio::tcp_socket&) :104 7553x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::dispatch(boost::capy::continuation&, boost::capy::executor_ref) const :111 7543x 100.0% 80.0% boost::corosio::tcp_acceptor::accept_awaitable::~accept_awaitable() :120 15106x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_awaitable::await_resume() const :126 6741x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_value_awaitable::accept_value_awaitable(boost::corosio::tcp_acceptor&) :144 41x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_value_awaitable::dispatch(boost::capy::continuation&, boost::capy::executor_ref) const :149 37x 100.0% 80.0% boost::corosio::tcp_acceptor::accept_value_awaitable::~accept_value_awaitable() :158 82x 100.0% 100.0% boost::corosio::tcp_acceptor::accept_value_awaitable::await_resume() :164 31x 100.0% 100.0% boost::corosio::tcp_acceptor::tcp_acceptor<boost::corosio::io_context::executor_type>(boost::corosio::io_context::executor_type const&) :215 1x 100.0% 100.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::tcp_acceptor&&) :258 182x 100.0% 100.0% boost::corosio::tcp_acceptor::operator=(boost::corosio::tcp_acceptor&&) :271 3x 100.0% 100.0% boost::corosio::tcp_acceptor::is_open() const :356 21360x 100.0% 100.0% boost::corosio::tcp_acceptor::accept(boost::corosio::tcp_socket&) :393 7553x 100.0% 100.0% boost::corosio::tcp_acceptor::accept() :434 41x 100.0% 100.0% boost::corosio::tcp_acceptor::wait(boost::corosio::wait_type) :464 31x 100.0% 100.0% std::error_code boost::corosio::tcp_acceptor::set_option<boost::corosio::native_socket_option::boolean<1, 15> >(boost::corosio::native_socket_option::boolean<1, 15> const&) :582 2x 83.3% 92.0% std::error_code boost::corosio::tcp_acceptor::set_option<boost::corosio::native_socket_option::boolean<1, 2> >(boost::corosio::native_socket_option::boolean<1, 2> const&) :582 12x 83.3% 92.0% std::error_code boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::reuse_address>(boost::corosio::socket_option::reuse_address const&) :582 1409x 100.0% 100.0% std::error_code boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::reuse_port>(boost::corosio::socket_option::reuse_port const&) :582 2x 83.3% 92.0% std::error_code boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::send_buffer_size>(boost::corosio::socket_option::send_buffer_size const&) :582 7x 83.3% 92.0% std::error_code boost::corosio::tcp_acceptor::set_option<boost::corosio::socket_option::v6_only>(boost::corosio::socket_option::v6_only const&) :582 4x 83.3% 92.0% std::tuple<std::error_code, boost::corosio::native_socket_option::boolean<1, 15> > boost::corosio::tcp_acceptor::get_option<boost::corosio::native_socket_option::boolean<1, 15> >() const :605 2x 80.0% 73.0% std::tuple<std::error_code, boost::corosio::socket_option::reuse_address> boost::corosio::tcp_acceptor::get_option<boost::corosio::socket_option::reuse_address>() const :605 10x 100.0% 88.0% std::tuple<std::error_code, boost::corosio::socket_option::reuse_port> boost::corosio::tcp_acceptor::get_option<boost::corosio::socket_option::reuse_port>() const :605 2x 80.0% 73.0% std::tuple<std::error_code, boost::corosio::socket_option::send_buffer_size> boost::corosio::tcp_acceptor::get_option<boost::corosio::socket_option::send_buffer_size>() const :605 7x 80.0% 73.0% std::tuple<std::error_code, boost::corosio::socket_option::v6_only> boost::corosio::tcp_acceptor::get_option<boost::corosio::socket_option::v6_only>() const :605 2x 70.0% 65.0% boost::corosio::tcp_acceptor::tcp_acceptor(boost::corosio::io_object::handle) :741 47x 100.0% 100.0% boost::corosio::tcp_acceptor::reset_peer_impl(boost::corosio::tcp_socket&, boost::corosio::io_object::implementation*) :763 15x 100.0% 100.0% boost::corosio::tcp_acceptor::take_peer(boost::corosio::tcp_acceptor&, std::error_code, boost::corosio::io_object::implementation*&) :781 39x 100.0% 100.0% boost::corosio::tcp_acceptor::get() const :799 33119x 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 // 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/family.hpp>
16 #include <boost/corosio/detail/config.hpp>
17 #include <boost/corosio/detail/native_handle.hpp>
18 #include <boost/corosio/detail/op_base.hpp>
19 #include <boost/corosio/error.hpp>
20 #include <boost/corosio/wait_type.hpp>
21 #include <boost/corosio/io/io_object.hpp>
22 #include <boost/capy/results.hpp>
23 #include <boost/corosio/endpoint.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/await_context.hpp>
28 #include <boost/capy/concept/executor.hpp>
29
30 #include <system_error>
31
32 #include <concepts>
33 #include <boost/capy/continuation.hpp>
34
35 #include <coroutine>
36 #include <cstddef>
37 #include <stop_token>
38 #include <type_traits>
39 #include <utility>
40
41 namespace boost::corosio {
42
43 /** Accepts inbound TCP connections, from a coroutine.
44
45 This class provides asynchronous TCP accept operations that return
46 awaitable types. The acceptor binds to a local endpoint and listens
47 for incoming connections.
48
49 Each accept operation participates in the affine awaitable protocol.
50 It resumes the awaiting coroutine through that coroutine's executor,
51 not through the executor of the context that owns the acceptor.
52
53 @par Thread Safety
54 Distinct objects: Safe.@n
55 Shared objects: Unsafe. An acceptor must not have concurrent accept
56 operations.
57
58 @par Semantics
59 Wraps the platform TCP listener. Operations dispatch to
60 OS accept APIs via the `io_context` reactor.
61
62 @par Example
63 @par !example convenience_construction
64
65 @par Example
66 @par !example fine_grained_setup
67 */
68 class BOOST_COROSIO_DECL tcp_acceptor : public io_object
69 {
70 struct wait_awaitable : detail::void_op_base<wait_awaitable>
71 {
72 private:
73 friend tcp_acceptor;
74
75 31x wait_awaitable(tcp_acceptor& acc, wait_type w) noexcept
76 62x : acc_(acc)
77 31x , w_(w)
78 {
79 31x }
80
81 friend detail::void_op_base<wait_awaitable>;
82
83 tcp_acceptor& acc_;
84 wait_type w_;
85
86 std::coroutine_handle<>
87 27x dispatch(capy::continuation& cont, capy::executor_ref ex) const
88 {
89 27x return acc_.get().wait(cont, ex, w_, token_, &ec_);
90 }
91 };
92
93 struct accept_awaitable : detail::void_op_base<accept_awaitable>
94 {
95 private:
96 friend tcp_acceptor;
97 friend detail::void_op_base<accept_awaitable>;
98
99 tcp_acceptor& acc_;
100 tcp_socket& peer_;
101 mutable io_object::implementation* peer_impl_ = nullptr;
102
103
104 7553x accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
105 15106x : acc_(acc)
106 7553x , peer_(peer)
107 {
108 7553x }
109
110 std::coroutine_handle<>
111 7543x dispatch(capy::continuation& cont, capy::executor_ref ex) const
112 {
113 22629x return acc_.get().accept(
114 22629x cont, ex, this->token_, &this->ec_, &peer_impl_);
115 }
116
117 public:
118 // A peer accepted for an awaiter that never resumed is closed
119 // here, or nothing would own it.
120 15106x ~accept_awaitable()
121 {
122 15106x if (peer_impl_)
123 2x discard_peer(acc_, peer_impl_);
124 15106x }
125
126 6741x [[nodiscard]] capy::results<> await_resume() const noexcept
127 {
128 6741x if (!this->ec_ && peer_impl_)
129 6580x peer_.h_.reset(std::exchange(peer_impl_, nullptr));
130 6741x return {this->ec_};
131 }
132 };
133
134 struct accept_value_awaitable : detail::void_op_base<accept_value_awaitable>
135 {
136 private:
137 friend tcp_acceptor;
138 friend detail::void_op_base<accept_value_awaitable>;
139
140 tcp_acceptor& acc_;
141 mutable io_object::implementation* peer_impl_ = nullptr;
142
143
144 41x explicit accept_value_awaitable(tcp_acceptor& acc) noexcept : acc_(acc)
145 {
146 41x }
147
148 std::coroutine_handle<>
149 37x dispatch(capy::continuation& cont, capy::executor_ref ex) const
150 {
151 111x return acc_.get().accept(
152 111x cont, ex, this->token_, &this->ec_, &peer_impl_);
153 }
154
155 public:
156 // A peer accepted for an awaiter that never resumed is closed
157 // here, or nothing would own it.
158 82x ~accept_value_awaitable()
159 {
160 82x if (peer_impl_)
161 2x discard_peer(acc_, peer_impl_);
162 82x }
163
164 31x [[nodiscard]] capy::results<tcp_socket> await_resume() noexcept
165 {
166 31x return take_peer(acc_, this->ec_, peer_impl_);
167 }
168 };
169
170 public:
171 /** Closes the acceptor if open, cancelling any pending operations.
172 */
173 ~tcp_acceptor() override;
174
175 /** Construct an acceptor from an execution context.
176
177 @param ctx The execution context that owns this acceptor.
178 */
179 explicit tcp_acceptor(capy::execution_context& ctx);
180
181 /** Convenience constructor: open + configure + bind + listen.
182
183 Creates a fully bound listening acceptor in a single
184 expression, throwing the codes the piecewise `open()` +
185 `set_option()` + `bind()` + `listen()` path reports. The
186 address family is deduced from @p ep.
187
188 Before binding, the constructor configures address reuse so a
189 server can rebind its port immediately after a restart. It
190 sets `SO_REUSEADDR` on POSIX and `SO_EXCLUSIVEADDRUSE` on
191 Windows. Windows does not use `SO_REUSEADDR` because it
192 instead grants other sockets bind-over rights. A second
193 listener on an occupied endpoint therefore throws
194 `errc::address_in_use` on every platform.
195
196 @param ctx The execution context that owns 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 open, configuration, bind, or
201 listen failure.
202 */
203 tcp_acceptor(capy::execution_context& ctx, endpoint ep, int backlog = 128);
204
205 /** Construct an acceptor from an executor.
206
207 The acceptor is associated with the executor's context. `Ex`
208 must satisfy `capy::Executor`.
209
210 @param ex The executor whose context owns the acceptor.
211 */
212 template<class Ex>
213 requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
214 capy::Executor<Ex>
215 1x explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
216 {
217 1x }
218
219 /** Convenience constructor from an executor.
220
221 Creates a fully bound listening acceptor in a single
222 expression, throwing the codes the piecewise `open()` +
223 `set_option()` + `bind()` + `listen()` path reports. The
224 address family is deduced from @p ep.
225
226 Before binding, the constructor configures address reuse so a
227 server can rebind its port immediately after a restart. It
228 sets `SO_REUSEADDR` on POSIX and `SO_EXCLUSIVEADDRUSE` on
229 Windows. Windows does not use `SO_REUSEADDR` because it
230 instead grants other sockets bind-over rights. A second
231 listener on an occupied endpoint therefore throws
232 `errc::address_in_use` on every platform.
233
234 `Ex` must satisfy `capy::Executor`.
235
236 @param ex The executor whose context owns the acceptor.
237 @param ep The local endpoint to bind to.
238 @param backlog The maximum pending connection queue length.
239
240 @throws std::system_error on open, configuration, bind, or
241 listen failure.
242 */
243 template<class Ex>
244 requires capy::Executor<Ex>
245 tcp_acceptor(Ex const& ex, endpoint ep, int backlog = 128)
246 : tcp_acceptor(ex.context(), ep, backlog)
247 {
248 }
249
250 /** Transfers ownership of the acceptor resources.
251
252 @param other The acceptor to move from.
253
254 @pre No awaitables returned by @p other's methods exist.
255 @pre The execution context associated with @p other must
256 outlive this acceptor.
257 */
258 182x tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
259
260 /** Closes any existing acceptor and transfers ownership.
261
262 @param other The acceptor to move from.
263
264 @pre No awaitables returned by either `*this` or @p other's
265 methods exist.
266 @pre The execution context associated with @p other must
267 outlive this acceptor.
268
269 @return Reference to this acceptor.
270 */
271 3x tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
272 {
273 3x if (this != &other)
274 {
275 3x close();
276 3x h_ = std::move(other.h_);
277 }
278 3x return *this;
279 }
280
281 /// Copy construction is disabled; the handle is uniquely owned.
282 tcp_acceptor(tcp_acceptor const&) = delete;
283 /// Copy assignment is disabled; the handle is uniquely owned.
284 tcp_acceptor& operator=(tcp_acceptor const&) = delete;
285
286 /** Create the acceptor socket without binding or listening.
287
288 Creates a TCP socket with dual-stack enabled for IPv6.
289 Does not set SO_REUSEADDR. Call `set_option` explicitly
290 if needed.
291
292 If the acceptor is already open, this function is a no-op.
293
294 Failures such as descriptor exhaustion are normal runtime
295 conditions and are reported through the returned error code.
296
297 @param f The address family (IPv4 or IPv6). Defaults to
298 `family::v4`.
299
300 @par Example
301 @par !example open
302
303 @see @ref bind, @ref listen
304
305 @return The error code, empty on success.
306 */
307 [[nodiscard]] std::error_code open(family f = family::v4) noexcept;
308
309 /** Bind to a local endpoint.
310
311 The acceptor must be open. Binds the socket to @p ep and
312 caches the resolved local endpoint (useful when port 0 is
313 used to request an ephemeral port).
314
315 @param ep The local endpoint to bind to.
316
317 @return An error code indicating success or the reason for
318 failure.
319
320 @par Error Conditions
321 @li `errc::address_in_use`: The endpoint is already in use.
322 @li `errc::address_not_available`: The address is not available
323 on any local interface.
324 @li `errc::permission_denied`: Insufficient privileges to bind
325 to the endpoint (e.g., privileged port).
326 @li `errc::bad_file_descriptor`: The acceptor is not open.
327 */
328 [[nodiscard]] std::error_code bind(endpoint ep) noexcept;
329
330 /** Start listening for incoming connections.
331
332 The acceptor must be open and bound. Registers the acceptor
333 with the platform reactor.
334
335 @param backlog The maximum length of the queue of pending
336 connections. Defaults to 128.
337
338 @return An error code indicating success or the reason for
339 failure.
340
341 A closed acceptor reports `errc::bad_file_descriptor`.
342 */
343 [[nodiscard]] std::error_code listen(int backlog = 128) noexcept;
344
345 /** Close the acceptor.
346
347 Releases acceptor resources. Any pending operations complete
348 with `errc::operation_canceled`.
349 */
350 void close() noexcept;
351
352 /** Check if the acceptor is listening.
353
354 @return `true` if the acceptor is open and listening.
355 */
356 21360x bool is_open() const noexcept
357 {
358 21360x return h_ && get().is_open();
359 }
360
361 /** Initiate an asynchronous accept operation.
362
363 Accepts an incoming connection and initializes the provided
364 socket with the new connection. The acceptor must be listening
365 before calling this function.
366
367 The operation supports cancellation via `std::stop_token` through
368 the affine awaitable protocol. If the associated stop token is
369 triggered, the operation completes immediately with
370 `errc::operation_canceled`.
371
372 @param peer The socket to receive the accepted connection. Any
373 existing connection on this socket is closed.
374
375 @return An awaitable that completes with `results<>`.
376 Returns success on successful accept, or an error code on
377 failure including:
378 - `operation_canceled`: Cancelled via stop_token or cancel().
379 Check `ec == cond::canceled` for portable comparison.
380
381 A closed acceptor completes with `errc::bad_file_descriptor`.
382
383 @pre The peer socket must be associated with the same execution context.
384
385 Both this acceptor and @p peer must outlive the returned
386 awaitable.
387
388 @par Example
389 @par !example accept_into_a_reused_socket
390
391 @see @ref accept()
392 */
393 7553x [[nodiscard]] auto accept(tcp_socket& peer)
394 {
395 7553x accept_awaitable aw(*this, peer);
396 7553x if (!is_open())
397 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
398 7553x return aw;
399 }
400
401 /** Initiate an asynchronous accept operation, returning the peer.
402
403 Accepts an incoming connection and returns a newly constructed
404 socket for it, associated with this acceptor's execution context.
405 The acceptor must be listening before calling this function.
406
407 The caller does not pre-construct the peer socket. The returned
408 socket shares this acceptor's execution context.
409
410 The operation supports cancellation via `std::stop_token` through
411 the affine awaitable protocol. If the associated stop token is
412 triggered, the operation completes immediately with
413 `errc::operation_canceled`.
414
415 @return An awaitable that completes with `results<tcp_socket>`.
416 On success the payload is the connected peer socket; on failure
417 (including cancellation) the error code is set and the payload
418 socket is unconnected. Errors include:
419 - `operation_canceled`: Cancelled via stop_token or cancel().
420 Check `ec == cond::canceled` for portable comparison.
421
422 A closed acceptor completes with `errc::bad_file_descriptor`.
423 On failure the returned socket is default-constructed and not
424 open. It has no context, so it may only be destroyed, assigned,
425 or checked with `is_open()`.
426
427 @pre This acceptor must outlive the returned awaitable.
428
429 @par Example
430 @par !example accept_returning_a_new_socket
431
432 @see @ref accept(tcp_socket&)
433 */
434 41x [[nodiscard]] auto accept()
435 {
436 41x accept_value_awaitable aw(*this);
437 41x if (!is_open())
438 4x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
439 41x return aw;
440 }
441
442 /** Wait for an incoming connection or readiness condition.
443
444 Suspends until the listen socket is ready in the
445 requested direction, or an error condition is reported.
446 For `wait_type::read`, completion signals that a
447 subsequent @ref accept succeeds without blocking. A
448 connection already queued when the wait begins completes
449 it immediately. No connection is consumed.
450
451 @note `wait_type::write` is not usable on an acceptor:
452 writability carries no meaning for a listening socket, so
453 the wait fails with `errc::operation_not_supported` on
454 every backend.
455
456 @param w The wait direction.
457
458 @return An awaitable that completes with `results<>`.
459
460 A closed acceptor completes with `errc::bad_file_descriptor`.
461
462 @pre This acceptor must outlive the returned awaitable.
463 */
464 31x [[nodiscard]] auto wait(wait_type w)
465 {
466 31x wait_awaitable aw(*this, w);
467 31x if (!is_open())
468 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
469 31x return aw;
470 }
471
472 /** Cancel any pending asynchronous operations.
473
474 Accept and wait transfer no bytes, so a cancellation always wins:
475 an operation reports `errc::operation_canceled` even when it had
476 already succeeded when the cancellation landed. Check
477 `ec == cond::canceled` for portable comparison.
478 */
479 void cancel() noexcept;
480
481 /** Get the native socket handle.
482
483 Returns the underlying platform-specific socket descriptor.
484 On POSIX systems this is an `int` file descriptor.
485 On Windows this is a `SOCKET` handle.
486
487 @return The native socket handle, or -1/INVALID_SOCKET if not open.
488
489 @pre None. May be called on closed acceptors.
490 */
491 native_handle_type native_handle() const noexcept;
492
493 /** Assign an existing native socket to this acceptor.
494
495 Adopts a listening socket created outside the library. The
496 socket may come from a service manager, be inherited, or be
497 created natively. Adoption registers the socket with the
498 backend. The socket must be a listening stream socket in the
499 `AF_INET` or `AF_INET6` family.
500 Adoption never alters the descriptor's flags or options: on
501 POSIX the fd must already be non-blocking, and on Windows the
502 socket must be overlapped-capable.
503
504 Adoption does not verify listen state; @ref accept reports the
505 error if the socket is not listening.
506
507 The object must be closed. To replace a held socket, `close()`
508 or `release()` it first.
509
510 @par Exception Safety
511 Throws nothing. On failure the object is unchanged and the
512 caller retains ownership of `fd`.
513
514 @param fd The native socket to adopt. On success the object
515 owns it and closes it.
516
517 @return `error::already_open` if this object is open.
518 Otherwise the error code, empty on success. Validation and
519 registration failures are normal runtime conditions when
520 adopting foreign descriptors.
521 */
522 [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
523
524 /** Release ownership of the native socket handle.
525
526 Deregisters the socket from the backend and cancels pending
527 operations without closing the descriptor. The caller takes
528 ownership of the returned handle.
529
530 A closed object reports `errc::bad_file_descriptor` and the
531 returned handle is the invalid sentinel ( -1, or
532 `INVALID_SOCKET` on Windows ), never zero.
533
534 @note With the IOCP backend, a socket released while operations
535 on it are in flight stays bound to this context's completion
536 port for good. Windows refuses to unbind a handle with I/O
537 outstanding, so no context, this one included, can assign it
538 afterwards. Its cancelled operations still complete normally.
539
540 @return The error code and the native handle.
541
542 @post is_open() == false
543 */
544 [[nodiscard]] capy::results<native_handle_type> release() noexcept;
545
546 /** Get the local endpoint of the acceptor.
547
548 Returns the local address and port to which the acceptor is bound.
549 This is useful when binding to port 0 (ephemeral port) to discover
550 the OS-assigned port number. The endpoint is cached when bind()
551 is called.
552
553 @return The local endpoint, or a default endpoint (0.0.0.0:0) if
554 the acceptor is not open.
555
556 @par Thread Safety
557 The cached endpoint value is set during bind() and cleared
558 during close(). This function may be called concurrently with
559 accept operations, but must not be called concurrently with
560 bind() or close().
561 */
562 endpoint local_endpoint() const noexcept;
563
564 /** Set a socket option on the acceptor.
565
566 Applies a type-safe socket option to the underlying listening
567 socket. The socket must be open (via `open()` or `listen()`).
568 This is useful for setting options between `open()` and
569 `listen()`, such as `socket_option::reuse_port`.
570
571 Fails with `errc::bad_file_descriptor` if the acceptor is not
572 open, or with the error the operating system reports.
573
574 @par Example
575 @par !example set_option
576
577 @param opt The option to set.
578
579 @return The error code, empty on success.
580 */
581 template<class Option>
582 1436x [[nodiscard]] std::error_code set_option(Option const& opt) noexcept
583 {
584 1436x if (!is_open())
585 2x return make_error_code(std::errc::bad_file_descriptor);
586 1434x auto const fam = get().family();
587 1434x return get().set_option(
588 1434x opt.level(fam), opt.name(fam), opt.data(fam), opt.size(fam));
589 }
590
591 /** Get a socket option from the acceptor.
592
593 Retrieves the current value of a type-safe socket option.
594
595 Fails with `errc::bad_file_descriptor` if the acceptor is not
596 open, or with the error the operating system reports. On
597 failure the option is value-initialized.
598
599 @par Example
600 @par !example get_option
601
602 @return The error code and the option value.
603 */
604 template<class Option>
605 23x [[nodiscard]] capy::results<Option> get_option() const noexcept
606 {
607 23x if (!is_open())
608 2x return {make_error_code(std::errc::bad_file_descriptor), Option{}};
609 21x Option opt{};
610 21x auto const fam = get().family();
611 21x std::size_t sz = opt.size(fam);
612 21x if (auto ec = get().get_option(
613 opt.level(fam), opt.name(fam), opt.data(fam), &sz))
614 8x return {ec, Option{}};
615 13x opt.resize(fam, sz);
616 13x return {std::error_code{}, std::move(opt)};
617 }
618
619 /** Define backend hooks for TCP acceptor operations.
620
621 Platform backends derive from this to implement
622 accept, endpoint query, open-state checks, cancellation,
623 and socket-option management.
624 */
625 struct implementation : io_object::implementation
626 {
627 /** Initiate an asynchronous accept operation.
628
629 @param cont Continuation to resume on completion; it lives in
630 the awaiting frame until then.
631 @param ex Executor for dispatching the completion.
632 @param token Stop token for cancellation.
633 @param ec Output error code.
634 @param impl_out Output implementation for the accepted peer.
635
636 @return Coroutine handle to resume immediately.
637 */
638 virtual std::coroutine_handle<> accept(
639 capy::continuation& cont,
640 capy::executor_ref ex,
641 std::stop_token token,
642 std::error_code* ec,
643 io_object::implementation** impl_out) = 0;
644
645 /** Initiate an asynchronous wait for acceptor readiness.
646
647 Completes when the listen socket becomes ready for
648 the specified direction (typically `wait_type::read`
649 for an incoming connection), or an error condition is
650 reported. No connection is consumed.
651
652 @param cont Continuation to resume on completion; it lives in
653 the awaiting frame until then.
654 @param ex Executor for dispatching the completion.
655 @param w The direction to wait on.
656 @param token Stop token for cancellation.
657 @param ec Output error code.
658
659 @return Coroutine handle to resume immediately.
660 */
661 virtual std::coroutine_handle<> wait(
662 capy::continuation& cont,
663 capy::executor_ref ex,
664 wait_type w,
665 std::stop_token token,
666 std::error_code* ec) = 0;
667
668 /** Returns the cached local endpoint.
669
670 @return The cached local endpoint.
671 */
672 virtual endpoint local_endpoint() const noexcept = 0;
673
674 /** Return true if the acceptor has a kernel resource open.
675
676 @return true if the acceptor has a kernel resource open.
677 */
678 virtual bool is_open() const noexcept = 0;
679
680 /** Return the native handle, or the platform sentinel if closed.
681
682 @return The native handle, or the platform sentinel if closed.
683 */
684 virtual native_handle_type native_handle() const noexcept = 0;
685
686 /** Return the socket's address family.
687
688 Socket options render for this family.
689
690 @return The socket's address family.
691 */
692 virtual corosio::family family() const noexcept = 0;
693
694 /** Release and return the native handle without closing.
695
696 @return The native handle.
697 */
698 virtual native_handle_type release_socket() noexcept = 0;
699
700 /** Cancel any pending asynchronous operations.
701
702 Accept and wait transfer no bytes, so a cancellation always
703 wins: an operation reports `operation_canceled` even when it
704 had already succeeded when the cancellation landed.
705 */
706 virtual void cancel() noexcept = 0;
707
708 /** Set a socket option.
709
710 @param level The protocol level.
711 @param optname The option name.
712 @param data Pointer to the option value.
713 @param size Size of the option value in bytes.
714 @return Error code on failure, empty on success.
715 */
716 virtual std::error_code set_option(
717 int level,
718 int optname,
719 void const* data,
720 std::size_t size) noexcept = 0;
721
722 /** Get a socket option.
723
724 @param level The protocol level.
725 @param optname The option name.
726 @param data Pointer to receive the option value.
727 @param size On entry, the size of the buffer. On exit,
728 the size of the option value.
729 @return Error code on failure, empty on success.
730 */
731 virtual std::error_code
732 get_option(int level, int optname, void* data, std::size_t* size)
733 const noexcept = 0;
734 };
735
736 protected:
737 /** Adopt an existing handle.
738
739 @param h The handle the acceptor takes ownership of.
740 */
741 47x explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
742
743 /** Adopt an existing handle, then open, configure, bind, and listen.
744
745 Lets a derived acceptor offer the convenience constructor while
746 supplying its own service.
747
748 @param h The handle the acceptor takes ownership of.
749 @param ep The local endpoint to bind to.
750 @param backlog The maximum pending connection queue length.
751
752 @throws std::system_error on open, configuration, bind, or
753 listen failure.
754 */
755 tcp_acceptor(handle h, endpoint ep, int backlog);
756
757 /** Transfer the accepted peer implementation to the peer socket.
758
759 @param peer The socket that receives the transferred implementation.
760 @param impl The accepted peer implementation, or null to do nothing.
761 */
762 static void
763 15x reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
764 {
765 15x if (impl)
766 15x peer.h_.reset(impl);
767 15x }
768
769 /** Return the result of an accept that yields the peer.
770
771 The peer socket is built only on success, so a failed accept
772 never touches the acceptor's context, which a moved-from
773 acceptor lacks. On failure the peer is default-constructed.
774
775 @param acc The acceptor that performed the accept.
776 @param ec The completion code.
777 @param impl The accepted peer implementation; taken on success.
778
779 @return The completion code and the peer.
780 */
781 39x static capy::results<tcp_socket> take_peer(
782 tcp_acceptor& acc,
783 std::error_code ec,
784 io_object::implementation*& impl) noexcept
785 {
786 39x if (ec || !impl)
787 12x return {ec, tcp_socket()};
788
789 27x tcp_socket peer(acc.context());
790 27x peer.h_.reset(std::exchange(impl, nullptr));
791 27x return {ec, std::move(peer)};
792 27x }
793
794 /// Close and release a non-null accepted peer no socket took over.
795 static void
796 discard_peer(tcp_acceptor& acc, io_object::implementation* impl) noexcept;
797
798 private:
799 33119x inline implementation& get() const noexcept
800 {
801 33119x return *static_cast<implementation*>(h_.get());
802 }
803 };
804
805 } // namespace boost::corosio
806
807 #endif
808