include/boost/corosio/local_stream_acceptor.hpp

100.0% Lines (83 / 83) 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 :303 489x 100.0% 100.0% boost::corosio::local_stream_acceptor::accept(boost::corosio::local_stream_socket&) :325 29x 100.0% 100.0% boost::corosio::local_stream_acceptor::wait(boost::corosio::wait_type) :356 8x 100.0% 100.0% boost::corosio::local_stream_acceptor::accept() :381 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&) :478 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&) :478 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 :504 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 :504 4x 90.9% 94.0% boost::corosio::local_stream_acceptor::local_stream_acceptor(boost::corosio::io_object::handle, boost::capy::execution_context&) :588 18x 100.0% 100.0% boost::corosio::local_stream_acceptor::local_stream_acceptor(boost::capy::execution_context&, boost::corosio::local_stream_acceptor&&) :594 2x 100.0% 100.0% boost::corosio::local_stream_acceptor::reset_peer_impl(boost::corosio::local_stream_socket&, boost::corosio::io_object::implementation*) :601 8x 100.0% 100.0% boost::corosio::local_stream_acceptor::get() const :611 564x 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 : 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 @param proto The protocol. Defaults to local_stream{}.
262
263 @return The error code, empty on success.
264 */
265 [[nodiscard]] std::error_code open(local_stream proto = {}) noexcept;
266
267 /** Bind to a local endpoint.
268
269 @param ep The local endpoint (path) to bind to.
270 @param opt Bind options. Pass bind_option::unlink_existing
271 to unlink the socket path before binding (ignored for
272 abstract sockets and empty endpoints).
273
274 @return An error code on failure, empty on success.
275
276 A closed acceptor reports `errc::bad_file_descriptor`.
277 */
278 [[nodiscard]] std::error_code bind(
279 corosio::local_endpoint ep,
280 bind_option opt = bind_option::none) noexcept;
281
282 /** Start listening for incoming connections.
283
284 @param backlog The maximum pending connection queue length.
285
286 @return An error code on failure, empty on success.
287
288 A closed acceptor reports `errc::bad_file_descriptor`.
289 */
290 [[nodiscard]] std::error_code listen(int backlog = 128) noexcept;
291
292 /** Close the acceptor.
293
294 Cancels any pending accept operations and releases the
295 underlying socket. Has no effect if the acceptor is not
296 open.
297
298 @post is_open() == false
299 */
300 void close() noexcept;
301
302 /// Check if the acceptor has an open socket handle.
303 489x bool is_open() const noexcept
304 {
305 489x return h_ && get().is_open();
306 }
307
308 /** Initiate an asynchronous accept into an existing socket.
309
310 Completes when a new connection is available. On success
311 @p peer is reset to the accepted connection. Only one
312 accept may be in flight at a time.
313
314 @param peer The socket to receive the accepted connection.
315
316 @par Cancellation
317 Supports cancellation via stop_token or cancel().
318 On cancellation, yields `capy::cond::canceled` and
319 @p peer is not modified.
320
321 @return An awaitable that completes with io_result<>.
322
323 A closed acceptor reports `errc::bad_file_descriptor`.
324 */
325 29x [[nodiscard]] auto accept(local_stream_socket& peer)
326 {
327 29x accept_awaitable aw(*this, peer);
328 29x if (!is_open())
329 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
330 29x return aw;
331 }
332
333 /** Wait for an incoming connection or readiness condition.
334
335 Suspends until the listen socket is ready in the
336 requested direction. For `wait_type::read`, completion
337 signals that a subsequent @ref accept will succeed
338 without blocking; a connection already queued when the
339 wait begins completes it immediately. No connection is
340 consumed.
341
342 @note `wait_type::write` is not usable on an acceptor:
343 writability carries no meaning for a listening socket, so
344 the wait fails with `errc::operation_not_supported` on
345 every backend.
346
347 @param w The wait direction.
348
349 @return An awaitable that completes with `io_result<>`.
350
351 A closed acceptor completes with `errc::bad_file_descriptor`.
352
353 @par Preconditions
354 This acceptor must outlive the returned awaitable.
355 */
356 8x [[nodiscard]] auto wait(wait_type w)
357 {
358 8x wait_awaitable aw(*this, w);
359 8x if (!is_open())
360 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
361 8x return aw;
362 }
363
364 /** Initiate an asynchronous accept, returning the socket.
365
366 Completes when a new connection is available. Only one
367 accept may be in flight at a time.
368
369 @par Cancellation
370 Supports cancellation via stop_token or cancel().
371 On cancellation, yields `capy::cond::canceled` with
372 a default-constructed socket.
373
374 @return An awaitable that completes with
375 io_result<local_stream_socket>.
376
377 A closed acceptor reports `errc::bad_file_descriptor`.
378 On failure the returned socket is default-constructed and
379 may only be destroyed or assigned.
380 */
381 6x [[nodiscard]] auto accept()
382 {
383 6x move_accept_awaitable aw(*this);
384 6x if (!is_open())
385 2x aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
386 6x return aw;
387 }
388
389 /** Cancel pending asynchronous accept operations.
390
391 Outstanding accept operations complete with
392 @c capy::cond::canceled. Safe to call when no
393 operations are pending (no-op).
394 */
395 void cancel() noexcept;
396
397 /** Release ownership of the native socket handle.
398
399 Deregisters the acceptor from the reactor and cancels
400 pending operations without closing the descriptor. The
401 caller takes ownership of the returned handle.
402
403 @return The native handle.
404
405 @throws std::system_error `errc::bad_file_descriptor` if the
406 acceptor is not open.
407
408 @post is_open() == false
409 */
410 native_handle_type release();
411
412 /** Get the native socket handle.
413
414 @return The native socket handle, or -1/INVALID_SOCKET if not
415 open.
416
417 @par Preconditions
418 None. May be called on closed acceptors.
419 */
420 native_handle_type native_handle() const noexcept;
421
422 /** Assign an existing native socket to this acceptor.
423
424 Adopts a listening socket created outside the library —
425 received from a service manager, inherited, or made natively —
426 and registers it with the backend. The socket must be a
427 listening stream socket in the local IPC family. Adoption
428 never alters the descriptor's flags or options: on POSIX the
429 fd must already be non-blocking, and on Windows the socket
430 must be overlapped-capable.
431
432 Adoption does not verify listen state; @ref accept reports the
433 error if the socket is not listening.
434
435 If this object is already open, pending operations complete
436 with `errc::operation_canceled` and the held socket is closed
437 before the new one is adopted.
438
439 @par Exception Safety
440 Strong guarantee on validation failure: the object is
441 unchanged. If backend registration fails, the object either
442 retains its previous socket or is left closed, depending on
443 the backend. In all failure cases the caller retains
444 ownership of `fd`.
445
446 @param fd The native socket to adopt. On success the object
447 owns it and will close it.
448
449 @return The error code, empty on success. Validation and
450 registration failures are normal runtime conditions when
451 adopting foreign descriptors.
452 */
453 [[nodiscard]] std::error_code assign(native_handle_type fd) noexcept;
454
455 /** Return the local endpoint the acceptor is bound to.
456
457 Returns a default-constructed (empty) endpoint if the
458 acceptor is not open or not yet bound. Safe to call in
459 any state.
460 */
461 corosio::local_endpoint local_endpoint() const noexcept;
462
463 /** Set a socket option on the acceptor.
464
465 Applies a type-safe socket option to the underlying socket.
466 The option type encodes the protocol level and option name.
467
468 @param opt The option to set.
469
470 @tparam Option A socket option type providing static
471 `level()` and `name()` members, and `data()` / `size()`
472 accessors.
473
474 @throws std::system_error `errc::bad_file_descriptor` if the
475 acceptor is not open; otherwise thrown on failure.
476 */
477 template<class Option>
478 6x void set_option(Option const& opt)
479 {
480 6x if (!is_open())
481 2x detail::throw_system_error(
482 4x make_error_code(std::errc::bad_file_descriptor),
483 "local_stream_acceptor::set_option");
484 4x std::error_code ec = get().set_option(
485 Option::level(), Option::name(), opt.data(), opt.size());
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 std::size_t sz = opt.size();
512 std::error_code ec =
513 4x get().get_option(Option::level(), Option::name(), opt.data(), &sz);
514 4x if (ec)
515 2x detail::throw_system_error(ec, "local_stream_acceptor::get_option");
516 2x opt.resize(sz);
517 2x return opt;
518 }
519
520 /** Backend hooks for local stream acceptor operations.
521
522 Platform backends derive from this to implement
523 accept, option, and lifecycle management.
524 */
525 struct implementation : io_object::implementation
526 {
527 /** Initiate an asynchronous accept.
528
529 On completion the backend sets @p *ec and, on
530 success, stores a pointer to the new socket
531 implementation in @p *impl_out.
532
533 @param h Coroutine handle to resume.
534 @param ex Executor for dispatching the completion.
535 @param token Stop token for cancellation.
536 @param ec Output error code.
537 @param impl_out Output pointer for the accepted socket.
538 @return Coroutine handle to resume immediately.
539 */
540 virtual std::coroutine_handle<> accept(
541 std::coroutine_handle<>,
542 capy::executor_ref,
543 std::stop_token,
544 std::error_code*,
545 io_object::implementation**) = 0;
546
547 /** Initiate an asynchronous wait for acceptor readiness.
548
549 Completes when the listen socket becomes ready for
550 the specified direction. No connection is consumed.
551 */
552 virtual std::coroutine_handle<> wait(
553 std::coroutine_handle<> h,
554 capy::executor_ref ex,
555 wait_type w,
556 std::stop_token token,
557 std::error_code* ec) = 0;
558
559 /// Return the cached local endpoint.
560 virtual corosio::local_endpoint local_endpoint() const noexcept = 0;
561
562 /// Return whether the underlying socket is open.
563 virtual bool is_open() const noexcept = 0;
564
565 /// Return the native handle, or the platform sentinel if closed.
566 virtual native_handle_type native_handle() const noexcept = 0;
567
568 /// Release and return the native handle without closing.
569 virtual native_handle_type release_socket() noexcept = 0;
570
571 /// Cancel pending accept operations.
572 virtual void cancel() noexcept = 0;
573
574 /// Set a raw socket option.
575 virtual std::error_code set_option(
576 int level,
577 int optname,
578 void const* data,
579 std::size_t size) noexcept = 0;
580
581 /// Get a raw socket option.
582 virtual std::error_code
583 get_option(int level, int optname, void* data, std::size_t* size)
584 const noexcept = 0;
585 };
586
587 protected:
588 18x local_stream_acceptor(handle h, capy::execution_context& ctx) noexcept
589 18x : io_object(std::move(h))
590 18x , ctx_(ctx)
591 {
592 18x }
593
594 2x local_stream_acceptor(
595 capy::execution_context& ctx, local_stream_acceptor&& other) noexcept
596 2x : io_object(std::move(other))
597 2x , ctx_(ctx)
598 {
599 2x }
600
601 8x static void reset_peer_impl(
602 local_stream_socket& peer, io_object::implementation* impl) noexcept
603 {
604 8x if (impl)
605 8x peer.h_.reset(impl);
606 8x }
607
608 private:
609 capy::execution_context& ctx_;
610
611 564x inline implementation& get() const noexcept
612 {
613 564x return *static_cast<implementation*>(h_.get());
614 }
615 };
616
617 } // namespace boost::corosio
618
619 #endif // BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP
620