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