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