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_EPOLL_EPOLL_OP_HPP
10  
#ifndef BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_OP_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_OP_HPP
11  
#define BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_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_EPOLL
15  
#if BOOST_COROSIO_HAS_EPOLL
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  

32  

33  
#include <atomic>
33  
#include <atomic>
34  
#include <cstddef>
34  
#include <cstddef>
35  
#include <memory>
35  
#include <memory>
36  
#include <mutex>
36  
#include <mutex>
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 <poll.h>
41  
#include <sys/socket.h>
42  
#include <sys/socket.h>
42  
#include <sys/uio.h>
43  
#include <sys/uio.h>
43  

44  

44  
/*
45  
/*
45  
    epoll Operation State
46  
    epoll Operation State
46  
    =====================
47  
    =====================
47  

48  

48  
    Each async I/O operation has a corresponding epoll_op-derived struct that
49  
    Each async I/O operation has a corresponding epoll_op-derived struct that
49  
    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
50  
    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
51  
    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.
52  

53  

53  
    Persistent Registration
54  
    Persistent Registration
54  
    -----------------------
55  
    -----------------------
55  
    File descriptors are registered with epoll once (via descriptor_state) and
56  
    File descriptors are registered with epoll once (via descriptor_state) and
56  
    stay registered until closed. The descriptor_state tracks which operations
57  
    stay registered until closed. The descriptor_state tracks which operations
57  
    are pending (read_op, write_op, connect_op). When an event arrives, the
58  
    are pending (read_op, write_op, connect_op). When an event arrives, the
58  
    reactor dispatches to the appropriate pending operation.
59  
    reactor dispatches to the appropriate pending operation.
59  

60  

60  
    Impl Lifetime Management
61  
    Impl Lifetime Management
61  
    ------------------------
62  
    ------------------------
62  
    When cancel() posts an op to the scheduler's ready queue, the socket impl
63  
    When cancel() posts an op to the scheduler's ready queue, the socket impl
63  
    might be destroyed before the scheduler processes the op. The `impl_ptr`
64  
    might be destroyed before the scheduler processes the op. The `impl_ptr`
64  
    member holds a shared_ptr to the impl, keeping it alive until the op
65  
    member holds a shared_ptr to the impl, keeping it alive until the op
65  
    completes. This is set by cancel() and cleared in operator() after the
66  
    completes. This is set by cancel() and cleared in operator() after the
66  
    coroutine is resumed.
67  
    coroutine is resumed.
67  

68  

68  
    EOF Detection
69  
    EOF Detection
69  
    -------------
70  
    -------------
70  
    For reads, 0 bytes with no error means EOF. But an empty user buffer also
71  
    For reads, 0 bytes with no error means EOF. But an empty user buffer also
71  
    returns 0 bytes. The `empty_buffer_read` flag distinguishes these cases.
72  
    returns 0 bytes. The `empty_buffer_read` flag distinguishes these cases.
72  

73  

73  
    SIGPIPE Prevention
74  
    SIGPIPE Prevention
74  
    ------------------
75  
    ------------------
75  
    Writes use sendmsg() with MSG_NOSIGNAL instead of writev() to prevent
76  
    Writes use sendmsg() with MSG_NOSIGNAL instead of writev() to prevent
76  
    SIGPIPE when the peer has closed.
77  
    SIGPIPE when the peer has closed.
77  
*/
78  
*/
78  

79  

79  
namespace boost::corosio::detail {
80  
namespace boost::corosio::detail {
80  

81  

81  
// Forward declarations
82  
// Forward declarations
82  
class epoll_socket;
83  
class epoll_socket;
83  
class epoll_acceptor;
84  
class epoll_acceptor;
84  
struct epoll_op;
85  
struct epoll_op;
85  

86  

86  
// Forward declaration
87  
// Forward declaration
87  
class epoll_scheduler;
88  
class epoll_scheduler;
88  

89  

89  
/** Per-descriptor state for persistent epoll registration.
90  
/** Per-descriptor state for persistent epoll registration.
90  

91  

91  
    Tracks pending operations for a file descriptor. The fd is registered
92  
    Tracks pending operations for a file descriptor. The fd is registered
92  
    once with epoll and stays registered until closed.
93  
    once with epoll and stays registered until closed.
93  

94  

94  
    This struct extends scheduler_op to support deferred I/O processing.
95  
    This struct extends scheduler_op to support deferred I/O processing.
95  
    When epoll events arrive, the reactor sets ready_events and queues
96  
    When epoll events arrive, the reactor sets ready_events and queues
96  
    this descriptor for processing. When popped from the scheduler queue,
97  
    this descriptor for processing. When popped from the scheduler queue,
97  
    operator() performs the actual I/O and queues completion handlers.
98  
    operator() performs the actual I/O and queues completion handlers.
98  

99  

99  
    @par Deferred I/O Model
100  
    @par Deferred I/O Model
100  
    The reactor no longer performs I/O directly. Instead:
101  
    The reactor no longer performs I/O directly. Instead:
101  
    1. Reactor sets ready_events and queues descriptor_state
102  
    1. Reactor sets ready_events and queues descriptor_state
102  
    2. Scheduler pops descriptor_state and calls operator()
103  
    2. Scheduler pops descriptor_state and calls operator()
103  
    3. operator() performs I/O under mutex and queues completions
104  
    3. operator() performs I/O under mutex and queues completions
104  

105  

105  
    This eliminates per-descriptor mutex locking from the reactor hot path.
106  
    This eliminates per-descriptor mutex locking from the reactor hot path.
106  

107  

107  
    @par Thread Safety
108  
    @par Thread Safety
108  
    The mutex protects operation pointers and ready flags during I/O.
109  
    The mutex protects operation pointers and ready flags during I/O.
109  
    ready_events_ and is_enqueued_ are atomic for lock-free reactor access.
110  
    ready_events_ and is_enqueued_ are atomic for lock-free reactor access.
110  
*/
111  
*/
111  
struct descriptor_state final : scheduler_op
112  
struct descriptor_state final : scheduler_op
112  
{
113  
{
113  
    std::mutex mutex;
114  
    std::mutex mutex;
114  

115  

115  
    // Protected by mutex
116  
    // Protected by mutex
116  
    epoll_op* read_op    = nullptr;
117  
    epoll_op* read_op    = nullptr;
117  
    epoll_op* write_op   = nullptr;
118  
    epoll_op* write_op   = nullptr;
118  
    epoll_op* connect_op = nullptr;
119  
    epoll_op* connect_op = nullptr;
119  

120  

120  
    // Caches edge events that arrived before an op was registered
121  
    // Caches edge events that arrived before an op was registered
121  
    bool read_ready  = false;
122  
    bool read_ready  = false;
122  
    bool write_ready = false;
123  
    bool write_ready = false;
123  

124  

124  
    // Deferred cancellation: set by cancel() when the target op is not
125  
    // Deferred cancellation: set by cancel() when the target op is not
125  
    // parked (e.g. completing inline via speculative I/O). Checked when
126  
    // parked (e.g. completing inline via speculative I/O). Checked when
126  
    // the next op parks; if set, the op is immediately self-cancelled.
127  
    // the next op parks; if set, the op is immediately self-cancelled.
127  
    // This matches IOCP semantics where CancelIoEx always succeeds.
128  
    // This matches IOCP semantics where CancelIoEx always succeeds.
128  
    bool read_cancel_pending    = false;
129  
    bool read_cancel_pending    = false;
129  
    bool write_cancel_pending   = false;
130  
    bool write_cancel_pending   = false;
130  
    bool connect_cancel_pending = false;
131  
    bool connect_cancel_pending = false;
131  

132  

132 -
    // Set during registration only (no mutex needed)
133 +
    // Protected by mutex (written by register_descriptor and ensure_write_events)
133  
    std::uint32_t registered_events = 0;
134  
    std::uint32_t registered_events = 0;
134  
    int fd                          = -1;
135  
    int fd                          = -1;
135  

136  

136  
    // For deferred I/O - set by reactor, read by scheduler
137  
    // For deferred I/O - set by reactor, read by scheduler
137  
    std::atomic<std::uint32_t> ready_events_{0};
138  
    std::atomic<std::uint32_t> ready_events_{0};
138  
    std::atomic<bool> is_enqueued_{false};
139  
    std::atomic<bool> is_enqueued_{false};
139  
    epoll_scheduler const* scheduler_ = nullptr;
140  
    epoll_scheduler const* scheduler_ = nullptr;
140  

141  

141  
    // Prevents impl destruction while this descriptor_state is queued.
142  
    // Prevents impl destruction while this descriptor_state is queued.
142  
    // Set by close_socket() when is_enqueued_ is true, cleared by operator().
143  
    // Set by close_socket() when is_enqueued_ is true, cleared by operator().
143  
    std::shared_ptr<void> impl_ref_;
144  
    std::shared_ptr<void> impl_ref_;
144  

145  

145  
    /// Add ready events atomically.
146  
    /// Add ready events atomically.
146  
    void add_ready_events(std::uint32_t ev) noexcept
147  
    void add_ready_events(std::uint32_t ev) noexcept
147  
    {
148  
    {
148  
        ready_events_.fetch_or(ev, std::memory_order_relaxed);
149  
        ready_events_.fetch_or(ev, std::memory_order_relaxed);
149  
    }
150  
    }
150  

151  

151  
    /// Perform deferred I/O and queue completions.
152  
    /// Perform deferred I/O and queue completions.
152  
    void operator()() override;
153  
    void operator()() override;
153  

154  

154  
    /// Destroy without invoking.
155  
    /// Destroy without invoking.
155  
    /// Called during scheduler::shutdown() drain. Clear impl_ref_ to break
156  
    /// Called during scheduler::shutdown() drain. Clear impl_ref_ to break
156  
    /// the self-referential cycle set by close_socket().
157  
    /// the self-referential cycle set by close_socket().
157  
    void destroy() override
158  
    void destroy() override
158  
    {
159  
    {
159  
        impl_ref_.reset();
160  
        impl_ref_.reset();
160  
    }
161  
    }
161  
};
162  
};
162  

163  

163  
struct epoll_op : scheduler_op
164  
struct epoll_op : scheduler_op
164  
{
165  
{
165  
    struct canceller
166  
    struct canceller
166  
    {
167  
    {
167  
        epoll_op* op;
168  
        epoll_op* op;
168  
        void operator()() const noexcept;
169  
        void operator()() const noexcept;
169  
    };
170  
    };
170  

171  

171  
    std::coroutine_handle<> h;
172  
    std::coroutine_handle<> h;
172  
    capy::executor_ref ex;
173  
    capy::executor_ref ex;
173  
    std::error_code* ec_out = nullptr;
174  
    std::error_code* ec_out = nullptr;
174  
    std::size_t* bytes_out  = nullptr;
175  
    std::size_t* bytes_out  = nullptr;
175  

176  

176  
    int fd                        = -1;
177  
    int fd                        = -1;
177  
    int errn                      = 0;
178  
    int errn                      = 0;
178  
    std::size_t bytes_transferred = 0;
179  
    std::size_t bytes_transferred = 0;
179  

180  

180  
    std::atomic<bool> cancelled{false};
181  
    std::atomic<bool> cancelled{false};
181  
    std::optional<std::stop_callback<canceller>> stop_cb;
182  
    std::optional<std::stop_callback<canceller>> stop_cb;
182  

183  

183  
    // Prevents use-after-free when socket is closed with pending ops.
184  
    // Prevents use-after-free when socket is closed with pending ops.
184  
    // See "Impl Lifetime Management" in file header.
185  
    // See "Impl Lifetime Management" in file header.
185  
    std::shared_ptr<void> impl_ptr;
186  
    std::shared_ptr<void> impl_ptr;
186  

187  

187  
    // For stop_token cancellation - pointer to owning socket/acceptor impl.
188  
    // For stop_token cancellation - pointer to owning socket/acceptor impl.
188  
    // When stop is requested, we call back to the impl to perform actual I/O cancellation.
189  
    // When stop is requested, we call back to the impl to perform actual I/O cancellation.
189  
    epoll_socket* socket_impl_     = nullptr;
190  
    epoll_socket* socket_impl_     = nullptr;
190  
    epoll_acceptor* acceptor_impl_ = nullptr;
191  
    epoll_acceptor* acceptor_impl_ = nullptr;
191  

192  

192  
    epoll_op() = default;
193  
    epoll_op() = default;
193  

194  

194  
    void reset() noexcept
195  
    void reset() noexcept
195  
    {
196  
    {
196  
        fd                = -1;
197  
        fd                = -1;
197  
        errn              = 0;
198  
        errn              = 0;
198  
        bytes_transferred = 0;
199  
        bytes_transferred = 0;
199  
        cancelled.store(false, std::memory_order_relaxed);
200  
        cancelled.store(false, std::memory_order_relaxed);
200  
        impl_ptr.reset();
201  
        impl_ptr.reset();
201  
        socket_impl_   = nullptr;
202  
        socket_impl_   = nullptr;
202  
        acceptor_impl_ = nullptr;
203  
        acceptor_impl_ = nullptr;
203  
    }
204  
    }
204  

205  

205  
    // Defined in sockets.cpp where epoll_socket is complete
206  
    // Defined in sockets.cpp where epoll_socket is complete
206  
    void operator()() override;
207  
    void operator()() override;
207  

208  

208  
    virtual bool is_read_operation() const noexcept
209  
    virtual bool is_read_operation() const noexcept
209  
    {
210  
    {
210  
        return false;
211  
        return false;
211  
    }
212  
    }
212  
    virtual void cancel() noexcept = 0;
213  
    virtual void cancel() noexcept = 0;
213  

214  

214  
    void destroy() override
215  
    void destroy() override
215  
    {
216  
    {
216  
        stop_cb.reset();
217  
        stop_cb.reset();
217  
        impl_ptr.reset();
218  
        impl_ptr.reset();
218  
    }
219  
    }
219  

220  

220  
    void request_cancel() noexcept
221  
    void request_cancel() noexcept
221  
    {
222  
    {
222  
        cancelled.store(true, std::memory_order_release);
223  
        cancelled.store(true, std::memory_order_release);
223  
    }
224  
    }
224  

225  

225  
    void start(std::stop_token const& token, epoll_socket* impl)
226  
    void start(std::stop_token const& token, epoll_socket* impl)
226  
    {
227  
    {
227  
        cancelled.store(false, std::memory_order_release);
228  
        cancelled.store(false, std::memory_order_release);
228  
        stop_cb.reset();
229  
        stop_cb.reset();
229  
        socket_impl_   = impl;
230  
        socket_impl_   = impl;
230  
        acceptor_impl_ = nullptr;
231  
        acceptor_impl_ = nullptr;
231  

232  

232  
        if (token.stop_possible())
233  
        if (token.stop_possible())
233  
            stop_cb.emplace(token, canceller{this});
234  
            stop_cb.emplace(token, canceller{this});
234  
    }
235  
    }
235  

236  

236  
    void start(std::stop_token const& token, epoll_acceptor* impl)
237  
    void start(std::stop_token const& token, epoll_acceptor* impl)
237  
    {
238  
    {
238  
        cancelled.store(false, std::memory_order_release);
239  
        cancelled.store(false, std::memory_order_release);
239  
        stop_cb.reset();
240  
        stop_cb.reset();
240  
        socket_impl_   = nullptr;
241  
        socket_impl_   = nullptr;
241  
        acceptor_impl_ = impl;
242  
        acceptor_impl_ = impl;
242  

243  

243  
        if (token.stop_possible())
244  
        if (token.stop_possible())
244  
            stop_cb.emplace(token, canceller{this});
245  
            stop_cb.emplace(token, canceller{this});
245  
    }
246  
    }
246  

247  

247  
    void complete(int err, std::size_t bytes) noexcept
248  
    void complete(int err, std::size_t bytes) noexcept
248  
    {
249  
    {
249  
        errn              = err;
250  
        errn              = err;
250  
        bytes_transferred = bytes;
251  
        bytes_transferred = bytes;
251  
    }
252  
    }
252  

253  

253  
    virtual void perform_io() noexcept {}
254  
    virtual void perform_io() noexcept {}
254  
};
255  
};
255  

256  

256  
struct epoll_connect_op final : epoll_op
257  
struct epoll_connect_op final : epoll_op
257  
{
258  
{
258  
    endpoint target_endpoint;
259  
    endpoint target_endpoint;
259  

260  

260  
    void reset() noexcept
261  
    void reset() noexcept
261  
    {
262  
    {
262  
        epoll_op::reset();
263  
        epoll_op::reset();
263  
        target_endpoint = endpoint{};
264  
        target_endpoint = endpoint{};
264  
    }
265  
    }
265  

266  

266  
    void perform_io() noexcept override
267  
    void perform_io() noexcept override
267  
    {
268  
    {
268 -
        // connect() completion status is retrieved via SO_ERROR, not return value
269 +
        // Guard against spurious write-ready events: a zero-timeout
 
270 +
        // poll confirms the fd is actually writable (connect finished).
 
271 +
        pollfd pfd{};
 
272 +
        pfd.fd     = fd;
 
273 +
        pfd.events = POLLOUT;
 
274 +
        if (::poll(&pfd, 1, 0) == 0)
 
275 +
        {
 
276 +
            complete(EAGAIN, 0);
 
277 +
            return;
 
278 +
        }
 
279 +

269  
        int err       = 0;
280  
        int err       = 0;
270  
        socklen_t len = sizeof(err);
281  
        socklen_t len = sizeof(err);
271  
        if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
282  
        if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0)
272  
            err = errno;
283  
            err = errno;
273  
        complete(err, 0);
284  
        complete(err, 0);
274  
    }
285  
    }
275  

286  

276  
    // Defined in sockets.cpp where epoll_socket is complete
287  
    // Defined in sockets.cpp where epoll_socket is complete
277  
    void operator()() override;
288  
    void operator()() override;
278  
    void cancel() noexcept override;
289  
    void cancel() noexcept override;
279  
};
290  
};
280  

291  

281  
struct epoll_read_op final : epoll_op
292  
struct epoll_read_op final : epoll_op
282  
{
293  
{
283  
    static constexpr std::size_t max_buffers = 16;
294  
    static constexpr std::size_t max_buffers = 16;
284  
    iovec iovecs[max_buffers];
295  
    iovec iovecs[max_buffers];
285  
    int iovec_count        = 0;
296  
    int iovec_count        = 0;
286  
    bool empty_buffer_read = false;
297  
    bool empty_buffer_read = false;
287  

298  

288  
    bool is_read_operation() const noexcept override
299  
    bool is_read_operation() const noexcept override
289  
    {
300  
    {
290  
        return !empty_buffer_read;
301  
        return !empty_buffer_read;
291  
    }
302  
    }
292  

303  

293  
    void reset() noexcept
304  
    void reset() noexcept
294  
    {
305  
    {
295  
        epoll_op::reset();
306  
        epoll_op::reset();
296  
        iovec_count       = 0;
307  
        iovec_count       = 0;
297  
        empty_buffer_read = false;
308  
        empty_buffer_read = false;
298  
    }
309  
    }
299  

310  

300  
    void perform_io() noexcept override
311  
    void perform_io() noexcept override
301  
    {
312  
    {
302  
        ssize_t n;
313  
        ssize_t n;
303  
        do
314  
        do
304  
        {
315  
        {
305  
            n = ::readv(fd, iovecs, iovec_count);
316  
            n = ::readv(fd, iovecs, iovec_count);
306  
        }
317  
        }
307  
        while (n < 0 && errno == EINTR);
318  
        while (n < 0 && errno == EINTR);
308  

319  

309  
        if (n >= 0)
320  
        if (n >= 0)
310  
            complete(0, static_cast<std::size_t>(n));
321  
            complete(0, static_cast<std::size_t>(n));
311  
        else
322  
        else
312  
            complete(errno, 0);
323  
            complete(errno, 0);
313  
    }
324  
    }
314  

325  

315  
    void cancel() noexcept override;
326  
    void cancel() noexcept override;
316  
};
327  
};
317  

328  

318  
struct epoll_write_op final : epoll_op
329  
struct epoll_write_op final : epoll_op
319  
{
330  
{
320  
    static constexpr std::size_t max_buffers = 16;
331  
    static constexpr std::size_t max_buffers = 16;
321  
    iovec iovecs[max_buffers];
332  
    iovec iovecs[max_buffers];
322  
    int iovec_count = 0;
333  
    int iovec_count = 0;
323  

334  

324  
    void reset() noexcept
335  
    void reset() noexcept
325  
    {
336  
    {
326  
        epoll_op::reset();
337  
        epoll_op::reset();
327  
        iovec_count = 0;
338  
        iovec_count = 0;
328  
    }
339  
    }
329  

340  

330  
    void perform_io() noexcept override
341  
    void perform_io() noexcept override
331  
    {
342  
    {
332  
        msghdr msg{};
343  
        msghdr msg{};
333  
        msg.msg_iov    = iovecs;
344  
        msg.msg_iov    = iovecs;
334  
        msg.msg_iovlen = static_cast<std::size_t>(iovec_count);
345  
        msg.msg_iovlen = static_cast<std::size_t>(iovec_count);
335  

346  

336  
        ssize_t n;
347  
        ssize_t n;
337  
        do
348  
        do
338  
        {
349  
        {
339  
            n = ::sendmsg(fd, &msg, MSG_NOSIGNAL);
350  
            n = ::sendmsg(fd, &msg, MSG_NOSIGNAL);
340  
        }
351  
        }
341  
        while (n < 0 && errno == EINTR);
352  
        while (n < 0 && errno == EINTR);
342  

353  

343  
        if (n >= 0)
354  
        if (n >= 0)
344  
            complete(0, static_cast<std::size_t>(n));
355  
            complete(0, static_cast<std::size_t>(n));
345  
        else
356  
        else
346  
            complete(errno, 0);
357  
            complete(errno, 0);
347  
    }
358  
    }
348  

359  

349  
    void cancel() noexcept override;
360  
    void cancel() noexcept override;
350  
};
361  
};
351  

362  

352  
struct epoll_accept_op final : epoll_op
363  
struct epoll_accept_op final : epoll_op
353  
{
364  
{
354  
    int accepted_fd                      = -1;
365  
    int accepted_fd                      = -1;
355  
    io_object::implementation** impl_out = nullptr;
366  
    io_object::implementation** impl_out = nullptr;
356  
    sockaddr_storage peer_storage{};
367  
    sockaddr_storage peer_storage{};
357  

368  

358  
    void reset() noexcept
369  
    void reset() noexcept
359  
    {
370  
    {
360  
        epoll_op::reset();
371  
        epoll_op::reset();
361  
        accepted_fd  = -1;
372  
        accepted_fd  = -1;
362  
        impl_out     = nullptr;
373  
        impl_out     = nullptr;
363  
        peer_storage = {};
374  
        peer_storage = {};
364  
    }
375  
    }
365  

376  

366  
    void perform_io() noexcept override
377  
    void perform_io() noexcept override
367  
    {
378  
    {
368  
        socklen_t addrlen = sizeof(peer_storage);
379  
        socklen_t addrlen = sizeof(peer_storage);
369  
        int new_fd;
380  
        int new_fd;
370  
        do
381  
        do
371  
        {
382  
        {
372  
            new_fd = ::accept4(
383  
            new_fd = ::accept4(
373  
                fd, reinterpret_cast<sockaddr*>(&peer_storage), &addrlen,
384  
                fd, reinterpret_cast<sockaddr*>(&peer_storage), &addrlen,
374  
                SOCK_NONBLOCK | SOCK_CLOEXEC);
385  
                SOCK_NONBLOCK | SOCK_CLOEXEC);
375  
        }
386  
        }
376  
        while (new_fd < 0 && errno == EINTR);
387  
        while (new_fd < 0 && errno == EINTR);
377  

388  

378  
        if (new_fd >= 0)
389  
        if (new_fd >= 0)
379  
        {
390  
        {
380  
            accepted_fd = new_fd;
391  
            accepted_fd = new_fd;
381  
            complete(0, 0);
392  
            complete(0, 0);
382  
        }
393  
        }
383  
        else
394  
        else
384  
        {
395  
        {
385  
            complete(errno, 0);
396  
            complete(errno, 0);
386  
        }
397  
        }
387  
    }
398  
    }
388  

399  

389  
    // Defined in acceptors.cpp where epoll_acceptor is complete
400  
    // Defined in acceptors.cpp where epoll_acceptor is complete
390  
    void operator()() override;
401  
    void operator()() override;
391  
    void cancel() noexcept override;
402  
    void cancel() noexcept override;
392  
};
403  
};
393  

404  

394  
} // namespace boost::corosio::detail
405  
} // namespace boost::corosio::detail
395  

406  

396  
#endif // BOOST_COROSIO_HAS_EPOLL
407  
#endif // BOOST_COROSIO_HAS_EPOLL
397  

408  

398  
#endif // BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_OP_HPP
409  
#endif // BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_OP_HPP