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

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