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/native/detail/reactor/reactor_op.hpp>
17  
#include <boost/corosio/native/detail/reactor/reactor_op.hpp>
18  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
18  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19  

19  

20  
/*
20  
/*
21  
    epoll Operation State
21  
    epoll Operation State
22  
    =====================
22  
    =====================
23  

23  

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

28  

29  
    Persistent Registration
29  
    Persistent Registration
30  
    -----------------------
30  
    -----------------------
31  
    File descriptors are registered with epoll once (via descriptor_state) and
31  
    File descriptors are registered with epoll once (via descriptor_state) and
32  
    stay registered until closed. The descriptor_state tracks which operations
32  
    stay registered until closed. The descriptor_state tracks which operations
33  
    are pending (read_op, write_op, connect_op). When an event arrives, the
33  
    are pending (read_op, write_op, connect_op). When an event arrives, the
34  
    reactor dispatches to the appropriate pending operation.
34  
    reactor dispatches to the appropriate pending operation.
35  

35  

36  
    Impl Lifetime Management
36  
    Impl Lifetime Management
37  
    ------------------------
37  
    ------------------------
38  
    When cancel() posts an op to the scheduler's ready queue, the socket impl
38  
    When cancel() posts an op to the scheduler's ready queue, the socket impl
39  
    might be destroyed before the scheduler processes the op. The `impl_ptr`
39  
    might be destroyed before the scheduler processes the op. The `impl_ptr`
40  
    member holds a shared_ptr to the impl, keeping it alive until the op
40  
    member holds a shared_ptr to the impl, keeping it alive until the op
41  
    completes. This is set by cancel() and cleared in operator() after the
41  
    completes. This is set by cancel() and cleared in operator() after the
42  
    coroutine is resumed.
42  
    coroutine is resumed.
43  

43  

44  
    EOF Detection
44  
    EOF Detection
45  
    -------------
45  
    -------------
46  
    For reads, 0 bytes with no error means EOF. But an empty user buffer also
46  
    For reads, 0 bytes with no error means EOF. But an empty user buffer also
47  
    returns 0 bytes. The `empty_buffer_read` flag distinguishes these cases.
47  
    returns 0 bytes. The `empty_buffer_read` flag distinguishes these cases.
48  

48  

49  
    SIGPIPE Prevention
49  
    SIGPIPE Prevention
50  
    ------------------
50  
    ------------------
51  
    Writes use sendmsg() with MSG_NOSIGNAL instead of writev() to prevent
51  
    Writes use sendmsg() with MSG_NOSIGNAL instead of writev() to prevent
52  
    SIGPIPE when the peer has closed.
52  
    SIGPIPE when the peer has closed.
53  
*/
53  
*/
54  

54  

55  
namespace boost::corosio::detail {
55  
namespace boost::corosio::detail {
56  

56  

57  
// Forward declarations
57  
// Forward declarations
58  
class epoll_tcp_socket;
58  
class epoll_tcp_socket;
59  
class epoll_tcp_acceptor;
59  
class epoll_tcp_acceptor;
60  
struct epoll_op;
60  
struct epoll_op;
61  

61  

62  
// Forward declaration
62  
// Forward declaration
63  
class epoll_scheduler;
63  
class epoll_scheduler;
64  

64  

65  
/// Per-descriptor state for persistent epoll registration.
65  
/// Per-descriptor state for persistent epoll registration.
66  
struct descriptor_state final : reactor_descriptor_state
66  
struct descriptor_state final : reactor_descriptor_state
67  
{};
67  
{};
68  

68  

69  
/// epoll base operation — thin wrapper over reactor_op.
69  
/// epoll base operation — thin wrapper over reactor_op.
70  
struct epoll_op : reactor_op<epoll_tcp_socket, epoll_tcp_acceptor>
70  
struct epoll_op : reactor_op<epoll_tcp_socket, epoll_tcp_acceptor>
71  
{
71  
{
72  
    void operator()() override;
72  
    void operator()() override;
73  
};
73  
};
74  

74  

75  
/// epoll connect operation.
75  
/// epoll connect operation.
76  
struct epoll_connect_op final : reactor_connect_op<epoll_op>
76  
struct epoll_connect_op final : reactor_connect_op<epoll_op>
77  
{
77  
{
78  
    void operator()() override;
78  
    void operator()() override;
79  
    void cancel() noexcept override;
79  
    void cancel() noexcept override;
80  
};
80  
};
81  

81  

82  
/// epoll scatter-read operation.
82  
/// epoll scatter-read operation.
83  
struct epoll_read_op final : reactor_read_op<epoll_op>
83  
struct epoll_read_op final : reactor_read_op<epoll_op>
84  
{
84  
{
85  
    void cancel() noexcept override;
85  
    void cancel() noexcept override;
86  
};
86  
};
87  

87  

88  
/** Provides sendmsg(MSG_NOSIGNAL) with EINTR retry for epoll writes. */
88  
/** Provides sendmsg(MSG_NOSIGNAL) with EINTR retry for epoll writes. */
89  
struct epoll_write_policy
89  
struct epoll_write_policy
90  
{
90  
{
91  
    static ssize_t write(int fd, iovec* iovecs, int count) noexcept
91  
    static ssize_t write(int fd, iovec* iovecs, int count) noexcept
92  
    {
92  
    {
93  
        msghdr msg{};
93  
        msghdr msg{};
94  
        msg.msg_iov    = iovecs;
94  
        msg.msg_iov    = iovecs;
95  
        msg.msg_iovlen = static_cast<std::size_t>(count);
95  
        msg.msg_iovlen = static_cast<std::size_t>(count);
96  

96  

97  
        ssize_t n;
97  
        ssize_t n;
98  
        do
98  
        do
99  
        {
99  
        {
100  
            n = ::sendmsg(fd, &msg, MSG_NOSIGNAL);
100  
            n = ::sendmsg(fd, &msg, MSG_NOSIGNAL);
101  
        }
101  
        }
102  
        while (n < 0 && errno == EINTR);
102  
        while (n < 0 && errno == EINTR);
103  
        return n;
103  
        return n;
104  
    }
104  
    }
105  
};
105  
};
106  

106  

107  
/// epoll gather-write operation.
107  
/// epoll gather-write operation.
108  
struct epoll_write_op final : reactor_write_op<epoll_op, epoll_write_policy>
108  
struct epoll_write_op final : reactor_write_op<epoll_op, epoll_write_policy>
109  
{
109  
{
110  
    void cancel() noexcept override;
110  
    void cancel() noexcept override;
111  
};
111  
};
112  

112  

113  
/** Provides accept4(SOCK_NONBLOCK|SOCK_CLOEXEC) with EINTR retry. */
113  
/** Provides accept4(SOCK_NONBLOCK|SOCK_CLOEXEC) with EINTR retry. */
114  
struct epoll_accept_policy
114  
struct epoll_accept_policy
115  
{
115  
{
116  
    static int do_accept(int fd, sockaddr_storage& peer) noexcept
116  
    static int do_accept(int fd, sockaddr_storage& peer) noexcept
117  
    {
117  
    {
118  
        socklen_t addrlen = sizeof(peer);
118  
        socklen_t addrlen = sizeof(peer);
119  
        int new_fd;
119  
        int new_fd;
120  
        do
120  
        do
121  
        {
121  
        {
122  
            new_fd = ::accept4(
122  
            new_fd = ::accept4(
123  
                fd, reinterpret_cast<sockaddr*>(&peer), &addrlen,
123  
                fd, reinterpret_cast<sockaddr*>(&peer), &addrlen,
124  
                SOCK_NONBLOCK | SOCK_CLOEXEC);
124  
                SOCK_NONBLOCK | SOCK_CLOEXEC);
125  
        }
125  
        }
126  
        while (new_fd < 0 && errno == EINTR);
126  
        while (new_fd < 0 && errno == EINTR);
127  
        return new_fd;
127  
        return new_fd;
128  
    }
128  
    }
129  
};
129  
};
130  

130  

131  
/// epoll accept operation.
131  
/// epoll accept operation.
132  
struct epoll_accept_op final : reactor_accept_op<epoll_op, epoll_accept_policy>
132  
struct epoll_accept_op final : reactor_accept_op<epoll_op, epoll_accept_policy>
133  
{
133  
{
134  
    void operator()() override;
134  
    void operator()() override;
135  
    void cancel() noexcept override;
135  
    void cancel() noexcept override;
136  
};
136  
};
137  

137  

138  
} // namespace boost::corosio::detail
138  
} // namespace boost::corosio::detail
139  

139  

140  
#endif // BOOST_COROSIO_HAS_EPOLL
140  
#endif // BOOST_COROSIO_HAS_EPOLL
141  

141  

142  
#endif // BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_OP_HPP
142  
#endif // BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_OP_HPP