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

82.9% Lines (92/111) 100.0% List of functions (14/14) 45.5% Branches (50/110)
kqueue_traits.hpp
f(x) Functions (14)
Function Calls Lines Branches Blocks
boost::corosio::detail::kqueue_traits::stream_socket_hook::stream_socket_hook() :56 26276x 100.0% 100.0% boost::corosio::detail::kqueue_traits::stream_socket_hook::on_set_option(int, int, int, void const*, unsigned long) :60 1684x 90.9% 58.3% 72.0% boost::corosio::detail::kqueue_traits::stream_socket_hook::pre_shutdown(int) :77 39361x 100.0% 100.0% boost::corosio::detail::kqueue_traits::stream_socket_hook::pre_destroy(int) :82 13070x 100.0% 100.0% boost::corosio::detail::kqueue_traits::stream_socket_hook::reset_linger(int) :88 52431x 100.0% 66.7% 83.0% boost::corosio::detail::kqueue_traits::write_policy::write(int, iovec*, int) :103 258x 100.0% 66.7% 88.0% boost::corosio::detail::kqueue_traits::write_policy::write_one(int, void const*, unsigned long) :117 443485x 100.0% 66.7% 88.0% boost::corosio::detail::kqueue_traits::accept_policy::do_accept(int, sockaddr_storage&, unsigned int&) :132 8648x 61.3% 33.3% 59.0% boost::corosio::detail::kqueue_traits::create_socket(int, int, int) :186 5474x 100.0% 50.0% 66.0% boost::corosio::detail::kqueue_traits::set_fd_options(int) :193 5474x 69.2% 33.3% 52.0% boost::corosio::detail::kqueue_traits::configure_ip_socket(int, int) :216 4425x 90.9% 66.7% 75.0% boost::corosio::detail::kqueue_traits::configure_ip_acceptor(int, int) :235 991x 90.9% 66.7% 75.0% boost::corosio::detail::kqueue_traits::configure_local_socket(int) :252 58x 100.0% 100.0% boost::corosio::detail::kqueue_traits::validate_assigned_fd(int) :260 48x 100.0% 100.0%
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 13138x struct stream_socket_hook
57 {
58 13138x bool user_set_linger_ = false;
59
60 1684x 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 1684 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1684 times.
1684x if (::setsockopt(
65 1684x fd, level, optname, data,
66 1684x static_cast<socklen_t>(size)) != 0)
67 return make_err(errno);
68
69
5/6
✓ Branch 0 taken 1669 times.
✓ Branch 1 taken 15 times.
✓ Branch 2 taken 1659 times.
✓ Branch 3 taken 10 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1659 times.
1684x if (level == SOL_SOCKET && optname == SO_LINGER &&
70 1659x size >= sizeof(struct ::linger))
71 1659x user_set_linger_ =
72 1659x static_cast<struct ::linger const*>(data)->l_onoff != 0;
73
74 1684x return {};
75 1684x }
76
77 39361x void pre_shutdown(int fd) noexcept
78 {
79 39361x reset_linger(fd);
80 39361x }
81
82 13070x void pre_destroy(int fd) noexcept
83 {
84 13070x reset_linger(fd);
85 13070x }
86
87 private:
88 52431x void reset_linger(int fd) noexcept
89 {
90
3/4
✓ Branch 0 taken 1657 times.
✓ Branch 1 taken 50774 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1657 times.
52431x if (user_set_linger_ && fd >= 0)
91 {
92 struct ::linger lg;
93 1657x lg.l_onoff = 0;
94 1657x lg.l_linger = 0;
95
1/2
✓ Branch 0 taken 1657 times.
✗ Branch 1 not taken.
1657x ::setsockopt(fd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg));
96 1657x }
97 52431x user_set_linger_ = false;
98 52431x }
99 };
100
101 struct write_policy
102 {
103 258x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
104 {
105 ssize_t n;
106 258x do
107 {
108
1/2
✓ Branch 0 taken 258 times.
✗ Branch 1 not taken.
258x n = ::writev(fd, iovecs, count);
109 516x }
110
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 256 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
258x while (n < 0 && errno == EINTR);
111 258x return n;
112 }
113
114 // Single-buffer fast path. macOS lacks MSG_NOSIGNAL; SIGPIPE is
115 // suppressed by the mandatory SO_NOSIGPIPE set in accept_policy
116 // and set_fd_options, so plain write() is safe here.
117 443485x static ssize_t write_one(
118 int fd, void const* data, std::size_t size) noexcept
119 {
120 ssize_t n;
121 443485x do
122 {
123
1/2
✓ Branch 0 taken 443485 times.
✗ Branch 1 not taken.
443485x n = ::write(fd, data, size);
124 886970x }
125
3/4
✓ Branch 0 taken 255 times.
✓ Branch 1 taken 443230 times.
✓ Branch 2 taken 255 times.
✗ Branch 3 not taken.
443485x while (n < 0 && errno == EINTR);
126 443485x return n;
127 }
128 };
129
130 struct accept_policy
131 {
132 8648x static int do_accept(
133 int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
134 {
135 int new_fd;
136 8648x do
137 {
138 8648x addrlen = sizeof(peer);
139
1/2
✓ Branch 0 taken 8648 times.
✗ Branch 1 not taken.
8648x new_fd = ::accept(
140 8648x fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
141 17296x }
142
3/4
✓ Branch 0 taken 4331 times.
✓ Branch 1 taken 4317 times.
✓ Branch 2 taken 4331 times.
✗ Branch 3 not taken.
8648x while (new_fd < 0 && errno == EINTR);
143
144
2/2
✓ Branch 0 taken 4331 times.
✓ Branch 1 taken 4317 times.
8648x if (new_fd < 0)
145 4331x return new_fd;
146
147
1/2
✓ Branch 0 taken 4317 times.
✗ Branch 1 not taken.
4317x int flags = ::fcntl(new_fd, F_GETFL, 0);
148
2/4
✓ Branch 0 taken 4317 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4317 times.
✗ Branch 3 not taken.
8634x if (flags == -1 ||
149
1/2
✓ Branch 0 taken 4317 times.
✗ Branch 1 not taken.
4317x ::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
150 {
151 int err = errno;
152 ::close(new_fd);
153 errno = err;
154 return -1;
155 }
156
157
2/4
✓ Branch 0 taken 4317 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4317 times.
4317x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
158 {
159 int err = errno;
160 ::close(new_fd);
161 errno = err;
162 return -1;
163 }
164
165 #ifndef BOOST_COROSIO_MRDOCS
166 // SO_NOSIGPIPE is mandatory on kqueue platforms (macOS lacks
167 // MSG_NOSIGNAL). Skipped under MRDOCS so the docs build can
168 // parse this header on Linux, where SO_NOSIGPIPE is absent.
169 4317x int one = 1;
170
2/4
✓ Branch 0 taken 4317 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4317 times.
4317x if (::setsockopt(
171 4317x new_fd, SOL_SOCKET, SO_NOSIGPIPE,
172 4317x &one, sizeof(one)) == -1)
173 {
174 int err = errno;
175 ::close(new_fd);
176 errno = err;
177 return -1;
178 }
179 #endif
180
181 4317x return new_fd;
182 8648x }
183 };
184
185 // Create a plain socket. Fd options are applied by configure_*().
186 5474x static int create_socket(int family, int type, int protocol) noexcept
187 {
188
1/2
✓ Branch 0 taken 5474 times.
✗ Branch 1 not taken.
5474x return ::socket(family, type, protocol);
189 }
190
191 // Set O_NONBLOCK, FD_CLOEXEC, and SO_NOSIGPIPE on a new fd.
192 // Caller is responsible for closing fd on error.
193 5474x static std::error_code set_fd_options(int fd) noexcept
194 {
195
1/2
✓ Branch 0 taken 5474 times.
✗ Branch 1 not taken.
5474x int flags = ::fcntl(fd, F_GETFL, 0);
196
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5474 times.
5474x if (flags == -1)
197 return make_err(errno);
198
2/4
✓ Branch 0 taken 5474 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5474 times.
5474x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
199 return make_err(errno);
200
2/4
✓ Branch 0 taken 5474 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5474 times.
5474x if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
201 return make_err(errno);
202
203 #ifndef BOOST_COROSIO_MRDOCS
204 // SO_NOSIGPIPE is mandatory on kqueue platforms (see accept_policy).
205 5474x int one = 1;
206
2/4
✓ Branch 0 taken 5474 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5474 times.
5474x if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
207 return make_err(errno);
208 #endif
209
210 5474x return {};
211 5474x }
212
213 // Apply protocol-specific options after socket creation.
214 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
215 static std::error_code
216 4425x configure_ip_socket(int fd, int family) noexcept
217 {
218 4425x auto ec = set_fd_options(fd);
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4425 times.
4425x if (ec)
220 return ec;
221
222
2/2
✓ Branch 0 taken 4408 times.
✓ Branch 1 taken 17 times.
4425x if (family == AF_INET6)
223 {
224 17x int v6only = 1;
225
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17x (void)::setsockopt(
226 17x fd, IPPROTO_IPV6, IPV6_V6ONLY,
227 &v6only, sizeof(v6only));
228 17x }
229 4425x return {};
230 4425x }
231
232 // Apply protocol-specific options for acceptor sockets.
233 // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
234 static std::error_code
235 991x configure_ip_acceptor(int fd, int family) noexcept
236 {
237 991x auto ec = set_fd_options(fd);
238
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 991 times.
991x if (ec)
239 return ec;
240
241
2/2
✓ Branch 0 taken 982 times.
✓ Branch 1 taken 9 times.
991x if (family == AF_INET6)
242 {
243 9x int val = 0;
244
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9x (void)::setsockopt(
245 9x fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
246 9x }
247 991x return {};
248 991x }
249
250 // Apply options for local (unix) sockets.
251 static std::error_code
252 58x configure_local_socket(int fd) noexcept
253 {
254 58x return set_fd_options(fd);
255 }
256
257 // Non-mutating validation for fds adopted via assign(). Used when
258 // the caller retains fd ownership responsibility.
259 static std::error_code
260 48x validate_assigned_fd(int /*fd*/) noexcept
261 {
262 48x return {};
263 }
264 };
265
266 } // namespace boost::corosio::detail
267
268 #endif // BOOST_COROSIO_HAS_KQUEUE
269
270 #endif // BOOST_COROSIO_NATIVE_DETAIL_KQUEUE_KQUEUE_TRAITS_HPP
271