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

58  

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

76  

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

82  

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

87  

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

102  

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

111  

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

123  

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

126  

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

130  

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

132  

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

136  

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

138  

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

140  

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

150  

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

152  

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

154  

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

163  

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

165  

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

168  

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

170  

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

172  

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

188  

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

191  

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

193  

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

197  

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

200  

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

203  

203 -
        @throws std::system_error on failure.
204 +
        @return An error code indicating success or the reason for failure.
 
205 +
            A default-constructed error code indicates success.
 
206 +

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

 
216 +
        @throws Nothing.
204  
    */
217  
    */
205 -
    void listen(endpoint ep, int backlog = 128);
218 +
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
206  

219  

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

221  

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

226  

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

228  

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

235  

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

237  

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

241  

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

246  

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

249  

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

255  

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

259  

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

275  

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

277  

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

282  

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

284  

272  
        Returns the local address and port to which the acceptor is bound.
285  
        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
286  
        This is useful when binding to port 0 (ephemeral port) to discover
274  
        the OS-assigned port number. The endpoint is cached when listen()
287  
        the OS-assigned port number. The endpoint is cached when listen()
275  
        is called.
288  
        is called.
276  

289  

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

292  

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

300  

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

309  

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

312  

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

314  

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

319  

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

326  

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

328  

316  
#endif
329  
#endif