include/boost/corosio/local_stream_acceptor.hpp

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