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_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 3121x 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 3121 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3118 times.
3121x if (::setsockopt(
69 3121x fd, level, optname, data, static_cast<socklen_t>(size)) !=
70 0)
71
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3x return make_err(errno);
72 3118x return {};
73 3121x }
74 59865x static void pre_shutdown(int) noexcept {}
75 19922x static void pre_destroy(int) noexcept {}
76 };
77
78 struct write_policy
79 {
80 518x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
81 {
82 ssize_t n;
83 518x do
84 {
85
1/2
✓ Branch 0 taken 519 times.
✗ Branch 1 not taken.
519x n = ::writev(fd, iovecs, count);
86 1038x }
87
3/4
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 516 times.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
519x while (n < 0 && errno == EINTR);
88 518x 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 606083x write_one(int fd, void const* data, std::size_t size) noexcept
96 {
97 ssize_t n;
98 606083x do
99 {
100
1/2
✓ Branch 0 taken 606084 times.
✗ Branch 1 not taken.
606084x n = ::write(fd, data, size);
101 1212168x }
102
3/4
✓ Branch 0 taken 519 times.
✓ Branch 1 taken 605565 times.
✓ Branch 2 taken 519 times.
✗ Branch 3 not taken.
606084x while (n < 0 && errno == EINTR);
103 606083x return n;
104 }
105 };
106
107 using descriptor_write_policy = detail::descriptor_write_policy;
108
109 struct accept_policy
110 {
111 static int
112 13020x do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
113 {
114 int new_fd;
115 13020x do
116 {
117 13021x addrlen = sizeof(peer);
118 13021x new_fd =
119
1/2
✓ Branch 0 taken 13021 times.
✗ Branch 1 not taken.
13021x ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
120 26042x }
121
3/4
✓ Branch 0 taken 6527 times.
✓ Branch 1 taken 6494 times.
✓ Branch 2 taken 6527 times.
✗ Branch 3 not taken.
13021x while (new_fd < 0 && errno == EINTR);
122
123
2/2
✓ Branch 0 taken 6526 times.
✓ Branch 1 taken 6494 times.
13020x if (new_fd < 0)
124 6526x return new_fd;
125
126
1/2
✓ Branch 0 taken 6494 times.
✗ Branch 1 not taken.
6494x int flags = ::fcntl(new_fd, F_GETFL, 0);
127
4/4
✓ Branch 0 taken 6493 times.
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 6492 times.
✓ Branch 3 taken 1 time.
12987x if (flags == -1 ||
128
1/2
✓ Branch 0 taken 6493 times.
✗ Branch 1 not taken.
6493x ::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 6492 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 6491 times.
6492x 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 6491x int one = 1;
151
3/4
✓ Branch 0 taken 6491 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 6489 times.
6491x if (::setsockopt(
152 6491x new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) == -1)
153 {
154
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x int err = errno;
155
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x ::close(new_fd);
156
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x errno = err;
157 2x return -1;
158 }
159 #endif
160
161 6489x return new_fd;
162 13020x }
163 };
164
165 // Create a plain socket. Fd options are applied by configure_*().
166 8919x static int create_socket(int family, int type, int protocol) noexcept
167 {
168
1/2
✓ Branch 0 taken 8919 times.
✗ Branch 1 not taken.
8919x 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 8914x static std::error_code set_fd_options(int fd) noexcept
174 {
175
1/2
✓ Branch 0 taken 8914 times.
✗ Branch 1 not taken.
8914x int flags = ::fcntl(fd, F_GETFL, 0);
176
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8912 times.
8914x 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 8912 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 8911 times.
8912x 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 8911 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 8910 times.
8911x 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 8910x int one = 1;
186
3/4
✓ Branch 0 taken 8910 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 8908 times.
8910x if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
187
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x return make_err(errno);
188 #endif
189
190 8908x return {};
191 8914x }
192
193 // Apply protocol-specific options after socket creation.
194 // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
195 6692x static std::error_code configure_ip_socket(int fd, int family) noexcept
196 {
197 6692x auto ec = set_fd_options(fd);
198
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6688 times.
6692x if (ec)
199 4x return ec;
200
201
2/2
✓ Branch 0 taken 6665 times.
✓ Branch 1 taken 23 times.
6688x 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 6688x return {};
208 6692x }
209
210 // Apply protocol-specific options for acceptor sockets.
211 // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
212 2111x static std::error_code configure_ip_acceptor(int fd, int family) noexcept
213 {
214 2111x auto ec = set_fd_options(fd);
215
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2109 times.
2111x if (ec)
216 2x return ec;
217
218
2/2
✓ Branch 0 taken 2098 times.
✓ Branch 1 taken 11 times.
2109x 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 2109x return {};
225 2111x }
226
227 // Apply options for local (unix) sockets.
228 111x static std::error_code configure_local_socket(int fd) noexcept
229 {
230 111x 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 151x static std::error_code validate_assigned_fd(int /*fd*/) noexcept
236 {
237 151x 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