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::io_object_impl* 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 -
            // Transfer the accepted impl to the peer socket
 
94 -
            // (acceptor is a friend of socket, so we can access impl_)
 
95  
            
93  
            
96  
            if (!ec_ && peer_impl_)
94  
            if (!ec_ && peer_impl_)
97 -
            {
95 +
                peer_.h_.reset(peer_impl_);
98 -
                peer_.close();
 
99 -
                peer_.impl_ = peer_impl_;
 
100 -
            }
 
101  
            return {ec_};
96  
            return {ec_};
102  
        }
97  
        }
103  

98  

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

107  

113  
public:
108  
public:
114  
    /** Destructor.
109  
    /** Destructor.
115  

110  

116  
        Closes the acceptor if open, cancelling any pending operations.
111  
        Closes the acceptor if open, cancelling any pending operations.
117  
    */
112  
    */
118  
    ~tcp_acceptor();
113  
    ~tcp_acceptor();
119  

114  

120  
    /** Construct an acceptor from an execution context.
115  
    /** Construct an acceptor from an execution context.
121  

116  

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

120  

126  
    /** Construct an acceptor from an executor.
121  
    /** Construct an acceptor from an executor.
127  

122  

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

124  

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

134  

140  
    /** Move constructor.
135  
    /** Move constructor.
141  

136  

142  
        Transfers ownership of the acceptor resources.
137  
        Transfers ownership of the acceptor resources.
143  

138  

144  
        @param other The acceptor to move from.
139  
        @param other The acceptor to move from.
145  
    */
140  
    */
146  
    tcp_acceptor(tcp_acceptor&& other) noexcept
141  
    tcp_acceptor(tcp_acceptor&& other) noexcept
147 -
        : io_object(other.context())
142 +
        : io_object(std::move(other))
148 -
        impl_ = other.impl_;
 
149 -
        other.impl_ = nullptr;
 
150  
    {
143  
    {
151  
    }
144  
    }
152  

145  

153  
    /** Move assignment operator.
146  
    /** Move assignment operator.
154  

147  

155  
        Closes any existing acceptor and transfers ownership.
148  
        Closes any existing acceptor and transfers ownership.
156  
        The source and destination must share the same execution context.
149  
        The source and destination must share the same execution context.
157  

150  

158  
        @param other The acceptor to move from.
151  
        @param other The acceptor to move from.
159  

152  

160  
        @return Reference to this acceptor.
153  
        @return Reference to this acceptor.
161  

154  

162  
        @throws std::logic_error if the acceptors have different execution contexts.
155  
        @throws std::logic_error if the acceptors have different execution contexts.
163  
    */
156  
    */
164  
    tcp_acceptor& operator=(tcp_acceptor&& other)
157  
    tcp_acceptor& operator=(tcp_acceptor&& other)
165  
    {
158  
    {
166  
        if (this != &other)
159  
        if (this != &other)
167  
        {
160  
        {
168 -
            if (ctx_ != other.ctx_)
161 +
            if (&context() != &other.context())
169  
                detail::throw_logic_error(
162  
                detail::throw_logic_error(
170  
                    "cannot move tcp_acceptor across execution contexts");
163  
                    "cannot move tcp_acceptor across execution contexts");
171  
            close();
164  
            close();
172 -
            impl_ = other.impl_;
165 +
            h_ = std::move(other.h_);
173 -
            other.impl_ = nullptr;
 
174  
        }
166  
        }
175  
        return *this;
167  
        return *this;
176  
    }
168  
    }
177  

169  

178  
    tcp_acceptor(tcp_acceptor const&) = delete;
170  
    tcp_acceptor(tcp_acceptor const&) = delete;
179  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
171  
    tcp_acceptor& operator=(tcp_acceptor const&) = delete;
180  

172  

181  
    /** Open, bind, and listen on an endpoint.
173  
    /** Open, bind, and listen on an endpoint.
182  

174  

183  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
175  
        Creates an IPv4 TCP socket, binds it to the specified endpoint,
184  
        and begins listening for incoming connections. This must be
176  
        and begins listening for incoming connections. This must be
185  
        called before initiating accept operations.
177  
        called before initiating accept operations.
186  

178  

187  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
179  
        @param ep The local endpoint to bind to. Use `endpoint(port)` to
188  
            bind to all interfaces on a specific port.
180  
            bind to all interfaces on a specific port.
189  

181  

190  
        @param backlog The maximum length of the queue of pending
182  
        @param backlog The maximum length of the queue of pending
191  
            connections. Defaults to 128.
183  
            connections. Defaults to 128.
192  

184  

193  
        @return An error code indicating success or the reason for failure.
185  
        @return An error code indicating success or the reason for failure.
194  
            A default-constructed error code indicates success.
186  
            A default-constructed error code indicates success.
195  

187  

196  
        @par Error Conditions
188  
        @par Error Conditions
197  
        @li `errc::address_in_use`: The endpoint is already in use.
189  
        @li `errc::address_in_use`: The endpoint is already in use.
198  
        @li `errc::address_not_available`: The address is not available
190  
        @li `errc::address_not_available`: The address is not available
199  
            on any local interface.
191  
            on any local interface.
200  
        @li `errc::permission_denied`: Insufficient privileges to bind
192  
        @li `errc::permission_denied`: Insufficient privileges to bind
201  
            to the endpoint (e.g., privileged port).
193  
            to the endpoint (e.g., privileged port).
202  
        @li `errc::operation_not_supported`: The acceptor service is
194  
        @li `errc::operation_not_supported`: The acceptor service is
203  
            unavailable in the context (POSIX only).
195  
            unavailable in the context (POSIX only).
204  

196  

205  
        @throws Nothing.
197  
        @throws Nothing.
206  
    */
198  
    */
207  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
199  
    [[nodiscard]] std::error_code listen(endpoint ep, int backlog = 128);
208  

200  

209  
    /** Close the acceptor.
201  
    /** Close the acceptor.
210  

202  

211  
        Releases acceptor resources. Any pending operations complete
203  
        Releases acceptor resources. Any pending operations complete
212  
        with `errc::operation_canceled`.
204  
        with `errc::operation_canceled`.
213  
    */
205  
    */
214  
    void close();
206  
    void close();
215  

207  

216  
    /** Check if the acceptor is listening.
208  
    /** Check if the acceptor is listening.
217  

209  

218  
        @return `true` if the acceptor is open and listening.
210  
        @return `true` if the acceptor is open and listening.
219  
    */
211  
    */
220  
    bool is_open() const noexcept
212  
    bool is_open() const noexcept
221  
    {
213  
    {
222 -
        return impl_ != nullptr;
214 +
        return h_ && get().is_open();
223  
    }
215  
    }
224  

216  

225  
    /** Initiate an asynchronous accept operation.
217  
    /** Initiate an asynchronous accept operation.
226  

218  

227  
        Accepts an incoming connection and initializes the provided
219  
        Accepts an incoming connection and initializes the provided
228  
        socket with the new connection. The acceptor must be listening
220  
        socket with the new connection. The acceptor must be listening
229  
        before calling this function.
221  
        before calling this function.
230  

222  

231  
        The operation supports cancellation via `std::stop_token` through
223  
        The operation supports cancellation via `std::stop_token` through
232  
        the affine awaitable protocol. If the associated stop token is
224  
        the affine awaitable protocol. If the associated stop token is
233  
        triggered, the operation completes immediately with
225  
        triggered, the operation completes immediately with
234  
        `errc::operation_canceled`.
226  
        `errc::operation_canceled`.
235  

227  

236  
        @param peer The socket to receive the accepted connection. Any
228  
        @param peer The socket to receive the accepted connection. Any
237  
            existing connection on this socket will be closed.
229  
            existing connection on this socket will be closed.
238  

230  

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

236  

245  
        @par Preconditions
237  
        @par Preconditions
246  
        The acceptor must be listening (`is_open() == true`).
238  
        The acceptor must be listening (`is_open() == true`).
247  
        The peer socket must be associated with the same execution context.
239  
        The peer socket must be associated with the same execution context.
248  

240  

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

256  

265  
    /** Cancel any pending asynchronous operations.
257  
    /** Cancel any pending asynchronous operations.
266  

258  

267  
        All outstanding operations complete with `errc::operation_canceled`.
259  
        All outstanding operations complete with `errc::operation_canceled`.
268  
        Check `ec == cond::canceled` for portable comparison.
260  
        Check `ec == cond::canceled` for portable comparison.
269  
    */
261  
    */
270  
    void cancel();
262  
    void cancel();
271  

263  

272  
    /** Get the local endpoint of the acceptor.
264  
    /** Get the local endpoint of the acceptor.
273  

265  

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

270  

279  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
271  
        @return The local endpoint, or a default endpoint (0.0.0.0:0) if
280  
            the acceptor is not listening.
272  
            the acceptor is not listening.
281  

273  

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

281  

290 -
    struct acceptor_impl : io_object_impl
282 +
    struct implementation : io_object::implementation
291  
    {
283  
    {
292  
        virtual std::coroutine_handle<> accept(
284  
        virtual std::coroutine_handle<> accept(
293  
            std::coroutine_handle<>,
285  
            std::coroutine_handle<>,
294  
            capy::executor_ref,
286  
            capy::executor_ref,
295  
            std::stop_token,
287  
            std::stop_token,
296  
            std::error_code*,
288  
            std::error_code*,
297 -
            io_object_impl**) = 0;
289 +
            io_object::implementation**) = 0;
298  

290  

299  
        /// Returns the cached local endpoint.
291  
        /// Returns the cached local endpoint.
300  
        virtual endpoint local_endpoint() const noexcept = 0;
292  
        virtual endpoint local_endpoint() const noexcept = 0;
301  

293  

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

302  
        /** Cancel any pending asynchronous operations.
297  
        /** Cancel any pending asynchronous operations.
303  

298  

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

303  

309  
private:
304  
private:
310 -
    inline acceptor_impl& get() const noexcept
305 +
    inline implementation& get() const noexcept
311  
    {
306  
    {
312 -
        return *static_cast<acceptor_impl*>(impl_);
307 +
        return *static_cast<implementation*>(h_.get());
313  
    }
308  
    }
314  
};
309  
};
315  

310  

316  
} // namespace boost::corosio
311  
} // namespace boost::corosio
317  

312  

318  
#endif
313  
#endif