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  
// Copyright (c) 2026 Steve Gerbino
4  
//
4  
//
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
5  
// Distributed under the Boost Software License, Version 1.0. (See accompanying
6  
// 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)
7  
//
7  
//
8  
// Official repository: https://github.com/cppalliance/corosio
8  
// Official repository: https://github.com/cppalliance/corosio
9  
//
9  
//
10  

10  

11  
#ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
11  
#ifndef BOOST_COROSIO_TCP_ACCEPTOR_HPP
12  
#define BOOST_COROSIO_TCP_ACCEPTOR_HPP
12  
#define BOOST_COROSIO_TCP_ACCEPTOR_HPP
13  

13  

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

24  

25  
#include <system_error>
25  
#include <system_error>
26  

26  

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

33  

34  
namespace boost::corosio {
34  
namespace boost::corosio {
35  

35  

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

37  

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

41  

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

44  

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

49  

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

53  

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

60  

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

78  

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

84  

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

89  

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

94  

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

99  

100  
        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)
101  
            -> std::coroutine_handle<>
101  
            -> std::coroutine_handle<>
102  
        {
102  
        {
103  
            token_ = env->stop_token;
103  
            token_ = env->stop_token;
104  
            return acc_.get().accept(
104  
            return acc_.get().accept(
105  
                h, env->executor, token_, &ec_, &peer_impl_);
105  
                h, env->executor, token_, &ec_, &peer_impl_);
106  
        }
106  
        }
107  
    };
107  
    };
108  

108  

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

111  

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

115  

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

117  

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

121  

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

123  

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

125  

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

134  

135  
    /** Move constructor.
135  
    /** Move constructor.
136  

136  

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

138  

139  
        @param other The acceptor to move from.
139  
        @param other The acceptor to move from.
 
140 +

 
141 +
        @pre No awaitables returned by @p other's methods exist.
 
142 +
        @pre The execution context associated with @p other must
 
143 +
            outlive this acceptor.
140  
    */
144  
    */
141  
    tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
145  
    tcp_acceptor(tcp_acceptor&& other) noexcept : io_object(std::move(other)) {}
142  

146  

143  
    /** Move assignment operator.
147  
    /** Move assignment operator.
144  

148  

145  
        Closes any existing acceptor and transfers ownership.
149  
        Closes any existing acceptor and transfers ownership.
 
150 +

146  
        @param other The acceptor to move from.
151  
        @param other The acceptor to move from.
147  

152  

 
153 +
        @pre No awaitables returned by either `*this` or @p other's
 
154 +
            methods exist.
 
155 +
        @pre The execution context associated with @p other must
 
156 +
            outlive this acceptor.
 
157 +

148  
        @return Reference to this acceptor.
158  
        @return Reference to this acceptor.
149  
    */
159  
    */
150  
    tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
160  
    tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
151  
    {
161  
    {
152  
        if (this != &other)
162  
        if (this != &other)
153  
        {
163  
        {
154  
            close();
164  
            close();
155  
            h_ = std::move(other.h_);
165  
            h_ = std::move(other.h_);
156  
        }
166  
        }
157  
        return *this;
167  
        return *this;
158  
    }
168  
    }
159  

169  

160  
    tcp_acceptor(tcp_acceptor const&)            = delete;
170  
    tcp_acceptor(tcp_acceptor const&)            = delete;
161  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
171  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
162  

172  

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

174  

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

178  

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

181  

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

184  

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

187  

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

196  

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

200  

191  
    /** Close the acceptor.
201  
    /** Close the acceptor.
192  

202  

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

207  

198  
    /** Check if the acceptor is listening.
208  
    /** Check if the acceptor is listening.
199  

209  

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

216  

207  
    /** Initiate an asynchronous accept operation.
217  
    /** Initiate an asynchronous accept operation.
208  

218  

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

222  

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

227  

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

230  

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

236  

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

 
241 +
        Both this acceptor and @p peer must outlive the returned
 
242 +
        awaitable.
230  

243  

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

259  

247  
    /** Cancel any pending asynchronous operations.
260  
    /** Cancel any pending asynchronous operations.
248  

261  

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

266  

254  
    /** Get the local endpoint of the acceptor.
267  
    /** Get the local endpoint of the acceptor.
255  

268  

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

273  

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

276  

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

284  

272  
    struct implementation : io_object::implementation
285  
    struct implementation : io_object::implementation
273  
    {
286  
    {
274  
        virtual std::coroutine_handle<> accept(
287  
        virtual std::coroutine_handle<> accept(
275  
            std::coroutine_handle<>,
288  
            std::coroutine_handle<>,
276  
            capy::executor_ref,
289  
            capy::executor_ref,
277  
            std::stop_token,
290  
            std::stop_token,
278  
            std::error_code*,
291  
            std::error_code*,
279  
            io_object::implementation**) = 0;
292  
            io_object::implementation**) = 0;
280  

293  

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

296  

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

299  

287  
        /** Cancel any pending asynchronous operations.
300  
        /** Cancel any pending asynchronous operations.
288  

301  

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

306  

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

309  

297  
    /// Transfer accepted peer impl to the peer socket.
310  
    /// Transfer accepted peer impl to the peer socket.
298  
    static void
311  
    static void
299  
    reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
312  
    reset_peer_impl(tcp_socket& peer, io_object::implementation* impl) noexcept
300  
    {
313  
    {
301  
        if (impl)
314  
        if (impl)
302  
            peer.h_.reset(impl);
315  
            peer.h_.reset(impl);
303  
    }
316  
    }
304  

317  

305  
private:
318  
private:
306  
    inline implementation& get() const noexcept
319  
    inline implementation& get() const noexcept
307  
    {
320  
    {
308  
        return *static_cast<implementation*>(h_.get());
321  
        return *static_cast<implementation*>(h_.get());
309  
    }
322  
    }
310  
};
323  
};
311  

324  

312  
} // namespace boost::corosio
325  
} // namespace boost::corosio
313  

326  

314  
#endif
327  
#endif