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/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 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 : std::coroutine_handle<>
84 6 : dispatch(std::coroutine_handle<> h, capy::executor_ref ex) const
85 : {
86 6 : 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 6 : explicit move_accept_awaitable(local_stream_acceptor& acc) noexcept
98 6 : : acc_(acc)
99 : {
100 6 : }
101 :
102 6 : bool await_ready() const noexcept
103 : {
104 : // A pre-set ec_ means the initiator failed before
105 : // dispatch (e.g. a closed object).
106 6 : return static_cast<bool>(ec_) || token_.stop_requested();
107 : }
108 :
109 : [[nodiscard]] capy::io_result<local_stream_socket>
110 6 : await_resume() const noexcept
111 : {
112 6 : if (token_.stop_requested())
113 : return {
114 2 : make_error_code(std::errc::operation_canceled),
115 2 : local_stream_socket()};
116 :
117 4 : if (ec_ || !peer_impl_)
118 2 : return {ec_, local_stream_socket()};
119 :
120 2 : local_stream_socket peer(acc_.ctx_);
121 2 : reset_peer_impl(peer, peer_impl_);
122 2 : return {ec_, std::move(peer)};
123 2 : }
124 :
125 4 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
126 : -> std::coroutine_handle<>
127 : {
128 4 : token_ = env->stop_token;
129 12 : return acc_.get().accept(
130 12 : 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 29 : accept_awaitable(
143 : local_stream_acceptor& acc, local_stream_socket& peer) noexcept
144 29 : : acc_(acc)
145 29 : , peer_(peer)
146 : {
147 29 : }
148 :
149 29 : bool await_ready() const noexcept
150 : {
151 : // A pre-set ec_ means the initiator failed before
152 : // dispatch (e.g. a closed object).
153 29 : return static_cast<bool>(ec_) || token_.stop_requested();
154 : }
155 :
156 27 : [[nodiscard]] capy::io_result<> await_resume() const noexcept
157 : {
158 27 : if (token_.stop_requested())
159 4 : return {make_error_code(std::errc::operation_canceled)};
160 :
161 23 : if (!ec_ && peer_impl_)
162 17 : peer_.h_.reset(peer_impl_);
163 23 : return {ec_};
164 : }
165 :
166 27 : auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
167 : -> std::coroutine_handle<>
168 : {
169 27 : token_ = env->stop_token;
170 81 : return acc_.get().accept(
171 81 : 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 2 : local_stream_acceptor(local_stream_acceptor&& other) noexcept
251 2 : : local_stream_acceptor(other.ctx_, std::move(other))
252 : {
253 2 : }
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 489 : bool is_open() const noexcept
332 : {
333 489 : 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 29 : [[nodiscard]] auto accept(local_stream_socket& peer)
354 : {
355 29 : accept_awaitable aw(*this, peer);
356 29 : if (!is_open())
357 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
358 29 : 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 8 : [[nodiscard]] auto wait(wait_type w)
385 : {
386 8 : wait_awaitable aw(*this, w);
387 8 : if (!is_open())
388 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
389 8 : 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 6 : [[nodiscard]] auto accept()
410 : {
411 6 : move_accept_awaitable aw(*this);
412 6 : if (!is_open())
413 2 : aw.ec_ = make_error_code(std::errc::bad_file_descriptor);
414 6 : 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 6 : void set_option(Option const& opt)
507 : {
508 6 : if (!is_open())
509 2 : detail::throw_system_error(
510 4 : make_error_code(std::errc::bad_file_descriptor),
511 : "local_stream_acceptor::set_option");
512 4 : std::error_code ec = get().set_option(
513 : Option::level(), Option::name(), opt.data(), opt.size());
514 4 : if (ec)
515 2 : detail::throw_system_error(ec, "local_stream_acceptor::set_option");
516 2 : }
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 6 : Option get_option() const
533 : {
534 6 : if (!is_open())
535 2 : detail::throw_system_error(
536 4 : make_error_code(std::errc::bad_file_descriptor),
537 : "local_stream_acceptor::get_option");
538 4 : Option opt{};
539 4 : std::size_t sz = opt.size();
540 : std::error_code ec =
541 4 : get().get_option(Option::level(), Option::name(), opt.data(), &sz);
542 4 : if (ec)
543 2 : detail::throw_system_error(ec, "local_stream_acceptor::get_option");
544 2 : opt.resize(sz);
545 2 : 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 18 : local_stream_acceptor(handle h, capy::execution_context& ctx) noexcept
617 18 : : io_object(std::move(h))
618 18 : , ctx_(ctx)
619 : {
620 18 : }
621 :
622 2 : local_stream_acceptor(
623 : capy::execution_context& ctx, local_stream_acceptor&& other) noexcept
624 2 : : io_object(std::move(other))
625 2 : , ctx_(ctx)
626 : {
627 2 : }
628 :
629 8 : static void reset_peer_impl(
630 : local_stream_socket& peer, io_object::implementation* impl) noexcept
631 : {
632 8 : if (impl)
633 8 : peer.h_.reset(impl);
634 8 : }
635 :
636 : private:
637 : capy::execution_context& ctx_;
638 :
639 566 : inline implementation& get() const noexcept
640 : {
641 566 : return *static_cast<implementation*>(h_.get());
642 : }
643 : };
644 :
645 : } // namespace boost::corosio
646 :
647 : #endif // BOOST_COROSIO_LOCAL_STREAM_ACCEPTOR_HPP
|