include/boost/corosio/local_stream_acceptor.hpp

92.7% Lines (51/55) 100.0% List of functions (14/14) 64.3% Branches (18/28)
local_stream_acceptor.hpp
f(x) Functions (14)
Function Calls Lines Branches Blocks
boost::corosio::local_stream_acceptor::move_accept_awaitable::move_accept_awaitable(boost::corosio::local_stream_acceptor&) :103 1x 100.0% 100.0% boost::corosio::local_stream_acceptor::move_accept_awaitable::await_ready() const :109 1x 100.0% 100.0% boost::corosio::local_stream_acceptor::move_accept_awaitable::await_resume() const :114 1x 70.0% 50.0% 61.5% boost::corosio::local_stream_acceptor::move_accept_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :128 1x 100.0% 100.0% 81.8% boost::corosio::local_stream_acceptor::accept_awaitable::accept_awaitable(boost::corosio::local_stream_acceptor&, boost::corosio::local_stream_socket&) :145 3x 100.0% 100.0% boost::corosio::local_stream_acceptor::accept_awaitable::await_ready() const :152 3x 100.0% 100.0% boost::corosio::local_stream_acceptor::accept_awaitable::await_resume() const :157 3x 83.3% 50.0% 76.9% boost::corosio::local_stream_acceptor::accept_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :167 3x 100.0% 100.0% 81.8% boost::corosio::local_stream_acceptor::is_open() const :293 68x 100.0% 75.0% 100.0% boost::corosio::local_stream_acceptor::accept(boost::corosio::local_stream_socket&) :315 4x 100.0% 100.0% 100.0% boost::corosio::local_stream_acceptor::accept() :358 2x 100.0% 100.0% 100.0% boost::corosio::local_stream_acceptor::local_stream_acceptor(boost::corosio::io_object::handle, boost::capy::execution_context&) :513 6x 100.0% 100.0% boost::corosio::local_stream_acceptor::reset_peer_impl(boost::corosio::local_stream_socket&, boost::corosio::io_object::implementation*) :526 4x 100.0% 50.0% 100.0% boost::corosio::local_stream_acceptor::get() const :536 74x 100.0% 100.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Michael Vandeberg
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_LOCAL_STREAM_ACCEPTOR_HPP
11 #define BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP
12
13 #include <boost/corosio/detail/config.hpp>
14 #include <boost/corosio/detail/except.hpp>
15 #include <boost/corosio/detail/op_base.hpp>
16 #include <boost/corosio/wait_type.hpp>
17 #include <boost/corosio/io/io_object.hpp>
18 #include <boost/capy/io_result.hpp>
19 #include <boost/corosio/local_endpoint.hpp>
20 #include <boost/corosio/local_stream.hpp>
21 #include <boost/corosio/local_stream_socket.hpp>
22 #include <boost/capy/ex/executor_ref.hpp>
23 #include <boost/capy/ex/execution_context.hpp>
24 #include <boost/capy/ex/io_env.hpp>
25 #include <boost/capy/concept/executor.hpp>
26
27 #include <system_error>
28
29 #include <cassert>
30 #include <concepts>
31 #include <coroutine>
32 #include <cstddef>
33 #include <stop_token>
34 #include <type_traits>
35
36 namespace boost::corosio {
37
38 /** Options for @ref local_stream_acceptor::bind().
39
40 Controls filesystem cleanup behavior before binding
41 to a Unix domain socket path.
42 */
43 enum class bind_option
44 {
45 none,
46 /// Unlink the socket path before binding (ignored for abstract paths).
47 unlink_existing
48 };
49
50 /** An asynchronous Unix domain stream acceptor for coroutine I/O.
51
52 This class provides asynchronous Unix domain stream accept
53 operations that return awaitable types. The acceptor binds
54 to a local endpoint (filesystem path or abstract name) and
55 listens for incoming connections.
56
57 The library does NOT automatically unlink the socket path
58 on close. Callers are responsible for removing the socket
59 file before bind (via @ref bind_option::unlink_existing) or
60 after close.
61
62 @par Thread Safety
63 Distinct objects: Safe.@n
64 Shared objects: Unsafe. An acceptor must not have concurrent
65 accept operations.
66
67 @par Example
68 @code
69 io_context ioc;
70 local_stream_acceptor acc(ioc);
71 acc.open();
72 acc.bind(local_endpoint("/tmp/my.sock"),
73 bind_option::unlink_existing);
74 acc.listen();
75 auto [ec, peer] = co_await acc.accept();
76 @endcode
77 */
78 class BOOST_COROSIO_DECL local_stream_acceptor : public io_object
79 {
80 struct wait_awaitable
81 : detail::void_op_base<wait_awaitable>
82 {
83 local_stream_acceptor& acc_;
84 wait_type w_;
85
86 wait_awaitable(local_stream_acceptor& acc, wait_type w) noexcept
87 : acc_(acc), w_(w) {}
88
89 std::coroutine_handle<> dispatch(
90 std::coroutine_handle<> h, capy::executor_ref ex) const
91 {
92 return acc_.get().wait(h, ex, w_, token_, &ec_);
93 }
94 };
95
96 struct move_accept_awaitable
97 {
98 local_stream_acceptor& acc_;
99 std::stop_token token_;
100 mutable std::error_code ec_;
101 mutable io_object::implementation* peer_impl_ = nullptr;
102
103 1x explicit move_accept_awaitable(
104 local_stream_acceptor& acc) noexcept
105 1x : acc_(acc)
106 {
107 1x }
108
109 1x bool await_ready() const noexcept
110 {
111 1x return token_.stop_requested();
112 }
113
114 1x capy::io_result<local_stream_socket> await_resume() const noexcept
115 {
116
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 1 time.
1x if (token_.stop_requested())
117 return {make_error_code(std::errc::operation_canceled),
118 local_stream_socket()};
119
120
3/6
✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 12 not taken.
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 19 taken 1 time.
1x if (ec_ || !peer_impl_)
121 return {ec_, local_stream_socket()};
122
123 1x local_stream_socket peer(acc_.ctx_);
124 1x reset_peer_impl(peer, peer_impl_);
125 1x return {ec_, std::move(peer)};
126 1x }
127
128 1x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
129 -> std::coroutine_handle<>
130 {
131 1x token_ = env->stop_token;
132
1/1
✓ Branch 5 → 6 taken 1 time.
3x return acc_.get().accept(
133 3x h, env->executor, token_, &ec_, &peer_impl_);
134 }
135 };
136
137 struct accept_awaitable
138 {
139 local_stream_acceptor& acc_;
140 local_stream_socket& peer_;
141 std::stop_token token_;
142 mutable std::error_code ec_;
143 mutable io_object::implementation* peer_impl_ = nullptr;
144
145 3x accept_awaitable(
146 local_stream_acceptor& acc, local_stream_socket& peer) noexcept
147 3x : acc_(acc)
148 3x , peer_(peer)
149 {
150 3x }
151
152 3x bool await_ready() const noexcept
153 {
154 3x return token_.stop_requested();
155 }
156
157 3x capy::io_result<> await_resume() const noexcept
158 {
159
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 3 times.
3x if (token_.stop_requested())
160 return {make_error_code(std::errc::operation_canceled)};
161
162
3/6
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 3 times.
✗ Branch 8 → 10 not taken.
✓ Branch 11 → 12 taken 3 times.
✗ Branch 11 → 13 not taken.
3x if (!ec_ && peer_impl_)
163 3x peer_.h_.reset(peer_impl_);
164 3x return {ec_};
165 }
166
167 3x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
168 -> std::coroutine_handle<>
169 {
170 3x token_ = env->stop_token;
171
1/1
✓ Branch 5 → 6 taken 3 times.
9x return acc_.get().accept(
172 9x h, env->executor, token_, &ec_, &peer_impl_);
173 }
174 };
175
176 public:
177 /** Destructor.
178
179 Closes the acceptor if open, cancelling any pending operations.
180 */
181 ~local_stream_acceptor() override;
182
183 /** Construct an acceptor from an execution context.
184
185 @param ctx The execution context that will own this acceptor.
186 */
187 explicit local_stream_acceptor(capy::execution_context& ctx);
188
189 /** Construct an acceptor from an executor.
190
191 The acceptor is associated with the executor's context.
192
193 @param ex The executor whose context will own the acceptor.
194
195 @tparam Ex A type satisfying @ref capy::Executor. Must not
196 be `local_stream_acceptor` itself (disables implicit
197 conversion from move).
198 */
199 template<class Ex>
200 requires(!std::same_as<std::remove_cvref_t<Ex>, local_stream_acceptor>) &&
201 capy::Executor<Ex>
202 explicit local_stream_acceptor(Ex const& ex) : local_stream_acceptor(ex.context())
203 {
204 }
205
206 /** Move constructor.
207
208 Transfers ownership of the acceptor resources.
209
210 @param other The acceptor to move from.
211
212 @pre No awaitables returned by @p other's methods exist.
213 @pre The execution context associated with @p other must
214 outlive this acceptor.
215 */
216 local_stream_acceptor(local_stream_acceptor&& other) noexcept
217 : local_stream_acceptor(other.ctx_, std::move(other))
218 {
219 }
220
221 /** Move assignment operator.
222
223 Closes any existing acceptor and transfers ownership.
224 Both acceptors must share the same execution context.
225
226 @param other The acceptor to move from.
227
228 @return Reference to this acceptor.
229
230 @pre `&ctx_ == &other.ctx_` (same execution context).
231 @pre No awaitables returned by either `*this` or @p other's
232 methods exist.
233 */
234 local_stream_acceptor& operator=(local_stream_acceptor&& other) noexcept
235 {
236 assert(&ctx_ == &other.ctx_ &&
237 "move-assign requires the same execution_context");
238 if (this != &other)
239 {
240 close();
241 io_object::operator=(std::move(other));
242 }
243 return *this;
244 }
245
246 local_stream_acceptor(local_stream_acceptor const&) = delete;
247 local_stream_acceptor& operator=(local_stream_acceptor const&) = delete;
248
249 /** Create the acceptor socket.
250
251 @param proto The protocol. Defaults to local_stream{}.
252
253 @throws std::system_error on failure.
254 */
255 void open(local_stream proto = {});
256
257 /** Bind to a local endpoint.
258
259 @param ep The local endpoint (path) to bind to.
260 @param opt Bind options. Pass bind_option::unlink_existing
261 to unlink the socket path before binding (ignored for
262 abstract sockets and empty endpoints).
263
264 @return An error code on failure, empty on success.
265
266 @throws std::logic_error if the acceptor is not open.
267 */
268 [[nodiscard]] std::error_code
269 bind(corosio::local_endpoint ep,
270 bind_option opt = bind_option::none);
271
272 /** Start listening for incoming connections.
273
274 @param backlog The maximum pending connection queue length.
275
276 @return An error code on failure, empty on success.
277
278 @throws std::logic_error if the acceptor is not open.
279 */
280 [[nodiscard]] std::error_code listen(int backlog = 128);
281
282 /** Close the acceptor.
283
284 Cancels any pending accept operations and releases the
285 underlying socket. Has no effect if the acceptor is not
286 open.
287
288 @post is_open() == false
289 */
290 void close();
291
292 /// Check if the acceptor has an open socket handle.
293 68x bool is_open() const noexcept
294 {
295
3/4
✓ Branch 3 → 4 taken 68 times.
✗ Branch 3 → 8 not taken.
✓ Branch 6 → 7 taken 38 times.
✓ Branch 6 → 8 taken 30 times.
68x return h_ && get().is_open();
296 }
297
298 /** Initiate an asynchronous accept into an existing socket.
299
300 Completes when a new connection is available. On success
301 @p peer is reset to the accepted connection. Only one
302 accept may be in flight at a time.
303
304 @param peer The socket to receive the accepted connection.
305
306 @par Cancellation
307 Supports cancellation via stop_token or cancel().
308 On cancellation, yields `capy::cond::canceled` and
309 @p peer is not modified.
310
311 @return An awaitable that completes with io_result<>.
312
313 @throws std::logic_error if the acceptor is not open.
314 */
315 4x auto accept(local_stream_socket& peer)
316 {
317
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 3 times.
4x if (!is_open())
318 1x detail::throw_logic_error("accept: acceptor not listening");
319 3x return accept_awaitable(*this, peer);
320 }
321
322 /** Wait for an incoming connection or readiness condition.
323
324 Suspends until the listen socket is ready in the
325 requested direction. For `wait_type::read`, completion
326 signals that a subsequent @ref accept will succeed
327 without blocking. No connection is consumed.
328
329 @param w The wait direction.
330
331 @return An awaitable that completes with `io_result<>`.
332
333 @par Preconditions
334 The acceptor must be listening.
335 */
336 [[nodiscard]] auto wait(wait_type w)
337 {
338 if (!is_open())
339 detail::throw_logic_error("wait: acceptor not listening");
340 return wait_awaitable(*this, w);
341 }
342
343 /** Initiate an asynchronous accept, returning the socket.
344
345 Completes when a new connection is available. Only one
346 accept may be in flight at a time.
347
348 @par Cancellation
349 Supports cancellation via stop_token or cancel().
350 On cancellation, yields `capy::cond::canceled` with
351 a default-constructed socket.
352
353 @return An awaitable that completes with
354 io_result<local_stream_socket>.
355
356 @throws std::logic_error if the acceptor is not open.
357 */
358 2x auto accept()
359 {
360
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1 time.
2x if (!is_open())
361 1x detail::throw_logic_error("accept: acceptor not listening");
362 1x return move_accept_awaitable(*this);
363 }
364
365 /** Cancel pending asynchronous accept operations.
366
367 Outstanding accept operations complete with
368 @c capy::cond::canceled. Safe to call when no
369 operations are pending (no-op).
370 */
371 void cancel();
372
373 /** Release ownership of the native socket handle.
374
375 Deregisters the acceptor from the reactor and cancels
376 pending operations without closing the descriptor. The
377 caller takes ownership of the returned handle.
378
379 @return The native handle.
380
381 @throws std::logic_error if the acceptor is not open.
382
383 @post is_open() == false
384 */
385 native_handle_type release();
386
387 /** Return the local endpoint the acceptor is bound to.
388
389 Returns a default-constructed (empty) endpoint if the
390 acceptor is not open or not yet bound. Safe to call in
391 any state.
392 */
393 corosio::local_endpoint local_endpoint() const noexcept;
394
395 /** Set a socket option on the acceptor.
396
397 Applies a type-safe socket option to the underlying socket.
398 The option type encodes the protocol level and option name.
399
400 @param opt The option to set.
401
402 @tparam Option A socket option type providing static
403 `level()` and `name()` members, and `data()` / `size()`
404 accessors.
405
406 @throws std::logic_error if the acceptor is not open.
407 @throws std::system_error on failure.
408 */
409 template<class Option>
410 void set_option(Option const& opt)
411 {
412 if (!is_open())
413 detail::throw_logic_error("set_option: acceptor not open");
414 std::error_code ec = get().set_option(
415 Option::level(), Option::name(), opt.data(), opt.size());
416 if (ec)
417 detail::throw_system_error(ec, "local_stream_acceptor::set_option");
418 }
419
420 /** Get a socket option from the acceptor.
421
422 Retrieves the current value of a type-safe socket option.
423
424 @return The current option value.
425
426 @tparam Option A socket option type providing static
427 `level()` and `name()` members, and `data()` / `size()`
428 / `resize()` members.
429
430 @throws std::logic_error if the acceptor is not open.
431 @throws std::system_error on failure.
432 */
433 template<class Option>
434 Option get_option() const
435 {
436 if (!is_open())
437 detail::throw_logic_error("get_option: acceptor not open");
438 Option opt{};
439 std::size_t sz = opt.size();
440 std::error_code ec =
441 get().get_option(Option::level(), Option::name(), opt.data(), &sz);
442 if (ec)
443 detail::throw_system_error(ec, "local_stream_acceptor::get_option");
444 opt.resize(sz);
445 return opt;
446 }
447
448 /** Backend hooks for local stream acceptor operations.
449
450 Platform backends derive from this to implement
451 accept, option, and lifecycle management.
452 */
453 struct implementation : io_object::implementation
454 {
455 /** Initiate an asynchronous accept.
456
457 On completion the backend sets @p *ec and, on
458 success, stores a pointer to the new socket
459 implementation in @p *impl_out.
460
461 @param h Coroutine handle to resume.
462 @param ex Executor for dispatching the completion.
463 @param token Stop token for cancellation.
464 @param ec Output error code.
465 @param impl_out Output pointer for the accepted socket.
466 @return Coroutine handle to resume immediately.
467 */
468 virtual std::coroutine_handle<> accept(
469 std::coroutine_handle<>,
470 capy::executor_ref,
471 std::stop_token,
472 std::error_code*,
473 io_object::implementation**) = 0;
474
475 /** Initiate an asynchronous wait for acceptor readiness.
476
477 Completes when the listen socket becomes ready for
478 the specified direction. No connection is consumed.
479 */
480 virtual std::coroutine_handle<> wait(
481 std::coroutine_handle<> h,
482 capy::executor_ref ex,
483 wait_type w,
484 std::stop_token token,
485 std::error_code* ec) = 0;
486
487 /// Return the cached local endpoint.
488 virtual corosio::local_endpoint local_endpoint() const noexcept = 0;
489
490 /// Return whether the underlying socket is open.
491 virtual bool is_open() const noexcept = 0;
492
493 /// Release and return the native handle without closing.
494 virtual native_handle_type release_socket() noexcept = 0;
495
496 /// Cancel pending accept operations.
497 virtual void cancel() noexcept = 0;
498
499 /// Set a raw socket option.
500 virtual std::error_code set_option(
501 int level,
502 int optname,
503 void const* data,
504 std::size_t size) noexcept = 0;
505
506 /// Get a raw socket option.
507 virtual std::error_code
508 get_option(int level, int optname, void* data, std::size_t* size)
509 const noexcept = 0;
510 };
511
512 protected:
513 6x local_stream_acceptor(handle h, capy::execution_context& ctx) noexcept
514 6x : io_object(std::move(h))
515 6x , ctx_(ctx)
516 {
517 6x }
518
519 local_stream_acceptor(
520 capy::execution_context& ctx, local_stream_acceptor&& other) noexcept
521 : io_object(std::move(other))
522 , ctx_(ctx)
523 {
524 }
525
526 4x static void reset_peer_impl(
527 local_stream_socket& peer, io_object::implementation* impl) noexcept
528 {
529
1/2
✓ Branch 2 → 3 taken 4 times.
✗ Branch 2 → 4 not taken.
4x if (impl)
530 4x peer.h_.reset(impl);
531 4x }
532
533 private:
534 capy::execution_context& ctx_;
535
536 74x inline implementation& get() const noexcept
537 {
538 74x return *static_cast<implementation*>(h_.get());
539 }
540 };
541
542 } // namespace boost::corosio
543
544 #endif // BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP
545