1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
 
3 +
// Copyright (c) 2026 Steve Gerbino
3  
//
4  
//
4  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// 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  
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  
//
7  
//
7  
// Official repository: https://github.com/cppalliance/corosio
8  
// Official repository: https://github.com/cppalliance/corosio
8  
//
9  
//
9  

10  

10  
#ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
11  
#ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
11  
#define BOOST_COROSIO_TCP_ACCEPTOR_HPP
12  
#define BOOST_COROSIO_TCP_ACCEPTOR_HPP
12  

13  

13  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/config.hpp>
14  
#include <boost/corosio/detail/except.hpp>
15  
#include <boost/corosio/detail/except.hpp>
15 -
#include <boost/corosio/io_object.hpp>
16 +
#include <boost/corosio/io/io_object.hpp>
16  
#include <boost/capy/io_result.hpp>
17  
#include <boost/capy/io_result.hpp>
17  
#include <boost/corosio/endpoint.hpp>
18  
#include <boost/corosio/endpoint.hpp>
18  
#include <boost/corosio/tcp_socket.hpp>
19  
#include <boost/corosio/tcp_socket.hpp>
19  
#include <boost/capy/ex/executor_ref.hpp>
20  
#include <boost/capy/ex/executor_ref.hpp>
20  
#include <boost/capy/ex/execution_context.hpp>
21  
#include <boost/capy/ex/execution_context.hpp>
21  
#include <boost/capy/ex/io_env.hpp>
22  
#include <boost/capy/ex/io_env.hpp>
22  
#include <boost/capy/concept/executor.hpp>
23  
#include <boost/capy/concept/executor.hpp>
23  

24  

24  
#include <system_error>
25  
#include <system_error>
25  

26  

26  
#include <concepts>
27  
#include <concepts>
27  
#include <coroutine>
28  
#include <coroutine>
28  
#include <cstddef>
29  
#include <cstddef>
29  
#include <memory>
30  
#include <memory>
30  
#include <stop_token>
31  
#include <stop_token>
31  
#include <type_traits>
32  
#include <type_traits>
32  

33  

33  
namespace boost::corosio {
34  
namespace boost::corosio {
34  

35  

35  
/** An asynchronous TCP acceptor for coroutine I/O.
36  
/** An asynchronous TCP acceptor for coroutine I/O.
36  

37  

37  
    This class provides asynchronous TCP accept operations that return
38  
    This class provides asynchronous TCP accept operations that return
38  
    awaitable types. The acceptor binds to a local endpoint and listens
39  
    awaitable types. The acceptor binds to a local endpoint and listens
39  
    for incoming connections.
40  
    for incoming connections.
40  

41  

41  
    Each accept operation participates in the affine awaitable protocol,
42  
    Each accept operation participates in the affine awaitable protocol,
42  
    ensuring coroutines resume on the correct executor.
43  
    ensuring coroutines resume on the correct executor.
43  

44  

44  
    @par Thread Safety
45  
    @par Thread Safety
45  
    Distinct objects: Safe.@n
46  
    Distinct objects: Safe.@n
46  
    Shared objects: Unsafe. An acceptor must not have concurrent accept
47  
    Shared objects: Unsafe. An acceptor must not have concurrent accept
47  
    operations.
48  
    operations.
48  

49  

49  
    @par Semantics
50  
    @par Semantics
50  
    Wraps the platform TCP listener. Operations dispatch to
51  
    Wraps the platform TCP listener. Operations dispatch to
51  
    OS accept APIs via the io_context reactor.
52  
    OS accept APIs via the io_context reactor.
52  

53  

53  
    @par Example
54  
    @par Example
54  
    @code
55  
    @code
55  
    io_context ioc;
56  
    io_context ioc;
56  
    tcp_acceptor acc(ioc);
57  
    tcp_acceptor acc(ioc);
57  
    if (auto ec = acc.listen(endpoint(8080)))  // Bind to port 8080
58  
    if (auto ec = acc.listen(endpoint(8080)))  // Bind to port 8080
58  
        return ec;
59  
        return ec;
59  

60  

60  
    tcp_socket peer(ioc);
61  
    tcp_socket peer(ioc);
61  
    auto [ec] = co_await acc.accept(peer);
62  
    auto [ec] = co_await acc.accept(peer);
62  
    if (!ec) {
63  
    if (!ec) {
63  
        // peer is now a connected socket
64  
        // peer is now a connected socket
64  
        auto [ec2, n] = co_await peer.read_some(buf);
65  
        auto [ec2, n] = co_await peer.read_some(buf);
65  
    }
66  
    }
66  
    @endcode
67  
    @endcode
67  
*/
68  
*/
68  
class BOOST_COROSIO_DECL tcp_acceptor : public io_object
69  
class BOOST_COROSIO_DECL tcp_acceptor : public io_object
69  
{
70  
{
70  
    struct accept_awaitable
71  
    struct accept_awaitable
71  
    {
72  
    {
72  
        tcp_acceptor& acc_;
73  
        tcp_acceptor& acc_;
73  
        tcp_socket& peer_;
74  
        tcp_socket& peer_;
74  
        std::stop_token token_;
75  
        std::stop_token token_;
75  
        mutable std::error_code ec_;
76  
        mutable std::error_code ec_;
76  
        mutable io_object::implementation* peer_impl_ = nullptr;
77  
        mutable io_object::implementation* peer_impl_ = nullptr;
77  

78  

78  
        accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
79  
        accept_awaitable(tcp_acceptor& acc, tcp_socket& peer) noexcept
79  
            : acc_(acc)
80  
            : acc_(acc)
80  
            , peer_(peer)
81  
            , peer_(peer)
81  
        {
82  
        {
82  
        }
83  
        }
83  

84  

84  
        bool await_ready() const noexcept
85  
        bool await_ready() const noexcept
85  
        {
86  
        {
86  
            return token_.stop_requested();
87  
            return token_.stop_requested();
87  
        }
88  
        }
88  

89  

89  
        capy::io_result<> await_resume() const noexcept
90  
        capy::io_result<> await_resume() const noexcept
90  
        {
91  
        {
91  
            if (token_.stop_requested())
92  
            if (token_.stop_requested())
92  
                return {make_error_code(std::errc::operation_canceled)};
93  
                return {make_error_code(std::errc::operation_canceled)};
93  

94  

94  
            if (!ec_ && peer_impl_)
95  
            if (!ec_ && peer_impl_)
95  
                peer_.h_.reset(peer_impl_);
96  
                peer_.h_.reset(peer_impl_);
96  
            return {ec_};
97  
            return {ec_};
97  
        }
98  
        }
98  

99  

99  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
100  
        auto await_suspend(std::coroutine_handle<> h, capy::io_env const* env)
100  
            -> std::coroutine_handle<>
101  
            -> std::coroutine_handle<>
101  
        {
102  
        {
102  
            token_ = env->stop_token;
103  
            token_ = env->stop_token;
103  
            return acc_.get().accept(
104  
            return acc_.get().accept(
104  
                h, env->executor, token_, &ec_, &peer_impl_);
105  
                h, env->executor, token_, &ec_, &peer_impl_);
105  
        }
106  
        }
106  
    };
107  
    };
107  

108  

108  
public:
109  
public:
109  
    /** Destructor.
110  
    /** Destructor.
110  

111  

111  
        Closes the acceptor if open, cancelling any pending operations.
112  
        Closes the acceptor if open, cancelling any pending operations.
112  
    */
113  
    */
113  
    ~tcp_acceptor() override;
114  
    ~tcp_acceptor() override;
114  

115  

115  
    /** Construct an acceptor from an execution context.
116  
    /** Construct an acceptor from an execution context.
116  

117  

117  
        @param ctx The execution context that will own this acceptor.
118  
        @param ctx The execution context that will own this acceptor.
118  
    */
119  
    */
119  
    explicit tcp_acceptor(capy::execution_context& ctx);
120  
    explicit tcp_acceptor(capy::execution_context& ctx);
120  

121  

121  
    /** Construct an acceptor from an executor.
122  
    /** Construct an acceptor from an executor.
122  

123  

123  
        The acceptor is associated with the executor's context.
124  
        The acceptor is associated with the executor's context.
124  

125  

125  
        @param ex The executor whose context will own the acceptor.
126  
        @param ex The executor whose context will own the acceptor.
126  
    */
127  
    */
127  
    template<class Ex>
128  
    template<class Ex>
128  
        requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
129  
        requires(!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
129  
        capy::Executor<Ex>
130  
        capy::Executor<Ex>
130  
    explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
131  
    explicit tcp_acceptor(Ex const& ex) : tcp_acceptor(ex.context())
131  
    {
132  
    {
132  
    }
133  
    }
133  

134  

134  
    /** Move constructor.
135  
    /** Move constructor.
135  

136  

136  
        Transfers ownership of the acceptor resources.
137  
        Transfers ownership of the acceptor resources.
137  

138  

138  
        @param other The acceptor to move from.
139  
        @param other The acceptor to move from.
139  
    */
140  
    */
140  
    tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
141  
    tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
141  

142  

142  
    /** Move assignment operator.
143  
    /** Move assignment operator.
143  

144  

144  
        Closes any existing acceptor and transfers ownership.
145  
        Closes any existing acceptor and transfers ownership.
145  
        @param other The acceptor to move from.
146  
        @param other The acceptor to move from.
146  

147  

147  
        @return Reference to this acceptor.
148  
        @return Reference to this acceptor.
148  
    */
149  
    */
149  
    tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
150  
    tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
150  
    {
151  
    {
151  
        if (this != &other)
152  
        if (this != &other)
152  
        {
153  
        {
153  
            close();
154  
            close();
154  
            h_ = std::move(other.h_);
155  
            h_ = std::move(other.h_);
155  
        }
156  
        }
156  
        return *this;
157  
        return *this;
157  
    }
158  
    }
158  

159  

159 -
    tcp_acceptor(tcp_acceptor const&) = delete;
160 +
    tcp_acceptor(tcp_acceptor const&)            = delete;
160  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
161  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
161  

162  

162  
    /** Open, bind, and listen on an endpoint.
163  
    /** Open, bind, and listen on an endpoint.
163  

164  

164  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
165  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
165  
        and begins listening for incoming connections. This must be
166  
        and begins listening for incoming connections. This must be
166  
        called before initiating accept operations.
167  
        called before initiating accept operations.
167  

168  

168  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
169  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
169  
            bind to all interfaces on a specific port.
170  
            bind to all interfaces on a specific port.
170  

171  

171  
        @param backlog The maximum length of the queue of pending
172  
        @param backlog The maximum length of the queue of pending
172  
            connections. Defaults to 128.
173  
            connections. Defaults to 128.
173  

174  

174  
        @return An error code indicating success or the reason for failure.
175  
        @return An error code indicating success or the reason for failure.
175  
            A default-constructed error code indicates success.
176  
            A default-constructed error code indicates success.
176  

177  

177  
        @par Error Conditions
178  
        @par Error Conditions
178  
        @li `errc::address_in_use`: The endpoint is already in use.
179  
        @li `errc::address_in_use`: The endpoint is already in use.
179  
        @li `errc::address_not_available`: The address is not available
180  
        @li `errc::address_not_available`: The address is not available
180  
            on any local interface.
181  
            on any local interface.
181  
        @li `errc::permission_denied`: Insufficient privileges to bind
182  
        @li `errc::permission_denied`: Insufficient privileges to bind
182  
            to the endpoint (e.g., privileged port).
183  
            to the endpoint (e.g., privileged port).
183  
        @li `errc::operation_not_supported`: The acceptor service is
184  
        @li `errc::operation_not_supported`: The acceptor service is
184  
            unavailable in the context (POSIX only).
185  
            unavailable in the context (POSIX only).
185  

186  

186  
        @throws Nothing.
187  
        @throws Nothing.
187  
    */
188  
    */
188  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
189  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
189  

190  

190  
    /** Close the acceptor.
191  
    /** Close the acceptor.
191  

192  

192  
        Releases acceptor resources. Any pending operations complete
193  
        Releases acceptor resources. Any pending operations complete
193  
        with `errc::operation_canceled`.
194  
        with `errc::operation_canceled`.
194  
    */
195  
    */
195  
    void close();
196  
    void close();
196  

197  

197  
    /** Check if the acceptor is listening.
198  
    /** Check if the acceptor is listening.
198  

199  

199  
        @return `true` if the acceptor is open and listening.
200  
        @return `true` if the acceptor is open and listening.
200  
    */
201  
    */
201  
    bool is_open() const noexcept
202  
    bool is_open() const noexcept
202  
    {
203  
    {
203  
        return h_ && get().is_open();
204  
        return h_ && get().is_open();
204  
    }
205  
    }
205  

206  

206  
    /** Initiate an asynchronous accept operation.
207  
    /** Initiate an asynchronous accept operation.
207  

208  

208  
        Accepts an incoming connection and initializes the provided
209  
        Accepts an incoming connection and initializes the provided
209  
        socket with the new connection. The acceptor must be listening
210  
        socket with the new connection. The acceptor must be listening
210  
        before calling this function.
211  
        before calling this function.
211  

212  

212  
        The operation supports cancellation via `std::stop_token` through
213  
        The operation supports cancellation via `std::stop_token` through
213  
        the affine awaitable protocol. If the associated stop token is
214  
        the affine awaitable protocol. If the associated stop token is
214  
        triggered, the operation completes immediately with
215  
        triggered, the operation completes immediately with
215  
        `errc::operation_canceled`.
216  
        `errc::operation_canceled`.
216  

217  

217  
        @param peer The socket to receive the accepted connection. Any
218  
        @param peer The socket to receive the accepted connection. Any
218  
            existing connection on this socket will be closed.
219  
            existing connection on this socket will be closed.
219  

220  

220  
        @return An awaitable that completes with `io_result<>`.
221  
        @return An awaitable that completes with `io_result<>`.
221  
            Returns success on successful accept, or an error code on
222  
            Returns success on successful accept, or an error code on
222  
            failure including:
223  
            failure including:
223  
            - operation_canceled: Cancelled via stop_token or cancel().
224  
            - operation_canceled: Cancelled via stop_token or cancel().
224  
                Check `ec == cond::canceled` for portable comparison.
225  
                Check `ec == cond::canceled` for portable comparison.
225  

226  

226  
        @par Preconditions
227  
        @par Preconditions
227  
        The acceptor must be listening (`is_open() == true`).
228  
        The acceptor must be listening (`is_open() == true`).
228  
        The peer socket must be associated with the same execution context.
229  
        The peer socket must be associated with the same execution context.
229  

230  

230  
        @par Example
231  
        @par Example
231  
        @code
232  
        @code
232  
        tcp_socket peer(ioc);
233  
        tcp_socket peer(ioc);
233  
        auto [ec] = co_await acc.accept(peer);
234  
        auto [ec] = co_await acc.accept(peer);
234  
        if (!ec) {
235  
        if (!ec) {
235  
            // Use peer socket
236  
            // Use peer socket
236  
        }
237  
        }
237  
        @endcode
238  
        @endcode
238  
    */
239  
    */
239  
    auto accept(tcp_socket& peer)
240  
    auto accept(tcp_socket& peer)
240  
    {
241  
    {
241  
        if (!is_open())
242  
        if (!is_open())
242  
            detail::throw_logic_error("accept: acceptor not listening");
243  
            detail::throw_logic_error("accept: acceptor not listening");
243  
        return accept_awaitable(*this, peer);
244  
        return accept_awaitable(*this, peer);
244  
    }
245  
    }
245  

246  

246  
    /** Cancel any pending asynchronous operations.
247  
    /** Cancel any pending asynchronous operations.
247  

248  

248  
        All outstanding operations complete with `errc::operation_canceled`.
249  
        All outstanding operations complete with `errc::operation_canceled`.
249  
        Check `ec == cond::canceled` for portable comparison.
250  
        Check `ec == cond::canceled` for portable comparison.
250  
    */
251  
    */
251  
    void cancel();
252  
    void cancel();
252  

253  

253  
    /** Get the local endpoint of the acceptor.
254  
    /** Get the local endpoint of the acceptor.
254  

255  

255  
        Returns the local address and port to which the acceptor is bound.
256  
        Returns the local address and port to which the acceptor is bound.
256  
        This is useful when binding to port 0 (ephemeral port) to discover
257  
        This is useful when binding to port 0 (ephemeral port) to discover
257  
        the OS-assigned port number. The endpoint is cached when listen()
258  
        the OS-assigned port number. The endpoint is cached when listen()
258  
        is called.
259  
        is called.
259  

260  

260  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
261  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
261  
            the acceptor is not listening.
262  
            the acceptor is not listening.
262  

263  

263  
        @par Thread Safety
264  
        @par Thread Safety
264  
        The cached endpoint value is set during listen() and cleared
265  
        The cached endpoint value is set during listen() and cleared
265  
        during close(). This function may be called concurrently with
266  
        during close(). This function may be called concurrently with
266  
        accept operations, but must not be called concurrently with
267  
        accept operations, but must not be called concurrently with
267  
        listen() or close().
268  
        listen() or close().
268  
    */
269  
    */
269  
    endpoint local_endpoint() const noexcept;
270  
    endpoint local_endpoint() const noexcept;
270  

271  

271  
    struct implementation : io_object::implementation
272  
    struct implementation : io_object::implementation
272  
    {
273  
    {
273  
        virtual std::coroutine_handle<> accept(
274  
        virtual std::coroutine_handle<> accept(
274  
            std::coroutine_handle<>,
275  
            std::coroutine_handle<>,
275  
            capy::executor_ref,
276  
            capy::executor_ref,
276  
            std::stop_token,
277  
            std::stop_token,
277  
            std::error_code*,
278  
            std::error_code*,
278  
            io_object::implementation**) = 0;
279  
            io_object::implementation**) = 0;
279  

280  

280  
        /// Returns the cached local endpoint.
281  
        /// Returns the cached local endpoint.
281  
        virtual endpoint local_endpoint() const noexcept = 0;
282  
        virtual endpoint local_endpoint() const noexcept = 0;
282  

283  

283  
        /// Return true if the acceptor has a kernel resource open.
284  
        /// Return true if the acceptor has a kernel resource open.
284  
        virtual bool is_open() const noexcept = 0;
285  
        virtual bool is_open() const noexcept = 0;
285  

286  

286  
        /** Cancel any pending asynchronous operations.
287  
        /** Cancel any pending asynchronous operations.
287  

288  

288  
            All outstanding operations complete with operation_canceled error.
289  
            All outstanding operations complete with operation_canceled error.
289  
        */
290  
        */
290  
        virtual void cancel() noexcept = 0;
291  
        virtual void cancel() noexcept = 0;
291  
    };
292  
    };
 
293 +

 
294 +
protected:
 
295 +
    explicit tcp_acceptor(handle h) noexcept : io_object(std::move(h)) {}
 
296 +

 
297 +
    /// Transfer accepted peer impl to the peer socket.
 
298 +
    static void
 
299 +
    reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
 
300 +
    {
 
301 +
        if (impl)
 
302 +
            peer.h_.reset(impl);
 
303 +
    }
292  

304  

293  
private:
305  
private:
294  
    inline implementation& get() const noexcept
306  
    inline implementation& get() const noexcept
295  
    {
307  
    {
296  
        return *static_cast<implementation*>(h_.get());
308  
        return *static_cast<implementation*>(h_.get());
297  
    }
309  
    }
298  
};
310  
};
299  

311  

300  
} // namespace boost::corosio
312  
} // namespace boost::corosio
301  

313  

302  
#endif
314  
#endif