1  
//
1  
//
2  
// Copyright (c) 2026 Steve Gerbino
2  
// Copyright (c) 2026 Steve Gerbino
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_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
12  

12  

13  
#include <boost/corosio/detail/platform.hpp>
13  
#include <boost/corosio/detail/platform.hpp>
14  

14  

15  
#if BOOST_COROSIO_HAS_SELECT
15  
#if BOOST_COROSIO_HAS_SELECT
16  

16  

17  
#include <boost/corosio/detail/config.hpp>
17  
#include <boost/corosio/detail/config.hpp>
18  
#include <boost/corosio/io/io_object.hpp>
18  
#include <boost/corosio/io/io_object.hpp>
19  
#include <boost/corosio/endpoint.hpp>
19  
#include <boost/corosio/endpoint.hpp>
20  
#include <boost/capy/ex/executor_ref.hpp>
20  
#include <boost/capy/ex/executor_ref.hpp>
21  
#include <coroutine>
21  
#include <coroutine>
22  
#include <boost/capy/error.hpp>
22  
#include <boost/capy/error.hpp>
23  
#include <system_error>
23  
#include <system_error>
24  

24  

25  
#include <boost/corosio/native/detail/make_err.hpp>
25  
#include <boost/corosio/native/detail/make_err.hpp>
26  
#include <boost/corosio/detail/dispatch_coro.hpp>
26  
#include <boost/corosio/detail/dispatch_coro.hpp>
27  
#include <boost/corosio/detail/scheduler_op.hpp>
27  
#include <boost/corosio/detail/scheduler_op.hpp>
28  
#include <boost/corosio/native/detail/endpoint_convert.hpp>
28  
#include <boost/corosio/native/detail/endpoint_convert.hpp>
29  

29  

30  
#include <unistd.h>
30  
#include <unistd.h>
31  
#include <errno.h>
31  
#include <errno.h>
32  
#include <fcntl.h>
32  
#include <fcntl.h>
33  

33  

34  
#include <atomic>
34  
#include <atomic>
35  
#include <cstddef>
35  
#include <cstddef>
36  
#include <memory>
36  
#include <memory>
37  
#include <optional>
37  
#include <optional>
38  
#include <stop_token>
38  
#include <stop_token>
39  

39  

40  
#include <netinet/in.h>
40  
#include <netinet/in.h>
41  
#include <sys/select.h>
41  
#include <sys/select.h>
42  
#include <sys/socket.h>
42  
#include <sys/socket.h>
43  
#include <sys/uio.h>
43  
#include <sys/uio.h>
44  

44  

45  
/*
45  
/*
46  
    select Operation State
46  
    select Operation State
47  
    ======================
47  
    ======================
48  

48  

49  
    Each async I/O operation has a corresponding select_op-derived struct that
49  
    Each async I/O operation has a corresponding select_op-derived struct that
50  
    holds the operation's state while it's in flight. The socket impl owns
50  
    holds the operation's state while it's in flight. The socket impl owns
51  
    fixed slots for each operation type (conn_, rd_, wr_), so only one
51  
    fixed slots for each operation type (conn_, rd_, wr_), so only one
52  
    operation of each type can be pending per socket at a time.
52  
    operation of each type can be pending per socket at a time.
53  

53  

54  
    This mirrors the epoll_op design for consistency across backends.
54  
    This mirrors the epoll_op design for consistency across backends.
55  

55  

56  
    Completion vs Cancellation Race
56  
    Completion vs Cancellation Race
57  
    -------------------------------
57  
    -------------------------------
58  
    The `registered` atomic uses a tri-state (unregistered, registering,
58  
    The `registered` atomic uses a tri-state (unregistered, registering,
59  
    registered) to handle two races: (1) between register_fd() and the
59  
    registered) to handle two races: (1) between register_fd() and the
60  
    reactor seeing an event, and (2) between reactor completion and cancel().
60  
    reactor seeing an event, and (2) between reactor completion and cancel().
61  

61  

62  
    The registering state closes the window where an event could arrive
62  
    The registering state closes the window where an event could arrive
63  
    after register_fd() but before the boolean was set. The reactor and
63  
    after register_fd() but before the boolean was set. The reactor and
64  
    cancel() both treat registering the same as registered when claiming.
64  
    cancel() both treat registering the same as registered when claiming.
65  

65  

66  
    Whoever atomically exchanges to unregistered "claims" the operation
66  
    Whoever atomically exchanges to unregistered "claims" the operation
67  
    and is responsible for completing it. The loser sees unregistered and
67  
    and is responsible for completing it. The loser sees unregistered and
68  
    does nothing. The initiating thread uses compare_exchange to transition
68  
    does nothing. The initiating thread uses compare_exchange to transition
69  
    from registering to registered; if this fails, the reactor or cancel
69  
    from registering to registered; if this fails, the reactor or cancel
70  
    already claimed the op.
70  
    already claimed the op.
71  

71  

72  
    Impl Lifetime Management
72  
    Impl Lifetime Management
73  
    ------------------------
73  
    ------------------------
74  
    When cancel() posts an op to the scheduler's ready queue, the socket impl
74  
    When cancel() posts an op to the scheduler's ready queue, the socket impl
75  
    might be destroyed before the scheduler processes the op. The `impl_ptr`
75  
    might be destroyed before the scheduler processes the op. The `impl_ptr`
76  
    member holds a shared_ptr to the impl, keeping it alive until the op
76  
    member holds a shared_ptr to the impl, keeping it alive until the op
77  
    completes.
77  
    completes.
78  

78  

79  
    EOF Detection
79  
    EOF Detection
80  
    -------------
80  
    -------------
81  
    For reads, 0 bytes with no error means EOF. But an empty user buffer also
81  
    For reads, 0 bytes with no error means EOF. But an empty user buffer also
82  
    returns 0 bytes. The `empty_buffer_read` flag distinguishes these cases.
82  
    returns 0 bytes. The `empty_buffer_read` flag distinguishes these cases.
83  

83  

84  
    SIGPIPE Prevention
84  
    SIGPIPE Prevention
85  
    ------------------
85  
    ------------------
86  
    Writes use sendmsg() with MSG_NOSIGNAL instead of writev() to prevent
86  
    Writes use sendmsg() with MSG_NOSIGNAL instead of writev() to prevent
87  
    SIGPIPE when the peer has closed.
87  
    SIGPIPE when the peer has closed.
88  
*/
88  
*/
89  

89  

90  
namespace boost::corosio::detail {
90  
namespace boost::corosio::detail {
91  

91  

92  
// Forward declarations for cancellation support
92  
// Forward declarations for cancellation support
93  
class select_socket;
93  
class select_socket;
94  
class select_acceptor;
94  
class select_acceptor;
95  

95  

96  
/** Registration state for async operations.
96  
/** Registration state for async operations.
97  

97  

98  
    Tri-state enum to handle the race between register_fd() and
98  
    Tri-state enum to handle the race between register_fd() and
99  
    run_reactor() seeing an event. Setting REGISTERING before
99  
    run_reactor() seeing an event. Setting REGISTERING before
100  
    calling register_fd() ensures events delivered during the
100  
    calling register_fd() ensures events delivered during the
101  
    registration window are not dropped.
101  
    registration window are not dropped.
102  
*/
102  
*/
103  
enum class select_registration_state : std::uint8_t
103  
enum class select_registration_state : std::uint8_t
104  
{
104  
{
105  
    unregistered, ///< Not registered with reactor
105  
    unregistered, ///< Not registered with reactor
106  
    registering,  ///< register_fd() called, not yet confirmed
106  
    registering,  ///< register_fd() called, not yet confirmed
107  
    registered    ///< Fully registered, ready for events
107  
    registered    ///< Fully registered, ready for events
108  
};
108  
};
109  

109  

110  
struct select_op : scheduler_op
110  
struct select_op : scheduler_op
111  
{
111  
{
112  
    struct canceller
112  
    struct canceller
113  
    {
113  
    {
114  
        select_op* op;
114  
        select_op* op;
115  
        void operator()() const noexcept;
115  
        void operator()() const noexcept;
116  
    };
116  
    };
117  

117  

118  
    std::coroutine_handle<> h;
118  
    std::coroutine_handle<> h;
119  
    capy::executor_ref ex;
119  
    capy::executor_ref ex;
120  
    std::error_code* ec_out = nullptr;
120  
    std::error_code* ec_out = nullptr;
121  
    std::size_t* bytes_out  = nullptr;
121  
    std::size_t* bytes_out  = nullptr;
122  

122  

123  
    int fd                        = -1;
123  
    int fd                        = -1;
124  
    int errn                      = 0;
124  
    int errn                      = 0;
125  
    std::size_t bytes_transferred = 0;
125  
    std::size_t bytes_transferred = 0;
126  

126  

127  
    std::atomic<bool> cancelled{false};
127  
    std::atomic<bool> cancelled{false};
128  
    std::atomic<select_registration_state> registered{
128  
    std::atomic<select_registration_state> registered{
129  
        select_registration_state::unregistered};
129  
        select_registration_state::unregistered};
130  
    std::optional<std::stop_callback<canceller>> stop_cb;
130  
    std::optional<std::stop_callback<canceller>> stop_cb;
131  

131  

132  
    // Prevents use-after-free when socket is closed with pending ops.
132  
    // Prevents use-after-free when socket is closed with pending ops.
133  
    std::shared_ptr<void> impl_ptr;
133  
    std::shared_ptr<void> impl_ptr;
134  

134  

135  
    // For stop_token cancellation - pointer to owning socket/acceptor impl.
135  
    // For stop_token cancellation - pointer to owning socket/acceptor impl.
136  
    select_socket* socket_impl_     = nullptr;
136  
    select_socket* socket_impl_     = nullptr;
137  
    select_acceptor* acceptor_impl_ = nullptr;
137  
    select_acceptor* acceptor_impl_ = nullptr;
138  

138  

139  
    select_op() = default;
139  
    select_op() = default;
140  

140  

141  
    void reset() noexcept
141  
    void reset() noexcept
142  
    {
142  
    {
143  
        fd                = -1;
143  
        fd                = -1;
144  
        errn              = 0;
144  
        errn              = 0;
145  
        bytes_transferred = 0;
145  
        bytes_transferred = 0;
146  
        cancelled.store(false, std::memory_order_relaxed);
146  
        cancelled.store(false, std::memory_order_relaxed);
147  
        registered.store(
147  
        registered.store(
148  
            select_registration_state::unregistered, std::memory_order_relaxed);
148  
            select_registration_state::unregistered, std::memory_order_relaxed);
149  
        impl_ptr.reset();
149  
        impl_ptr.reset();
150  
        socket_impl_   = nullptr;
150  
        socket_impl_   = nullptr;
151  
        acceptor_impl_ = nullptr;
151  
        acceptor_impl_ = nullptr;
152  
    }
152  
    }
153  

153  

154  
    void operator()() override
154  
    void operator()() override
155  
    {
155  
    {
156  
        stop_cb.reset();
156  
        stop_cb.reset();
157  

157  

158  
        if (ec_out)
158  
        if (ec_out)
159  
        {
159  
        {
160  
            if (cancelled.load(std::memory_order_acquire))
160  
            if (cancelled.load(std::memory_order_acquire))
161  
                *ec_out = capy::error::canceled;
161  
                *ec_out = capy::error::canceled;
162  
            else if (errn != 0)
162  
            else if (errn != 0)
163  
                *ec_out = make_err(errn);
163  
                *ec_out = make_err(errn);
164  
            else if (is_read_operation() && bytes_transferred == 0)
164  
            else if (is_read_operation() && bytes_transferred == 0)
165  
                *ec_out = capy::error::eof;
165  
                *ec_out = capy::error::eof;
166  
            else
166  
            else
167  
                *ec_out = {};
167  
                *ec_out = {};
168  
        }
168  
        }
169  

169  

170  
        if (bytes_out)
170  
        if (bytes_out)
171  
            *bytes_out = bytes_transferred;
171  
            *bytes_out = bytes_transferred;
172  

172  

173  
        // Move to stack before destroying the frame
173  
        // Move to stack before destroying the frame
174  
        capy::executor_ref saved_ex(ex);
174  
        capy::executor_ref saved_ex(ex);
175  
        std::coroutine_handle<> saved_h(h);
175  
        std::coroutine_handle<> saved_h(h);
176  
        impl_ptr.reset();
176  
        impl_ptr.reset();
177  
        dispatch_coro(saved_ex, saved_h).resume();
177  
        dispatch_coro(saved_ex, saved_h).resume();
178  
    }
178  
    }
179  

179  

180  
    virtual bool is_read_operation() const noexcept
180  
    virtual bool is_read_operation() const noexcept
181  
    {
181  
    {
182  
        return false;
182  
        return false;
183  
    }
183  
    }
184  
    virtual void cancel() noexcept = 0;
184  
    virtual void cancel() noexcept = 0;
185  

185  

186  
    void destroy() override
186  
    void destroy() override
187  
    {
187  
    {
188  
        stop_cb.reset();
188  
        stop_cb.reset();
189  
        impl_ptr.reset();
189  
        impl_ptr.reset();
190  
    }
190  
    }
191  

191  

192  
    void request_cancel() noexcept
192  
    void request_cancel() noexcept
193  
    {
193  
    {
194  
        cancelled.store(true, std::memory_order_release);
194  
        cancelled.store(true, std::memory_order_release);
195  
    }
195  
    }
196  

196  

197  
    void start(std::stop_token const& token)
197  
    void start(std::stop_token const& token)
198  
    {
198  
    {
199  
        cancelled.store(false, std::memory_order_release);
199  
        cancelled.store(false, std::memory_order_release);
200  
        stop_cb.reset();
200  
        stop_cb.reset();
201  
        socket_impl_   = nullptr;
201  
        socket_impl_   = nullptr;
202  
        acceptor_impl_ = nullptr;
202  
        acceptor_impl_ = nullptr;
203  

203  

204  
        if (token.stop_possible())
204  
        if (token.stop_possible())
205  
            stop_cb.emplace(token, canceller{this});
205  
            stop_cb.emplace(token, canceller{this});
206  
    }
206  
    }
207  

207  

208  
    void start(std::stop_token const& token, select_socket* impl)
208  
    void start(std::stop_token const& token, select_socket* impl)
209  
    {
209  
    {
210  
        cancelled.store(false, std::memory_order_release);
210  
        cancelled.store(false, std::memory_order_release);
211  
        stop_cb.reset();
211  
        stop_cb.reset();
212  
        socket_impl_   = impl;
212  
        socket_impl_   = impl;
213  
        acceptor_impl_ = nullptr;
213  
        acceptor_impl_ = nullptr;
214  

214  

215  
        if (token.stop_possible())
215  
        if (token.stop_possible())
216  
            stop_cb.emplace(token, canceller{this});
216  
            stop_cb.emplace(token, canceller{this});
217  
    }
217  
    }
218  

218  

219  
    void start(std::stop_token const& token, select_acceptor* impl)
219  
    void start(std::stop_token const& token, select_acceptor* impl)
220  
    {
220  
    {
221  
        cancelled.store(false, std::memory_order_release);
221  
        cancelled.store(false, std::memory_order_release);
222  
        stop_cb.reset();
222  
        stop_cb.reset();
223  
        socket_impl_   = nullptr;
223  
        socket_impl_   = nullptr;
224  
        acceptor_impl_ = impl;
224  
        acceptor_impl_ = impl;
225  

225  

226  
        if (token.stop_possible())
226  
        if (token.stop_possible())
227  
            stop_cb.emplace(token, canceller{this});
227  
            stop_cb.emplace(token, canceller{this});
228  
    }
228  
    }
229  

229  

230  
    void complete(int err, std::size_t bytes) noexcept
230  
    void complete(int err, std::size_t bytes) noexcept
231  
    {
231  
    {
232  
        errn              = err;
232  
        errn              = err;
233  
        bytes_transferred = bytes;
233  
        bytes_transferred = bytes;
234  
    }
234  
    }
235  

235  

236  
    virtual void perform_io() noexcept {}
236  
    virtual void perform_io() noexcept {}
237  
};
237  
};
238  

238  

239  
struct select_connect_op final : select_op
239  
struct select_connect_op final : select_op
240  
{
240  
{
241  
    endpoint target_endpoint;
241  
    endpoint target_endpoint;
242  

242  

243  
    void reset() noexcept
243  
    void reset() noexcept
244  
    {
244  
    {
245  
        select_op::reset();
245  
        select_op::reset();
246  
        target_endpoint = endpoint{};
246  
        target_endpoint = endpoint{};
247  
    }
247  
    }
248  

248  

249  
    void perform_io() noexcept override
249  
    void perform_io() noexcept override
250  
    {
250  
    {
251  
        // connect() completion status is retrieved via SO_ERROR, not return value
251  
        // connect() completion status is retrieved via SO_ERROR, not return value
252  
        int err       = 0;
252  
        int err       = 0;
253  
        socklen_t len = sizeof(err);
253  
        socklen_t len = sizeof(err);
254  
        if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
254  
        if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
255  
            err = errno;
255  
            err = errno;
256  
        complete(err, 0);
256  
        complete(err, 0);
257  
    }
257  
    }
258  

258  

259  
    // Defined in sockets.cpp where select_socket is complete
259  
    // Defined in sockets.cpp where select_socket is complete
260  
    void operator()() override;
260  
    void operator()() override;
261  
    void cancel() noexcept override;
261  
    void cancel() noexcept override;
262  
};
262  
};
263  

263  

264  
struct select_read_op final : select_op
264  
struct select_read_op final : select_op
265  
{
265  
{
266  
    static constexpr std::size_t max_buffers = 16;
266  
    static constexpr std::size_t max_buffers = 16;
267  
    iovec iovecs[max_buffers];
267  
    iovec iovecs[max_buffers];
268  
    int iovec_count        = 0;
268  
    int iovec_count        = 0;
269  
    bool empty_buffer_read = false;
269  
    bool empty_buffer_read = false;
270  

270  

271  
    bool is_read_operation() const noexcept override
271  
    bool is_read_operation() const noexcept override
272  
    {
272  
    {
273  
        return !empty_buffer_read;
273  
        return !empty_buffer_read;
274  
    }
274  
    }
275  

275  

276  
    void reset() noexcept
276  
    void reset() noexcept
277  
    {
277  
    {
278  
        select_op::reset();
278  
        select_op::reset();
279  
        iovec_count       = 0;
279  
        iovec_count       = 0;
280  
        empty_buffer_read = false;
280  
        empty_buffer_read = false;
281  
    }
281  
    }
282  

282  

283  
    void perform_io() noexcept override
283  
    void perform_io() noexcept override
284  
    {
284  
    {
285  
        ssize_t n = ::readv(fd, iovecs, iovec_count);
285  
        ssize_t n = ::readv(fd, iovecs, iovec_count);
286  
        if (n >= 0)
286  
        if (n >= 0)
287  
            complete(0, static_cast<std::size_t>(n));
287  
            complete(0, static_cast<std::size_t>(n));
288  
        else
288  
        else
289  
            complete(errno, 0);
289  
            complete(errno, 0);
290  
    }
290  
    }
291  

291  

292  
    void cancel() noexcept override;
292  
    void cancel() noexcept override;
293  
};
293  
};
294  

294  

295  
struct select_write_op final : select_op
295  
struct select_write_op final : select_op
296  
{
296  
{
297  
    static constexpr std::size_t max_buffers = 16;
297  
    static constexpr std::size_t max_buffers = 16;
298  
    iovec iovecs[max_buffers];
298  
    iovec iovecs[max_buffers];
299  
    int iovec_count = 0;
299  
    int iovec_count = 0;
300  

300  

301  
    void reset() noexcept
301  
    void reset() noexcept
302  
    {
302  
    {
303  
        select_op::reset();
303  
        select_op::reset();
304  
        iovec_count = 0;
304  
        iovec_count = 0;
305  
    }
305  
    }
306  

306  

307  
    void perform_io() noexcept override
307  
    void perform_io() noexcept override
308  
    {
308  
    {
309  
        msghdr msg{};
309  
        msghdr msg{};
310  
        msg.msg_iov    = iovecs;
310  
        msg.msg_iov    = iovecs;
311  
        msg.msg_iovlen = static_cast<std::size_t>(iovec_count);
311  
        msg.msg_iovlen = static_cast<std::size_t>(iovec_count);
312  

312  

313  
        ssize_t n = ::sendmsg(fd, &msg, MSG_NOSIGNAL);
313  
        ssize_t n = ::sendmsg(fd, &msg, MSG_NOSIGNAL);
314  
        if (n >= 0)
314  
        if (n >= 0)
315  
            complete(0, static_cast<std::size_t>(n));
315  
            complete(0, static_cast<std::size_t>(n));
316  
        else
316  
        else
317  
            complete(errno, 0);
317  
            complete(errno, 0);
318  
    }
318  
    }
319  

319  

320  
    void cancel() noexcept override;
320  
    void cancel() noexcept override;
321  
};
321  
};
322  

322  

323  
struct select_accept_op final : select_op
323  
struct select_accept_op final : select_op
324  
{
324  
{
325  
    int accepted_fd                      = -1;
325  
    int accepted_fd                      = -1;
326  
    io_object::implementation* peer_impl = nullptr;
326  
    io_object::implementation* peer_impl = nullptr;
327  
    io_object::implementation** impl_out = nullptr;
327  
    io_object::implementation** impl_out = nullptr;
328  

328  

329  
    void reset() noexcept
329  
    void reset() noexcept
330  
    {
330  
    {
331  
        select_op::reset();
331  
        select_op::reset();
332  
        accepted_fd = -1;
332  
        accepted_fd = -1;
333  
        peer_impl   = nullptr;
333  
        peer_impl   = nullptr;
334  
        impl_out    = nullptr;
334  
        impl_out    = nullptr;
335  
    }
335  
    }
336  

336  

337  
    void perform_io() noexcept override
337  
    void perform_io() noexcept override
338  
    {
338  
    {
339  
        sockaddr_storage addr_storage{};
339  
        sockaddr_storage addr_storage{};
340  
        socklen_t addrlen = sizeof(addr_storage);
340  
        socklen_t addrlen = sizeof(addr_storage);
341  

341  

342  
        // Note: select backend uses accept() + fcntl instead of accept4()
342  
        // Note: select backend uses accept() + fcntl instead of accept4()
343  
        // for broader POSIX compatibility
343  
        // for broader POSIX compatibility
344  
        int new_fd =
344  
        int new_fd =
345  
            ::accept(fd, reinterpret_cast<sockaddr*>(&addr_storage), &addrlen);
345  
            ::accept(fd, reinterpret_cast<sockaddr*>(&addr_storage), &addrlen);
346  

346  

347  
        if (new_fd >= 0)
347  
        if (new_fd >= 0)
348  
        {
348  
        {
349  
            // Reject fds that exceed select()'s FD_SETSIZE limit.
349  
            // Reject fds that exceed select()'s FD_SETSIZE limit.
350  
            // Better to fail now than during later async operations.
350  
            // Better to fail now than during later async operations.
351  
            if (new_fd >= FD_SETSIZE)
351  
            if (new_fd >= FD_SETSIZE)
352  
            {
352  
            {
353  
                ::close(new_fd);
353  
                ::close(new_fd);
354  
                complete(EINVAL, 0);
354  
                complete(EINVAL, 0);
355  
                return;
355  
                return;
356  
            }
356  
            }
357  

357  

358  
            // Set non-blocking and close-on-exec flags.
358  
            // Set non-blocking and close-on-exec flags.
359  
            // A non-blocking socket is essential for the async reactor;
359  
            // A non-blocking socket is essential for the async reactor;
360  
            // if we can't configure it, fail rather than risk blocking.
360  
            // if we can't configure it, fail rather than risk blocking.
361  
            int flags = ::fcntl(new_fd, F_GETFL, 0);
361  
            int flags = ::fcntl(new_fd, F_GETFL, 0);
362  
            if (flags == -1)
362  
            if (flags == -1)
363  
            {
363  
            {
364  
                int err = errno;
364  
                int err = errno;
365  
                ::close(new_fd);
365  
                ::close(new_fd);
366  
                complete(err, 0);
366  
                complete(err, 0);
367  
                return;
367  
                return;
368  
            }
368  
            }
369  

369  

370  
            if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
370  
            if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
371  
            {
371  
            {
372  
                int err = errno;
372  
                int err = errno;
373  
                ::close(new_fd);
373  
                ::close(new_fd);
374  
                complete(err, 0);
374  
                complete(err, 0);
375  
                return;
375  
                return;
376  
            }
376  
            }
377  

377  

378  
            if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
378  
            if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
379  
            {
379  
            {
380  
                int err = errno;
380  
                int err = errno;
381  
                ::close(new_fd);
381  
                ::close(new_fd);
382  
                complete(err, 0);
382  
                complete(err, 0);
383  
                return;
383  
                return;
384  
            }
384  
            }
385  

385  

386  
            accepted_fd = new_fd;
386  
            accepted_fd = new_fd;
387  
            complete(0, 0);
387  
            complete(0, 0);
388  
        }
388  
        }
389  
        else
389  
        else
390  
        {
390  
        {
391  
            complete(errno, 0);
391  
            complete(errno, 0);
392  
        }
392  
        }
393  
    }
393  
    }
394  

394  

395  
    // Defined in acceptors.cpp where select_acceptor is complete
395  
    // Defined in acceptors.cpp where select_acceptor is complete
396  
    void operator()() override;
396  
    void operator()() override;
397  
    void cancel() noexcept override;
397  
    void cancel() noexcept override;
398  
};
398  
};
399  

399  

400  
} // namespace boost::corosio::detail
400  
} // namespace boost::corosio::detail
401  

401  

402  
#endif // BOOST_COROSIO_HAS_SELECT
402  
#endif // BOOST_COROSIO_HAS_SELECT
403  

403  

404  
#endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
404  
#endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP