include/boost/corosio/native/detail/kqueue/kqueue_traits.hpp

81.9% Lines (86/105) 100.0% List of functions (13/13) 44.2% Branches (46/104)
kqueue_traits.hpp
f(x) Functions (13)
Line Branch 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_KQUEUE_KQUEUE_TRAITS_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_KQUEUE_KQUEUE_TRAITS_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_HAS_KQUEUE
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
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <netinet/in.h>
25 #include <sys/socket.h>
26 #include <sys/uio.h>
27 #include <unistd.h>
28
29 /* kqueue backend traits.
30
31 Captures the platform-specific behavior of the BSD/macOS kqueue backend:
32 manual fcntl for O_NONBLOCK/FD_CLOEXEC, mandatory SO_NOSIGPIPE (macOS
33 lacks MSG_NOSIGNAL), writev() for writes, and accept()+fcntl for
34 accepted connections.
35 */
36
37 namespace boost::corosio::detail {
38
39 class kqueue_scheduler;
40
41 struct kqueue_traits
42 {
43 using scheduler_type = kqueue_scheduler;
44 using desc_state_type = reactor_descriptor_state;
45
46 static constexpr bool needs_write_notification = false;
47
48 /* macOS kqueue workaround: RST doesn't reliably trigger EV_EOF.
49 If the user sets SO_LINGER, we clear it before close so the
50 destructor doesn't block and close() sends FIN instead of RST.
51
52 The hook tracks whether the user explicitly set SO_LINGER via
53 set_option(). On pre_shutdown/pre_destroy, if the flag is set,
54 we reset linger to off before the fd is closed.
55 */
56 16354x struct stream_socket_hook
57 {
58 16354x bool user_set_linger_ = false;
59
60 1938x std::error_code on_set_option(
61 int fd, int level, int optname,
62 void const* data, std::size_t size) noexcept
63 {
64
2/4
✓ Branch 0 taken 1938 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1938 times.
1938x if (::setsockopt(
65 1938x fd, level, optname, data,
66 1938x static_cast<socklen_t>(size)) != 0)
67 return make_err(errno);
68
69
5/6
✓ Branch 0 taken 1925 times.
✓ Branch 1 taken 13 times.
✓ Branch 2 taken 1917 times.
✓ Branch 3 taken 8 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1917 times.
1938x if (level == SOL_SOCKET && optname == SO_LINGER &&
70 1917x size >= sizeof(struct ::linger))
71 1917x user_set_linger_ =
72 1917x static_cast<struct ::linger const*>(data)->l_onoff != 0;
73
74 1938x return {};
75 1938x }
76
77 49011x void pre_shutdown(int fd) noexcept
78 {
79 49011x reset_linger(fd);
80 49011x }
81
82 16337x void pre_destroy(int fd) noexcept
83 {
84 16337x reset_linger(fd);
85 16337x }
86
87 private:
88 65348x void reset_linger(int fd) noexcept
89 {
90
3/4
✓ Branch 0 taken 1915 times.
✓ Branch 1 taken 63433 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1915 times.
65348x if (user_set_linger_ && fd >= 0)
91 {
92 struct ::linger lg;
93 1915x lg.l_onoff = 0;
94 1915x lg.l_linger = 0;
95
1/2
✓ Branch 0 taken 1915 times.
✗ Branch 1 not taken.
1915x ::setsockopt(fd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg));
96 1915x }
97 65348x user_set_linger_ = false;
98 65348x }
99 };
100
101 struct write_policy
102 {
103 473080x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
104 {
105 ssize_t n;
106 473080x do
107 {
108
1/2
✓ Branch 0 taken 473080 times.
✗ Branch 1 not taken.
473080x n = ::writev(fd, iovecs, count);
109 946160x }
110
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 473079 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
473080x while (n < 0 && errno == EINTR);
111 473080x return n;
112 }
113 };
114
115 struct accept_policy
116 {
117 10825x static int do_accept(
118 int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
119 {
120 int new_fd;
121 10825x do
122 {
123 10825x addrlen = sizeof(peer);
124
1/2
✓ Branch 0 taken 10825 times.
✗ Branch 1 not taken.
10825x new_fd = ::accept(
125 10825x fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
126 21650x }
127
3/4
✓ Branch 0 taken 5417 times.
✓ Branch 1 taken 5408 times.
✓ Branch 2 taken 5417 times.
✗ Branch 3 not taken.
10825x while (new_fd < 0 && errno == EINTR);
128
129
2/2
✓ Branch 0 taken 5417 times.
✓ Branch 1 taken 5408 times.
10825x if (new_fd < 0)
130 5417x return new_fd;
131
132
1/2
✓ Branch 0 taken 5408 times.
✗ Branch 1 not taken.
5408x int flags = ::fcntl(new_fd, F_GETFL, 0);
133
2/4
✓ Branch 0 taken 5408 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5408 times.
✗ Branch 3 not taken.
10816x if (flags == -1 ||
134
1/2
✓ Branch 0 taken 5408 times.
✗ Branch 1 not taken.
5408x ::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
135 {
136 int err = errno;
137 ::close(new_fd);
138 errno = err;
139 return -1;
140 }
141
142
2/4
✓ Branch 0 taken 5408 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5408 times.
5408x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
143 {
144 int err = errno;
145 ::close(new_fd);
146 errno = err;
147 return -1;
148 }
149
150 #ifndef BOOST_COROSIO_MRDOCS
151 // SO_NOSIGPIPE is mandatory on kqueue platforms (macOS lacks
152 // MSG_NOSIGNAL). Skipped under MRDOCS so the docs build can
153 // parse this header on Linux, where SO_NOSIGPIPE is absent.
154 5408x int one = 1;
155
2/4
✓ Branch 0 taken 5408 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5408 times.
5408x if (::setsockopt(
156 5408x new_fd, SOL_SOCKET, SO_NOSIGPIPE,
157 5408x &one, sizeof(one)) == -1)
158 {
159 int err = errno;
160 ::close(new_fd);
161 errno = err;
162 return -1;
163 }
164 #endif
165
166 5408x return new_fd;
167 10825x }
168 };
169
170 // Create a plain socket. Fd options are applied by configure_*().
171 6593x static int create_socket(int family, int type, int protocol) noexcept
172 {
173
1/2
✓ Branch 0 taken 6593 times.
✗ Branch 1 not taken.
6593x return ::socket(family, type, protocol);
174 }
175
176 // Set O_NONBLOCK, FD_CLOEXEC, and SO_NOSIGPIPE on a new fd.
177 // Caller is responsible for closing fd on error.
178 6593x static std::error_code set_fd_options(int fd) noexcept
179 {
180
1/2
✓ Branch 0 taken 6593 times.
✗ Branch 1 not taken.
6593x int flags = ::fcntl(fd, F_GETFL, 0);
181
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6593 times.
6593x if (flags == -1)
182 return make_err(errno);
183
2/4
✓ Branch 0 taken 6593 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6593 times.
6593x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
184 return make_err(errno);
185
2/4
✓ Branch 0 taken 6593 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6593 times.
6593x if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
186 return make_err(errno);
187
188 #ifndef BOOST_COROSIO_MRDOCS
189 // SO_NOSIGPIPE is mandatory on kqueue platforms (see accept_policy).
190 6593x int one = 1;
191
2/4
✓ Branch 0 taken 6593 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6593 times.
6593x if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
192 return make_err(errno);
193 #endif
194
195 6593x return {};
196 6593x }
197
198 // Apply protocol-specific options after socket creation.
199 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
200 static std::error_code
201 5484x configure_ip_socket(int fd, int family) noexcept
202 {
203 5484x auto ec = set_fd_options(fd);
204
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5484 times.
5484x if (ec)
205 return ec;
206
207
2/2
✓ Branch 0 taken 5470 times.
✓ Branch 1 taken 14 times.
5484x if (family == AF_INET6)
208 {
209 14x int v6only = 1;
210
1/2
✓ Branch 0 taken 14 times.
✗ Branch 1 not taken.
14x (void)::setsockopt(
211 14x fd, IPPROTO_IPV6, IPV6_V6ONLY,
212 &v6only, sizeof(v6only));
213 14x }
214 5484x return {};
215 5484x }
216
217 // Apply protocol-specific options for acceptor sockets.
218 // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
219 static std::error_code
220 1091x configure_ip_acceptor(int fd, int family) noexcept
221 {
222 1091x auto ec = set_fd_options(fd);
223
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1091 times.
1091x if (ec)
224 return ec;
225
226
2/2
✓ Branch 0 taken 1082 times.
✓ Branch 1 taken 9 times.
1091x if (family == AF_INET6)
227 {
228 9x int val = 0;
229
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9x (void)::setsockopt(
230 9x fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
231 9x }
232 1091x return {};
233 1091x }
234
235 // Apply options for local (unix) sockets.
236 static std::error_code
237 18x configure_local_socket(int fd) noexcept
238 {
239 18x return set_fd_options(fd);
240 }
241
242 // Non-mutating validation for fds adopted via assign(). Used when
243 // the caller retains fd ownership responsibility.
244 static std::error_code
245 14x validate_assigned_fd(int /*fd*/) noexcept
246 {
247 14x return {};
248 }
249 };
250
251 } // namespace boost::corosio::detail
252
253 #endif // BOOST_COROSIO_HAS_KQUEUE
254
255 #endif // BOOST_COROSIO_NATIVE_DETAIL_KQUEUE_KQUEUE_TRAITS_HPP
256