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  
    if (auto ec = acc.listen(endpoint(8080)))  // Bind to port 8080
56  
    if (auto ec = acc.listen(endpoint(8080)))  // Bind to port 8080
57  
        return ec;
57  
        return ec;
58  

58  

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

76  

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

82  

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

87  

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

102  

103  
        template<typename Ex>
103  
        template<typename Ex>
104  
        auto await_suspend(
104  
        auto await_suspend(
105  
            std::coroutine_handle<> h,
105  
            std::coroutine_handle<> h,
106  
            Ex const& ex) -> std::coroutine_handle<>
106  
            Ex const& ex) -> std::coroutine_handle<>
107  
        {
107  
        {
108  
            return acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
108  
            return acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
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  
            return acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
118  
            return acc_.get().accept(h, ex, token_, &ec_, &peer_impl_);
119  
        }
119  
        }
120  
    };
120  
    };
121  

121  

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

124  

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

128  

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

130  

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

134  

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

136  

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

138  

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

148  

149  
    /** Move constructor.
149  
    /** Move constructor.
150  

150  

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

152  

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

161  

162  
    /** Move assignment operator.
162  
    /** Move assignment operator.
163  

163  

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

166  

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

168  

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

170  

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

186  

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

189  

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

191  

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

195  

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

198  

199  
        @param backlog The maximum length of the queue of pending
199  
        @param backlog The maximum length of the queue of pending
200  
            connections. Defaults to 128.
200  
            connections. Defaults to 128.
201  

201  

202  
        @return An error code indicating success or the reason for failure.
202  
        @return An error code indicating success or the reason for failure.
203  
            A default-constructed error code indicates success.
203  
            A default-constructed error code indicates success.
204  

204  

205  
        @par Error Conditions
205  
        @par Error Conditions
206  
        @li `errc::address_in_use`: The endpoint is already in use.
206  
        @li `errc::address_in_use`: The endpoint is already in use.
207  
        @li `errc::address_not_available`: The address is not available
207  
        @li `errc::address_not_available`: The address is not available
208  
            on any local interface.
208  
            on any local interface.
209  
        @li `errc::permission_denied`: Insufficient privileges to bind
209  
        @li `errc::permission_denied`: Insufficient privileges to bind
210  
            to the endpoint (e.g., privileged port).
210  
            to the endpoint (e.g., privileged port).
211  
        @li `errc::operation_not_supported`: The acceptor service is
211  
        @li `errc::operation_not_supported`: The acceptor service is
212  
            unavailable in the context (POSIX only).
212  
            unavailable in the context (POSIX only).
213  

213  

214  
        @throws Nothing.
214  
        @throws Nothing.
215  
    */
215  
    */
216  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
216  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
217  

217  

218  
    /** Close the acceptor.
218  
    /** Close the acceptor.
219  

219  

220  
        Releases acceptor resources. Any pending operations complete
220  
        Releases acceptor resources. Any pending operations complete
221  
        with `errc::operation_canceled`.
221  
        with `errc::operation_canceled`.
222  
    */
222  
    */
223  
    void close();
223  
    void close();
224  

224  

225  
    /** Check if the acceptor is listening.
225  
    /** Check if the acceptor is listening.
226  

226  

227  
        @return `true` if the acceptor is open and listening.
227  
        @return `true` if the acceptor is open and listening.
228  
    */
228  
    */
229  
    bool is_open() const noexcept
229  
    bool is_open() const noexcept
230  
    {
230  
    {
231  
        return impl_ != nullptr;
231  
        return impl_ != nullptr;
232  
    }
232  
    }
233  

233  

234  
    /** Initiate an asynchronous accept operation.
234  
    /** Initiate an asynchronous accept operation.
235  

235  

236  
        Accepts an incoming connection and initializes the provided
236  
        Accepts an incoming connection and initializes the provided
237  
        socket with the new connection. The acceptor must be listening
237  
        socket with the new connection. The acceptor must be listening
238  
        before calling this function.
238  
        before calling this function.
239  

239  

240  
        The operation supports cancellation via `std::stop_token` through
240  
        The operation supports cancellation via `std::stop_token` through
241  
        the affine awaitable protocol. If the associated stop token is
241  
        the affine awaitable protocol. If the associated stop token is
242  
        triggered, the operation completes immediately with
242  
        triggered, the operation completes immediately with
243  
        `errc::operation_canceled`.
243  
        `errc::operation_canceled`.
244  

244  

245  
        @param peer The socket to receive the accepted connection. Any
245  
        @param peer The socket to receive the accepted connection. Any
246  
            existing connection on this socket will be closed.
246  
            existing connection on this socket will be closed.
247  

247  

248  
        @return An awaitable that completes with `io_result<>`.
248  
        @return An awaitable that completes with `io_result<>`.
249  
            Returns success on successful accept, or an error code on
249  
            Returns success on successful accept, or an error code on
250  
            failure including:
250  
            failure including:
251  
            - operation_canceled: Cancelled via stop_token or cancel().
251  
            - operation_canceled: Cancelled via stop_token or cancel().
252  
                Check `ec == cond::canceled` for portable comparison.
252  
                Check `ec == cond::canceled` for portable comparison.
253  

253  

254  
        @par Preconditions
254  
        @par Preconditions
255  
        The acceptor must be listening (`is_open() == true`).
255  
        The acceptor must be listening (`is_open() == true`).
256  
        The peer socket must be associated with the same execution context.
256  
        The peer socket must be associated with the same execution context.
257  

257  

258  
        @par Example
258  
        @par Example
259  
        @code
259  
        @code
260  
        tcp_socket peer(ioc);
260  
        tcp_socket peer(ioc);
261  
        auto [ec] = co_await acc.accept(peer);
261  
        auto [ec] = co_await acc.accept(peer);
262  
        if (!ec) {
262  
        if (!ec) {
263  
            // Use peer socket
263  
            // Use peer socket
264  
        }
264  
        }
265  
        @endcode
265  
        @endcode
266  
    */
266  
    */
267  
    auto accept(tcp_socket& peer)
267  
    auto accept(tcp_socket& peer)
268  
    {
268  
    {
269  
        if (!impl_)
269  
        if (!impl_)
270  
            detail::throw_logic_error("accept: acceptor not listening");
270  
            detail::throw_logic_error("accept: acceptor not listening");
271  
        return accept_awaitable(*this, peer);
271  
        return accept_awaitable(*this, peer);
272  
    }
272  
    }
273  

273  

274  
    /** Cancel any pending asynchronous operations.
274  
    /** Cancel any pending asynchronous operations.
275  

275  

276  
        All outstanding operations complete with `errc::operation_canceled`.
276  
        All outstanding operations complete with `errc::operation_canceled`.
277  
        Check `ec == cond::canceled` for portable comparison.
277  
        Check `ec == cond::canceled` for portable comparison.
278  
    */
278  
    */
279  
    void cancel();
279  
    void cancel();
280  

280  

281  
    /** Get the local endpoint of the acceptor.
281  
    /** Get the local endpoint of the acceptor.
282  

282  

283  
        Returns the local address and port to which the acceptor is bound.
283  
        Returns the local address and port to which the acceptor is bound.
284  
        This is useful when binding to port 0 (ephemeral port) to discover
284  
        This is useful when binding to port 0 (ephemeral port) to discover
285  
        the OS-assigned port number. The endpoint is cached when listen()
285  
        the OS-assigned port number. The endpoint is cached when listen()
286  
        is called.
286  
        is called.
287  

287  

288  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
288  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
289  
            the acceptor is not listening.
289  
            the acceptor is not listening.
290  

290  

291  
        @par Thread Safety
291  
        @par Thread Safety
292  
        The cached endpoint value is set during listen() and cleared
292  
        The cached endpoint value is set during listen() and cleared
293  
        during close(). This function may be called concurrently with
293  
        during close(). This function may be called concurrently with
294  
        accept operations, but must not be called concurrently with
294  
        accept operations, but must not be called concurrently with
295  
        listen() or close().
295  
        listen() or close().
296  
    */
296  
    */
297  
    endpoint local_endpoint() const noexcept;
297  
    endpoint local_endpoint() const noexcept;
298  

298  

299  
    struct acceptor_impl : io_object_impl
299  
    struct acceptor_impl : io_object_impl
300  
    {
300  
    {
301  
        virtual std::coroutine_handle<> accept(
301  
        virtual std::coroutine_handle<> accept(
302  
            std::coroutine_handle<>,
302  
            std::coroutine_handle<>,
303  
            capy::executor_ref,
303  
            capy::executor_ref,
304  
            std::stop_token,
304  
            std::stop_token,
305  
            std::error_code*,
305  
            std::error_code*,
306  
            io_object_impl**) = 0;
306  
            io_object_impl**) = 0;
307  

307  

308  
        /// Returns the cached local endpoint.
308  
        /// Returns the cached local endpoint.
309  
        virtual endpoint local_endpoint() const noexcept = 0;
309  
        virtual endpoint local_endpoint() const noexcept = 0;
310  

310  

311  
        /** Cancel any pending asynchronous operations.
311  
        /** Cancel any pending asynchronous operations.
312  

312  

313  
            All outstanding operations complete with operation_canceled error.
313  
            All outstanding operations complete with operation_canceled error.
314  
        */
314  
        */
315  
        virtual void cancel() noexcept = 0;
315  
        virtual void cancel() noexcept = 0;
316  
    };
316  
    };
317  

317  

318  
private:
318  
private:
319  
    inline acceptor_impl& get() const noexcept
319  
    inline acceptor_impl& get() const noexcept
320  
    {
320  
    {
321  
        return *static_cast<acceptor_impl*>(impl_);
321  
        return *static_cast<acceptor_impl*>(impl_);
322  
    }
322  
    }
323  
};
323  
};
324  

324  

325  
} // namespace boost::corosio
325  
} // namespace boost::corosio
326  

326  

327  
#endif
327  
#endif