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

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