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