include/boost/corosio/tcp_acceptor.hpp

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