TLA Line data 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 HIT 2 : wait_awaitable(tcp_acceptor& acc, wait_type w) noexcept
92 2 : : acc_(acc), w_(w) {}
93 :
94 2 : std::coroutine_handle<> dispatch(
95 : std::coroutine_handle<> h, capy::executor_ref ex) const
96 : {
97 2 : 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 8415 : accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
110 8415 : : acc_(acc)
111 8415 : , peer_(peer)
112 : {
113 8415 : }
114 :
115 8415 : bool await_ready() const noexcept
116 : {
117 8415 : return token_.stop_requested();
118 : }
119 :
120 8415 : capy::io_result<> await_resume() const noexcept
121 : {
122 8415 : if (token_.stop_requested())
123 6 : return {make_error_code(std::errc::operation_canceled)};
124 :
125 8409 : if (!ec_ && peer_impl_)
126 8403 : peer_.h_.reset(peer_impl_);
127 8409 : return {ec_};
128 : }
129 :
130 8415 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
131 : -> std::coroutine_handle<>
132 : {
133 8415 : token_ = env->stop_token;
134 25245 : return acc_.get().accept(
135 25245 : 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 2 : 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 2 : tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
219 : {
220 2 : if (this != &other)
221 : {
222 2 : close();
223 2 : h_ = std::move(other.h_);
224 : }
225 2 : 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 9641 : bool is_open() const noexcept
305 : {
306 9641 : 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 8415 : auto accept(tcp_socket& peer)
346 : {
347 8415 : if (!is_open())
348 MIS 0 : detail::throw_logic_error("accept: acceptor not listening");
349 HIT 8415 : 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 2 : [[nodiscard]] auto wait(wait_type w)
369 : {
370 2 : if (!is_open())
371 MIS 0 : detail::throw_logic_error("wait: acceptor not listening");
372 HIT 2 : 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 171 : void set_option(Option const& opt)
422 : {
423 171 : if (!is_open())
424 MIS 0 : detail::throw_logic_error("set_option: acceptor not open");
425 HIT 171 : std::error_code ec = get().set_option(
426 : Option::level(), Option::name(), opt.data(), opt.size());
427 171 : if (ec)
428 MIS 0 : detail::throw_system_error(ec, "tcp_acceptor::set_option");
429 HIT 171 : }
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 4 : 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 4 : reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
536 : {
537 4 : if (impl)
538 4 : peer.h_.reset(impl);
539 4 : }
540 :
541 : private:
542 18389 : inline implementation& get() const noexcept
543 : {
544 18389 : return *static_cast<implementation*>(h_.get());
545 : }
546 : };
547 :
548 : } // namespace boost::corosio
549 :
550 : #endif
|