TLA Line data 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 HIT 126128 : static ssize_t write(int fd, iovec* iovecs, int count) noexcept
84 : {
85 126128 : msghdr msg{};
86 126128 : msg.msg_iov = iovecs;
87 126128 : msg.msg_iovlen = static_cast<std::size_t>(count);
88 :
89 : #ifdef MSG_NOSIGNAL
90 126128 : 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 126128 : n = ::sendmsg(fd, &msg, send_flags);
99 : }
100 126128 : while (n < 0 && errno == EINTR);
101 126128 : 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 3082 : static int do_accept(int fd, sockaddr_storage& peer) noexcept
118 : {
119 3082 : socklen_t addrlen = sizeof(peer);
120 : int new_fd;
121 : do
122 : {
123 3082 : new_fd = ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
124 : }
125 3082 : while (new_fd < 0 && errno == EINTR);
126 :
127 3082 : if (new_fd < 0)
128 MIS 0 : return new_fd;
129 :
130 HIT 3082 : if (new_fd >= FD_SETSIZE)
131 : {
132 MIS 0 : ::close(new_fd);
133 0 : errno = EINVAL;
134 0 : return -1;
135 : }
136 :
137 HIT 3082 : int flags = ::fcntl(new_fd, F_GETFL, 0);
138 3082 : if (flags == -1)
139 : {
140 MIS 0 : int err = errno;
141 0 : ::close(new_fd);
142 0 : errno = err;
143 0 : return -1;
144 : }
145 :
146 HIT 3082 : if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
147 : {
148 MIS 0 : int err = errno;
149 0 : ::close(new_fd);
150 0 : errno = err;
151 0 : return -1;
152 : }
153 :
154 HIT 3082 : if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
155 : {
156 MIS 0 : int err = errno;
157 0 : ::close(new_fd);
158 0 : errno = err;
159 0 : 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 HIT 3082 : 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
|