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

100.0% Lines (91/91) 100.0% List of functions (12/12) 67.3% Branches (66/98)
kqueue_traits.hpp
f(x) Functions (12)
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 #include <tuple>
22
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <netinet/in.h>
26 #include <sys/socket.h>
27 #include <sys/uio.h>
28 #include <unistd.h>
29
30 /* kqueue backend traits.
31
32 Captures the platform-specific behavior of the BSD/macOS kqueue backend:
33 manual fcntl for O_NONBLOCK/FD_CLOEXEC, mandatory SO_NOSIGPIPE
34 (MSG_NOSIGNAL is not universal across kqueue platforms, and writev()
35 takes no flags at all), writev() for writes, and accept()+fcntl for
36 accepted connections.
37 */
38
39 namespace boost::corosio::detail {
40
41 class kqueue_scheduler;
42
43 struct kqueue_traits
44 {
45 using scheduler_type = kqueue_scheduler;
46 using desc_state_type = reactor_descriptor_state;
47
48 static constexpr bool needs_write_notification = false;
49
50 // No per-socket state or lifecycle hooks: the stream socket hook is a
51 // plain setsockopt passthrough, like epoll/select. SO_LINGER in particular
52 // is passed through unmodified. An abortive close (SO_LINGER{on,0}) thus
53 // sends a RST, which the peer's kqueue reports as EV_EOF carrying
54 // ECONNRESET in fflags -- so the peer surfaces connection_reset rather than
55 // a graceful eof, consistent with the other backends (#304). A positive
56 // linger timeout is likewise honored, so close() with unsent data blocks
57 // up to that timeout, as POSIX specifies.
58 struct stream_socket_hook
59 {
60 1647x std::error_code on_set_option(
61 int fd,
62 int level,
63 int optname,
64 void const* data,
65 std::size_t size) noexcept
66 {
67
3/4
✓ Branch 0 taken 1647 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1644 times.
1647x if (::setsockopt(
68 1647x fd, level, optname, data, static_cast<socklen_t>(size)) !=
69 0)
70
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3x return make_err(errno);
71 1644x return {};
72 1647x }
73 43221x static void pre_shutdown(int) noexcept {}
74 14353x static void pre_destroy(int) noexcept {}
75 };
76
77 struct write_policy
78 {
79 516x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
80 {
81 ssize_t n;
82 516x do
83 {
84
1/2
✓ Branch 0 taken 517 times.
✗ Branch 1 not taken.
517x n = ::writev(fd, iovecs, count);
85 1034x }
86
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 512 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
517x while (n < 0 && errno == EINTR);
87 516x return n;
88 }
89
90 // Single-buffer fast path. write() carries no flag to suppress
91 // SIGPIPE; the mandatory SO_NOSIGPIPE set in accept_policy and
92 // set_fd_options does it per descriptor instead.
93 static ssize_t
94 263357x write_one(int fd, void const* data, std::size_t size) noexcept
95 {
96 ssize_t n;
97 263357x do
98 {
99
1/2
✓ Branch 0 taken 263358 times.
✗ Branch 1 not taken.
263358x n = ::write(fd, data, size);
100 526716x }
101
3/4
✓ Branch 0 taken 512 times.
✓ Branch 1 taken 262846 times.
✓ Branch 2 taken 512 times.
✗ Branch 3 not taken.
263358x while (n < 0 && errno == EINTR);
102 263357x return n;
103 }
104 };
105
106 struct accept_policy
107 {
108 static int
109 9317x do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
110 {
111 int new_fd;
112 9317x do
113 {
114 9318x addrlen = sizeof(peer);
115 9318x new_fd =
116
1/2
✓ Branch 0 taken 9318 times.
✗ Branch 1 not taken.
9318x ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
117 18636x }
118
3/4
✓ Branch 0 taken 4677 times.
✓ Branch 1 taken 4641 times.
✓ Branch 2 taken 4677 times.
✗ Branch 3 not taken.
9318x while (new_fd < 0 && errno == EINTR);
119
120
2/2
✓ Branch 0 taken 4676 times.
✓ Branch 1 taken 4641 times.
9317x if (new_fd < 0)
121 4676x return new_fd;
122
123
1/2
✓ Branch 0 taken 4641 times.
✗ Branch 1 not taken.
4641x int flags = ::fcntl(new_fd, F_GETFL, 0);
124
4/4
✓ Branch 0 taken 4640 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 4639 times.
✓ Branch 3 taken 1 time.
9281x if (flags == -1 ||
125
1/2
✓ Branch 0 taken 4640 times.
✗ Branch 1 not taken.
4640x ::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
126 {
127
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x int err = errno;
128
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x ::close(new_fd);
129
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x errno = err;
130 2x return -1;
131 }
132
133
3/4
✓ Branch 0 taken 4639 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 4638 times.
4639x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
134 {
135
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x int err = errno;
136
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x ::close(new_fd);
137
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x errno = err;
138 1x return -1;
139 }
140
141 #ifndef BOOST_COROSIO_MRDOCS
142 // SO_NOSIGPIPE is mandatory on kqueue platforms: MSG_NOSIGNAL
143 // is not universal across them, and the writev() the write
144 // path uses takes no flags. Skipped under MRDOCS so the docs
145 // build can parse this header on Linux, where SO_NOSIGPIPE is
146 // absent.
147 4638x int one = 1;
148
3/4
✓ Branch 0 taken 4638 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 4636 times.
4638x if (::setsockopt(
149 4638x new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) == -1)
150 {
151
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x int err = errno;
152
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x ::close(new_fd);
153
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x errno = err;
154 2x return -1;
155 }
156 #endif
157
158 4636x return new_fd;
159 9317x }
160 };
161
162 // Create a plain socket. Fd options are applied by configure_*().
163 6314x static int create_socket(int family, int type, int protocol) noexcept
164 {
165
1/2
✓ Branch 0 taken 6314 times.
✗ Branch 1 not taken.
6314x return ::socket(family, type, protocol);
166 }
167
168 // Set O_NONBLOCK, FD_CLOEXEC, and SO_NOSIGPIPE on a new fd.
169 // Caller is responsible for closing fd on error.
170 6309x static std::error_code set_fd_options(int fd) noexcept
171 {
172
1/2
✓ Branch 0 taken 6309 times.
✗ Branch 1 not taken.
6309x int flags = ::fcntl(fd, F_GETFL, 0);
173
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6307 times.
6309x if (flags == -1)
174
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x return make_err(errno);
175
3/4
✓ Branch 0 taken 6307 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 6306 times.
6307x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
176
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x return make_err(errno);
177
3/4
✓ Branch 0 taken 6306 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 6305 times.
6306x if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
178
1/2
✓ Branch 0 taken 1 time.
✗ Branch 1 not taken.
1x return make_err(errno);
179
180 #ifndef BOOST_COROSIO_MRDOCS
181 // SO_NOSIGPIPE is mandatory on kqueue platforms (see accept_policy).
182 6305x int one = 1;
183
3/4
✓ Branch 0 taken 6305 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 6303 times.
6305x if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
184
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x return make_err(errno);
185 #endif
186
187 6303x return {};
188 6309x }
189
190 // Apply protocol-specific options after socket creation.
191 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
192 4831x static std::error_code configure_ip_socket(int fd, int family) noexcept
193 {
194 4831x auto ec = set_fd_options(fd);
195
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 4827 times.
4831x if (ec)
196 4x return ec;
197
198
2/2
✓ Branch 0 taken 4805 times.
✓ Branch 1 taken 22 times.
4827x if (family == AF_INET6)
199 {
200 22x int v6only = 1;
201
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22x std::ignore = ::setsockopt(
202 22x fd, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only));
203 22x }
204 4827x return {};
205 4831x }
206
207 // Apply protocol-specific options for acceptor sockets.
208 // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
209 1368x static std::error_code configure_ip_acceptor(int fd, int family) noexcept
210 {
211 1368x auto ec = set_fd_options(fd);
212
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1366 times.
1368x if (ec)
213 2x return ec;
214
215
2/2
✓ Branch 0 taken 1355 times.
✓ Branch 1 taken 11 times.
1366x if (family == AF_INET6)
216 {
217 11x int val = 0;
218 11x std::ignore =
219
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11x ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
220 11x }
221 1366x return {};
222 1368x }
223
224 // Apply options for local (unix) sockets.
225 110x static std::error_code configure_local_socket(int fd) noexcept
226 {
227 110x return set_fd_options(fd);
228 }
229
230 // Non-mutating validation for fds adopted via assign(). Used when
231 // the caller retains fd ownership responsibility.
232 153x static std::error_code validate_assigned_fd(int /*fd*/) noexcept
233 {
234 153x return {};
235 }
236 };
237
238 } // namespace boost::corosio::detail
239
240 #endif // BOOST_COROSIO_HAS_KQUEUE
241
242 #endif // BOOST_COROSIO_NATIVE_DETAIL_KQUEUE_KQUEUE_TRAITS_HPP
243