include/boost/corosio/tcp_acceptor.hpp

96.7% Lines (29/30) 100.0% Functions (9/9)
include/boost/corosio/tcp_acceptor.hpp
Line Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
11 #define BOOST_COROSIO_TCP_ACCEPTOR_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14 #include <boost/corosio/detail/except.hpp>
15 #include <boost/corosio/io_object.hpp>
16 #include <boost/capy/io_result.hpp>
17 #include <boost/corosio/endpoint.hpp>
18 #include <boost/corosio/tcp_socket.hpp>
19 #include <boost/capy/ex/executor_ref.hpp>
20 #include <boost/capy/ex/execution_context.hpp>
21 #include <boost/capy/ex/io_env.hpp>
22 #include <boost/capy/concept/executor.hpp>
23
24 #include <system_error>
25
26 #include <concepts>
27 #include <coroutine>
28 #include <cstddef>
29 #include <memory>
30 #include <stop_token>
31 #include <type_traits>
32
33 namespace boost::corosio {
34
35 /** An asynchronous TCP acceptor for coroutine I/O.
36
37 This class provides asynchronous TCP accept operations that return
38 awaitable types. The acceptor binds to a local endpoint and listens
39 for incoming connections.
40
41 Each accept operation participates in the affine awaitable protocol,
42 ensuring coroutines resume on the correct executor.
43
44 @par Thread Safety
45 Distinct objects: Safe.@n
46 Shared objects: Unsafe. An acceptor must not have concurrent accept
47 operations.
48
49 @par Semantics
50 Wraps the platform TCP listener. Operations dispatch to
51 OS accept APIs via the io_context reactor.
52
53 @par Example
54 @code
55 io_context ioc;
56 tcp_acceptor acc(ioc);
57 if (auto ec = acc.listen(endpoint(8080))) // Bind to port 8080
58 return ec;
59
60 tcp_socket peer(ioc);
61 auto [ec] = co_await acc.accept(peer);
62 if (!ec) {
63 // peer is now a connected socket
64 auto [ec2, n] = co_await peer.read_some(buf);
65 }
66 @endcode
67 */
68 class BOOST_COROSIO_DECL tcp_acceptor : public io_object
69 {
70 struct accept_awaitable
71 {
72 tcp_acceptor& acc_;
73 tcp_socket& peer_;
74 std::stop_token token_;
75 mutable std::error_code ec_;
76 mutable io_object::implementation* peer_impl_ = nullptr;
77
78 8387 accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
79 8387 : acc_(acc)
80 8387 , peer_(peer)
81 {
82 8387 }
83
84 8387 bool await_ready() const noexcept
85 {
86 8387 return token_.stop_requested();
87 }
88
89 8387 capy::io_result<> await_resume() const noexcept
90 {
91 8387 if (token_.stop_requested())
92 6 return {make_error_code(std::errc::operation_canceled)};
93
94 8381 if (!ec_ && peer_impl_)
95 8375 peer_.h_.reset(peer_impl_);
96 8381 return {ec_};
97 }
98
99 8387 auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
100 -> std::coroutine_handle<>
101 {
102 8387 token_ = env->stop_token;
103 25161 return acc_.get().accept(
104 25161 h, env->executor, token_, &ec_, &peer_impl_);
105 }
106 };
107
108 public:
109 /** Destructor.
110
111 Closes the acceptor if open, cancelling any pending operations.
112 */
113 ~tcp_acceptor() override;
114
115 /** Construct an acceptor from an execution context.
116
117 @param ctx The execution context that will own this acceptor.
118 */
119 explicit tcp_acceptor(capy::execution_context& ctx);
120
121 /** Construct an acceptor from an executor.
122
123 The acceptor is associated with the executor's context.
124
125 @param ex The executor whose context will own the acceptor.
126 */
127 template<class Ex>
128 requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
129 capy::Executor<Ex>
130 explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
131 {
132 }
133
134 /** Move constructor.
135
136 Transfers ownership of the acceptor resources.
137
138 @param other The acceptor to move from.
139 */
140 2 tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
141
142 /** Move assignment operator.
143
144 Closes any existing acceptor and transfers ownership.
145 @param other The acceptor to move from.
146
147 @return Reference to this acceptor.
148 */
149 2 tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
150 {
151 2 if (this != &other)
152 {
153 2 close();
154 2 h_ = std::move(other.h_);
155 }
156 2 return *this;
157 }
158
159 tcp_acceptor(tcp_acceptor const&) = delete;
160 tcp_acceptor& operator=(tcp_acceptor const&) = delete;
161
162 /** Open, bind, and listen on an endpoint.
163
164 Creates an IPv4 TCP socket, binds it to the specified endpoint,
165 and begins listening for incoming connections. This must be
166 called before initiating accept operations.
167
168 @param ep The local endpoint to bind to. Use `endpoint(port)` to
169 bind to all interfaces on a specific port.
170
171 @param backlog The maximum length of the queue of pending
172 connections. Defaults to 128.
173
174 @return An error code indicating success or the reason for failure.
175 A default-constructed error code indicates success.
176
177 @par Error Conditions
178 @li `errc::address_in_use`: The endpoint is already in use.
179 @li `errc::address_not_available`: The address is not available
180 on any local interface.
181 @li `errc::permission_denied`: Insufficient privileges to bind
182 to the endpoint (e.g., privileged port).
183 @li `errc::operation_not_supported`: The acceptor service is
184 unavailable in the context (POSIX only).
185
186 @throws Nothing.
187 */
188 [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
189
190 /** Close the acceptor.
191
192 Releases acceptor resources. Any pending operations complete
193 with `errc::operation_canceled`.
194 */
195 void close();
196
197 /** Check if the acceptor is listening.
198
199 @return `true` if the acceptor is open and listening.
200 */
201 8812 bool is_open() const noexcept
202 {
203 8812 return h_ && get().is_open();
204 }
205
206 /** Initiate an asynchronous accept operation.
207
208 Accepts an incoming connection and initializes the provided
209 socket with the new connection. The acceptor must be listening
210 before calling this function.
211
212 The operation supports cancellation via `std::stop_token` through
213 the affine awaitable protocol. If the associated stop token is
214 triggered, the operation completes immediately with
215 `errc::operation_canceled`.
216
217 @param peer The socket to receive the accepted connection. Any
218 existing connection on this socket will be closed.
219
220 @return An awaitable that completes with `io_result<>`.
221 Returns success on successful accept, or an error code on
222 failure including:
223 - operation_canceled: Cancelled via stop_token or cancel().
224 Check `ec == cond::canceled` for portable comparison.
225
226 @par Preconditions
227 The acceptor must be listening (`is_open() == true`).
228 The peer socket must be associated with the same execution context.
229
230 @par Example
231 @code
232 tcp_socket peer(ioc);
233 auto [ec] = co_await acc.accept(peer);
234 if (!ec) {
235 // Use peer socket
236 }
237 @endcode
238 */
239 8387 auto accept(tcp_socket& peer)
240 {
241 8387 if (!is_open())
242 detail::throw_logic_error("accept: acceptor not listening");
243 8387 return accept_awaitable(*this, peer);
244 }
245
246 /** Cancel any pending asynchronous operations.
247
248 All outstanding operations complete with `errc::operation_canceled`.
249 Check `ec == cond::canceled` for portable comparison.
250 */
251 void cancel();
252
253 /** Get the local endpoint of the acceptor.
254
255 Returns the local address and port to which the acceptor is bound.
256 This is useful when binding to port 0 (ephemeral port) to discover
257 the OS-assigned port number. The endpoint is cached when listen()
258 is called.
259
260 @return The local endpoint, or a default endpoint (0.0.0.0:0) if
261 the acceptor is not listening.
262
263 @par Thread Safety
264 The cached endpoint value is set during listen() and cleared
265 during close(). This function may be called concurrently with
266 accept operations, but must not be called concurrently with
267 listen() or close().
268 */
269 endpoint local_endpoint() const noexcept;
270
271 struct implementation : io_object::implementation
272 {
273 virtual std::coroutine_handle<> accept(
274 std::coroutine_handle<>,
275 capy::executor_ref,
276 std::stop_token,
277 std::error_code*,
278 io_object::implementation**) = 0;
279
280 /// Returns the cached local endpoint.
281 virtual endpoint local_endpoint() const noexcept = 0;
282
283 /// Return true if the acceptor has a kernel resource open.
284 virtual bool is_open() const noexcept = 0;
285
286 /** Cancel any pending asynchronous operations.
287
288 All outstanding operations complete with operation_canceled error.
289 */
290 virtual void cancel() noexcept = 0;
291 };
292
293 private:
294 17279 inline implementation& get() const noexcept
295 {
296 17279 return *static_cast<implementation*>(h_.get());
297 }
298 };
299
300 } // namespace boost::corosio
301
302 #endif
303