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