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