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

16  

18  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
17  
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19 -
/*
 
20 -
    epoll Operation State
 
21 -
    =====================
 
22 -

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

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

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

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

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

 
54  

18  

55  
namespace boost::corosio::detail {
19  
namespace boost::corosio::detail {
56 -
// Forward declarations
 
57 -
class epoll_tcp_socket;
 
58 -
class epoll_tcp_acceptor;
 
59 -
struct epoll_op;
 
60 -

 
61 -
// Forward declaration
 
62 -
class epoll_scheduler;
 
63 -

 
64  

20  

65  
/// Per-descriptor state for persistent epoll registration.
21  
/// Per-descriptor state for persistent epoll registration.
66  
struct descriptor_state final : reactor_descriptor_state
22  
struct descriptor_state final : reactor_descriptor_state
67 -

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

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

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

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

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

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

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

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

24  

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

26  

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

28  

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