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

54.3% Lines (19/35) 100.0% List of functions (2/2)
f(x) Functions (2)
Line 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_socket;
46 class select_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_socket, select_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 126128x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
84 {
85 126128x msghdr msg{};
86 126128x msg.msg_iov = iovecs;
87 126128x msg.msg_iovlen = static_cast<std::size_t>(count);
88
89 #ifdef MSG_NOSIGNAL
90 126128x constexpr int send_flags = MSG_NOSIGNAL;
91 #else
92 constexpr int send_flags = 0;
93 #endif
94
95 ssize_t n;
96 do
97 {
98 126128x n = ::sendmsg(fd, &msg, send_flags);
99 }
100 126128x while (n < 0 && errno == EINTR);
101 126128x 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 3082x static int do_accept(int fd, sockaddr_storage& peer) noexcept
118 {
119 3082x socklen_t addrlen = sizeof(peer);
120 int new_fd;
121 do
122 {
123 3082x new_fd = ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
124 }
125 3082x while (new_fd < 0 && errno == EINTR);
126
127 3082x if (new_fd < 0)
128 return new_fd;
129
130 3082x if (new_fd >= FD_SETSIZE)
131 {
132 ::close(new_fd);
133 errno = EINVAL;
134 return -1;
135 }
136
137 3082x int flags = ::fcntl(new_fd, F_GETFL, 0);
138 3082x if (flags == -1)
139 {
140 int err = errno;
141 ::close(new_fd);
142 errno = err;
143 return -1;
144 }
145
146 3082x 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 3082x 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 int one = 1;
164 if (::setsockopt(
165 new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) == -1)
166 {
167 int err = errno;
168 ::close(new_fd);
169 errno = err;
170 return -1;
171 }
172 #endif
173
174 3082x return new_fd;
175 }
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