1 -
//
 
2 -
// Copyright (c) 2026 Steve Gerbino
 
3 -
//
 
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)
 
6 -
//
 
7 -
// Official repository: https://github.com/cppalliance/corosio
 
8 -
//
 
9 -

 
10 -
#ifndef BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_OP_HPP
 
11 -
#define BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_OP_HPP
 
12 -

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

 
15 -
#if BOOST_COROSIO_HAS_EPOLL
 
16 -

 
17 -
#include <boost/corosio/native/detail/reactor/reactor_op.hpp>
 
18 -
#include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
 
19 -

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

 
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
 
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.
 
28 -

 
29 -
    Persistent Registration
 
30 -
    -----------------------
 
31 -
    File descriptors are registered with epoll once (via descriptor_state) and
 
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
 
34 -
    reactor dispatches to the appropriate pending operation.
 
35 -

 
36 -
    Impl Lifetime Management
 
37 -
    ------------------------
 
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`
 
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
 
42 -
    coroutine is resumed.
 
43 -

 
44 -
    EOF Detection
 
45 -
    -------------
 
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.
 
48 -

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

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

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

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

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

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

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

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

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

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

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

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

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

 
139 -
} // namespace boost::corosio::detail
 
140 -

 
141 -
#endif // BOOST_COROSIO_HAS_EPOLL
 
142 -

 
143 -
#endif // BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_OP_HPP