include/boost/corosio/native/detail/select/select_op.hpp

56.5% Lines (26/46) 100.0% List of functions (12/12) 26.7% Branches (16/60)
f(x) Functions (12)
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
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_SELECT_SELECT_OP_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_HAS_SELECT
16
17 #include <boost/corosio/native/detail/reactor/reactor_op.hpp>
18 #include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <sys/socket.h>
23 #include <unistd.h>
24
25 /*
26 File descriptors are registered with the select scheduler once (via
27 select_descriptor_state) and stay registered until closed.
28
29 select() is level-triggered but the descriptor_state pattern
30 (designed for edge-triggered) works correctly: is_enqueued_ CAS
31 prevents double-enqueue, add_ready_events is idempotent, and
32 EAGAIN ops stay parked until the next select() re-reports readiness.
33
34 cancel() captures shared_from_this() into op.impl_ptr to prevent
35 use-after-free when the socket is closed with pending ops.
36
37 Writes use sendmsg(MSG_NOSIGNAL) on Linux. On macOS/BSD where
38 MSG_NOSIGNAL may be absent, SO_NOSIGPIPE is set at socket creation
39 and accepted-socket setup instead.
40 */
41
42 namespace boost::corosio::detail {
43
44 // Forward declarations
45 class select_tcp_socket;
46 class select_tcp_acceptor;
47 struct select_op;
48
49 // Forward declaration
50 class select_scheduler;
51
52 /// Per-descriptor state for persistent select registration.
53 struct select_descriptor_state final : reactor_descriptor_state
54 {};
55
56 /// select base operation — thin wrapper over reactor_op.
57 struct select_op : reactor_op<select_tcp_socket, select_tcp_acceptor>
58 {
59 void operator()() override;
60 };
61
62 /// select connect operation.
63 struct select_connect_op final : reactor_connect_op<select_op>
64 {
65 void operator()() override;
66 void cancel() noexcept override;
67 };
68
69 /// select scatter-read operation.
70 struct select_read_op final : reactor_read_op<select_op>
71 {
72 void cancel() noexcept override;
73 };
74
75 /** Provides sendmsg() with EINTR retry for select writes.
76
77 Uses MSG_NOSIGNAL where available (Linux). On platforms without
78 it (macOS/BSD), SO_NOSIGPIPE is set at socket creation time
79 and flags=0 is used here.
80 */
81 struct select_write_policy
82 {
83 466290x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
84 {
85 466290x msghdr msg{};
86 466290x msg.msg_iov = iovecs;
87 466290x msg.msg_iovlen = static_cast<std::size_t>(count);
88
89 #ifdef MSG_NOSIGNAL
90 466290x constexpr int send_flags = MSG_NOSIGNAL;
91 #else
92 constexpr int send_flags = 0;
93 #endif
94
95 ssize_t n;
96 466290x do
97 {
98
1/2
✓ Branch 0 taken 466290 times.
✗ Branch 1 not taken.
466290x n = ::sendmsg(fd, &msg, send_flags);
99 932580x }
100
3/4
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 466289 times.
✓ Branch 2 taken 1 time.
✗ Branch 3 not taken.
466290x while (n < 0 && errno == EINTR);
101 466290x return n;
102 }
103 };
104
105 /// select gather-write operation.
106 struct select_write_op final : reactor_write_op<select_op, select_write_policy>
107 {
108 void cancel() noexcept override;
109 };
110
111 /** Provides accept() + fcntl(O_NONBLOCK|FD_CLOEXEC) with FD_SETSIZE check.
112
113 Uses accept() instead of accept4() for broader POSIX compatibility.
114 */
115 struct select_accept_policy
116 {
117 1786x static int do_accept(int fd, sockaddr_storage& peer) noexcept
118 {
119 1786x socklen_t addrlen = sizeof(peer);
120 int new_fd;
121 1786x do
122 {
123
1/2
✓ Branch 0 taken 1786 times.
✗ Branch 1 not taken.
1786x new_fd = ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
124 3572x }
125
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1786 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1786x while (new_fd < 0 && errno == EINTR);
126
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1786 times.
1786x if (new_fd < 0)
128 return new_fd;
129
130
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1786 times.
1786x if (new_fd >= FD_SETSIZE)
131 {
132 ::close(new_fd);
133 errno = EINVAL;
134 return -1;
135 }
136
137
1/2
✓ Branch 0 taken 1786 times.
✗ Branch 1 not taken.
1786x int flags = ::fcntl(new_fd, F_GETFL, 0);
138
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1786 times.
1786x if (flags == -1)
139 {
140 int err = errno;
141 ::close(new_fd);
142 errno = err;
143 return -1;
144 }
145
146
2/4
✓ Branch 0 taken 1786 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1786 times.
1786x if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
147 {
148 int err = errno;
149 ::close(new_fd);
150 errno = err;
151 return -1;
152 }
153
154
2/4
✓ Branch 0 taken 1786 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1786 times.
1786x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
155 {
156 int err = errno;
157 ::close(new_fd);
158 errno = err;
159 return -1;
160 }
161
162 #ifdef SO_NOSIGPIPE
163 1786x int one = 1;
164
2/4
✓ Branch 0 taken 1786 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1786 times.
1786x if (::setsockopt(new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) ==
165 -1)
166 {
167 int err = errno;
168 ::close(new_fd);
169 errno = err;
170 return -1;
171 }
172 #endif
173
174 1786x return new_fd;
175 1786x }
176 };
177
178 /// select accept operation.
179 struct select_accept_op final
180 : reactor_accept_op<select_op, select_accept_policy>
181 {
182 void operator()() override;
183 void cancel() noexcept override;
184 };
185
186 } // namespace boost::corosio::detail
187
188 #endif // BOOST_COROSIO_HAS_SELECT
189
190 #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
191