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/io/io_object.hpp>
17 : #include <boost/capy/io_result.hpp>
18 : #include <boost/corosio/endpoint.hpp>
19 : #include <boost/corosio/tcp.hpp>
20 : #include <boost/corosio/tcp_socket.hpp>
21 : #include <boost/capy/ex/executor_ref.hpp>
22 : #include <boost/capy/ex/execution_context.hpp>
23 : #include <boost/capy/ex/io_env.hpp>
24 : #include <boost/capy/concept/executor.hpp>
25 :
26 : #include <system_error>
27 :
28 : #include <concepts>
29 : #include <coroutine>
30 : #include <cstddef>
31 : #include <memory>
32 : #include <stop_token>
33 : #include <type_traits>
34 :
35 : namespace boost::corosio {
36 :
37 : /** An asynchronous TCP acceptor for coroutine I/O.
38 :
39 : This class provides asynchronous TCP accept operations that return
40 : awaitable types. The acceptor binds to a local endpoint and listens
41 : for incoming connections.
42 :
43 : Each accept operation participates in the affine awaitable protocol,
44 : ensuring coroutines resume on the correct executor.
45 :
46 : @par Thread Safety
47 : Distinct objects: Safe.@n
48 : Shared objects: Unsafe. An acceptor must not have concurrent accept
49 : operations.
50 :
51 : @par Semantics
52 : Wraps the platform TCP listener. Operations dispatch to
53 : OS accept APIs via the io_context reactor.
54 :
55 : @par Example
56 : @code
57 : // Convenience constructor: open + SO_REUSEADDR + bind + listen
58 : io_context ioc;
59 : tcp_acceptor acc( ioc, endpoint( 8080 ) );
60 :
61 : tcp_socket peer( ioc );
62 : auto [ec] = co_await acc.accept( peer );
63 : if ( !ec ) {
64 : // peer is now a connected socket
65 : auto [ec2, n] = co_await peer.read_some( buf );
66 : }
67 : @endcode
68 :
69 : @par Example
70 : @code
71 : // Fine-grained setup
72 : tcp_acceptor acc( ioc );
73 : acc.open( tcp::v6() );
74 : acc.set_option( socket_option::reuse_address( true ) );
75 : acc.set_option( socket_option::v6_only( true ) );
76 : if ( auto ec = acc.bind( endpoint( ipv6_address::any(), 8080 ) ) )
77 : return ec;
78 : if ( auto ec = acc.listen() )
79 : return ec;
80 : @endcode
81 : */
82 : class BOOST_COROSIO_DECL tcp_acceptor : public io_object
83 : {
84 : struct accept_awaitable
85 : {
86 : tcp_acceptor& acc_;
87 : tcp_socket& peer_;
88 : std::stop_token token_;
89 : mutable std::error_code ec_;
90 : mutable io_object::implementation* peer_impl_ = nullptr;
91 :
92 HIT 7695 : accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
93 7695 : : acc_(acc)
94 7695 : , peer_(peer)
95 : {
96 7695 : }
97 :
98 7695 : bool await_ready() const noexcept
99 : {
100 7695 : return token_.stop_requested();
101 : }
102 :
103 7695 : capy::io_result<> await_resume() const noexcept
104 : {
105 7695 : if (token_.stop_requested())
106 6 : return {make_error_code(std::errc::operation_canceled)};
107 :
108 7689 : if (!ec_ && peer_impl_)
109 7683 : peer_.h_.reset(peer_impl_);
110 7689 : return {ec_};
111 : }
112 :
113 7695 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
114 : -> std::coroutine_handle<>
115 : {
116 7695 : token_ = env->stop_token;
117 23085 : return acc_.get().accept(
118 23085 : h, env->executor, token_, &ec_, &peer_impl_);
119 : }
120 : };
121 :
122 : public:
123 : /** Destructor.
124 :
125 : Closes the acceptor if open, cancelling any pending operations.
126 : */
127 : ~tcp_acceptor() override;
128 :
129 : /** Construct an acceptor from an execution context.
130 :
131 : @param ctx The execution context that will own this acceptor.
132 : */
133 : explicit tcp_acceptor(capy::execution_context& ctx);
134 :
135 : /** Convenience constructor: open + SO_REUSEADDR + bind + listen.
136 :
137 : Creates a fully-bound listening acceptor in a single
138 : expression. The address family is deduced from @p ep.
139 :
140 : @param ctx The execution context that will own this acceptor.
141 : @param ep The local endpoint to bind to.
142 : @param backlog The maximum pending connection queue length.
143 :
144 : @throws std::system_error on bind or listen failure.
145 : */
146 : tcp_acceptor(
147 : capy::execution_context& ctx, endpoint ep, int backlog = 128 );
148 :
149 : /** Construct an acceptor from an executor.
150 :
151 : The acceptor is associated with the executor's context.
152 :
153 : @param ex The executor whose context will own the acceptor.
154 : */
155 : template<class Ex>
156 : requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
157 : capy::Executor<Ex>
158 : explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
159 : {
160 : }
161 :
162 : /** Convenience constructor from an executor.
163 :
164 : @param ex The executor whose context will own the acceptor.
165 : @param ep The local endpoint to bind to.
166 : @param backlog The maximum pending connection queue length.
167 :
168 : @throws std::system_error on bind or listen failure.
169 : */
170 : template<class Ex>
171 : requires capy::Executor<Ex>
172 : tcp_acceptor(Ex const& ex, endpoint ep, int backlog = 128 )
173 : : tcp_acceptor(ex.context(), ep, backlog)
174 : {
175 : }
176 :
177 : /** Move constructor.
178 :
179 : Transfers ownership of the acceptor resources.
180 :
181 : @param other The acceptor to move from.
182 :
183 : @pre No awaitables returned by @p other's methods exist.
184 : @pre The execution context associated with @p other must
185 : outlive this acceptor.
186 : */
187 2 : tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
188 :
189 : /** Move assignment operator.
190 :
191 : Closes any existing acceptor and transfers ownership.
192 :
193 : @param other The acceptor to move from.
194 :
195 : @pre No awaitables returned by either `*this` or @p other's
196 : methods exist.
197 : @pre The execution context associated with @p other must
198 : outlive this acceptor.
199 :
200 : @return Reference to this acceptor.
201 : */
202 2 : tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
203 : {
204 2 : if (this != &other)
205 : {
206 2 : close();
207 2 : h_ = std::move(other.h_);
208 : }
209 2 : return *this;
210 : }
211 :
212 : tcp_acceptor(tcp_acceptor const&) = delete;
213 : tcp_acceptor& operator=(tcp_acceptor const&) = delete;
214 :
215 : /** Create the acceptor socket without binding or listening.
216 :
217 : Creates a TCP socket with dual-stack enabled for IPv6.
218 : Does not set SO_REUSEADDR — call `set_option` explicitly
219 : if needed.
220 :
221 : If the acceptor is already open, this function is a no-op.
222 :
223 : @param proto The protocol (IPv4 or IPv6). Defaults to
224 : `tcp::v4()`.
225 :
226 : @throws std::system_error on failure.
227 :
228 : @par Example
229 : @code
230 : acc.open( tcp::v6() );
231 : acc.set_option( socket_option::reuse_address( true ) );
232 : acc.bind( endpoint( ipv6_address::any(), 8080 ) );
233 : acc.listen();
234 : @endcode
235 :
236 : @see bind, listen
237 : */
238 : void open( tcp proto = tcp::v4() );
239 :
240 : /** Bind to a local endpoint.
241 :
242 : The acceptor must be open. Binds the socket to @p ep and
243 : caches the resolved local endpoint (useful when port 0 is
244 : used to request an ephemeral port).
245 :
246 : @param ep The local endpoint to bind to.
247 :
248 : @return An error code indicating success or the reason for
249 : failure.
250 :
251 : @par Error Conditions
252 : @li `errc::address_in_use`: The endpoint is already in use.
253 : @li `errc::address_not_available`: The address is not available
254 : on any local interface.
255 : @li `errc::permission_denied`: Insufficient privileges to bind
256 : to the endpoint (e.g., privileged port).
257 :
258 : @throws std::logic_error if the acceptor is not open.
259 : */
260 : [[nodiscard]] std::error_code bind( endpoint ep );
261 :
262 : /** Start listening for incoming connections.
263 :
264 : The acceptor must be open and bound. Registers the acceptor
265 : with the platform reactor.
266 :
267 : @param backlog The maximum length of the queue of pending
268 : connections. Defaults to 128.
269 :
270 : @return An error code indicating success or the reason for
271 : failure.
272 :
273 : @throws std::logic_error if the acceptor is not open.
274 : */
275 : [[nodiscard]] std::error_code listen( int backlog = 128 );
276 :
277 : /** Close the acceptor.
278 :
279 : Releases acceptor resources. Any pending operations complete
280 : with `errc::operation_canceled`.
281 : */
282 : void close();
283 :
284 : /** Check if the acceptor is listening.
285 :
286 : @return `true` if the acceptor is open and listening.
287 : */
288 8691 : bool is_open() const noexcept
289 : {
290 8691 : return h_ && get().is_open();
291 : }
292 :
293 : /** Initiate an asynchronous accept operation.
294 :
295 : Accepts an incoming connection and initializes the provided
296 : socket with the new connection. The acceptor must be listening
297 : before calling this function.
298 :
299 : The operation supports cancellation via `std::stop_token` through
300 : the affine awaitable protocol. If the associated stop token is
301 : triggered, the operation completes immediately with
302 : `errc::operation_canceled`.
303 :
304 : @param peer The socket to receive the accepted connection. Any
305 : existing connection on this socket will be closed.
306 :
307 : @return An awaitable that completes with `io_result<>`.
308 : Returns success on successful accept, or an error code on
309 : failure including:
310 : - operation_canceled: Cancelled via stop_token or cancel().
311 : Check `ec == cond::canceled` for portable comparison.
312 :
313 : @par Preconditions
314 : The acceptor must be listening (`is_open() == true`).
315 : The peer socket must be associated with the same execution context.
316 :
317 : Both this acceptor and @p peer must outlive the returned
318 : awaitable.
319 :
320 : @par Example
321 : @code
322 : tcp_socket peer(ioc);
323 : auto [ec] = co_await acc.accept(peer);
324 : if (!ec) {
325 : // Use peer socket
326 : }
327 : @endcode
328 : */
329 7695 : auto accept(tcp_socket& peer)
330 : {
331 7695 : if (!is_open())
332 MIS 0 : detail::throw_logic_error("accept: acceptor not listening");
333 HIT 7695 : return accept_awaitable(*this, peer);
334 : }
335 :
336 : /** Cancel any pending asynchronous operations.
337 :
338 : All outstanding operations complete with `errc::operation_canceled`.
339 : Check `ec == cond::canceled` for portable comparison.
340 : */
341 : void cancel();
342 :
343 : /** Get the local endpoint of the acceptor.
344 :
345 : Returns the local address and port to which the acceptor is bound.
346 : This is useful when binding to port 0 (ephemeral port) to discover
347 : the OS-assigned port number. The endpoint is cached when listen()
348 : is called.
349 :
350 : @return The local endpoint, or a default endpoint (0.0.0.0:0) if
351 : the acceptor is not listening.
352 :
353 : @par Thread Safety
354 : The cached endpoint value is set during listen() and cleared
355 : during close(). This function may be called concurrently with
356 : accept operations, but must not be called concurrently with
357 : listen() or close().
358 : */
359 : endpoint local_endpoint() const noexcept;
360 :
361 : /** Set a socket option on the acceptor.
362 :
363 : Applies a type-safe socket option to the underlying listening
364 : socket. The socket must be open (via `open()` or `listen()`).
365 : This is useful for setting options between `open()` and
366 : `listen()`, such as `socket_option::reuse_port`.
367 :
368 : @par Example
369 : @code
370 : acc.open( tcp::v6() );
371 : acc.set_option( socket_option::reuse_port( true ) );
372 : acc.bind( endpoint( ipv6_address::any(), 8080 ) );
373 : acc.listen();
374 : @endcode
375 :
376 : @param opt The option to set.
377 :
378 : @throws std::logic_error if the acceptor is not open.
379 : @throws std::system_error on failure.
380 : */
381 : template<class Option>
382 137 : void set_option( Option const& opt )
383 : {
384 137 : if (!is_open())
385 MIS 0 : detail::throw_logic_error(
386 : "set_option: acceptor not open" );
387 HIT 137 : std::error_code ec = get().set_option(
388 : Option::level(), Option::name(), opt.data(), opt.size() );
389 137 : if (ec)
390 MIS 0 : detail::throw_system_error(
391 : ec, "tcp_acceptor::set_option" );
392 HIT 137 : }
393 :
394 : /** Get a socket option from the acceptor.
395 :
396 : Retrieves the current value of a type-safe socket option.
397 :
398 : @par Example
399 : @code
400 : auto opt = acc.get_option<socket_option::reuse_address>();
401 : @endcode
402 :
403 : @return The current option value.
404 :
405 : @throws std::logic_error if the acceptor is not open.
406 : @throws std::system_error on failure.
407 : */
408 : template<class Option>
409 : Option get_option() const
410 : {
411 : if (!is_open())
412 : detail::throw_logic_error(
413 : "get_option: acceptor not open" );
414 : Option opt{};
415 : std::size_t sz = opt.size();
416 : std::error_code ec = get().get_option(
417 : Option::level(), Option::name(), opt.data(), &sz );
418 : if (ec)
419 : detail::throw_system_error(
420 : ec, "tcp_acceptor::get_option" );
421 : opt.resize( sz );
422 : return opt;
423 : }
424 :
425 : struct implementation : io_object::implementation
426 : {
427 : virtual std::coroutine_handle<> accept(
428 : std::coroutine_handle<>,
429 : capy::executor_ref,
430 : std::stop_token,
431 : std::error_code*,
432 : io_object::implementation**) = 0;
433 :
434 : /// Returns the cached local endpoint.
435 : virtual endpoint local_endpoint() const noexcept = 0;
436 :
437 : /// Return true if the acceptor has a kernel resource open.
438 : virtual bool is_open() const noexcept = 0;
439 :
440 : /** Cancel any pending asynchronous operations.
441 :
442 : All outstanding operations complete with operation_canceled error.
443 : */
444 : virtual void cancel() noexcept = 0;
445 :
446 : /** Set a socket option.
447 :
448 : @param level The protocol level.
449 : @param optname The option name.
450 : @param data Pointer to the option value.
451 : @param size Size of the option value in bytes.
452 : @return Error code on failure, empty on success.
453 : */
454 : virtual std::error_code set_option(
455 : int level, int optname,
456 : void const* data, std::size_t size) noexcept = 0;
457 :
458 : /** Get a socket option.
459 :
460 : @param level The protocol level.
461 : @param optname The option name.
462 : @param data Pointer to receive the option value.
463 : @param size On entry, the size of the buffer. On exit,
464 : the size of the option value.
465 : @return Error code on failure, empty on success.
466 : */
467 : virtual std::error_code get_option(
468 : int level, int optname,
469 : void* data, std::size_t* size) const noexcept = 0;
470 : };
471 :
472 : protected:
473 4 : explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
474 :
475 : /// Transfer accepted peer impl to the peer socket.
476 : static void
477 4 : reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
478 : {
479 4 : if (impl)
480 4 : peer.h_.reset(impl);
481 4 : }
482 :
483 : private:
484 16643 : inline implementation& get() const noexcept
485 : {
486 16643 : return *static_cast<implementation*>(h_.get());
487 : }
488 : };
489 :
490 : } // namespace boost::corosio
491 :
492 : #endif
|