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

9  

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

12  

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

23  

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

25  

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

32  

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

34  

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

36  

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

40  

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

43  

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

48  

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

52  

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

59  

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

77  

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

83  

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

88  

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

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

98  

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

107  

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

110  

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

114  

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

116  

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

120  

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

122  

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

124  

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

133  

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

135  

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

137  

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

141  

146  
    /** Move assignment operator.
142  
    /** Move assignment operator.
147  

143  

148 -
        The source and destination must share the same execution context.
 
149 -

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

146  

153 -

 
154 -
        @throws std::logic_error if the acceptors have different execution contexts.
 
155  
        @return Reference to this acceptor.
147  
        @return Reference to this acceptor.
156  
    */
148  
    */
157 -
    tcp_acceptor& operator=(tcp_acceptor&& other)
149 +
    tcp_acceptor& operator=(tcp_acceptor&& other) noexcept
158  
    {
150  
    {
159  
        if (this != &other)
151  
        if (this != &other)
160 -
            if (&context() != &other.context())
 
161 -
                detail::throw_logic_error(
 
162 -
                    "cannot move tcp_acceptor across execution contexts");
 
163  
        {
152  
        {
164  
            close();
153  
            close();
165  
            h_ = std::move(other.h_);
154  
            h_ = std::move(other.h_);
166  
        }
155  
        }
167  
        return *this;
156  
        return *this;
168  
    }
157  
    }
169  

158  

170  
    tcp_acceptor(tcp_acceptor const&) = delete;
159  
    tcp_acceptor(tcp_acceptor const&) = delete;
171  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
160  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
172  

161  

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

163  

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

167  

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

170  

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

173  

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

176  

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

185  

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

189  

201  
    /** Close the acceptor.
190  
    /** Close the acceptor.
202  

191  

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

196  

208  
    /** Check if the acceptor is listening.
197  
    /** Check if the acceptor is listening.
209  

198  

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

205  

217  
    /** Initiate an asynchronous accept operation.
206  
    /** Initiate an asynchronous accept operation.
218  

207  

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

211  

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

216  

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

219  

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

225  

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

229  

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

245  

257  
    /** Cancel any pending asynchronous operations.
246  
    /** Cancel any pending asynchronous operations.
258  

247  

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

252  

264  
    /** Get the local endpoint of the acceptor.
253  
    /** Get the local endpoint of the acceptor.
265  

254  

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

259  

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

262  

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

270  

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

279  

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

282  

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

285  

297  
        /** Cancel any pending asynchronous operations.
286  
        /** Cancel any pending asynchronous operations.
298  

287  

299  
            All outstanding operations complete with operation_canceled error.
288  
            All outstanding operations complete with operation_canceled error.
300  
        */
289  
        */
301  
        virtual void cancel() noexcept = 0;
290  
        virtual void cancel() noexcept = 0;
302  
    };
291  
    };
303  

292  

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

299  

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

301  

313  
#endif
302  
#endif