include/boost/corosio/native/detail/epoll/epoll_traits.hpp

100.0% Lines (47 / 47) 100.0% Functions (13 / 13)
epoll_traits.hpp
f(x) Functions (13)
Line TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Michael Vandeberg
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_TRAITS_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_TRAITS_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_HAS_EPOLL
16
17 #include <boost/corosio/native/detail/make_err.hpp>
18 #include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19
20 #include <system_error>
21 #include <tuple>
22
23 #include <errno.h>
24 #include <netinet/in.h>
25 #include <sys/socket.h>
26 #include <sys/uio.h>
27 #include <unistd.h>
28
29 /* epoll backend traits.
30
31 Captures the platform-specific behavior of the Linux epoll backend:
32 atomic SOCK_NONBLOCK|SOCK_CLOEXEC on socket(), accept4() for
33 accepted connections, and sendmsg(MSG_NOSIGNAL) for writes.
34 */
35
36 namespace boost::corosio::detail {
37
38 class epoll_scheduler;
39
40 struct epoll_traits
41 {
42 using scheduler_type = epoll_scheduler;
43 using desc_state_type = reactor_descriptor_state;
44
45 static constexpr bool needs_write_notification = false;
46
47 // No extra per-socket state or lifecycle hooks needed for epoll.
48 struct stream_socket_hook
49 {
50 191x std::error_code on_set_option(
51 int fd,
52 int level,
53 int optname,
54 void const* data,
55 std::size_t size) noexcept
56 {
57 191x if (::setsockopt(
58 191x fd, level, optname, data, static_cast<socklen_t>(size)) !=
59 0)
60 5x return make_err(errno);
61 186x return {};
62 }
63 23008x static void pre_shutdown(int) noexcept {}
64 7483x static void pre_destroy(int) noexcept {}
65 };
66
67 struct write_policy
68 {
69 75x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
70 {
71 75x msghdr msg{};
72 75x msg.msg_iov = iovecs;
73 75x msg.msg_iovlen = static_cast<std::size_t>(count);
74
75 ssize_t n;
76 do
77 {
78 76x n = ::sendmsg(fd, &msg, MSG_NOSIGNAL);
79 }
80 76x while (n < 0 && errno == EINTR);
81 75x return n;
82 }
83
84 static ssize_t
85 101261x write_one(int fd, void const* data, std::size_t size) noexcept
86 {
87 ssize_t n;
88 do
89 {
90 101262x n = ::send(fd, data, size, MSG_NOSIGNAL);
91 }
92 101262x while (n < 0 && errno == EINTR);
93 101261x return n;
94 }
95 };
96
97 // Descriptors are not sockets: sendmsg() fails with ENOTSOCK on a
98 // pipe or character device, so the write path is writev()/write()
99 // and SIGPIPE suppression is structurally unavailable -- MSG_NOSIGNAL
100 // is a send() flag and SO_NOSIGPIPE a socket option. A write to a
101 // pipe whose read end has closed raises SIGPIPE, exactly as a plain
102 // write(2) would; callers install SIG_IGN.
103 struct descriptor_write_policy
104 {
105 2x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
106 {
107 ssize_t n;
108 do
109 {
110 2x n = ::writev(fd, iovecs, count);
111 }
112 2x while (n < 0 && errno == EINTR);
113 2x return n;
114 }
115
116 // Single-buffer fast path: skips the kernel's iov_iter setup.
117 static ssize_t
118 2x write_one(int fd, void const* data, std::size_t size) noexcept
119 {
120 ssize_t n;
121 do
122 {
123 2x n = ::write(fd, data, size);
124 }
125 2x while (n < 0 && errno == EINTR);
126 2x return n;
127 }
128 };
129
130 struct accept_policy
131 {
132 static int
133 4958x do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
134 {
135 4958x addrlen = sizeof(peer);
136 int new_fd;
137 do
138 {
139 4959x new_fd = ::accept4(
140 fd, reinterpret_cast<sockaddr*>(&peer), &addrlen,
141 SOCK_NONBLOCK | SOCK_CLOEXEC);
142 }
143 4959x while (new_fd < 0 && errno == EINTR);
144 4958x return new_fd;
145 }
146 };
147
148 // Create a nonblocking, close-on-exec socket using Linux's atomic flags.
149 3126x static int create_socket(int family, int type, int protocol) noexcept
150 {
151 3126x return ::socket(family, type | SOCK_NONBLOCK | SOCK_CLOEXEC, protocol);
152 }
153
154 // Apply protocol-specific options after socket creation.
155 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
156 2652x static std::error_code configure_ip_socket(int fd, int family) noexcept
157 {
158 2652x if (family == AF_INET6)
159 {
160 23x int one = 1;
161 std::ignore =
162 23x ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
163 }
164 2652x return {};
165 }
166
167 // Apply protocol-specific options for acceptor sockets.
168 // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
169 349x static std::error_code configure_ip_acceptor(int fd, int family) noexcept
170 {
171 349x if (family == AF_INET6)
172 {
173 11x int val = 0;
174 std::ignore =
175 11x ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
176 }
177 349x return {};
178 }
179
180 // No extra configuration needed for local (unix) sockets on epoll.
181 116x static std::error_code configure_local_socket(int /*fd*/) noexcept
182 {
183 116x return {};
184 }
185
186 // Non-mutating validation for fds adopted via assign(). Used when
187 // the caller retains fd ownership responsibility.
188 168x static std::error_code validate_assigned_fd(int /*fd*/) noexcept
189 {
190 168x return {};
191 }
192 };
193
194 } // namespace boost::corosio::detail
195
196 #endif // BOOST_COROSIO_HAS_EPOLL
197
198 #endif // BOOST_COROSIO_NATIVE_DETAIL_EPOLL_EPOLL_TRAITS_HPP
199