include/boost/corosio/local_stream_acceptor.hpp

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