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