1  
//
1  
//
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot com)
2  
// Copyright (c) 2025 Vinnie Falco (vinnie dot falco at gmail dot 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/concept/executor.hpp>
21  
#include <boost/capy/concept/executor.hpp>
22  

22  

23  
#include <system_error>
23  
#include <system_error>
24  

24  

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

31  

32  
namespace boost::corosio {
32  
namespace boost::corosio {
33  

33  

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

35  

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

39  

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

42  

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

47  

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

51  

52  
    @par Example
52  
    @par Example
53  
    @code
53  
    @code
54  
    io_context ioc;
54  
    io_context ioc;
55  
    tcp_acceptor acc(ioc);
55  
    tcp_acceptor acc(ioc);
56  
    acc.listen(endpoint(8080));  // Bind to port 8080
56  
    acc.listen(endpoint(8080));  // Bind to port 8080
57  

57  

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

75  

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

81  

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

86  

87  
        capy::io_result<> await_resume() const noexcept
87  
        capy::io_result<> await_resume() const noexcept
88  
        {
88  
        {
89  
            if (token_.stop_requested())
89  
            if (token_.stop_requested())
90  
                return {make_error_code(std::errc::operation_canceled)};
90  
                return {make_error_code(std::errc::operation_canceled)};
91  
            
91  
            
92  
            // Transfer the accepted impl to the peer socket
92  
            // Transfer the accepted impl to the peer socket
93  
            // (acceptor is a friend of socket, so we can access impl_)
93  
            // (acceptor is a friend of socket, so we can access impl_)
94  
            if (!ec_ && peer_impl_)
94  
            if (!ec_ && peer_impl_)
95  
            {
95  
            {
96  
                peer_.close();
96  
                peer_.close();
97  
                peer_.impl_ = peer_impl_;
97  
                peer_.impl_ = peer_impl_;
98  
            }
98  
            }
99  
            return {ec_};
99  
            return {ec_};
100  
        }
100  
        }
101  

101  

102  
        template<typename Ex>
102  
        template<typename Ex>
103  
        auto await_suspend(
103  
        auto await_suspend(
104  
            std::coroutine_handle<> h,
104  
            std::coroutine_handle<> h,
105  
            Ex const& ex) -> std::coroutine_handle<>
105  
            Ex const& ex) -> std::coroutine_handle<>
106  
        {
106  
        {
107  
            acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
107  
            acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
108  
            return std::noop_coroutine();
108  
            return std::noop_coroutine();
109  
        }
109  
        }
110  

110  

111  
        template<typename Ex>
111  
        template<typename Ex>
112  
        auto await_suspend(
112  
        auto await_suspend(
113  
            std::coroutine_handle<> h,
113  
            std::coroutine_handle<> h,
114  
            Ex const& ex,
114  
            Ex const& ex,
115  
            std::stop_token token) -> std::coroutine_handle<>
115  
            std::stop_token token) -> std::coroutine_handle<>
116  
        {
116  
        {
117  
            token_ = std::move(token);
117  
            token_ = std::move(token);
118  
            acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
118  
            acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
119  
            return std::noop_coroutine();
119  
            return std::noop_coroutine();
120  
        }
120  
        }
121  
    };
121  
    };
122  

122  

123  
public:
123  
public:
124  
    /** Destructor.
124  
    /** Destructor.
125  

125  

126  
        Closes the acceptor if open, cancelling any pending operations.
126  
        Closes the acceptor if open, cancelling any pending operations.
127  
    */
127  
    */
128  
    ~tcp_acceptor();
128  
    ~tcp_acceptor();
129  

129  

130  
    /** Construct an acceptor from an execution context.
130  
    /** Construct an acceptor from an execution context.
131  

131  

132  
        @param ctx The execution context that will own this acceptor.
132  
        @param ctx The execution context that will own this acceptor.
133  
    */
133  
    */
134  
    explicit tcp_acceptor(capy::execution_context& ctx);
134  
    explicit tcp_acceptor(capy::execution_context& ctx);
135  

135  

136  
    /** Construct an acceptor from an executor.
136  
    /** Construct an acceptor from an executor.
137  

137  

138  
        The acceptor is associated with the executor's context.
138  
        The acceptor is associated with the executor's context.
139  

139  

140  
        @param ex The executor whose context will own the acceptor.
140  
        @param ex The executor whose context will own the acceptor.
141  
    */
141  
    */
142  
    template<class Ex>
142  
    template<class Ex>
143  
        requires (!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
143  
        requires (!std::same_as<std::remove_cvref_t<Ex>, tcp_acceptor>) &&
144  
                 capy::Executor<Ex>
144  
                 capy::Executor<Ex>
145  
    explicit tcp_acceptor(Ex const& ex)
145  
    explicit tcp_acceptor(Ex const& ex)
146  
        : tcp_acceptor(ex.context())
146  
        : tcp_acceptor(ex.context())
147  
    {
147  
    {
148  
    }
148  
    }
149  

149  

150  
    /** Move constructor.
150  
    /** Move constructor.
151  

151  

152  
        Transfers ownership of the acceptor resources.
152  
        Transfers ownership of the acceptor resources.
153  

153  

154  
        @param other The acceptor to move from.
154  
        @param other The acceptor to move from.
155  
    */
155  
    */
156  
    tcp_acceptor(tcp_acceptor&& other) noexcept
156  
    tcp_acceptor(tcp_acceptor&& other) noexcept
157  
        : io_object(other.context())
157  
        : io_object(other.context())
158  
    {
158  
    {
159  
        impl_ = other.impl_;
159  
        impl_ = other.impl_;
160  
        other.impl_ = nullptr;
160  
        other.impl_ = nullptr;
161  
    }
161  
    }
162  

162  

163  
    /** Move assignment operator.
163  
    /** Move assignment operator.
164  

164  

165  
        Closes any existing acceptor and transfers ownership.
165  
        Closes any existing acceptor and transfers ownership.
166  
        The source and destination must share the same execution context.
166  
        The source and destination must share the same execution context.
167  

167  

168  
        @param other The acceptor to move from.
168  
        @param other The acceptor to move from.
169  

169  

170  
        @return Reference to this acceptor.
170  
        @return Reference to this acceptor.
171  

171  

172  
        @throws std::logic_error if the acceptors have different execution contexts.
172  
        @throws std::logic_error if the acceptors have different execution contexts.
173  
    */
173  
    */
174  
    tcp_acceptor& operator=(tcp_acceptor&& other)
174  
    tcp_acceptor& operator=(tcp_acceptor&& other)
175  
    {
175  
    {
176  
        if (this != &other)
176  
        if (this != &other)
177  
        {
177  
        {
178  
            if (ctx_ != other.ctx_)
178  
            if (ctx_ != other.ctx_)
179  
                detail::throw_logic_error(
179  
                detail::throw_logic_error(
180  
                    "cannot move tcp_acceptor across execution contexts");
180  
                    "cannot move tcp_acceptor across execution contexts");
181  
            close();
181  
            close();
182  
            impl_ = other.impl_;
182  
            impl_ = other.impl_;
183  
            other.impl_ = nullptr;
183  
            other.impl_ = nullptr;
184  
        }
184  
        }
185  
        return *this;
185  
        return *this;
186  
    }
186  
    }
187  

187  

188  
    tcp_acceptor(tcp_acceptor const&) = delete;
188  
    tcp_acceptor(tcp_acceptor const&) = delete;
189  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
189  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
190  

190  

191  
    /** Open, bind, and listen on an endpoint.
191  
    /** Open, bind, and listen on an endpoint.
192  

192  

193  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
193  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
194  
        and begins listening for incoming connections. This must be
194  
        and begins listening for incoming connections. This must be
195  
        called before initiating accept operations.
195  
        called before initiating accept operations.
196  

196  

197  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
197  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
198  
            bind to all interfaces on a specific port.
198  
            bind to all interfaces on a specific port.
199  

199  

200  
        @param backlog The maximum length of the queue of pending
200  
        @param backlog The maximum length of the queue of pending
201  
            connections. Defaults to a reasonable system value.
201  
            connections. Defaults to a reasonable system value.
202  

202  

203  
        @throws std::system_error on failure.
203  
        @throws std::system_error on failure.
204  
    */
204  
    */
205  
    void listen(endpoint ep, int backlog = 128);
205  
    void listen(endpoint ep, int backlog = 128);
206  

206  

207  
    /** Close the acceptor.
207  
    /** Close the acceptor.
208  

208  

209  
        Releases acceptor resources. Any pending operations complete
209  
        Releases acceptor resources. Any pending operations complete
210  
        with `errc::operation_canceled`.
210  
        with `errc::operation_canceled`.
211  
    */
211  
    */
212  
    void close();
212  
    void close();
213  

213  

214  
    /** Check if the acceptor is listening.
214  
    /** Check if the acceptor is listening.
215  

215  

216  
        @return `true` if the acceptor is open and listening.
216  
        @return `true` if the acceptor is open and listening.
217  
    */
217  
    */
218  
    bool is_open() const noexcept
218  
    bool is_open() const noexcept
219  
    {
219  
    {
220  
        return impl_ != nullptr;
220  
        return impl_ != nullptr;
221  
    }
221  
    }
222  

222  

223  
    /** Initiate an asynchronous accept operation.
223  
    /** Initiate an asynchronous accept operation.
224  

224  

225  
        Accepts an incoming connection and initializes the provided
225  
        Accepts an incoming connection and initializes the provided
226  
        socket with the new connection. The acceptor must be listening
226  
        socket with the new connection. The acceptor must be listening
227  
        before calling this function.
227  
        before calling this function.
228  

228  

229  
        The operation supports cancellation via `std::stop_token` through
229  
        The operation supports cancellation via `std::stop_token` through
230  
        the affine awaitable protocol. If the associated stop token is
230  
        the affine awaitable protocol. If the associated stop token is
231  
        triggered, the operation completes immediately with
231  
        triggered, the operation completes immediately with
232  
        `errc::operation_canceled`.
232  
        `errc::operation_canceled`.
233  

233  

234  
        @param peer The socket to receive the accepted connection. Any
234  
        @param peer The socket to receive the accepted connection. Any
235  
            existing connection on this socket will be closed.
235  
            existing connection on this socket will be closed.
236  

236  

237  
        @return An awaitable that completes with `io_result<>`.
237  
        @return An awaitable that completes with `io_result<>`.
238  
            Returns success on successful accept, or an error code on
238  
            Returns success on successful accept, or an error code on
239  
            failure including:
239  
            failure including:
240  
            - operation_canceled: Cancelled via stop_token or cancel().
240  
            - operation_canceled: Cancelled via stop_token or cancel().
241  
                Check `ec == cond::canceled` for portable comparison.
241  
                Check `ec == cond::canceled` for portable comparison.
242  

242  

243  
        @par Preconditions
243  
        @par Preconditions
244  
        The acceptor must be listening (`is_open() == true`).
244  
        The acceptor must be listening (`is_open() == true`).
245  
        The peer socket must be associated with the same execution context.
245  
        The peer socket must be associated with the same execution context.
246  

246  

247  
        @par Example
247  
        @par Example
248  
        @code
248  
        @code
249  
        tcp_socket peer(ioc);
249  
        tcp_socket peer(ioc);
250  
        auto [ec] = co_await acc.accept(peer);
250  
        auto [ec] = co_await acc.accept(peer);
251  
        if (!ec) {
251  
        if (!ec) {
252  
            // Use peer socket
252  
            // Use peer socket
253  
        }
253  
        }
254  
        @endcode
254  
        @endcode
255  
    */
255  
    */
256  
    auto accept(tcp_socket& peer)
256  
    auto accept(tcp_socket& peer)
257  
    {
257  
    {
258  
        if (!impl_)
258  
        if (!impl_)
259  
            detail::throw_logic_error("accept: acceptor not listening");
259  
            detail::throw_logic_error("accept: acceptor not listening");
260  
        return accept_awaitable(*this, peer);
260  
        return accept_awaitable(*this, peer);
261  
    }
261  
    }
262  

262  

263  
    /** Cancel any pending asynchronous operations.
263  
    /** Cancel any pending asynchronous operations.
264  

264  

265  
        All outstanding operations complete with `errc::operation_canceled`.
265  
        All outstanding operations complete with `errc::operation_canceled`.
266  
        Check `ec == cond::canceled` for portable comparison.
266  
        Check `ec == cond::canceled` for portable comparison.
267  
    */
267  
    */
268  
    void cancel();
268  
    void cancel();
269  

269  

270  
    /** Get the local endpoint of the acceptor.
270  
    /** Get the local endpoint of the acceptor.
271  

271  

272  
        Returns the local address and port to which the acceptor is bound.
272  
        Returns the local address and port to which the acceptor is bound.
273  
        This is useful when binding to port 0 (ephemeral port) to discover
273  
        This is useful when binding to port 0 (ephemeral port) to discover
274  
        the OS-assigned port number. The endpoint is cached when listen()
274  
        the OS-assigned port number. The endpoint is cached when listen()
275  
        is called.
275  
        is called.
276  

276  

277  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
277  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
278  
            the acceptor is not listening.
278  
            the acceptor is not listening.
279  

279  

280  
        @par Thread Safety
280  
        @par Thread Safety
281  
        The cached endpoint value is set during listen() and cleared
281  
        The cached endpoint value is set during listen() and cleared
282  
        during close(). This function may be called concurrently with
282  
        during close(). This function may be called concurrently with
283  
        accept operations, but must not be called concurrently with
283  
        accept operations, but must not be called concurrently with
284  
        listen() or close().
284  
        listen() or close().
285  
    */
285  
    */
286  
    endpoint local_endpoint() const noexcept;
286  
    endpoint local_endpoint() const noexcept;
287  

287  

288  
    struct acceptor_impl : io_object_impl
288  
    struct acceptor_impl : io_object_impl
289  
    {
289  
    {
290  
        virtual void accept(
290  
        virtual void accept(
291  
            std::coroutine_handle<>,
291  
            std::coroutine_handle<>,
292  
            capy::executor_ref,
292  
            capy::executor_ref,
293  
            std::stop_token,
293  
            std::stop_token,
294  
            std::error_code*,
294  
            std::error_code*,
295  
            io_object_impl**) = 0;
295  
            io_object_impl**) = 0;
296  

296  

297  
        /// Returns the cached local endpoint.
297  
        /// Returns the cached local endpoint.
298  
        virtual endpoint local_endpoint() const noexcept = 0;
298  
        virtual endpoint local_endpoint() const noexcept = 0;
299  

299  

300  
        /** Cancel any pending asynchronous operations.
300  
        /** Cancel any pending asynchronous operations.
301  

301  

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

306  

307  
private:
307  
private:
308  
    inline acceptor_impl& get() const noexcept
308  
    inline acceptor_impl& get() const noexcept
309  
    {
309  
    {
310  
        return *static_cast<acceptor_impl*>(impl_);
310  
        return *static_cast<acceptor_impl*>(impl_);
311  
    }
311  
    }
312  
};
312  
};
313  

313  

314  
} // namespace boost::corosio
314  
} // namespace boost::corosio
315  

315  

316  
#endif
316  
#endif