include/boost/corosio/local_stream_acceptor.hpp

97.8% Lines (90/0/92) 100.0% List of functions (23/0/23)
local_stream_acceptor.hpp
f(x) Functions (23)
Function Calls Lines Blocks
boost::corosio::local_stream_acceptor::wait_awaitable::wait_awaitable(boost::corosio::local_stream_acceptor&, boost::corosio::wait_type) :89 8x 100.0% 100.0% boost::corosio::local_stream_acceptor::wait_awaitable::dispatch(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref) const :92 6x 100.0% 80.0% boost::corosio::local_stream_acceptor::move_accept_awaitable::move_accept_awaitable(boost::corosio::local_stream_acceptor&) :106 4x 100.0% 100.0% boost::corosio::local_stream_acceptor::move_accept_awaitable::await_ready() const :112 4x 100.0% 100.0% boost::corosio::local_stream_acceptor::move_accept_awaitable::await_resume() const :119 4x 80.0% 79.0% boost::corosio::local_stream_acceptor::move_accept_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :133 2x 100.0% 82.0% boost::corosio::local_stream_acceptor::accept_awaitable::accept_awaitable(boost::corosio::local_stream_acceptor&, boost::corosio::local_stream_socket&) :150 29x 100.0% 100.0% boost::corosio::local_stream_acceptor::accept_awaitable::await_ready() const :157 29x 100.0% 100.0% boost::corosio::local_stream_acceptor::accept_awaitable::await_resume() const :164 27x 100.0% 100.0% boost::corosio::local_stream_acceptor::accept_awaitable::await_suspend(std::__n4861::coroutine_handle<void>, boost::capy::io_env const*) :174 27x 100.0% 82.0% boost::corosio::local_stream_acceptor::local_stream_acceptor(boost::corosio::local_stream_acceptor&&) :256 2x 100.0% 100.0% boost::corosio::local_stream_acceptor::is_open() const :336 421x 100.0% 100.0% boost::corosio::local_stream_acceptor::accept(boost::corosio::local_stream_socket&) :358 29x 100.0% 100.0% boost::corosio::local_stream_acceptor::wait(boost::corosio::wait_type) :389 8x 100.0% 100.0% boost::corosio::local_stream_acceptor::accept() :414 4x 100.0% 100.0% void boost::corosio::local_stream_acceptor::set_option<boost::corosio::socket_option::no_delay>(boost::corosio::socket_option::no_delay const&) :511 2x 62.5% 75.0% void boost::corosio::local_stream_acceptor::set_option<boost::corosio::socket_option::reuse_address>(boost::corosio::socket_option::reuse_address const&) :511 4x 87.5% 94.0% boost::corosio::socket_option::no_delay boost::corosio::local_stream_acceptor::get_option<boost::corosio::socket_option::no_delay>() const :537 2x 63.6% 67.0% boost::corosio::socket_option::reuse_address boost::corosio::local_stream_acceptor::get_option<boost::corosio::socket_option::reuse_address>() const :537 4x 90.9% 94.0% boost::corosio::local_stream_acceptor::local_stream_acceptor(boost::corosio::io_object::handle, boost::capy::execution_context&) :621 16x 100.0% 100.0% boost::corosio::local_stream_acceptor::local_stream_acceptor(boost::capy::execution_context&, boost::corosio::local_stream_acceptor&&) :627 2x 100.0% 100.0% boost::corosio::local_stream_acceptor::reset_peer_impl(boost::corosio::local_stream_socket&, boost::corosio::io_object::implementation*) :634 8x 100.0% 100.0% boost::corosio::local_stream_acceptor::get() const :644 494x 100.0% 100.0%
Line 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 if (auto ec = acc.open())
72 co_return ec;
73 if (auto ec = acc.bind(local_endpoint("/tmp/my.sock"),
74 bind_option::unlink_existing))
75 co_return ec;
76 if (auto ec = acc.listen())
77 co_return ec;
78 auto [aec, peer] = co_await acc.accept();
79 @endcode
80 */
81 class BOOST_COROSIO_DECL local_stream_acceptor : public io_object
82 {
83 struct wait_awaitable
84 : detail::void_op_base<wait_awaitable>
85 {
86 local_stream_acceptor& acc_;
87 wait_type w_;
88
89 8x wait_awaitable(local_stream_acceptor& acc, wait_type w) noexcept
90 8x : acc_(acc), w_(w) {}
91
92 6x std::coroutine_handle<> dispatch(
93 std::coroutine_handle<> h, capy::executor_ref ex) const
94 {
95 6x return acc_.get().wait(h, ex, w_, token_, &ec_);
96 }
97 };
98
99 struct move_accept_awaitable
100 {
101 local_stream_acceptor& acc_;
102 std::stop_token token_;
103 mutable std::error_code ec_;
104 mutable io_object::implementation* peer_impl_ = nullptr;
105
106 4x explicit move_accept_awaitable(
107 local_stream_acceptor& acc) noexcept
108 4x : acc_(acc)
109 {
110 4x }
111
112 4x bool await_ready() const noexcept
113 {
114 // A pre-set ec_ means the initiator failed before
115 // dispatch (e.g. a closed object).
116 4x return static_cast<bool>(ec_) || token_.stop_requested();
117 }
118
119 4x [[nodiscard]] capy::io_result<local_stream_socket> await_resume() const noexcept
120 {
121 4x if (token_.stop_requested())
122 return {make_error_code(std::errc::operation_canceled),
123 local_stream_socket()};
124
125 4x if (ec_ || !peer_impl_)
126 2x return {ec_, local_stream_socket()};
127
128 2x local_stream_socket peer(acc_.ctx_);
129 2x reset_peer_impl(peer, peer_impl_);
130 2x return {ec_, std::move(peer)};
131 2x }
132
133 2x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
134 -> std::coroutine_handle<>
135 {
136 2x token_ = env->stop_token;
137 6x return acc_.get().accept(
138 6x h, env->executor, token_, &ec_, &peer_impl_);
139 }
140 };
141
142 struct accept_awaitable
143 {
144 local_stream_acceptor& acc_;
145 local_stream_socket& peer_;
146 std::stop_token token_;
147 mutable std::error_code ec_;
148 mutable io_object::implementation* peer_impl_ = nullptr;
149
150 29x accept_awaitable(
151 local_stream_acceptor& acc, local_stream_socket& peer) noexcept
152 29x : acc_(acc)
153 29x , peer_(peer)
154 {
155 29x }
156
157 29x bool await_ready() const noexcept
158 {
159 // A pre-set ec_ means the initiator failed before
160 // dispatch (e.g. a closed object).
161 29x return static_cast<bool>(ec_) || token_.stop_requested();
162 }
163
164 27x [[nodiscard]] capy::io_result<> await_resume() const noexcept
165 {
166 27x if (token_.stop_requested())
167 4x return {make_error_code(std::errc::operation_canceled)};
168
169 23x if (!ec_ && peer_impl_)
170 17x peer_.h_.reset(peer_impl_);
171 23x return {ec_};
172 }
173
174 27x auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
175 -> std::coroutine_handle<>
176 {
177 27x token_ = env->stop_token;
178 81x return acc_.get().accept(
179 81x h, env->executor, token_, &ec_, &peer_impl_);
180 }
181 };
182
183 public:
184 /** Destructor.
185
186 Closes the acceptor if open, cancelling any pending operations.
187 */
188 ~local_stream_acceptor() override;
189
190 /** Construct an acceptor from an execution context.
191
192 @param ctx The execution context that will own this acceptor.
193 */
194 explicit local_stream_acceptor(capy::execution_context& ctx);
195
196 /** Convenience constructor: open + bind + listen.
197
198 Creates a fully-bound listening acceptor in a single
199 expression, throwing the codes the piecewise `open()` +
200 `bind()` + `listen()` path returns.
201
202 @param ctx The execution context that will own this acceptor.
203 @param ep The local endpoint to bind to.
204 @param backlog The maximum pending connection queue length.
205
206 @throws std::system_error on open, bind, or listen failure.
207 */
208 local_stream_acceptor(
209 capy::execution_context& ctx,
210 corosio::local_endpoint ep,
211 int backlog = 128);
212
213 /** Construct an acceptor from an executor.
214
215 The acceptor is associated with the executor's context.
216
217 @param ex The executor whose context will own the acceptor.
218
219 @tparam Ex A type satisfying @ref capy::Executor. Must not
220 be `local_stream_acceptor` itself (disables implicit
221 conversion from move).
222 */
223 template<class Ex>
224 requires(!std::same_as<std::remove_cvref_t<Ex>, local_stream_acceptor>) &&
225 capy::Executor<Ex>
226 explicit local_stream_acceptor(Ex const& ex) : local_stream_acceptor(ex.context())
227 {
228 }
229
230 /** Convenience constructor from an executor.
231
232 @param ex The executor whose context will own the acceptor.
233 @param ep The local endpoint to bind to.
234 @param backlog The maximum pending connection queue length.
235
236 @throws std::system_error on open, bind, or listen failure.
237 */
238 template<class Ex>
239 requires capy::Executor<Ex>
240 local_stream_acceptor(
241 Ex const& ex, corosio::local_endpoint ep, int backlog = 128)
242 : local_stream_acceptor(ex.context(), std::move(ep), backlog)
243 {
244 }
245
246 /** Move constructor.
247
248 Transfers ownership of the acceptor resources.
249
250 @param other The acceptor to move from.
251
252 @pre No awaitables returned by @p other's methods exist.
253 @pre The execution context associated with @p other must
254 outlive this acceptor.
255 */
256 2x local_stream_acceptor(local_stream_acceptor&& other) noexcept
257 2x : local_stream_acceptor(other.ctx_, std::move(other))
258 {
259 2x }
260
261 /** Move assignment operator.
262
263 Closes any existing acceptor and transfers ownership.
264 Both acceptors must share the same execution context.
265
266 @param other The acceptor to move from.
267
268 @return Reference to this acceptor.
269
270 @pre `&ctx_ == &other.ctx_` (same execution context).
271 @pre No awaitables returned by either `*this` or @p other's
272 methods exist.
273 */
274 local_stream_acceptor& operator=(local_stream_acceptor&& other) noexcept
275 {
276 assert(&ctx_ == &other.ctx_ &&
277 "move-assign requires the same execution_context");
278 if (this != &other)
279 {
280 close();
281 io_object::operator=(std::move(other));
282 }
283 return *this;
284 }
285
286 local_stream_acceptor(local_stream_acceptor const&) = delete;
287 local_stream_acceptor& operator=(local_stream_acceptor const&) = delete;
288
289 /** Create the acceptor socket.
290
291 Failures such as descriptor exhaustion are normal runtime
292 conditions and are reported through the returned error code.
293
294 @param proto The protocol. Defaults to local_stream{}.
295
296 @return The error code, empty on success.
297 */
298 [[nodiscard]] std::error_code open(local_stream proto = {}) noexcept;
299
300 /** Bind to a local endpoint.
301
302 @param ep The local endpoint (path) to bind to.
303 @param opt Bind options. Pass bind_option::unlink_existing
304 to unlink the socket path before binding (ignored for
305 abstract sockets and empty endpoints).
306
307 @return An error code on failure, empty on success.
308
309 A closed acceptor reports `errc::bad_file_descriptor`.
310 */
311 [[nodiscard]] std::error_code
312 bind(corosio::local_endpoint ep,
313 bind_option opt = bind_option::none) noexcept;
314
315 /** Start listening for incoming connections.
316
317 @param backlog The maximum pending connection queue length.
318
319 @return An error code on failure, empty on success.
320
321 A closed acceptor reports `errc::bad_file_descriptor`.
322 */
323 [[nodiscard]] std::error_code listen(int backlog = 128) noexcept;
324
325 /** Close the acceptor.
326
327 Cancels any pending accept operations and releases the
328 underlying socket. Has no effect if the acceptor is not
329 open.
330
331 @post is_open() == false
332 */
333 void close() noexcept;
334
335 /// Check if the acceptor has an open socket handle.
336 421x bool is_open() const noexcept
337 {
338 421x return h_ && get().is_open();
339 }
340
341 /** Initiate an asynchronous accept into an existing socket.
342
343 Completes when a new connection is available. On success
344 @p peer is reset to the accepted connection. Only one
345 accept may be in flight at a time.
346
347 @param peer The socket to receive the accepted connection.
348
349 @par Cancellation
350 Supports cancellation via stop_token or cancel().
351 On cancellation, yields `capy::cond::canceled` and
352 @p peer is not modified.
353
354 @return An awaitable that completes with io_result<>.
355
356 A closed acceptor reports `errc::bad_file_descriptor`.
357 */
358 29x [[nodiscard]] auto accept(local_stream_socket& peer)
359 {
360 29x accept_awaitable aw(*this, peer);
361 29x if (!is_open())
362 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
363 29x return aw;
364 }
365
366 /** Wait for an incoming connection or readiness condition.
367
368 Suspends until the listen socket is ready in the
369 requested direction. For `wait_type::read`, completion
370 signals that a subsequent @ref accept will succeed
371 without blocking; a connection already queued when the
372 wait begins completes it immediately. No connection is
373 consumed.
374
375 @note `wait_type::write` is not usable on an acceptor:
376 writability carries no meaning for a listening socket, so
377 the wait fails with `errc::operation_not_supported` on
378 every backend.
379
380 @param w The wait direction.
381
382 @return An awaitable that completes with `io_result<>`.
383
384 A closed acceptor completes with `errc::bad_file_descriptor`.
385
386 @par Preconditions
387 This acceptor must outlive the returned awaitable.
388 */
389 8x [[nodiscard]] auto wait(wait_type w)
390 {
391 8x wait_awaitable aw(*this, w);
392 8x if (!is_open())
393 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
394 8x return aw;
395 }
396
397 /** Initiate an asynchronous accept, returning the socket.
398
399 Completes when a new connection is available. Only one
400 accept may be in flight at a time.
401
402 @par Cancellation
403 Supports cancellation via stop_token or cancel().
404 On cancellation, yields `capy::cond::canceled` with
405 a default-constructed socket.
406
407 @return An awaitable that completes with
408 io_result<local_stream_socket>.
409
410 A closed acceptor reports `errc::bad_file_descriptor`.
411 On failure the returned socket is default-constructed and
412 may only be destroyed or assigned.
413 */
414 4x [[nodiscard]] auto accept()
415 {
416 4x move_accept_awaitable aw(*this);
417 4x if (!is_open())
418 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
419 4x return aw;
420 }
421
422 /** Cancel pending asynchronous accept operations.
423
424 Outstanding accept operations complete with
425 @c capy::cond::canceled. Safe to call when no
426 operations are pending (no-op).
427 */
428 void cancel() noexcept;
429
430 /** Release ownership of the native socket handle.
431
432 Deregisters the acceptor from the reactor and cancels
433 pending operations without closing the descriptor. The
434 caller takes ownership of the returned handle.
435
436 @return The native handle.
437
438 @throws std::system_error `errc::bad_file_descriptor` if the
439 acceptor is not open.
440
441 @post is_open() == false
442 */
443 native_handle_type release();
444
445 /** Get the native socket handle.
446
447 @return The native socket handle, or -1/INVALID_SOCKET if not
448 open.
449
450 @par Preconditions
451 None. May be called on closed acceptors.
452 */
453 native_handle_type native_handle() const noexcept;
454
455 /** Assign an existing native socket to this acceptor.
456
457 Adopts a listening socket created outside the library —
458 received from a service manager, inherited, or made natively —
459 and registers it with the backend. The socket must be a
460 listening stream socket in the local IPC family. Adoption
461 never alters the descriptor's flags or options: on POSIX the
462 fd must already be non-blocking, and on Windows the socket
463 must be overlapped-capable.
464
465 Adoption does not verify listen state; @ref accept reports the
466 error if the socket is not listening.
467
468 If this object is already open, pending operations complete
469 with `errc::operation_canceled` and the held socket is closed
470 before the new one is adopted.
471
472 @par Exception Safety
473 Strong guarantee on validation failure: the object is
474 unchanged. If backend registration fails, the object either
475 retains its previous socket or is left closed, depending on
476 the backend. In all failure cases the caller retains
477 ownership of `fd`.
478
479 @param fd The native socket to adopt. On success the object
480 owns it and will close it.
481
482 @return The error code, empty on success. Validation and
483 registration failures are normal runtime conditions when
484 adopting foreign descriptors.
485 */
486 [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
487
488 /** Return the local endpoint the acceptor is bound to.
489
490 Returns a default-constructed (empty) endpoint if the
491 acceptor is not open or not yet bound. Safe to call in
492 any state.
493 */
494 corosio::local_endpoint local_endpoint() const noexcept;
495
496 /** Set a socket option on the acceptor.
497
498 Applies a type-safe socket option to the underlying socket.
499 The option type encodes the protocol level and option name.
500
501 @param opt The option to set.
502
503 @tparam Option A socket option type providing static
504 `level()` and `name()` members, and `data()` / `size()`
505 accessors.
506
507 @throws std::system_error `errc::bad_file_descriptor` if the
508 acceptor is not open; otherwise thrown on failure.
509 */
510 template<class Option>
511 6x void set_option(Option const& opt)
512 {
513 6x if (!is_open())
514 2x detail::throw_system_error(
515 4x make_error_code(std::errc::bad_file_descriptor),
516 "local_stream_acceptor::set_option");
517 4x std::error_code ec = get().set_option(
518 Option::level(), Option::name(), opt.data(), opt.size());
519 4x if (ec)
520 2x detail::throw_system_error(ec, "local_stream_acceptor::set_option");
521 2x }
522
523 /** Get a socket option from the acceptor.
524
525 Retrieves the current value of a type-safe socket option.
526
527 @return The current option value.
528
529 @tparam Option A socket option type providing static
530 `level()` and `name()` members, and `data()` / `size()`
531 / `resize()` members.
532
533 @throws std::system_error `errc::bad_file_descriptor` if the
534 acceptor is not open; otherwise thrown on failure.
535 */
536 template<class Option>
537 6x Option get_option() const
538 {
539 6x if (!is_open())
540 2x detail::throw_system_error(
541 4x make_error_code(std::errc::bad_file_descriptor),
542 "local_stream_acceptor::get_option");
543 4x Option opt{};
544 4x std::size_t sz = opt.size();
545 std::error_code ec =
546 4x get().get_option(Option::level(), Option::name(), opt.data(), &sz);
547 4x if (ec)
548 2x detail::throw_system_error(ec, "local_stream_acceptor::get_option");
549 2x opt.resize(sz);
550 2x return opt;
551 }
552
553 /** Backend hooks for local stream acceptor operations.
554
555 Platform backends derive from this to implement
556 accept, option, and lifecycle management.
557 */
558 struct implementation : io_object::implementation
559 {
560 /** Initiate an asynchronous accept.
561
562 On completion the backend sets @p *ec and, on
563 success, stores a pointer to the new socket
564 implementation in @p *impl_out.
565
566 @param h Coroutine handle to resume.
567 @param ex Executor for dispatching the completion.
568 @param token Stop token for cancellation.
569 @param ec Output error code.
570 @param impl_out Output pointer for the accepted socket.
571 @return Coroutine handle to resume immediately.
572 */
573 virtual std::coroutine_handle<> accept(
574 std::coroutine_handle<>,
575 capy::executor_ref,
576 std::stop_token,
577 std::error_code*,
578 io_object::implementation**) = 0;
579
580 /** Initiate an asynchronous wait for acceptor readiness.
581
582 Completes when the listen socket becomes ready for
583 the specified direction. No connection is consumed.
584 */
585 virtual std::coroutine_handle<> wait(
586 std::coroutine_handle<> h,
587 capy::executor_ref ex,
588 wait_type w,
589 std::stop_token token,
590 std::error_code* ec) = 0;
591
592 /// Return the cached local endpoint.
593 virtual corosio::local_endpoint local_endpoint() const noexcept = 0;
594
595 /// Return whether the underlying socket is open.
596 virtual bool is_open() const noexcept = 0;
597
598 /// Return the native handle, or the platform sentinel if closed.
599 virtual native_handle_type native_handle() const noexcept = 0;
600
601 /// Release and return the native handle without closing.
602 virtual native_handle_type release_socket() noexcept = 0;
603
604 /// Cancel pending accept operations.
605 virtual void cancel() noexcept = 0;
606
607 /// Set a raw socket option.
608 virtual std::error_code set_option(
609 int level,
610 int optname,
611 void const* data,
612 std::size_t size) noexcept = 0;
613
614 /// Get a raw socket option.
615 virtual std::error_code
616 get_option(int level, int optname, void* data, std::size_t* size)
617 const noexcept = 0;
618 };
619
620 protected:
621 16x local_stream_acceptor(handle h, capy::execution_context& ctx) noexcept
622 16x : io_object(std::move(h))
623 16x , ctx_(ctx)
624 {
625 16x }
626
627 2x local_stream_acceptor(
628 capy::execution_context& ctx, local_stream_acceptor&& other) noexcept
629 2x : io_object(std::move(other))
630 2x , ctx_(ctx)
631 {
632 2x }
633
634 8x static void reset_peer_impl(
635 local_stream_socket& peer, io_object::implementation* impl) noexcept
636 {
637 8x if (impl)
638 8x peer.h_.reset(impl);
639 8x }
640
641 private:
642 capy::execution_context& ctx_;
643
644 494x inline implementation& get() const noexcept
645 {
646 494x return *static_cast<implementation*>(h_.get());
647 }
648 };
649
650 } // namespace boost::corosio
651
652 #endif // BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP
653