100.00% Lines (75/75) 100.00% Functions (12/12)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Michael Vandeberg 2   // Copyright (c) 2026 Michael Vandeberg
3   // 3   //
4   // Distributed under the Boost Software License, Version 1.0. (See accompanying 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) 5   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6   // 6   //
7   // Official repository: https://github.com/cppalliance/corosio 7   // Official repository: https://github.com/cppalliance/corosio
8   // 8   //
9   9  
10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP 10   #ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
11   #define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP 11   #define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP
12   12  
13   #include <boost/corosio/detail/platform.hpp> 13   #include <boost/corosio/detail/platform.hpp>
14   14  
15   #if BOOST_COROSIO_HAS_SELECT 15   #if BOOST_COROSIO_HAS_SELECT
16   16  
17   #include <boost/corosio/native/detail/make_err.hpp> 17   #include <boost/corosio/native/detail/make_err.hpp>
  18 + #include <boost/corosio/native/detail/reactor/reactor_descriptor_ops.hpp>
18   #include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp> 19   #include <boost/corosio/native/detail/reactor/reactor_descriptor_state.hpp>
19   20  
20   #include <system_error> 21   #include <system_error>
21   #include <tuple> 22   #include <tuple>
22   23  
23   #include <errno.h> 24   #include <errno.h>
24   #include <fcntl.h> 25   #include <fcntl.h>
25   #include <netinet/in.h> 26   #include <netinet/in.h>
26   #include <sys/select.h> 27   #include <sys/select.h>
27   #include <sys/socket.h> 28   #include <sys/socket.h>
  29 + #include <sys/uio.h>
28   #include <unistd.h> 30   #include <unistd.h>
29   31  
30   /* select backend traits. 32   /* select backend traits.
31   33  
32   Captures the platform-specific behavior of the portable select() backend: 34   Captures the platform-specific behavior of the portable select() backend:
33   manual fcntl for O_NONBLOCK/FD_CLOEXEC, FD_SETSIZE validation, 35   manual fcntl for O_NONBLOCK/FD_CLOEXEC, FD_SETSIZE validation,
34   mandatory SO_NOSIGPIPE where the platform defines it, 36   mandatory SO_NOSIGPIPE where the platform defines it,
35   sendmsg(MSG_NOSIGNAL) where available, and accept()+fcntl for 37   sendmsg(MSG_NOSIGNAL) where available, and accept()+fcntl for
36   accepted connections. 38   accepted connections.
37   */ 39   */
38   40  
39   namespace boost::corosio::detail { 41   namespace boost::corosio::detail {
40   42  
41   class select_scheduler; 43   class select_scheduler;
42   44  
43   struct select_traits 45   struct select_traits
44   { 46   {
45   using scheduler_type = select_scheduler; 47   using scheduler_type = select_scheduler;
46   using desc_state_type = reactor_descriptor_state; 48   using desc_state_type = reactor_descriptor_state;
47   49  
48 - static constexpr bool needs_write_notification = true; 50 + static constexpr bool needs_park_notification = true;
  51 +
  52 + /// select() cannot watch a descriptor at or above this.
  53 + static constexpr int max_descriptor = FD_SETSIZE;
49   54  
50   // No extra per-socket state or lifecycle hooks needed for select. 55   // No extra per-socket state or lifecycle hooks needed for select.
51   struct stream_socket_hook 56   struct stream_socket_hook
52   { 57   {
HITCBC 53   107 std::error_code on_set_option( 58   115 std::error_code on_set_option(
54   int fd, 59   int fd,
55   int level, 60   int level,
56   int optname, 61   int optname,
57   void const* data, 62   void const* data,
58   std::size_t size) noexcept 63   std::size_t size) noexcept
59   { 64   {
HITCBC 60   107 if (::setsockopt( 65   115 if (::setsockopt(
HITCBC 61   107 fd, level, optname, data, static_cast<socklen_t>(size)) != 66   115 fd, level, optname, data, static_cast<socklen_t>(size)) !=
62   0) 67   0)
HITCBC 63   4 return make_err(errno); 68   4 return make_err(errno);
HITCBC 64   103 return {}; 69   111 return {};
65   } 70   }
HITCBC 66   20689 static void pre_shutdown(int) noexcept {} 71   19854 static void pre_shutdown(int) noexcept {}
HITCBC 67   6731 static void pre_destroy(int) noexcept {} 72   6474 static void pre_destroy(int) noexcept {}
68   }; 73   };
69   74  
70   struct write_policy 75   struct write_policy
71   { 76   {
HITCBC 72   72 static ssize_t write(int fd, iovec* iovecs, int count) noexcept 77   73 static ssize_t write(int fd, iovec* iovecs, int count) noexcept
73   { 78   {
HITCBC 74   72 msghdr msg{}; 79   73 msghdr msg{};
HITCBC 75   72 msg.msg_iov = iovecs; 80   73 msg.msg_iov = iovecs;
HITCBC 76   72 msg.msg_iovlen = static_cast<std::size_t>(count); 81   73 msg.msg_iovlen = static_cast<std::size_t>(count);
77   82  
78   #ifdef MSG_NOSIGNAL 83   #ifdef MSG_NOSIGNAL
HITCBC 79   72 constexpr int send_flags = MSG_NOSIGNAL; 84   73 constexpr int send_flags = MSG_NOSIGNAL;
80   #else 85   #else
81   constexpr int send_flags = 0; 86   constexpr int send_flags = 0;
82   #endif 87   #endif
83   88  
84   ssize_t n; 89   ssize_t n;
85   do 90   do
86   { 91   {
HITCBC 87   73 n = ::sendmsg(fd, &msg, send_flags); 92   74 n = ::sendmsg(fd, &msg, send_flags);
88   } 93   }
HITCBC 89   73 while (n < 0 && errno == EINTR); 94   74 while (n < 0 && errno == EINTR);
HITCBC 90   72 return n; 95   73 return n;
91   } 96   }
92   97  
93   // Single-buffer fast path. Where MSG_NOSIGNAL exists we use 98   // Single-buffer fast path. Where MSG_NOSIGNAL exists we use
94   // send() to suppress SIGPIPE inline; otherwise fall back to 99   // send() to suppress SIGPIPE inline; otherwise fall back to
95   // write() and rely on the SO_NOSIGPIPE set in accept_policy 100   // write() and rely on the SO_NOSIGPIPE set in accept_policy
96   // and set_fd_options. 101   // and set_fd_options.
97   static ssize_t 102   static ssize_t
HITCBC 98   105255 write_one(int fd, void const* data, std::size_t size) noexcept 103   110194 write_one(int fd, void const* data, std::size_t size) noexcept
99   { 104   {
100   ssize_t n; 105   ssize_t n;
101   do 106   do
102   { 107   {
103   #ifdef MSG_NOSIGNAL 108   #ifdef MSG_NOSIGNAL
HITCBC 104   105256 n = ::send(fd, data, size, MSG_NOSIGNAL); 109   110195 n = ::send(fd, data, size, MSG_NOSIGNAL);
105   #else 110   #else
106   n = ::write(fd, data, size); 111   n = ::write(fd, data, size);
107   #endif 112   #endif
108   } 113   }
HITCBC 109   105256 while (n < 0 && errno == EINTR); 114   110195 while (n < 0 && errno == EINTR);
HITCBC 110   105255 return n; 115   110194 return n;
111   } 116   }
112   }; 117   };
  118 +
  119 + using descriptor_write_policy = detail::descriptor_write_policy;
113   120  
114   struct accept_policy 121   struct accept_policy
115   { 122   {
116   static int 123   static int
HITCBC 117   4426 do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept 124   4255 do_accept(int fd, sockaddr_storage& peer, socklen_t& addrlen) noexcept
118   { 125   {
HITCBC 119   4426 addrlen = sizeof(peer); 126   4255 addrlen = sizeof(peer);
120   int new_fd; 127   int new_fd;
121   do 128   do
122   { 129   {
123   new_fd = 130   new_fd =
HITCBC 124   4427 ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen); 131   4256 ::accept(fd, reinterpret_cast<sockaddr*>(&peer), &addrlen);
125   } 132   }
HITCBC 126   4427 while (new_fd < 0 && errno == EINTR); 133   4256 while (new_fd < 0 && errno == EINTR);
127   134  
HITCBC 128   4426 if (new_fd < 0) 135   4255 if (new_fd < 0)
HITCBC 129   2218 return new_fd; 136   2132 return new_fd;
130   137  
HITCBC 131   2208 if (new_fd >= FD_SETSIZE) 138   2123 if (new_fd >= FD_SETSIZE)
132   { 139   {
HITCBC 133   1 ::close(new_fd); 140   1 ::close(new_fd);
HITCBC 134   1 errno = EMFILE; 141   1 errno = EMFILE;
HITCBC 135   1 return -1; 142   1 return -1;
136   } 143   }
137   144  
HITCBC 138   2207 int flags = ::fcntl(new_fd, F_GETFL, 0); 145   2122 int flags = ::fcntl(new_fd, F_GETFL, 0);
HITCBC 139   2207 if (flags == -1) 146   2122 if (flags == -1)
140   { 147   {
HITCBC 141   1 int err = errno; 148   1 int err = errno;
HITCBC 142   1 ::close(new_fd); 149   1 ::close(new_fd);
HITCBC 143   1 errno = err; 150   1 errno = err;
HITCBC 144   1 return -1; 151   1 return -1;
145   } 152   }
146   153  
HITCBC 147   2206 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1) 154   2121 if (::fcntl(new_fd, F_SETFL, flags | O_NONBLOCK) == -1)
148   { 155   {
HITCBC 149   1 int err = errno; 156   1 int err = errno;
HITCBC 150   1 ::close(new_fd); 157   1 ::close(new_fd);
HITCBC 151   1 errno = err; 158   1 errno = err;
HITCBC 152   1 return -1; 159   1 return -1;
153   } 160   }
154   161  
HITCBC 155   2205 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1) 162   2120 if (::fcntl(new_fd, F_SETFD, FD_CLOEXEC) == -1)
156   { 163   {
HITCBC 157   1 int err = errno; 164   1 int err = errno;
HITCBC 158   1 ::close(new_fd); 165   1 ::close(new_fd);
HITCBC 159   1 errno = err; 166   1 errno = err;
HITCBC 160   1 return -1; 167   1 return -1;
161   } 168   }
162   169  
163   #ifdef SO_NOSIGPIPE 170   #ifdef SO_NOSIGPIPE
164   // MSG_NOSIGNAL is not universal across the platforms this 171   // MSG_NOSIGNAL is not universal across the platforms this
165   // portable backend covers, and the write() the fast path 172   // portable backend covers, and the write() the fast path
166   // falls back to there takes no flag at all; SO_NOSIGPIPE is 173   // falls back to there takes no flag at all; SO_NOSIGPIPE is
167   // the per-descriptor guard that covers both. Treat failure 174   // the per-descriptor guard that covers both. Treat failure
168   // as fatal, matching the kqueue backend. 175   // as fatal, matching the kqueue backend.
169   int one = 1; 176   int one = 1;
170   if (::setsockopt( 177   if (::setsockopt(
171   new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0) 178   new_fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 0)
172   { 179   {
173   int err = errno; 180   int err = errno;
174   ::close(new_fd); 181   ::close(new_fd);
175   errno = err; 182   errno = err;
176   return -1; 183   return -1;
177   } 184   }
178   #endif 185   #endif
179   186  
HITCBC 180   2204 return new_fd; 187   2119 return new_fd;
181   } 188   }
182   }; 189   };
183   190  
184   // Create a plain socket (no atomic flags -- select is POSIX-portable). 191   // Create a plain socket (no atomic flags -- select is POSIX-portable).
HITCBC 185   2813 static int create_socket(int family, int type, int protocol) noexcept 192   2737 static int create_socket(int family, int type, int protocol) noexcept
186   { 193   {
HITCBC 187   2813 return ::socket(family, type, protocol); 194   2737 return ::socket(family, type, protocol);
188   } 195   }
189   196  
190   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE. 197   // Set O_NONBLOCK, FD_CLOEXEC; check FD_SETSIZE; optionally SO_NOSIGPIPE.
191   // Caller is responsible for closing fd on error. 198   // Caller is responsible for closing fd on error.
HITCBC 192   2807 static std::error_code set_fd_options(int fd) noexcept 199   2731 static std::error_code set_fd_options(int fd) noexcept
193   { 200   {
HITCBC 194   2807 int flags = ::fcntl(fd, F_GETFL, 0); 201   2731 int flags = ::fcntl(fd, F_GETFL, 0);
HITCBC 195   2807 if (flags == -1) 202   2731 if (flags == -1)
HITCBC 196   2 return make_err(errno); 203   2 return make_err(errno);
HITCBC 197   2805 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) 204   2729 if (::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
HITCBC 198   2 return make_err(errno); 205   2 return make_err(errno);
HITCBC 199   2803 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) 206   2727 if (::fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
HITCBC 200   2 return make_err(errno); 207   2 return make_err(errno);
201   208  
HITCBC 202   2801 if (fd >= FD_SETSIZE) 209   2725 if (fd >= FD_SETSIZE)
HITCBC 203   2 return make_err(EMFILE); 210   2 return make_err(EMFILE);
204   211  
205   #ifdef SO_NOSIGPIPE 212   #ifdef SO_NOSIGPIPE
206   // MSG_NOSIGNAL is not universal across the platforms this 213   // MSG_NOSIGNAL is not universal across the platforms this
207   // portable backend covers, and the write() the fast path falls 214   // portable backend covers, and the write() the fast path falls
208   // back to there takes no flag at all; SO_NOSIGPIPE is the 215   // back to there takes no flag at all; SO_NOSIGPIPE is the
209   // per-descriptor guard that covers both. Treat failure as fatal, 216   // per-descriptor guard that covers both. Treat failure as fatal,
210   // matching the kqueue backend. Caller closes fd on error. 217   // matching the kqueue backend. Caller closes fd on error.
211   { 218   {
212   int one = 1; 219   int one = 1;
213   if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) != 220   if (::setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(one)) !=
214   0) 221   0)
215   return make_err(errno); 222   return make_err(errno);
216   } 223   }
217   #endif 224   #endif
218   225  
HITCBC 219   2799 return {}; 226   2723 return {};
220   } 227   }
221   228  
222   // Apply protocol-specific options after socket creation. 229   // Apply protocol-specific options after socket creation.
223   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort). 230   // For IP sockets, sets IPV6_V6ONLY on AF_INET6 (best-effort).
HITCBC 224   2422 static std::error_code configure_ip_socket(int fd, int family) noexcept 231   2338 static std::error_code configure_ip_socket(int fd, int family) noexcept
225   { 232   {
HITCBC 226   2422 if (family == AF_INET6) 233   2338 if (family == AF_INET6)
227   { 234   {
HITCBC 228   23 int one = 1; 235   23 int one = 1;
229   std::ignore = 236   std::ignore =
HITCBC 230   23 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); 237   23 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one));
231   } 238   }
232   239  
HITCBC 233   2422 return set_fd_options(fd); 240   2338 return set_fd_options(fd);
234   } 241   }
235   242  
236   // Apply protocol-specific options for acceptor sockets. 243   // Apply protocol-specific options for acceptor sockets.
237   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort). 244   // For IP acceptors, sets IPV6_V6ONLY=0 (dual-stack, best-effort).
HITCBC 238   276 static std::error_code configure_ip_acceptor(int fd, int family) noexcept 245   283 static std::error_code configure_ip_acceptor(int fd, int family) noexcept
239   { 246   {
HITCBC 240   276 if (family == AF_INET6) 247   283 if (family == AF_INET6)
241   { 248   {
HITCBC 242   11 int val = 0; 249   11 int val = 0;
243   std::ignore = 250   std::ignore =
HITCBC 244   11 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); 251   11 ::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
245   } 252   }
246   253  
HITCBC 247   276 return set_fd_options(fd); 254   283 return set_fd_options(fd);
248   } 255   }
249   256  
250   // Apply options for local (unix) sockets. 257   // Apply options for local (unix) sockets.
HITCBC 251   109 static std::error_code configure_local_socket(int fd) noexcept 258   110 static std::error_code configure_local_socket(int fd) noexcept
252   { 259   {
HITCBC 253   109 return set_fd_options(fd); 260   110 return set_fd_options(fd);
254   } 261   }
255   262  
256   // Non-mutating validation for fds adopted via assign(). Select's 263   // Non-mutating validation for fds adopted via assign(). Select's
257   // reactor cannot handle fds above FD_SETSIZE, so reject them up 264   // reactor cannot handle fds above FD_SETSIZE, so reject them up
258   // front instead of letting FD_SET clobber unrelated memory. 265   // front instead of letting FD_SET clobber unrelated memory.
HITCBC 259   155 static std::error_code validate_assigned_fd(int fd) noexcept 266   151 static std::error_code validate_assigned_fd(int fd) noexcept
260   { 267   {
HITCBC 261   155 if (fd >= FD_SETSIZE) 268   151 if (fd >= FD_SETSIZE)
HITCBC 262   2 return make_err(EMFILE); 269   2 return make_err(EMFILE);
HITCBC 263   153 return {}; 270   149 return {};
264   } 271   }
265   }; 272   };
266   273  
267   } // namespace boost::corosio::detail 274   } // namespace boost::corosio::detail
268   275  
269   #endif // BOOST_COROSIO_HAS_SELECT 276   #endif // BOOST_COROSIO_HAS_SELECT
270   277  
271   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP 278   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_TRAITS_HPP