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

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