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