99.44% Lines (177/178) 100.00% Functions (12/12)
TLA Baseline Branch
Line Hits Code Line Hits Code
1   // 1   //
2   // Copyright (c) 2026 Steve Gerbino 2   // Copyright (c) 2026 Steve Gerbino
3   // Copyright (c) 2026 Michael Vandeberg 3   // Copyright (c) 2026 Michael Vandeberg
4   // 4   //
5   // Distributed under the Boost Software License, Version 1.0. (See accompanying 5   // Distributed under the Boost Software License, Version 1.0. (See accompanying
6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 6   // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7   // 7   //
8   // Official repository: https://github.com/cppalliance/corosio 8   // Official repository: https://github.com/cppalliance/corosio
9   // 9   //
10   10  
11   #ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_SCHEDULER_HPP 11   #ifndef BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_SCHEDULER_HPP
12   #define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_SCHEDULER_HPP 12   #define BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_SCHEDULER_HPP
13   13  
14   #include <boost/corosio/detail/platform.hpp> 14   #include <boost/corosio/detail/platform.hpp>
15   15  
16   #if BOOST_COROSIO_HAS_SELECT 16   #if BOOST_COROSIO_HAS_SELECT
17   17  
18   #include <boost/corosio/detail/config.hpp> 18   #include <boost/corosio/detail/config.hpp>
19   #include <boost/capy/ex/execution_context.hpp> 19   #include <boost/capy/ex/execution_context.hpp>
20   20  
21   #include <boost/corosio/native/detail/reactor/reactor_scheduler.hpp> 21   #include <boost/corosio/native/detail/reactor/reactor_scheduler.hpp>
22   #include <boost/corosio/native/detail/reactor/reactor_signal_pipe.hpp> 22   #include <boost/corosio/native/detail/reactor/reactor_signal_pipe.hpp>
23   23  
24   #include <boost/corosio/native/detail/select/select_traits.hpp> 24   #include <boost/corosio/native/detail/select/select_traits.hpp>
25   #include <boost/corosio/detail/timer_service.hpp> 25   #include <boost/corosio/detail/timer_service.hpp>
26   #include <boost/corosio/native/detail/make_err.hpp> 26   #include <boost/corosio/native/detail/make_err.hpp>
27   27  
28   #include <boost/corosio/detail/except.hpp> 28   #include <boost/corosio/detail/except.hpp>
29   29  
30   #include <sys/select.h> 30   #include <sys/select.h>
31   #include <unistd.h> 31   #include <unistd.h>
32   #include <errno.h> 32   #include <errno.h>
33   #include <fcntl.h> 33   #include <fcntl.h>
34   34  
35   #include <atomic> 35   #include <atomic>
36   #include <chrono> 36   #include <chrono>
37   #include <cstdint> 37   #include <cstdint>
38   #include <limits> 38   #include <limits>
39   #include <mutex> 39   #include <mutex>
40   #include <new> 40   #include <new>
41   #include <unordered_map> 41   #include <unordered_map>
42   42  
43   namespace boost::corosio::detail { 43   namespace boost::corosio::detail {
44   44  
45   struct select_op; 45   struct select_op;
46   46  
47   /** POSIX scheduler using select() for I/O multiplexing. 47   /** POSIX scheduler using select() for I/O multiplexing.
48   48  
49   This scheduler implements the scheduler interface using the POSIX select() 49   This scheduler implements the scheduler interface using the POSIX select()
50   call for I/O event notification. It inherits the shared reactor threading 50   call for I/O event notification. It inherits the shared reactor threading
51   model from reactor_scheduler: signal state machine, inline completion 51   model from reactor_scheduler: signal state machine, inline completion
52   budget, work counting, and the do_one event loop. 52   budget, work counting, and the do_one event loop.
53   53  
54   The design mirrors epoll_scheduler for behavioral consistency: 54   The design mirrors epoll_scheduler for behavioral consistency:
55   - Same single-reactor thread coordination model 55   - Same single-reactor thread coordination model
56   - Same deferred I/O pattern (reactor marks ready; workers do I/O) 56   - Same deferred I/O pattern (reactor marks ready; workers do I/O)
57   - Same timer integration pattern 57   - Same timer integration pattern
58   58  
59   Known Limitations: 59   Known Limitations:
60   - FD_SETSIZE (~1024) limits maximum concurrent connections 60   - FD_SETSIZE (~1024) limits maximum concurrent connections
61   - O(n) scanning: rebuilds fd_sets each iteration 61   - O(n) scanning: rebuilds fd_sets each iteration
62   - Level-triggered only (no edge-triggered mode) 62   - Level-triggered only (no edge-triggered mode)
63   63  
64   @par Thread Safety 64   @par Thread Safety
65   All public member functions are thread-safe. 65   All public member functions are thread-safe.
66   */ 66   */
67   class BOOST_COROSIO_DECL select_scheduler final : public reactor_scheduler 67   class BOOST_COROSIO_DECL select_scheduler final : public reactor_scheduler
68   { 68   {
69   public: 69   public:
70   /** Construct the scheduler. 70   /** Construct the scheduler.
71   71  
72   Creates a self-pipe for reactor interruption. 72   Creates a self-pipe for reactor interruption.
73   73  
74   @param ctx Reference to the owning execution_context. 74   @param ctx Reference to the owning execution_context.
75   @param concurrency_hint Hint for expected thread count (unused). 75   @param concurrency_hint Hint for expected thread count (unused).
76   */ 76   */
77   select_scheduler(capy::execution_context& ctx, int concurrency_hint = -1); 77   select_scheduler(capy::execution_context& ctx, int concurrency_hint = -1);
78   78  
79   /// Destroy the scheduler. 79   /// Destroy the scheduler.
80   ~select_scheduler() override; 80   ~select_scheduler() override;
81   81  
82   select_scheduler(select_scheduler const&) = delete; 82   select_scheduler(select_scheduler const&) = delete;
83   select_scheduler& operator=(select_scheduler const&) = delete; 83   select_scheduler& operator=(select_scheduler const&) = delete;
84   84  
85   /// Shut down the scheduler, draining pending operations. 85   /// Shut down the scheduler, draining pending operations.
86   void shutdown() override; 86   void shutdown() override;
87   87  
88   /** Return the maximum file descriptor value supported. 88   /** Return the maximum file descriptor value supported.
89   89  
90   Returns FD_SETSIZE - 1, the maximum fd value that can be 90   Returns FD_SETSIZE - 1, the maximum fd value that can be
91   monitored by select(). Operations with fd >= FD_SETSIZE 91   monitored by select(). Operations with fd >= FD_SETSIZE
92   will fail with EINVAL. 92   will fail with EINVAL.
93   93  
94   @return The maximum supported file descriptor value. 94   @return The maximum supported file descriptor value.
95   */ 95   */
96   static constexpr int max_fd() noexcept 96   static constexpr int max_fd() noexcept
97   { 97   {
98   return FD_SETSIZE - 1; 98   return FD_SETSIZE - 1;
99   } 99   }
100   100  
101   /** Register a descriptor for persistent monitoring. 101   /** Register a descriptor for persistent monitoring.
102   102  
103   The fd is added to the registered_descs_ map and will be 103   The fd is added to the registered_descs_ map and will be
104   included in subsequent select() calls. The reactor is 104   included in subsequent select() calls. The reactor is
105   interrupted so a blocked select() rebuilds its fd_sets. 105   interrupted so a blocked select() rebuilds its fd_sets.
106   106  
107   @param fd The file descriptor to register. 107   @param fd The file descriptor to register.
108   @param desc Pointer to descriptor state for this fd. 108   @param desc Pointer to descriptor state for this fd.
109   109  
110   @return The error if the fd cannot be tracked, otherwise a 110   @return The error if the fd cannot be tracked, otherwise a
111   default constructed error code. 111   default constructed error code.
112   */ 112   */
113   std::error_code 113   std::error_code
114   register_descriptor(int fd, reactor_descriptor_state* desc) const; 114   register_descriptor(int fd, reactor_descriptor_state* desc) const;
115   115  
116   /// No-op: write readiness is watched from registration on. 116   /// No-op: write readiness is watched from registration on.
117   std::error_code 117   std::error_code
HITCBC 118   2818 ensure_write_registered(int, reactor_descriptor_state*) const noexcept 118   2798 ensure_write_registered(int, reactor_descriptor_state*) const noexcept
119   { 119   {
HITCBC 120   2818 return {}; 120   2798 return {};
121   } 121   }
122   122  
123   /** Deregister a persistently registered descriptor. 123   /** Deregister a persistently registered descriptor.
124   124  
125   @param fd The file descriptor to deregister. 125   @param fd The file descriptor to deregister.
126   */ 126   */
127   void deregister_descriptor(int fd) const; 127   void deregister_descriptor(int fd) const;
128   128  
129   /** Interrupt the reactor so it rebuilds its fd_sets. 129   /** Interrupt the reactor so it rebuilds its fd_sets.
130   130  
131   Called when a write, connect, or write-wait op is registered 131   Called when a write, connect, or write-wait op is registered
132   after the reactor's snapshot was taken. Without this, 132   after the reactor's snapshot was taken. Without this,
133   select() may block not watching for writability on the fd. 133   select() may block not watching for writability on the fd.
134   */ 134   */
135   void notify_reactor() const; 135   void notify_reactor() const;
136   136  
137   /// Watch the read end of the POSIX signal self-pipe (see scheduler.hpp). 137   /// Watch the read end of the POSIX signal self-pipe (see scheduler.hpp).
HITCBC 138   274 [[nodiscard]] std::error_code register_signal_reader(int read_fd) override 138   274 [[nodiscard]] std::error_code register_signal_reader(int read_fd) override
139   { 139   {
HITCBC 140   274 return register_descriptor(read_fd, signal_pipe_reader_.arm()); 140   274 return register_descriptor(read_fd, signal_pipe_reader_.arm());
141   } 141   }
142   142  
143   private: 143   private:
144   void run_task(lock_type& lock, context_type& ctx, long timeout_us) override; 144   void run_task(lock_type& lock, context_type& ctx, long timeout_us) override;
145   void interrupt_reactor() const override; 145   void interrupt_reactor() const override;
146   146  
147   /// Rebuild the poll set a blocked select() was given. 147   /// Rebuild the poll set a blocked select() was given.
HITCBC 148   495 void notify_reparked() const override 148   402 void notify_reparked() const override
149   { 149   {
HITCBC 150   495 interrupt_reactor(); 150   402 interrupt_reactor();
HITCBC 151   495 } 151   402 }
152   long calculate_timeout(long requested_timeout_us) const; 152   long calculate_timeout(long requested_timeout_us) const;
153   153  
154   // Watches the global signal self-pipe's read end (armed lazily by 154   // Watches the global signal self-pipe's read end (armed lazily by
155   // register_signal_reader on the first signal registration). 155   // register_signal_reader on the first signal registration).
156   reactor_signal_pipe_reader signal_pipe_reader_; 156   reactor_signal_pipe_reader signal_pipe_reader_;
157   157  
158   // Self-pipe for interrupting select() 158   // Self-pipe for interrupting select()
159   int pipe_fds_[2]; // [0]=read, [1]=write 159   int pipe_fds_[2]; // [0]=read, [1]=write
160   160  
161   // Per-fd tracking for fd_set building 161   // Per-fd tracking for fd_set building
162   mutable std::unordered_map<int, reactor_descriptor_state*> 162   mutable std::unordered_map<int, reactor_descriptor_state*>
163   registered_descs_; 163   registered_descs_;
164   mutable int max_fd_ = -1; 164   mutable int max_fd_ = -1;
165   }; 165   };
166   166  
HITCBC 167   1993 inline select_scheduler::select_scheduler(capy::execution_context&, int) 167   1996 inline select_scheduler::select_scheduler(capy::execution_context&, int)
HITCBC 168   1993 : pipe_fds_{-1, -1} 168   1996 : pipe_fds_{-1, -1}
HITCBC 169   1993 , max_fd_(-1) 169   1996 , max_fd_(-1)
170   { 170   {
HITCBC 171   1993 if (::pipe(pipe_fds_) < 0) 171   1996 if (::pipe(pipe_fds_) < 0)
HITCBC 172   1 detail::throw_system_error(make_err(errno), "pipe"); 172   1 detail::throw_system_error(make_err(errno), "pipe");
173   173  
HITCBC 174   5967 for (int i = 0; i < 2; ++i) 174   5976 for (int i = 0; i < 2; ++i)
175   { 175   {
HITCBC 176   3981 int flags = ::fcntl(pipe_fds_[i], F_GETFL, 0); 176   3987 int flags = ::fcntl(pipe_fds_[i], F_GETFL, 0);
HITCBC 177   3981 if (flags == -1) 177   3987 if (flags == -1)
178   { 178   {
HITCBC 179   2 int errn = errno; 179   2 int errn = errno;
HITCBC 180   2 ::close(pipe_fds_[0]); 180   2 ::close(pipe_fds_[0]);
HITCBC 181   2 ::close(pipe_fds_[1]); 181   2 ::close(pipe_fds_[1]);
HITCBC 182   2 detail::throw_system_error(make_err(errn), "fcntl F_GETFL"); 182   2 detail::throw_system_error(make_err(errn), "fcntl F_GETFL");
183   } 183   }
HITCBC 184   3979 if (::fcntl(pipe_fds_[i], F_SETFL, flags | O_NONBLOCK) == -1) 184   3985 if (::fcntl(pipe_fds_[i], F_SETFL, flags | O_NONBLOCK) == -1)
185   { 185   {
HITCBC 186   2 int errn = errno; 186   2 int errn = errno;
HITCBC 187   2 ::close(pipe_fds_[0]); 187   2 ::close(pipe_fds_[0]);
HITCBC 188   2 ::close(pipe_fds_[1]); 188   2 ::close(pipe_fds_[1]);
HITCBC 189   2 detail::throw_system_error(make_err(errn), "fcntl F_SETFL"); 189   2 detail::throw_system_error(make_err(errn), "fcntl F_SETFL");
190   } 190   }
HITCBC 191   3977 if (::fcntl(pipe_fds_[i], F_SETFD, FD_CLOEXEC) == -1) 191   3983 if (::fcntl(pipe_fds_[i], F_SETFD, FD_CLOEXEC) == -1)
192   { 192   {
HITCBC 193   2 int errn = errno; 193   2 int errn = errno;
HITCBC 194   2 ::close(pipe_fds_[0]); 194   2 ::close(pipe_fds_[0]);
HITCBC 195   2 ::close(pipe_fds_[1]); 195   2 ::close(pipe_fds_[1]);
HITCBC 196   2 detail::throw_system_error(make_err(errn), "fcntl F_SETFD"); 196   2 detail::throw_system_error(make_err(errn), "fcntl F_SETFD");
197   } 197   }
198   } 198   }
199   199  
HITCBC 200   1986 completed_ops_.push(&task_op_); 200   1989 completed_ops_.push(&task_op_);
HITCBC 201   2007 } 201   2010 }
202   202  
HITCBC 203   3972 inline select_scheduler::~select_scheduler() 203   3978 inline select_scheduler::~select_scheduler()
204   { 204   {
HITCBC 205   1986 if (pipe_fds_[0] >= 0) 205   1989 if (pipe_fds_[0] >= 0)
HITCBC 206   1986 ::close(pipe_fds_[0]); 206   1989 ::close(pipe_fds_[0]);
HITCBC 207   1986 if (pipe_fds_[1] >= 0) 207   1989 if (pipe_fds_[1] >= 0)
HITCBC 208   1986 ::close(pipe_fds_[1]); 208   1989 ::close(pipe_fds_[1]);
HITCBC 209   3972 } 209   3978 }
210   210  
211   inline void 211   inline void
HITCBC 212   1986 select_scheduler::shutdown() 212   1989 select_scheduler::shutdown()
213   { 213   {
HITCBC 214   1986 shutdown_drain(); 214   1989 shutdown_drain();
215   215  
HITCBC 216   1986 if (pipe_fds_[1] >= 0) 216   1989 if (pipe_fds_[1] >= 0)
HITCBC 217   1986 interrupt_reactor(); 217   1989 interrupt_reactor();
HITCBC 218   1986 } 218   1989 }
219   219  
220   inline std::error_code 220   inline std::error_code
HITCBC 221   10019 select_scheduler::register_descriptor( 221   10155 select_scheduler::register_descriptor(
222   int fd, reactor_descriptor_state* desc) const 222   int fd, reactor_descriptor_state* desc) const
223   { 223   {
HITCBC 224   10019 if (fd < 0 || fd >= FD_SETSIZE) 224   10155 if (fd < 0 || fd >= FD_SETSIZE)
HITCBC 225   1 return make_err(EMFILE); 225   1 return make_err(EMFILE);
226   226  
227   // A completion queued by an earlier registration of the same state 227   // A completion queued by an earlier registration of the same state
228   // may still be running, so the fields it reads change under its 228   // may still be running, so the fields it reads change under its
229   // mutex, and the mutex mode is only written when it differs. 229   // mutex, and the mutex mode is only written when it differs.
HITCBC 230   10018 if (desc->mutex.enabled() != reactor_io_locking_) 230   10154 if (desc->mutex.enabled() != reactor_io_locking_)
HITCBC 231   3 desc->mutex.set_enabled(reactor_io_locking_); 231   3 desc->mutex.set_enabled(reactor_io_locking_);
232   { 232   {
HITCBC 233   10018 conditionally_enabled_mutex::scoped_lock lock(desc->mutex); 233   10154 conditionally_enabled_mutex::scoped_lock lock(desc->mutex);
HITCBC 234   10018 desc->registered_events = reactor_event_read | reactor_event_write; 234   10154 desc->registered_events = reactor_event_read | reactor_event_write;
HITCBC 235   10018 desc->unpollable = false; // the state is reused across adoptions 235   10154 desc->unpollable = false; // the state is reused across adoptions
HITCBC 236   10018 desc->fd = fd; 236   10154 desc->fd = fd;
HITCBC 237   10018 desc->scheduler_ = this; 237   10154 desc->scheduler_ = this;
HITCBC 238   10018 desc->ready_events_.store(0, std::memory_order_release); 238   10154 desc->ready_events_.store(0, std::memory_order_release);
239   // object_ref_ is not touched: a queued invocation from an 239   // object_ref_ is not touched: a queued invocation from an
240   // earlier registration still owns the reference in it. 240   // earlier registration still owns the reference in it.
HITCBC 241   10018 desc->read_ready = false; 241   10154 desc->read_ready = false;
HITCBC 242   10018 desc->write_ready = false; 242   10154 desc->write_ready = false;
HITCBC 243   10018 } 243   10154 }
244   244  
245   { 245   {
HITCBC 246   10018 mutex_type::scoped_lock lock(mutex_); 246   10154 mutex_type::scoped_lock lock(mutex_);
247   try 247   try
248   { 248   {
HITCBC 249   10018 registered_descs_[fd] = desc; 249   10154 registered_descs_[fd] = desc;
250   } 250   }
HITCBC 251   1 catch (std::bad_alloc const&) 251   1 catch (std::bad_alloc const&)
252   { 252   {
HITCBC 253   1 return make_err(ENOMEM); 253   1 return make_err(ENOMEM);
HITCBC 254   1 } 254   1 }
HITCBC 255   10017 if (fd > max_fd_) 255   10153 if (fd > max_fd_)
HITCBC 256   9556 max_fd_ = fd; 256   9604 max_fd_ = fd;
HITCBC 257   10018 } 257   10154 }
258   258  
HITCBC 259   10017 interrupt_reactor(); 259   10153 interrupt_reactor();
HITCBC 260   10017 return {}; 260   10153 return {};
261   } 261   }
262   262  
263   inline void 263   inline void
HITCBC 264   9744 select_scheduler::deregister_descriptor(int fd) const 264   9880 select_scheduler::deregister_descriptor(int fd) const
265   { 265   {
HITCBC 266   9744 mutex_type::scoped_lock lock(mutex_); 266   9880 mutex_type::scoped_lock lock(mutex_);
267   267  
HITCBC 268   9744 auto it = registered_descs_.find(fd); 268   9880 auto it = registered_descs_.find(fd);
HITCBC 269   9744 if (it == registered_descs_.end()) 269   9880 if (it == registered_descs_.end())
MISUBC 270   ✗ return; 270   ✗ return;
271   271  
HITCBC 272   9744 registered_descs_.erase(it); 272   9880 registered_descs_.erase(it);
273   273  
HITCBC 274   9744 if (fd == max_fd_) 274   9880 if (fd == max_fd_)
275   { 275   {
HITCBC 276   7956 max_fd_ = pipe_fds_[0]; 276   7967 max_fd_ = pipe_fds_[0];
HITCBC 277   13286 for (auto& [registered_fd, state] : registered_descs_) 277   13414 for (auto& [registered_fd, state] : registered_descs_)
278   { 278   {
HITCBC 279   5330 if (registered_fd > max_fd_) 279   5447 if (registered_fd > max_fd_)
HITCBC 280   5102 max_fd_ = registered_fd; 280   5132 max_fd_ = registered_fd;
281   } 281   }
282   } 282   }
HITCBC 283   9744 } 283   9880 }
284   284  
285   inline void 285   inline void
HITCBC 286   6924 select_scheduler::notify_reactor() const 286   6912 select_scheduler::notify_reactor() const
287   { 287   {
HITCBC 288   6924 interrupt_reactor(); 288   6912 interrupt_reactor();
HITCBC 289   6924 } 289   6912 }
290   290  
291   inline void 291   inline void
HITCBC 292   25847 select_scheduler::interrupt_reactor() const 292   26030 select_scheduler::interrupt_reactor() const
293   { 293   {
HITCBC 294   25847 char byte = 1; 294   26030 char byte = 1;
HITCBC 295   25847 [[maybe_unused]] auto r = ::write(pipe_fds_[1], &byte, 1); 295   26030 [[maybe_unused]] auto r = ::write(pipe_fds_[1], &byte, 1);
HITCBC 296   25847 } 296   26030 }
297   297  
298   inline long 298   inline long
HITCBC 299   15051 select_scheduler::calculate_timeout(long requested_timeout_us) const 299   12987 select_scheduler::calculate_timeout(long requested_timeout_us) const
300   { 300   {
HITCBC 301   15051 if (requested_timeout_us == 0) 301   12987 if (requested_timeout_us == 0)
302   return 0; // LCOV_EXCL_LINE run_task passes 0 via task_interrupted_, never through this argument 302   return 0; // LCOV_EXCL_LINE run_task passes 0 via task_interrupted_, never through this argument
303   303  
HITCBC 304   15051 auto* tsvc = timers(); 304   12987 auto* tsvc = timers();
HITCBC 305   15051 if (!tsvc) 305   12987 if (!tsvc)
HITCBC 306   8182 return requested_timeout_us; 306   6184 return requested_timeout_us;
HITCBC 307   6869 auto nearest = tsvc->nearest_expiry(); 307   6803 auto nearest = tsvc->nearest_expiry();
HITCBC 308   6869 if (nearest == timer_service::time_point::max()) 308   6803 if (nearest == timer_service::time_point::max())
HITCBC 309   139 return requested_timeout_us; 309   140 return requested_timeout_us;
310   310  
HITCBC 311   6730 auto now = std::chrono::steady_clock::now(); 311   6663 auto now = std::chrono::steady_clock::now();
HITCBC 312   6730 if (nearest <= now) 312   6663 if (nearest <= now)
HITCBC 313   61 return 0; 313   72 return 0;
314   314  
315   auto timer_timeout_us = 315   auto timer_timeout_us =
HITCBC 316   6669 std::chrono::duration_cast<std::chrono::microseconds>(nearest - now) 316   6591 std::chrono::duration_cast<std::chrono::microseconds>(nearest - now)
HITCBC 317   6669 .count(); 317   6591 .count();
318   318  
HITCBC 319   6669 constexpr auto long_max = 319   6591 constexpr auto long_max =
320   static_cast<long long>((std::numeric_limits<long>::max)()); 320   static_cast<long long>((std::numeric_limits<long>::max)());
321   auto capped_timer_us = 321   auto capped_timer_us =
HITCBC 322   6669 (std::min)((std::max)(static_cast<long long>(timer_timeout_us), 322   6591 (std::min)((std::max)(static_cast<long long>(timer_timeout_us),
HITCBC 323   6669 static_cast<long long>(0)), 323   6591 static_cast<long long>(0)),
HITCBC 324   6669 long_max); 324   6591 long_max);
325   325  
HITCBC 326   6669 if (requested_timeout_us < 0) 326   6591 if (requested_timeout_us < 0)
HITCBC 327   6663 return static_cast<long>(capped_timer_us); 327   6585 return static_cast<long>(capped_timer_us);
328   328  
329   return static_cast<long>( 329   return static_cast<long>(
HITCBC 330   6 (std::min)(static_cast<long long>(requested_timeout_us), 330   6 (std::min)(static_cast<long long>(requested_timeout_us),
HITCBC 331   6 capped_timer_us)); 331   6 capped_timer_us));
332   } 332   }
333   333  
334   inline void 334   inline void
HITCBC 335   140558 select_scheduler::run_task(lock_type& lock, context_type& ctx, long timeout_us) 335   136550 select_scheduler::run_task(lock_type& lock, context_type& ctx, long timeout_us)
336   { 336   {
337   long effective_timeout_us = 337   long effective_timeout_us =
HITCBC 338   140558 task_interrupted_ ? 0 : calculate_timeout(timeout_us); 338   136550 task_interrupted_ ? 0 : calculate_timeout(timeout_us);
339   339  
340   // Snapshot registered descriptors while holding lock. 340   // Snapshot registered descriptors while holding lock.
341   // Record which directions each fd needs monitored to avoid a hot 341   // Record which directions each fd needs monitored to avoid a hot
342   // loop: select is level-triggered, so a writable socket (nearly 342   // loop: select is level-triggered, so a writable socket (nearly
343   // always writable) or an always-readable fd (/dev/zero, a pipe at 343   // always writable) or an always-readable fd (/dev/zero, a pipe at
344   // EOF) would return select() immediately every iteration if 344   // EOF) would return select() immediately every iteration if
345   // unconditionally added. Membership in both sets is opt-in: a 345   // unconditionally added. Membership in both sets is opt-in: a
346   // parked op or wait in a direction opts that direction in. The 346   // parked op or wait in a direction opts that direction in. The
347   // exceptional set is opt-in too, for any parked op or wait: 347   // exceptional set is opt-in too, for any parked op or wait:
348   // Darwin reports a character device (/dev/zero, /dev/null) as 348   // Darwin reports a character device (/dev/zero, /dev/null) as
349   // exceptional on every call. 349   // exceptional on every call.
350   struct fd_entry 350   struct fd_entry
351   { 351   {
352   int fd; 352   int fd;
353   reactor_descriptor_state* desc; 353   reactor_descriptor_state* desc;
354   std::uint32_t want; 354   std::uint32_t want;
355   }; 355   };
356   fd_entry snapshot[FD_SETSIZE]; 356   fd_entry snapshot[FD_SETSIZE];
HITCBC 357   140558 int snapshot_count = 0; 357   136550 int snapshot_count = 0;
358   358  
359   // The caller may have released the lock to hand queued work to a 359   // The caller may have released the lock to hand queued work to a
360   // peer, and deregister_descriptor() erases from this map under it. 360   // peer, and deregister_descriptor() erases from this map under it.
HITCBC 361   140558 if (!lock.owns_lock()) 361   136550 if (!lock.owns_lock())
HITCBC 362   125504 lock.lock(); 362   122989 lock.lock();
363   363  
HITCBC 364   391397 for (auto& [fd, desc] : registered_descs_) 364   374645 for (auto& [fd, desc] : registered_descs_)
365   { 365   {
HITCBC 366   250839 if (snapshot_count < FD_SETSIZE) 366   238095 if (snapshot_count < FD_SETSIZE)
367   { 367   {
HITCBC 368   250839 conditionally_enabled_mutex::scoped_lock desc_lock(desc->mutex); 368   238095 conditionally_enabled_mutex::scoped_lock desc_lock(desc->mutex);
HITCBC 369   250839 snapshot[snapshot_count].fd = fd; 369   238095 snapshot[snapshot_count].fd = fd;
HITCBC 370   250839 snapshot[snapshot_count].desc = desc; 370   238095 snapshot[snapshot_count].desc = desc;
HITCBC 371   250839 snapshot[snapshot_count].want = 371   238095 snapshot[snapshot_count].want =
HITCBC 372   250839 ((desc->read_op || desc->wait_read_op) ? reactor_event_read 372   238095 ((desc->read_op || desc->wait_read_op) ? reactor_event_read
HITCBC 373   250839 : 0) | 373   238095 : 0) |
HITCBC 374   250465 ((desc->write_op || desc->connect_op || desc->wait_write_op) 374   237721 ((desc->write_op || desc->connect_op || desc->wait_write_op)
HITCBC 375   501304 ? reactor_event_write 375   475816 ? reactor_event_write
HITCBC 376   250839 : 0) | 376   238095 : 0) |
HITCBC 377   250839 (desc->wait_error_op ? reactor_event_error : 0); 377   238095 (desc->wait_error_op ? reactor_event_error : 0);
HITCBC 378   250839 ++snapshot_count; 378   238095 ++snapshot_count;
HITCBC 379   250839 } 379   238095 }
380   } 380   }
381   381  
HITCBC 382   140558 if (lock.owns_lock()) 382   136550 if (lock.owns_lock())
HITCBC 383   140557 lock.unlock(); 383   136549 lock.unlock();
384   384  
HITCBC 385   140558 task_cleanup on_exit{this, &lock, ctx}; 385   136550 task_cleanup on_exit{this, &lock, ctx};
386   386  
387   fd_set read_fds, write_fds, except_fds; 387   fd_set read_fds, write_fds, except_fds;
HITCBC 388   2389486 FD_ZERO(&read_fds); 388   2321350 FD_ZERO(&read_fds);
HITCBC 389   2389486 FD_ZERO(&write_fds); 389   2321350 FD_ZERO(&write_fds);
HITCBC 390   2389486 FD_ZERO(&except_fds); 390   2321350 FD_ZERO(&except_fds);
391   391  
HITCBC 392   140558 FD_SET(pipe_fds_[0], &read_fds); 392   136550 FD_SET(pipe_fds_[0], &read_fds);
HITCBC 393   140558 int nfds = pipe_fds_[0]; 393   136550 int nfds = pipe_fds_[0];
394   394  
HITCBC 395   391397 for (int i = 0; i < snapshot_count; ++i) 395   374645 for (int i = 0; i < snapshot_count; ++i)
396   { 396   {
HITCBC 397   250839 int fd = snapshot[i].fd; 397   238095 int fd = snapshot[i].fd;
HITCBC 398   250839 if (snapshot[i].want & reactor_event_read) 398   238095 if (snapshot[i].want & reactor_event_read)
HITCBC 399   114011 FD_SET(fd, &read_fds); 399   113208 FD_SET(fd, &read_fds);
HITCBC 400   250839 if (snapshot[i].want & reactor_event_write) 400   238095 if (snapshot[i].want & reactor_event_write)
HITCBC 401   3082 FD_SET(fd, &write_fds); 401   3062 FD_SET(fd, &write_fds);
HITCBC 402   250839 if (snapshot[i].want != 0) 402   238095 if (snapshot[i].want != 0)
HITCBC 403   117288 FD_SET(fd, &except_fds); 403   116465 FD_SET(fd, &except_fds);
HITCBC 404   250839 if (fd > nfds) 404   238095 if (fd > nfds)
HITCBC 405   138516 nfds = fd; 405   134330 nfds = fd;
406   } 406   }
407   407  
408   struct timeval tv; 408   struct timeval tv;
HITCBC 409   140558 struct timeval* tv_ptr = nullptr; 409   136550 struct timeval* tv_ptr = nullptr;
HITCBC 410   140558 if (effective_timeout_us >= 0) 410   136550 if (effective_timeout_us >= 0)
411   { 411   {
HITCBC 412   132928 tv.tv_sec = effective_timeout_us / 1000000; 412   130908 tv.tv_sec = effective_timeout_us / 1000000;
HITCBC 413   132928 tv.tv_usec = effective_timeout_us % 1000000; 413   130908 tv.tv_usec = effective_timeout_us % 1000000;
HITCBC 414   132928 tv_ptr = &tv; 414   130908 tv_ptr = &tv;
415   } 415   }
416   416  
HITCBC 417   140558 int ready = ::select(nfds + 1, &read_fds, &write_fds, &except_fds, tv_ptr); 417   136550 int ready = ::select(nfds + 1, &read_fds, &write_fds, &except_fds, tv_ptr);
418   418  
419   // EINTR: signal interrupted select(), just retry. 419   // EINTR: signal interrupted select(), just retry.
420   // EBADF: an fd was closed between snapshot and select(); retry 420   // EBADF: an fd was closed between snapshot and select(); retry
421   // with a fresh snapshot from registered_descs_. 421   // with a fresh snapshot from registered_descs_.
422   // Both fall through with no ready descriptors rather than 422   // Both fall through with no ready descriptors rather than
423   // returning: the caller handed this function an owned lock that 423   // returning: the caller handed this function an owned lock that
424   // only the epilogue below re-acquires. 424   // only the epilogue below re-acquires.
HITCBC 425   140558 if (ready < 0) 425   136550 if (ready < 0)
426   { 426   {
HITCBC 427   3 if (errno != EINTR && errno != EBADF) 427   3 if (errno != EINTR && errno != EBADF)
HITCBC 428   1 detail::throw_system_error(make_err(errno), "select"); 428   1 detail::throw_system_error(make_err(errno), "select");
HITCBC 429   2 ready = 0; 429   2 ready = 0;
430   } 430   }
431   431  
432   // Process timers outside the lock 432   // Process timers outside the lock
HITCBC 433   140557 if (auto* tsvc = timers()) 433   136549 if (auto* tsvc = timers())
HITCBC 434   27714 tsvc->process_expired(); 434   24825 tsvc->process_expired();
435   435  
HITCBC 436   140557 ready_queue local_ops; 436   136549 ready_queue local_ops;
437   437  
HITCBC 438   140557 if (ready > 0) 438   136549 if (ready > 0)
439   { 439   {
HITCBC 440   15305 if (FD_ISSET(pipe_fds_[0], &read_fds)) 440   13763 if (FD_ISSET(pipe_fds_[0], &read_fds))
441   { 441   {
442   char buf[256]; 442   char buf[256];
HITCBC 443   22896 while (::read(pipe_fds_[0], buf, sizeof(buf)) > 0) 443   23823 while (::read(pipe_fds_[0], buf, sizeof(buf)) > 0)
444   { 444   {
445   } 445   }
446   } 446   }
447   447  
HITCBC 448   98263 for (int i = 0; i < snapshot_count; ++i) 448   96130 for (int i = 0; i < snapshot_count; ++i)
449   { 449   {
HITCBC 450   82958 int fd = snapshot[i].fd; 450   82367 int fd = snapshot[i].fd;
HITCBC 451   82958 reactor_descriptor_state* desc = snapshot[i].desc; 451   82367 reactor_descriptor_state* desc = snapshot[i].desc;
452   452  
HITCBC 453   82958 std::uint32_t flags = 0; 453   82367 std::uint32_t flags = 0;
HITCBC 454   82958 if (FD_ISSET(fd, &read_fds)) 454   82367 if (FD_ISSET(fd, &read_fds))
HITCBC 455   6794 flags |= reactor_event_read; 455   4699 flags |= reactor_event_read;
HITCBC 456   82958 if (FD_ISSET(fd, &write_fds)) 456   82367 if (FD_ISSET(fd, &write_fds))
HITCBC 457   2798 flags |= reactor_event_write; 457   2778 flags |= reactor_event_write;
HITCBC 458   82958 if (FD_ISSET(fd, &except_fds)) 458   82367 if (FD_ISSET(fd, &except_fds))
HITCBC 459   12 flags |= reactor_event_error; 459   13 flags |= reactor_event_error;
460   460  
HITCBC 461   82958 if (flags == 0) 461   82367 if (flags == 0)
HITCBC 462   73364 continue; 462   74888 continue;
463   463  
HITCBC 464   9594 desc->add_ready_events(flags); 464   7479 desc->add_ready_events(flags);
465   465  
HITCBC 466   9594 bool expected = false; 466   7479 bool expected = false;
HITCBC 467   9594 if (desc->is_enqueued_.compare_exchange_strong( 467   7479 if (desc->is_enqueued_.compare_exchange_strong(
468   expected, true, std::memory_order_release, 468   expected, true, std::memory_order_release,
469   std::memory_order_relaxed)) 469   std::memory_order_relaxed))
470   { 470   {
HITCBC 471   6459 local_ops.push(desc); 471   6334 local_ops.push(desc);
472   } 472   }
473   } 473   }
474   } 474   }
475   475  
HITCBC 476   140557 lock.lock(); 476   136549 lock.lock();
477   477  
HITCBC 478   140557 completed_ops_.splice(local_ops); 478   136549 completed_ops_.splice(local_ops);
HITCBC 479   140558 } 479   136550 }
480   480  
481   } // namespace boost::corosio::detail 481   } // namespace boost::corosio::detail
482   482  
483   #endif // BOOST_COROSIO_HAS_SELECT 483   #endif // BOOST_COROSIO_HAS_SELECT
484   484  
485   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_SCHEDULER_HPP 485   #endif // BOOST_COROSIO_NATIVE_DETAIL_SELECT_SELECT_SCHEDULER_HPP