include/boost/corosio/native/detail/iocp/win_signals.hpp

89.4% Lines (219/245) 100.0% Functions (25/25) 68.0% Branches (104/153)
include/boost/corosio/native/detail/iocp/win_signals.hpp
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Steve Gerbino
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_IOCP_WIN_SIGNALS_HPP
12 #define BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_SIGNALS_HPP
13
14 #include <boost/corosio/detail/platform.hpp>
15
16 #if BOOST_COROSIO_HAS_IOCP
17
18 #include <boost/corosio/native/detail/iocp/win_signal.hpp>
19
20 #include <boost/corosio/detail/config.hpp>
21 #include <boost/capy/ex/execution_context.hpp>
22 #include <boost/corosio/native/detail/iocp/win_mutex.hpp>
23 #include <boost/corosio/native/detail/iocp/win_scheduler.hpp>
24 #include <boost/corosio/detail/dispatch_coro.hpp>
25 #include <boost/capy/error.hpp>
26
27 #include <csignal>
28 #include <mutex>
29
30 #include <signal.h>
31
32 /*
33 Windows Signal Implementation - Header
34 ======================================
35
36 This header declares the internal types for Windows signal handling.
37
38 Key Differences from POSIX:
39 - Uses C runtime signal() instead of sigaction() (Windows has no sigaction)
40 - Only `none` and `dont_care` flags are supported; other flags return
41 `operation_not_supported` (Windows has no equivalent to SA_* flags)
42 - Windows resets handler to SIG_DFL after each signal, so we must re-register
43 - Only supports: SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGILL, SIGSEGV
44 - max_signal_number is 32 (vs 64 on Linux)
45
46 The data structures mirror the POSIX implementation for consistency:
47 - signal_op, signal_registration, win_signal, win_signals
48
49 Threading note: Windows signal handling is synchronous (runs on faulting
50 thread), so we can safely acquire locks in the signal handler. This differs
51 from POSIX where the handler must be async-signal-safe.
52 */
53
54 /*
55 Windows Signal Handling Implementation
56 ======================================
57
58 This file implements POSIX-style signal handling on Windows, integrated with
59 the IOCP scheduler. Windows lacks native async signal support, so we use the
60 C standard library's signal() function and manually bridge signals into the
61 completion-based I/O model.
62
63 Architecture Overview
64 ---------------------
65
66 Three layers manage signal registrations:
67
68 1. signal_state (global singleton)
69 - Tracks the global service list and per-signal registration counts
70 - Owns the mutex that protects signal handler installation/removal
71 - Multiple execution_contexts share this; each gets a win_signals entry
72
73 2. win_signals (one per execution_context)
74 - Maintains registrations_[] table indexed by signal number
75 - Each slot is a doubly-linked list of all signal_registrations for that signal
76 - Also maintains impl_list_ of all win_signal objects it owns
77
78 3. win_signal (one per signal_set)
79 - Owns a singly-linked list (sorted by signal number) of signal_registrations
80 - Contains the pending_op_ used for wait operations
81
82 The signal_registration struct links these together:
83 - next_in_set / (implicit via sorted order): links registrations within one signal_set
84 - prev_in_table / next_in_table: links registrations for the same signal across sets
85
86 Signal Delivery Flow
87 --------------------
88
89 1. corosio_signal_handler() (C handler, must be async-signal-safe)
90 - Called by the OS when a signal arrives
91 - Delegates to deliver_signal() and re-registers itself (Windows resets to SIG_DFL)
92
93 2. deliver_signal() broadcasts to all win_signals services:
94 - If a signal_set is waiting (impl->waiting_ == true), complete it immediately
95 by posting the signal_op to the scheduler
96 - Otherwise, increment reg->undelivered to queue the signal for later
97
98 3. start_wait() checks for queued signals first:
99 - If undelivered > 0, consume one and post immediate completion
100 - Otherwise, set waiting_ = true and call work_started() to keep context alive
101
102 Locking Protocol
103 ----------------
104
105 Two mutex levels exist (must be acquired in this order to avoid deadlock):
106 1. signal_state::mutex - protects handler registration and service list
107 2. win_signals::mutex_ - protects per-service registration tables and wait state
108
109 deliver_signal() acquires both locks because it iterates the global service list
110 and modifies per-service state.
111
112 Work Tracking
113 -------------
114
115 When waiting for a signal:
116 - start_wait() calls sched_.work_started() to keep io_context::run() alive
117 - signal_op::svc is set to point to the service
118 - signal_op::operator()() calls work_finished() after resuming the coroutine
119
120 If a signal was already queued (undelivered > 0), no work tracking is needed
121 because completion is posted immediately.
122
123 Signal Flags
124 ------------
125
126 Windows only supports `none` and `dont_care` flags. Any other flags
127 (restart, no_child_stop, etc.) return `operation_not_supported`. The
128 C runtime signal() function has no equivalent to sigaction() flags
129 like SA_RESTART or SA_NOCLDSTOP.
130 */
131
132 namespace boost::corosio::detail {
133
134 class win_scheduler;
135
136 /** Windows signal management service.
137
138 This service owns all signal set implementations and coordinates
139 their lifecycle. It provides:
140
141 - Signal implementation allocation and deallocation
142 - Signal registration via the C runtime signal() function
143 - Global signal state management
144 - Graceful shutdown - destroys all implementations when io_context stops
145
146 @par Thread Safety
147 All public member functions are thread-safe.
148
149 @note Only available on Windows platforms.
150 */
151 class BOOST_COROSIO_DECL win_signals final
152 : public capy::execution_context::service
153 , public io_object::io_service
154 {
155 public:
156 using key_type = win_signals;
157
158 io_object::implementation* construct() override;
159 void destroy(io_object::implementation*) override;
160
161 /** Construct the signal service.
162
163 @param ctx Reference to the owning execution_context.
164 */
165 explicit win_signals(capy::execution_context& ctx);
166
167 /** Destroy the signal service. */
168 ~win_signals();
169
170 win_signals(win_signals const&) = delete;
171 win_signals& operator=(win_signals const&) = delete;
172
173 /** Shut down the service. */
174 void shutdown() override;
175
176 /** Destroy a signal implementation. */
177 void destroy_impl(win_signal& impl);
178
179 /** Add a signal to a signal set.
180
181 @param impl The signal implementation to modify.
182 @param signal_number The signal to register.
183 @param flags The flags to apply (ignored on Windows).
184 @return Success, or an error.
185 */
186 std::error_code
187 add_signal(win_signal& impl, int signal_number, signal_set::flags_t flags);
188
189 /** Remove a signal from a signal set.
190
191 @param impl The signal implementation to modify.
192 @param signal_number The signal to unregister.
193 @return Success, or an error.
194 */
195 std::error_code remove_signal(win_signal& impl, int signal_number);
196
197 /** Remove all signals from a signal set.
198
199 @param impl The signal implementation to clear.
200 @return Success, or an error.
201 */
202 std::error_code clear_signals(win_signal& impl);
203
204 /** Cancel pending wait operations.
205
206 @param impl The signal implementation to cancel.
207 */
208 void cancel_wait(win_signal& impl);
209
210 /** Start a wait operation.
211
212 @param impl The signal implementation.
213 @param op The operation to start.
214 */
215 void start_wait(win_signal& impl, signal_op* op);
216
217 /** Deliver a signal to all registered handlers.
218
219 Called from the signal handler.
220
221 @param signal_number The signal that occurred.
222 */
223 static void deliver_signal(int signal_number);
224
225 /** Notify scheduler of pending work. */
226 void work_started() noexcept;
227
228 /** Notify scheduler that work completed. */
229 void work_finished() noexcept;
230
231 /** Post an operation for completion. */
232 void post(signal_op* op);
233
234 private:
235 static void add_service(win_signals* service);
236 static void remove_service(win_signals* service);
237
238 win_scheduler& sched_;
239 win_mutex mutex_;
240 intrusive_list<win_signal> impl_list_;
241
242 // Per-signal registration table for this service
243 signal_registration* registrations_[max_signal_number];
244
245 // Linked list of services for global signal delivery
246 win_signals* next_ = nullptr;
247 win_signals* prev_ = nullptr;
248 };
249
250 //
251 // Global signal state
252 //
253
254 namespace signal_detail {
255
256 struct signal_state
257 {
258 std::mutex mutex;
259 win_signals* service_list = nullptr;
260 std::size_t registration_count[max_signal_number] = {};
261 };
262
263 BOOST_COROSIO_DECL signal_state* get_signal_state();
264
265 // C signal handler. Note: On POSIX this would need to be async-signal-safe,
266 // but Windows signal handling is synchronous (runs on the faulting thread)
267 // so we can safely acquire locks here.
268 extern "C" inline void
269 9 corosio_signal_handler(int signal_number)
270 {
271 9 win_signals::deliver_signal(signal_number);
272
273 // Windows uses "one-shot" semantics: the handler reverts to SIG_DFL
274 // after each delivery. Re-register to maintain our handler.
275 9 ::signal(signal_number, corosio_signal_handler);
276 9 }
277
278 } // namespace signal_detail
279
280 //
281 // signal_op
282 //
283
284 35 inline signal_op::signal_op() noexcept : scheduler_op(&do_complete) {}
285
286 inline void
287 10 signal_op::do_complete(
288 void* owner,
289 scheduler_op* base,
290 std::uint32_t /*bytes*/,
291 std::uint32_t /*error*/)
292 {
293 10 auto* op = static_cast<signal_op*>(base);
294
295 // Destroy path - no-op: signal_op is embedded in win_signal
296
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 10 times.
10 if (!owner)
297 return;
298
299
1/2
✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 7 not taken.
10 if (op->ec_out)
300 10 *op->ec_out = {};
301
1/2
✓ Branch 7 → 8 taken 10 times.
✗ Branch 7 → 9 not taken.
10 if (op->signal_out)
302 10 *op->signal_out = op->signal_number;
303
304 10 auto* service = op->svc;
305 10 op->svc = nullptr;
306
307
2/2
✓ Branch 9 → 10 taken 10 times.
✓ Branch 10 → 11 taken 10 times.
10 dispatch_coro(op->d, op->h).resume();
308
309
2/2
✓ Branch 11 → 12 taken 5 times.
✓ Branch 11 → 13 taken 5 times.
10 if (service)
310 5 service->work_finished();
311 }
312
313 //
314 // win_signal
315 //
316
317 35 inline win_signal::win_signal(win_signals& svc) noexcept : svc_(svc) {}
318
319 inline std::coroutine_handle<>
320 12 win_signal::wait(
321 std::coroutine_handle<> h,
322 capy::executor_ref d,
323 std::stop_token token,
324 std::error_code* ec,
325 int* signal_out)
326 {
327 12 pending_op_.h = h;
328 12 pending_op_.d = d;
329 12 pending_op_.ec_out = ec;
330 12 pending_op_.signal_out = signal_out;
331 12 pending_op_.signal_number = 0;
332
333 // Check for immediate cancellation
334
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 13 taken 12 times.
12 if (token.stop_requested())
335 {
336 if (ec)
337 *ec = make_error_code(capy::error::canceled);
338 if (signal_out)
339 *signal_out = 0;
340 dispatch_coro(d, h).resume();
341 // completion is always posted to scheduler queue, never inline.
342 return std::noop_coroutine();
343 }
344
345 12 svc_.start_wait(*this, &pending_op_);
346 // completion is always posted to scheduler queue, never inline.
347 12 return std::noop_coroutine();
348 }
349
350 inline std::error_code
351 34 win_signal::add(int signal_number, signal_set::flags_t flags)
352 {
353 34 return svc_.add_signal(*this, signal_number, flags);
354 }
355
356 inline std::error_code
357 2 win_signal::remove(int signal_number)
358 {
359 2 return svc_.remove_signal(*this, signal_number);
360 }
361
362 inline std::error_code
363 37 win_signal::clear()
364 {
365 37 return svc_.clear_signals(*this);
366 }
367
368 inline void
369 41 win_signal::cancel()
370 {
371 41 svc_.cancel_wait(*this);
372 41 }
373
374 //
375 // win_signals
376 //
377
378 290 inline win_signals::win_signals(capy::execution_context& ctx)
379
2/2
✓ Branch 4 → 5 taken 290 times.
✓ Branch 5 → 6 taken 290 times.
290 : sched_(ctx.use_service<win_scheduler>())
380 {
381
2/2
✓ Branch 9 → 8 taken 9280 times.
✓ Branch 9 → 10 taken 290 times.
9570 for (int i = 0; i < max_signal_number; ++i)
382 9280 registrations_[i] = nullptr;
383
384
1/1
✓ Branch 10 → 11 taken 290 times.
290 add_service(this);
385 290 }
386
387 580 inline win_signals::~win_signals()
388 {
389 290 remove_service(this);
390 580 }
391
392 inline void
393 290 win_signals::shutdown()
394 {
395 290 std::lock_guard<win_mutex> lock(mutex_);
396
397
1/2
✗ Branch 11 → 4 not taken.
✓ Branch 11 → 12 taken 290 times.
290 for (auto* impl = impl_list_.pop_front(); impl != nullptr;
398 impl = impl_list_.pop_front())
399 {
400 // Clear registrations
401 while (auto* reg = impl->signals_)
402 {
403 impl->signals_ = reg->next_in_set;
404 delete reg;
405 }
406 delete impl;
407 }
408 290 }
409
410 inline io_object::implementation*
411 35 win_signals::construct()
412 {
413 35 auto* impl = new win_signal(*this);
414
415 {
416 35 std::lock_guard<win_mutex> lock(mutex_);
417 35 impl_list_.push_back(impl);
418 35 }
419
420 35 return impl;
421 }
422
423 inline void
424 35 win_signals::destroy(io_object::implementation* p)
425 {
426 35 auto& impl = static_cast<win_signal&>(*p);
427
1/1
✓ Branch 2 → 3 taken 35 times.
35 impl.clear();
428 35 impl.cancel();
429 35 destroy_impl(impl);
430 35 }
431
432 inline void
433 35 win_signals::destroy_impl(win_signal& impl)
434 {
435 {
436 35 std::lock_guard<win_mutex> lock(mutex_);
437 35 impl_list_.remove(&impl);
438 35 }
439
440
1/2
✓ Branch 5 → 6 taken 35 times.
✗ Branch 5 → 7 not taken.
35 delete &impl;
441 35 }
442
443 inline std::error_code
444 34 win_signals::add_signal(
445 win_signal& impl, int signal_number, signal_set::flags_t flags)
446 {
447
3/4
✓ Branch 2 → 3 taken 33 times.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 33 times.
34 if (signal_number < 0 || signal_number >= max_signal_number)
448 1 return make_error_code(std::errc::invalid_argument);
449
450 // Windows only supports none and dont_care flags
451 33 constexpr auto supported = signal_set::none | signal_set::dont_care;
452
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 32 times.
33 if ((flags & ~supported) != signal_set::none)
453 1 return make_error_code(std::errc::operation_not_supported);
454
455
1/1
✓ Branch 9 → 10 taken 32 times.
32 signal_detail::signal_state* state = signal_detail::get_signal_state();
456
1/1
✓ Branch 10 → 11 taken 32 times.
32 std::lock_guard<std::mutex> state_lock(state->mutex);
457 32 std::lock_guard<win_mutex> lock(mutex_);
458
459 // Check if already registered in this set
460 32 signal_registration** insertion_point = &impl.signals_;
461 32 signal_registration* reg = impl.signals_;
462
4/4
✓ Branch 14 → 15 taken 7 times.
✓ Branch 14 → 16 taken 31 times.
✓ Branch 15 → 13 taken 6 times.
✓ Branch 15 → 16 taken 1 time.
38 while (reg && reg->signal_number < signal_number)
463 {
464 6 insertion_point = &reg->next_in_set;
465 6 reg = reg->next_in_set;
466 }
467
468
3/4
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 19 taken 31 times.
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 19 not taken.
32 if (reg && reg->signal_number == signal_number)
469 1 return {}; // Already registered
470
471 // Create new registration
472
1/1
✓ Branch 19 → 20 taken 31 times.
31 auto* new_reg = new signal_registration;
473 31 new_reg->signal_number = signal_number;
474 31 new_reg->owner = &impl;
475 31 new_reg->undelivered = 0;
476
477 // Register signal handler if first registration
478
2/2
✓ Branch 21 → 22 taken 30 times.
✓ Branch 21 → 27 taken 1 time.
31 if (state->registration_count[signal_number] == 0)
479 {
480
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 27 taken 30 times.
30 if (::signal(signal_number, signal_detail::corosio_signal_handler) ==
481 SIG_ERR)
482 {
483 delete new_reg;
484 return make_error_code(std::errc::invalid_argument);
485 }
486 }
487
488 // Insert into set's registration list (sorted by signal number)
489 31 new_reg->next_in_set = reg;
490 31 *insertion_point = new_reg;
491
492 // Insert into service's registration table
493 31 new_reg->next_in_table = registrations_[signal_number];
494 31 new_reg->prev_in_table = nullptr;
495
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 30 times.
31 if (registrations_[signal_number])
496 1 registrations_[signal_number]->prev_in_table = new_reg;
497 31 registrations_[signal_number] = new_reg;
498
499 31 ++state->registration_count[signal_number];
500
501 31 return {};
502 32 }
503
504 inline std::error_code
505 2 win_signals::remove_signal(win_signal& impl, int signal_number)
506 {
507
2/4
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2 if (signal_number < 0 || signal_number >= max_signal_number)
508 return make_error_code(std::errc::invalid_argument);
509
510
1/1
✓ Branch 5 → 6 taken 2 times.
2 signal_detail::signal_state* state = signal_detail::get_signal_state();
511
1/1
✓ Branch 6 → 7 taken 2 times.
2 std::lock_guard<std::mutex> state_lock(state->mutex);
512 2 std::lock_guard<win_mutex> lock(mutex_);
513
514 // Find the registration in the set
515 2 signal_registration** deletion_point = &impl.signals_;
516 2 signal_registration* reg = impl.signals_;
517
3/4
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 1 time.
✗ Branch 11 → 9 not taken.
✓ Branch 11 → 12 taken 1 time.
2 while (reg && reg->signal_number < signal_number)
518 {
519 deletion_point = &reg->next_in_set;
520 reg = reg->next_in_set;
521 }
522
523
3/4
✓ Branch 12 → 13 taken 1 time.
✓ Branch 12 → 14 taken 1 time.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 1 time.
2 if (!reg || reg->signal_number != signal_number)
524 1 return {}; // Not found, no-op
525
526 // Restore default handler if last registration
527
1/2
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 19 not taken.
1 if (state->registration_count[signal_number] == 1)
528 {
529
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 1 time.
1 if (::signal(signal_number, SIG_DFL) == SIG_ERR)
530 return make_error_code(std::errc::invalid_argument);
531 }
532
533 // Remove from set's list
534 1 *deletion_point = reg->next_in_set;
535
536 // Remove from service's registration table
537
1/2
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
1 if (registrations_[signal_number] == reg)
538 1 registrations_[signal_number] = reg->next_in_table;
539
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 1 time.
1 if (reg->prev_in_table)
540 reg->prev_in_table->next_in_table = reg->next_in_table;
541
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 1 time.
1 if (reg->next_in_table)
542 reg->next_in_table->prev_in_table = reg->prev_in_table;
543
544 1 --state->registration_count[signal_number];
545
546
1/2
✓ Branch 25 → 26 taken 1 time.
✗ Branch 25 → 27 not taken.
1 delete reg;
547 1 return {};
548 2 }
549
550 inline std::error_code
551 37 win_signals::clear_signals(win_signal& impl)
552 {
553
1/1
✓ Branch 2 → 3 taken 37 times.
37 signal_detail::signal_state* state = signal_detail::get_signal_state();
554
1/1
✓ Branch 3 → 4 taken 37 times.
37 std::lock_guard<std::mutex> state_lock(state->mutex);
555 37 std::lock_guard<win_mutex> lock(mutex_);
556
557 37 std::error_code first_error;
558
559
2/2
✓ Branch 6 → 7 taken 30 times.
✓ Branch 6 → 25 taken 37 times.
67 while (signal_registration* reg = impl.signals_)
560 {
561 30 int signal_number = reg->signal_number;
562
563 // Restore default handler if last registration
564
2/2
✓ Branch 7 → 8 taken 29 times.
✓ Branch 7 → 16 taken 1 time.
30 if (state->registration_count[signal_number] == 1)
565 {
566
2/6
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 13 taken 29 times.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 29 times.
29 if (::signal(signal_number, SIG_DFL) == SIG_ERR && !first_error)
567 first_error = make_error_code(std::errc::invalid_argument);
568 }
569
570 // Remove from set's list
571 30 impl.signals_ = reg->next_in_set;
572
573 // Remove from service's registration table
574
1/2
✓ Branch 16 → 17 taken 30 times.
✗ Branch 16 → 18 not taken.
30 if (registrations_[signal_number] == reg)
575 30 registrations_[signal_number] = reg->next_in_table;
576
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 30 times.
30 if (reg->prev_in_table)
577 reg->prev_in_table->next_in_table = reg->next_in_table;
578
2/2
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 22 taken 29 times.
30 if (reg->next_in_table)
579 1 reg->next_in_table->prev_in_table = reg->prev_in_table;
580
581 30 --state->registration_count[signal_number];
582
583
1/2
✓ Branch 22 → 23 taken 30 times.
✗ Branch 22 → 24 not taken.
30 delete reg;
584 30 }
585
586
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 37 times.
37 if (first_error)
587 return first_error;
588 37 return {};
589 37 }
590
591 inline void
592 41 win_signals::cancel_wait(win_signal& impl)
593 {
594 41 bool was_waiting = false;
595 41 signal_op* op = nullptr;
596
597 {
598 41 std::lock_guard<win_mutex> lock(mutex_);
599
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 39 times.
41 if (impl.waiting_)
600 {
601 2 was_waiting = true;
602 2 impl.waiting_ = false;
603 2 op = &impl.pending_op_;
604 }
605 41 }
606
607
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 14 taken 39 times.
41 if (was_waiting)
608 {
609
1/2
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 9 not taken.
2 if (op->ec_out)
610 2 *op->ec_out = make_error_code(capy::error::canceled);
611
1/2
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 11 not taken.
2 if (op->signal_out)
612 2 *op->signal_out = 0;
613
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
2 dispatch_coro(op->d, op->h).resume();
614 2 sched_.work_finished();
615 }
616 41 }
617
618 inline void
619 12 win_signals::start_wait(win_signal& impl, signal_op* op)
620 {
621 {
622 12 std::lock_guard<win_mutex> lock(mutex_);
623
624 // Check for queued signals first
625 12 signal_registration* reg = impl.signals_;
626
2/2
✓ Branch 8 → 4 taken 13 times.
✓ Branch 8 → 9 taken 7 times.
20 while (reg)
627 {
628
2/2
✓ Branch 4 → 5 taken 5 times.
✓ Branch 4 → 7 taken 8 times.
13 if (reg->undelivered > 0)
629 {
630 5 --reg->undelivered;
631 5 op->signal_number = reg->signal_number;
632 5 op->svc = nullptr; // No extra work_finished needed
633 // Post for immediate completion - post() handles work tracking
634
1/1
✓ Branch 5 → 6 taken 5 times.
5 post(op);
635 5 return;
636 }
637 8 reg = reg->next_in_set;
638 }
639
640 // No queued signals, wait for delivery
641 // We call work_started() to keep io_context alive while waiting.
642 // Set svc so signal_op::operator() will call work_finished().
643 7 impl.waiting_ = true;
644 7 op->svc = this;
645 7 sched_.work_started();
646 12 }
647 }
648
649 inline void
650 9 win_signals::deliver_signal(int signal_number)
651 {
652
2/4
✓ Branch 2 → 3 taken 9 times.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 9 times.
9 if (signal_number < 0 || signal_number >= max_signal_number)
653 return;
654
655
1/1
✓ Branch 5 → 6 taken 9 times.
9 signal_detail::signal_state* state = signal_detail::get_signal_state();
656
1/1
✓ Branch 6 → 7 taken 9 times.
9 std::lock_guard<std::mutex> lock(state->mutex);
657
658 // Deliver to all services. We hold state->mutex while iterating, and
659 // acquire each service's mutex_ inside (matching the lock order used by
660 // add_signal/remove_signal) to safely read and modify registration state.
661 9 win_signals* service = state->service_list;
662
2/2
✓ Branch 17 → 8 taken 9 times.
✓ Branch 17 → 18 taken 9 times.
18 while (service)
663 {
664 9 std::lock_guard<win_mutex> svc_lock(service->mutex_);
665
666 // Find registrations for this signal
667 9 signal_registration* reg = service->registrations_[signal_number];
668
2/2
✓ Branch 14 → 10 taken 10 times.
✓ Branch 14 → 15 taken 9 times.
19 while (reg)
669 {
670 10 win_signal* impl = reg->owner;
671
672
2/2
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 5 times.
10 if (impl->waiting_)
673 {
674 // Complete the pending wait
675 5 impl->waiting_ = false;
676 5 impl->pending_op_.signal_number = signal_number;
677
1/1
✓ Branch 11 → 13 taken 5 times.
5 service->post(&impl->pending_op_);
678 }
679 else
680 {
681 // No waiter yet; increment undelivered so start_wait() will
682 // find this signal immediately without blocking
683 5 ++reg->undelivered;
684 }
685
686 10 reg = reg->next_in_table;
687 }
688
689 9 service = service->next_;
690 9 }
691 9 }
692
693 inline void
694 win_signals::work_started() noexcept
695 {
696 sched_.work_started();
697 }
698
699 inline void
700 5 win_signals::work_finished() noexcept
701 {
702 5 sched_.work_finished();
703 5 }
704
705 inline void
706 10 win_signals::post(signal_op* op)
707 {
708 10 sched_.post(op);
709 10 }
710
711 inline void
712 290 win_signals::add_service(win_signals* service)
713 {
714
1/1
✓ Branch 2 → 3 taken 290 times.
290 signal_detail::signal_state* state = signal_detail::get_signal_state();
715
1/1
✓ Branch 3 → 4 taken 290 times.
290 std::lock_guard<std::mutex> lock(state->mutex);
716
717 290 service->next_ = state->service_list;
718 290 service->prev_ = nullptr;
719
2/2
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 287 times.
290 if (state->service_list)
720 3 state->service_list->prev_ = service;
721 290 state->service_list = service;
722 290 }
723
724 inline void
725 290 win_signals::remove_service(win_signals* service)
726 {
727
1/1
✓ Branch 2 → 3 taken 290 times.
290 signal_detail::signal_state* state = signal_detail::get_signal_state();
728
1/1
✓ Branch 3 → 4 taken 290 times.
290 std::lock_guard<std::mutex> lock(state->mutex);
729
730
4/6
✓ Branch 4 → 5 taken 287 times.
✓ Branch 4 → 7 taken 3 times.
✓ Branch 5 → 6 taken 287 times.
✗ Branch 5 → 7 not taken.
✓ Branch 6 → 7 taken 287 times.
✗ Branch 6 → 14 not taken.
290 if (service->next_ || service->prev_ || state->service_list == service)
731 {
732
1/2
✓ Branch 7 → 8 taken 290 times.
✗ Branch 7 → 9 not taken.
290 if (state->service_list == service)
733 290 state->service_list = service->next_;
734
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 290 times.
290 if (service->prev_)
735 service->prev_->next_ = service->next_;
736
2/2
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 287 times.
290 if (service->next_)
737 3 service->next_->prev_ = service->prev_;
738 290 service->next_ = nullptr;
739 290 service->prev_ = nullptr;
740 }
741 290 }
742
743 } // namespace boost::corosio::detail
744
745 #endif // BOOST_COROSIO_HAS_IOCP
746
747 #endif // BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_SIGNALS_HPP
748