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