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

79.6% Lines (74/93) 100.0% List of functions (12/12) 41.8% Branches (41/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
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 // No per-socket state or lifecycle hooks: the stream socket hook is a
49 // plain setsockopt passthrough, like epoll/select. SO_LINGER in particular
50 // is passed through unmodified. An abortive close (SO_LINGER{on,0}) thus
51 // sends a RST, which the peer's kqueue reports as EV_EOF carrying
52 // ECONNRESET in fflags -- so the peer surfaces connection_reset rather than
53 // a graceful eof, consistent with the other backends (#304). A positive
54 // linger timeout is likewise honored, so close() with unsent data blocks
55 // up to that timeout, as POSIX specifies.
56 struct stream_socket_hook
57 {
58 1865x std::error_code on_set_option(
59 int fd, int level, int optname,
60 void const* data, std::size_t size) noexcept
61 {
62
2/4
✓ Branch 0 taken 1865 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1865 times.
1865x if (::setsockopt(
63 1865x fd, level, optname, data,
64 1865x static_cast<socklen_t>(size)) != 0)
65 return make_err(errno);
66 1865x return {};
67 1865x }
68 44435x static void pre_shutdown(int) noexcept {}
69 14745x static void pre_destroy(int) noexcept {}
70 };
71
72 struct write_policy
73 {
74 498x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
75 {
76 ssize_t n;
77 498x do
78 {
79
1/2
✓ Branch 0 taken 498 times.
✗ Branch 1 not taken.
498x n = ::writev(fd, iovecs, count);
80 996x }
81
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 495 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
498x while (n < 0 && errno == EINTR);
82 498x return n;
83 }
84
85 // Single-buffer fast path. macOS lacks MSG_NOSIGNAL; SIGPIPE is
86 // suppressed by the mandatory SO_NOSIGPIPE set in accept_policy
87 // and set_fd_options, so plain write() is safe here.
88 374584x static ssize_t write_one(
89 int fd, void const* data, std::size_t size) noexcept
90 {
91 ssize_t n;
92 374584x do
93 {
94
1/2
✓ Branch 0 taken 374584 times.
✗ Branch 1 not taken.
374584x n = ::write(fd, data, size);
95 749168x }
96
3/4
✓ Branch 0 taken 495 times.
✓ Branch 1 taken 374089 times.
✓ Branch 2 taken 495 times.
✗ Branch 3 not taken.
374584x while (n < 0 && errno == EINTR);
97 374584x return n;
98 }
99 };
100
101 struct accept_policy
102 {
103 9761x static int do_accept(
104 int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
105 {
106 int new_fd;
107 9761x do
108 {
109 9761x addrlen = sizeof(peer);
110
1/2
✓ Branch 0 taken 9761 times.
✗ Branch 1 not taken.
9761x new_fd = ::accept(
111 9761x fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
112 19522x }
113
3/4
✓ Branch 0 taken 4889 times.
✓ Branch 1 taken 4872 times.
✓ Branch 2 taken 4889 times.
✗ Branch 3 not taken.
9761x while (new_fd < 0 && errno == EINTR);
114
115
2/2
✓ Branch 0 taken 4889 times.
✓ Branch 1 taken 4872 times.
9761x if (new_fd < 0)
116 4889x return new_fd;
117
118
1/2
✓ Branch 0 taken 4872 times.
✗ Branch 1 not taken.
4872x int flags = ::fcntl(new_fd, F_GETFL, 0);
119
2/4
✓ Branch 0 taken 4872 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4872 times.
✗ Branch 3 not taken.
9744x if (flags == -1 ||
120
1/2
✓ Branch 0 taken 4872 times.
✗ Branch 1 not taken.
4872x ::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
121 {
122 int err = errno;
123 ::close(new_fd);
124 errno = err;
125 return -1;
126 }
127
128
2/4
✓ Branch 0 taken 4872 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4872 times.
4872x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
129 {
130 int err = errno;
131 ::close(new_fd);
132 errno = err;
133 return -1;
134 }
135
136 #ifndef BOOST_COROSIO_MRDOCS
137 // SO_NOSIGPIPE is mandatory on kqueue platforms (macOS lacks
138 // MSG_NOSIGNAL). Skipped under MRDOCS so the docs build can
139 // parse this header on Linux, where SO_NOSIGPIPE is absent.
140 4872x int one = 1;
141
2/4
✓ Branch 0 taken 4872 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4872 times.
4872x if (::setsockopt(
142 4872x new_fd, SOL_SOCKET, SO_NOSIGPIPE,
143 4872x &one, sizeof(one)) == -1)
144 {
145 int err = errno;
146 ::close(new_fd);
147 errno = err;
148 return -1;
149 }
150 #endif
151
152 4872x return new_fd;
153 9761x }
154 };
155
156 // Create a plain socket. Fd options are applied by configure_*().
157 6161x static int create_socket(int family, int type, int protocol) noexcept
158 {
159
1/2
✓ Branch 0 taken 6161 times.
✗ Branch 1 not taken.
6161x return ::socket(family, type, protocol);
160 }
161
162 // Set O_NONBLOCK, FD_CLOEXEC, and SO_NOSIGPIPE on a new fd.
163 // Caller is responsible for closing fd on error.
164 6161x static std::error_code set_fd_options(int fd) noexcept
165 {
166
1/2
✓ Branch 0 taken 6161 times.
✗ Branch 1 not taken.
6161x int flags = ::fcntl(fd, F_GETFL, 0);
167
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 6161 times.
6161x if (flags == -1)
168 return make_err(errno);
169
2/4
✓ Branch 0 taken 6161 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6161 times.
6161x if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
170 return make_err(errno);
171
2/4
✓ Branch 0 taken 6161 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6161 times.
6161x if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
172 return make_err(errno);
173
174 #ifndef BOOST_COROSIO_MRDOCS
175 // SO_NOSIGPIPE is mandatory on kqueue platforms (see accept_policy).
176 6161x int one = 1;
177
2/4
✓ Branch 0 taken 6161 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6161 times.
6161x if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
178 return make_err(errno);
179 #endif
180
181 6161x return {};
182 6161x }
183
184 // Apply protocol-specific options after socket creation.
185 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
186 static std::error_code
187 4995x configure_ip_socket(int fd, int family) noexcept
188 {
189 4995x auto ec = set_fd_options(fd);
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4995 times.
4995x if (ec)
191 return ec;
192
193
2/2
✓ Branch 0 taken 4976 times.
✓ Branch 1 taken 19 times.
4995x if (family == AF_INET6)
194 {
195 19x int v6only = 1;
196
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19x (void)::setsockopt(
197 19x fd, IPPROTO_IPV6, IPV6_V6ONLY,
198 &v6only, sizeof(v6only));
199 19x }
200 4995x return {};
201 4995x }
202
203 // Apply protocol-specific options for acceptor sockets.
204 // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
205 static std::error_code
206 1092x configure_ip_acceptor(int fd, int family) noexcept
207 {
208 1092x auto ec = set_fd_options(fd);
209
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1092 times.
1092x if (ec)
210 return ec;
211
212
2/2
✓ Branch 0 taken 1082 times.
✓ Branch 1 taken 10 times.
1092x if (family == AF_INET6)
213 {
214 10x int val = 0;
215
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10x (void)::setsockopt(
216 10x fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
217 10x }
218 1092x return {};
219 1092x }
220
221 // Apply options for local (unix) sockets.
222 static std::error_code
223 74x configure_local_socket(int fd) noexcept
224 {
225 74x return set_fd_options(fd);
226 }
227
228 // Non-mutating validation for fds adopted via assign(). Used when
229 // the caller retains fd ownership responsibility.
230 static std::error_code
231 76x validate_assigned_fd(int /*fd*/) noexcept
232 {
233 76x return {};
234 }
235 };
236
237 } // namespace boost::corosio::detail
238
239 #endif // BOOST_COROSIO_HAS_KQUEUE
240
241 #endif // BOOST_COROSIO_NATIVE_DETAIL_KQUEUE_KQUEUE_TRAITS_HPP
242