TLA Line data 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 HIT 191 : 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 191 : if (::setsockopt(
58 191 : fd, level, optname, data, static_cast<socklen_t>(size)) !=
59 : 0)
60 5 : return make_err(errno);
61 186 : return {};
62 : }
63 23008 : static void pre_shutdown(int) noexcept {}
64 7483 : static void pre_destroy(int) noexcept {}
65 : };
66 :
67 : struct write_policy
68 : {
69 75 : static ssize_t write(int fd, iovec* iovecs, int count) noexcept
70 : {
71 75 : msghdr msg{};
72 75 : msg.msg_iov = iovecs;
73 75 : msg.msg_iovlen = static_cast<std::size_t>(count);
74 :
75 : ssize_t n;
76 : do
77 : {
78 76 : n = ::sendmsg(fd, &msg, MSG_NOSIGNAL);
79 : }
80 76 : while (n < 0 && errno == EINTR);
81 75 : return n;
82 : }
83 :
84 : static ssize_t
85 101261 : write_one(int fd, void const* data, std::size_t size) noexcept
86 : {
87 : ssize_t n;
88 : do
89 : {
90 101262 : n = ::send(fd, data, size, MSG_NOSIGNAL);
91 : }
92 101262 : while (n < 0 && errno == EINTR);
93 101261 : 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 2 : static ssize_t write(int fd, iovec* iovecs, int count) noexcept
106 : {
107 : ssize_t n;
108 : do
109 : {
110 2 : n = ::writev(fd, iovecs, count);
111 : }
112 2 : while (n < 0 && errno == EINTR);
113 2 : return n;
114 : }
115 :
116 : // Single-buffer fast path: skips the kernel's iov_iter setup.
117 : static ssize_t
118 2 : write_one(int fd, void const* data, std::size_t size) noexcept
119 : {
120 : ssize_t n;
121 : do
122 : {
123 2 : n = ::write(fd, data, size);
124 : }
125 2 : while (n < 0 && errno == EINTR);
126 2 : return n;
127 : }
128 : };
129 :
130 : struct accept_policy
131 : {
132 : static int
133 4958 : do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
134 : {
135 4958 : addrlen = sizeof(peer);
136 : int new_fd;
137 : do
138 : {
139 4959 : new_fd = ::accept4(
140 : fd, reinterpret_cast<sockaddr*>(&peer), &addrlen,
141 : SOCK_NONBLOCK | SOCK_CLOEXEC);
142 : }
143 4959 : while (new_fd < 0 && errno == EINTR);
144 4958 : return new_fd;
145 : }
146 : };
147 :
148 : // Create a nonblocking, close-on-exec socket using Linux's atomic flags.
149 3126 : static int create_socket(int family, int type, int protocol) noexcept
150 : {
151 3126 : 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 2652 : static std::error_code configure_ip_socket(int fd, int family) noexcept
157 : {
158 2652 : if (family == AF_INET6)
159 : {
160 23 : int one = 1;
161 : std::ignore =
162 23 : ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
163 : }
164 2652 : return {};
165 : }
166 :
167 : // Apply protocol-specific options for acceptor sockets.
168 : // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
169 349 : static std::error_code configure_ip_acceptor(int fd, int family) noexcept
170 : {
171 349 : if (family == AF_INET6)
172 : {
173 11 : int val = 0;
174 : std::ignore =
175 11 : ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
176 : }
177 349 : return {};
178 : }
179 :
180 : // No extra configuration needed for local (unix) sockets on epoll.
181 116 : static std::error_code configure_local_socket(int /*fd*/) noexcept
182 : {
183 116 : return {};
184 : }
185 :
186 : // Non-mutating validation for fds adopted via assign(). Used when
187 : // the caller retains fd ownership responsibility.
188 168 : static std::error_code validate_assigned_fd(int /*fd*/) noexcept
189 : {
190 168 : 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
|