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

55.6% Lines (20/36) 100.0% List of functions (2/2)
select_op.hpp
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_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 169091x static ssize_t write(int fd, iovec* iovecs, int count) noexcept
84 {
85 169091x msghdr msg{};
86 169091x msg.msg_iov = iovecs;
87 169091x msg.msg_iovlen = static_cast<std::size_t>(count);
88
89 #ifdef MSG_NOSIGNAL
90 169091x 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 169091x n = ::sendmsg(fd, &msg, send_flags);
99 }
100 169091x while (n < 0 && errno == EINTR);
101 169091x 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 3222x static int do_accept(
118 int fd, sockaddr_storage& peer, socklen_t& addrlen_out) noexcept
119 {
120 3222x addrlen_out = sizeof(peer);
121 int new_fd;
122 do
123 {
124 3222x addrlen_out = sizeof(peer);
125 3222x new_fd = ::accept(
126 fd, reinterpret_cast<sockaddr*>(&peer), &addrlen_out);
127 }
128 3222x while (new_fd < 0 && errno == EINTR);
129
130 3222x if (new_fd < 0)
131 return new_fd;
132
133 3222x if (new_fd >= FD_SETSIZE)
134 {
135 ::close(new_fd);
136 errno = EINVAL;
137 return -1;
138 }
139
140 3222x int flags = ::fcntl(new_fd, F_GETFL, 0);
141 3222x if (flags == -1)
142 {
143 int err = errno;
144 ::close(new_fd);
145 errno = err;
146 return -1;
147 }
148
149 3222x if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
150 {
151 int err = errno;
152 ::close(new_fd);
153 errno = err;
154 return -1;
155 }
156
157 3222x if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
158 {
159 int err = errno;
160 ::close(new_fd);
161 errno = err;
162 return -1;
163 }
164
165 #ifdef SO_NOSIGPIPE
166 int one = 1;
167 if (::setsockopt(new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) ==
168 -1)
169 {
170 int err = errno;
171 ::close(new_fd);
172 errno = err;
173 return -1;
174 }
175 #endif
176
177 3222x return new_fd;
178 }
179 };
180
181 /// select accept operation.
182 struct select_accept_op final
183 : reactor_accept_op<select_op, select_accept_policy>
184 {
185 void operator()() override;
186 void cancel() noexcept override;
187 };
188
189 } // namespace boost::corosio::detail
190
191 #endif // BOOST_COROSIO_HAS_SELECT
192
193 #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_OP_HPP
194