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

89.6% Lines (233/260) 100.0% List of functions (25/25) 68.7% Branches (112/163)
win_signals.hpp
f(x) Functions (25)
Function Calls Lines Branches Blocks
corosio_signal_handler :269 9x 100.0% 100.0% boost::corosio::detail::signal_op::signal_op() :284 36x 100.0% 100.0% boost::corosio::detail::signal_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :287 10x 92.9% 70.0% 92.3% boost::corosio::detail::win_signal::win_signal(boost::corosio::detail::win_signals&) :318 36x 100.0% 100.0% boost::corosio::detail::win_signal::wait(std::__n4861::coroutine_handle<void>, boost::capy::executor_ref, std::stop_token, std::error_code*, int*) :321 13x 56.2% 12.5% 47.1% boost::corosio::detail::win_signal::add(int, boost::corosio::signal_set::flags_t) :353 35x 100.0% 100.0% boost::corosio::detail::win_signal::remove(int) :359 2x 100.0% 100.0% boost::corosio::detail::win_signal::clear() :365 38x 100.0% 100.0% boost::corosio::detail::win_signal::cancel() :371 43x 100.0% 100.0% boost::corosio::detail::win_signals::win_signals(boost::capy::execution_context&) :380 442x 100.0% 100.0% 68.8% boost::corosio::detail::win_signals::~win_signals() :389 884x 100.0% 100.0% boost::corosio::detail::win_signals::shutdown() :395 442x 40.0% 12.5% 41.7% boost::corosio::detail::win_signals::construct() :413 36x 100.0% 100.0% boost::corosio::detail::win_signals::destroy(boost::corosio::io_object::implementation*) :426 36x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::destroy_impl(boost::corosio::detail::win_signal&) :435 36x 100.0% 50.0% 100.0% boost::corosio::detail::win_signals::add_signal(boost::corosio::detail::win_signal&, int, boost::corosio::signal_set::flags_t) :446 35x 94.1% 80.0% 81.1% boost::corosio::detail::win_signals::remove_signal(boost::corosio::detail::win_signal&, int) :507 2x 77.8% 61.5% 83.9% boost::corosio::detail::win_signals::clear_signals(boost::corosio::detail::win_signal&) :553 38x 87.5% 63.6% 81.2% boost::corosio::detail::win_signals::cancel_wait(boost::corosio::detail::win_signal&) :594 43x 100.0% 80.0% 100.0% boost::corosio::detail::win_signals::start_wait(boost::corosio::detail::win_signal&, boost::corosio::detail::signal_op*) :623 13x 100.0% 86.7% 89.3% boost::corosio::detail::win_signals::deliver_signal(int) :675 9x 95.0% 84.6% 76.0% boost::corosio::detail::win_signals::work_finished() :725 5x 100.0% 100.0% boost::corosio::detail::win_signals::post(boost::corosio::detail::signal_op*) :731 10x 100.0% 100.0% boost::corosio::detail::win_signals::add_service(boost::corosio::detail::win_signals*) :737 442x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::remove_service(boost::corosio::detail::win_signals*) :750 442x 92.3% 71.4% 93.3%
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 9x corosio_signal_handler(int signal_number)
270 {
271 9x 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 9x ::signal(signal_number, corosio_signal_handler);
276 9x }
277
278 } // namespace signal_detail
279
280 //
281 // signal_op
282 //
283
284 36x inline signal_op::signal_op() noexcept : scheduler_op(&do_complete) {}
285
286 inline void
287 10x signal_op::do_complete(
288 void* owner,
289 scheduler_op* base,
290 std::uint32_t /*bytes*/,
291 std::uint32_t /*error*/)
292 {
293 10x 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.
10x if (!owner)
297 return;
298
299
1/2
✓ Branch 4 → 5 taken 10 times.
✗ Branch 4 → 7 not taken.
10x if (op->ec_out)
300 10x *op->ec_out = {};
301
1/2
✓ Branch 7 → 8 taken 10 times.
✗ Branch 7 → 9 not taken.
10x if (op->signal_out)
302 10x *op->signal_out = op->signal_number;
303
304 10x auto* service = op->svc;
305 10x op->svc = nullptr;
306
307 10x op->cont_op.cont.h = op->h;
308
2/2
✓ Branch 9 → 10 taken 10 times.
✓ Branch 10 → 11 taken 10 times.
10x dispatch_coro(op->d, op->cont_op.cont).resume();
309
310
2/2
✓ Branch 11 → 12 taken 5 times.
✓ Branch 11 → 13 taken 5 times.
10x if (service)
311 5x service->work_finished();
312 }
313
314 //
315 // win_signal
316 //
317
318 36x inline win_signal::win_signal(win_signals& svc) noexcept : svc_(svc) {}
319
320 inline std::coroutine_handle<>
321 13x win_signal::wait(
322 std::coroutine_handle<> h,
323 capy::executor_ref d,
324 std::stop_token token,
325 std::error_code* ec,
326 int* signal_out)
327 {
328 13x pending_op_.h = h;
329 13x pending_op_.d = d;
330 13x pending_op_.ec_out = ec;
331 13x pending_op_.signal_out = signal_out;
332 13x pending_op_.signal_number = 0;
333
334 // Check for immediate cancellation
335
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 13 taken 13 times.
13x if (token.stop_requested())
336 {
337 if (ec)
338 *ec = make_error_code(capy::error::canceled);
339 if (signal_out)
340 *signal_out = 0;
341 pending_op_.cont_op.cont.h = h;
342 dispatch_coro(d, pending_op_.cont_op.cont).resume();
343 // completion is always posted to scheduler queue, never inline.
344 return std::noop_coroutine();
345 }
346
347 13x svc_.start_wait(*this, &pending_op_);
348 // completion is always posted to scheduler queue, never inline.
349 13x return std::noop_coroutine();
350 }
351
352 inline std::error_code
353 35x win_signal::add(int signal_number, signal_set::flags_t flags)
354 {
355 35x return svc_.add_signal(*this, signal_number, flags);
356 }
357
358 inline std::error_code
359 2x win_signal::remove(int signal_number)
360 {
361 2x return svc_.remove_signal(*this, signal_number);
362 }
363
364 inline std::error_code
365 38x win_signal::clear()
366 {
367 38x return svc_.clear_signals(*this);
368 }
369
370 inline void
371 43x win_signal::cancel()
372 {
373 43x svc_.cancel_wait(*this);
374 43x }
375
376 //
377 // win_signals
378 //
379
380 442x inline win_signals::win_signals(capy::execution_context& ctx)
381
2/2
✓ Branch 4 → 5 taken 442 times.
✓ Branch 5 → 6 taken 442 times.
442x : sched_(ctx.use_service<win_scheduler>())
382 {
383
2/2
✓ Branch 9 → 8 taken 14144 times.
✓ Branch 9 → 10 taken 442 times.
14586x for (int i = 0; i < max_signal_number; ++i)
384 14144x registrations_[i] = nullptr;
385
386
1/1
✓ Branch 10 → 11 taken 442 times.
442x add_service(this);
387 442x }
388
389 884x inline win_signals::~win_signals()
390 {
391 442x remove_service(this);
392 884x }
393
394 inline void
395 442x win_signals::shutdown()
396 {
397 442x std::lock_guard<win_mutex> lock(mutex_);
398
399
1/2
✗ Branch 11 → 4 not taken.
✓ Branch 11 → 12 taken 442 times.
442x for (auto* impl = impl_list_.pop_front(); impl != nullptr;
400 impl = impl_list_.pop_front())
401 {
402 // Clear registrations
403 while (auto* reg = impl->signals_)
404 {
405 impl->signals_ = reg->next_in_set;
406 delete reg;
407 }
408 delete impl;
409 }
410 442x }
411
412 inline io_object::implementation*
413 36x win_signals::construct()
414 {
415 36x auto* impl = new win_signal(*this);
416
417 {
418 36x std::lock_guard<win_mutex> lock(mutex_);
419 36x impl_list_.push_back(impl);
420 36x }
421
422 36x return impl;
423 }
424
425 inline void
426 36x win_signals::destroy(io_object::implementation* p)
427 {
428 36x auto& impl = static_cast<win_signal&>(*p);
429
1/1
✓ Branch 2 → 3 taken 36 times.
36x impl.clear();
430 36x impl.cancel();
431 36x destroy_impl(impl);
432 36x }
433
434 inline void
435 36x win_signals::destroy_impl(win_signal& impl)
436 {
437 {
438 36x std::lock_guard<win_mutex> lock(mutex_);
439 36x impl_list_.remove(&impl);
440 36x }
441
442
1/2
✓ Branch 5 → 6 taken 36 times.
✗ Branch 5 → 7 not taken.
36x delete &impl;
443 36x }
444
445 inline std::error_code
446 35x win_signals::add_signal(
447 win_signal& impl, int signal_number, signal_set::flags_t flags)
448 {
449
3/4
✓ Branch 2 → 3 taken 34 times.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 34 times.
35x if (signal_number < 0 || signal_number >= max_signal_number)
450 1x return make_error_code(std::errc::invalid_argument);
451
452 // Windows only supports none and dont_care flags
453 34x constexpr auto supported = signal_set::none | signal_set::dont_care;
454
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 33 times.
34x if ((flags & ~supported) != signal_set::none)
455 1x return make_error_code(std::errc::operation_not_supported);
456
457
1/1
✓ Branch 9 → 10 taken 33 times.
33x signal_detail::signal_state* state = signal_detail::get_signal_state();
458
1/1
✓ Branch 10 → 11 taken 33 times.
33x std::lock_guard<std::mutex> state_lock(state->mutex);
459 33x std::lock_guard<win_mutex> lock(mutex_);
460
461 // Check if already registered in this set
462 33x signal_registration** insertion_point = &impl.signals_;
463 33x signal_registration* reg = impl.signals_;
464
4/4
✓ Branch 14 → 15 taken 7 times.
✓ Branch 14 → 16 taken 32 times.
✓ Branch 15 → 13 taken 6 times.
✓ Branch 15 → 16 taken 1 time.
39x while (reg && reg->signal_number < signal_number)
465 {
466 6x insertion_point = &reg->next_in_set;
467 6x reg = reg->next_in_set;
468 }
469
470
3/4
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 19 taken 32 times.
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 19 not taken.
33x if (reg && reg->signal_number == signal_number)
471 1x return {}; // Already registered
472
473 // Create new registration
474
1/1
✓ Branch 19 → 20 taken 32 times.
32x auto* new_reg = new signal_registration;
475 32x new_reg->signal_number = signal_number;
476 32x new_reg->owner = &impl;
477 32x new_reg->undelivered = 0;
478
479 // Register signal handler if first registration
480
2/2
✓ Branch 21 → 22 taken 31 times.
✓ Branch 21 → 27 taken 1 time.
32x if (state->registration_count[signal_number] == 0)
481 {
482
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 27 taken 31 times.
31x if (::signal(signal_number, signal_detail::corosio_signal_handler) ==
483 SIG_ERR)
484 {
485 delete new_reg;
486 return make_error_code(std::errc::invalid_argument);
487 }
488 }
489
490 // Insert into set's registration list (sorted by signal number)
491 32x new_reg->next_in_set = reg;
492 32x *insertion_point = new_reg;
493
494 // Insert into service's registration table
495 32x new_reg->next_in_table = registrations_[signal_number];
496 32x new_reg->prev_in_table = nullptr;
497
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 31 times.
32x if (registrations_[signal_number])
498 1x registrations_[signal_number]->prev_in_table = new_reg;
499 32x registrations_[signal_number] = new_reg;
500
501 32x ++state->registration_count[signal_number];
502
503 32x return {};
504 33x }
505
506 inline std::error_code
507 2x win_signals::remove_signal(win_signal& impl, int signal_number)
508 {
509
2/4
✓ Branch 2 → 3 taken 2 times.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
2x if (signal_number < 0 || signal_number >= max_signal_number)
510 return make_error_code(std::errc::invalid_argument);
511
512
1/1
✓ Branch 5 → 6 taken 2 times.
2x signal_detail::signal_state* state = signal_detail::get_signal_state();
513
1/1
✓ Branch 6 → 7 taken 2 times.
2x std::lock_guard<std::mutex> state_lock(state->mutex);
514 2x std::lock_guard<win_mutex> lock(mutex_);
515
516 // Find the registration in the set
517 2x signal_registration** deletion_point = &impl.signals_;
518 2x signal_registration* reg = impl.signals_;
519
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.
2x while (reg && reg->signal_number < signal_number)
520 {
521 deletion_point = &reg->next_in_set;
522 reg = reg->next_in_set;
523 }
524
525
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.
2x if (!reg || reg->signal_number != signal_number)
526 1x return {}; // Not found, no-op
527
528 // Restore default handler if last registration
529
1/2
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 19 not taken.
1x if (state->registration_count[signal_number] == 1)
530 {
531
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 1 time.
1x if (::signal(signal_number, SIG_DFL) == SIG_ERR)
532 return make_error_code(std::errc::invalid_argument);
533 }
534
535 // Remove from set's list
536 1x *deletion_point = reg->next_in_set;
537
538 // Remove from service's registration table
539
1/2
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 21 not taken.
1x if (registrations_[signal_number] == reg)
540 1x registrations_[signal_number] = reg->next_in_table;
541
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 1 time.
1x if (reg->prev_in_table)
542 reg->prev_in_table->next_in_table = reg->next_in_table;
543
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 25 taken 1 time.
1x if (reg->next_in_table)
544 reg->next_in_table->prev_in_table = reg->prev_in_table;
545
546 1x --state->registration_count[signal_number];
547
548
1/2
✓ Branch 25 → 26 taken 1 time.
✗ Branch 25 → 27 not taken.
1x delete reg;
549 1x return {};
550 2x }
551
552 inline std::error_code
553 38x win_signals::clear_signals(win_signal& impl)
554 {
555
1/1
✓ Branch 2 → 3 taken 38 times.
38x signal_detail::signal_state* state = signal_detail::get_signal_state();
556
1/1
✓ Branch 3 → 4 taken 38 times.
38x std::lock_guard<std::mutex> state_lock(state->mutex);
557 38x std::lock_guard<win_mutex> lock(mutex_);
558
559 38x std::error_code first_error;
560
561
2/2
✓ Branch 6 → 7 taken 31 times.
✓ Branch 6 → 25 taken 38 times.
69x while (signal_registration* reg = impl.signals_)
562 {
563 31x int signal_number = reg->signal_number;
564
565 // Restore default handler if last registration
566
2/2
✓ Branch 7 → 8 taken 30 times.
✓ Branch 7 → 16 taken 1 time.
31x if (state->registration_count[signal_number] == 1)
567 {
568
2/6
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 13 taken 30 times.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 30 times.
30x if (::signal(signal_number, SIG_DFL) == SIG_ERR && !first_error)
569 first_error = make_error_code(std::errc::invalid_argument);
570 }
571
572 // Remove from set's list
573 31x impl.signals_ = reg->next_in_set;
574
575 // Remove from service's registration table
576
1/2
✓ Branch 16 → 17 taken 31 times.
✗ Branch 16 → 18 not taken.
31x if (registrations_[signal_number] == reg)
577 31x registrations_[signal_number] = reg->next_in_table;
578
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 31 times.
31x if (reg->prev_in_table)
579 reg->prev_in_table->next_in_table = reg->next_in_table;
580
2/2
✓ Branch 20 → 21 taken 1 time.
✓ Branch 20 → 22 taken 30 times.
31x if (reg->next_in_table)
581 1x reg->next_in_table->prev_in_table = reg->prev_in_table;
582
583 31x --state->registration_count[signal_number];
584
585
1/2
✓ Branch 22 → 23 taken 31 times.
✗ Branch 22 → 24 not taken.
31x delete reg;
586 31x }
587
588
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 38 times.
38x if (first_error)
589 return first_error;
590 38x return {};
591 38x }
592
593 inline void
594 43x win_signals::cancel_wait(win_signal& impl)
595 {
596 43x bool was_waiting = false;
597 43x signal_op* op = nullptr;
598
599 {
600 43x std::lock_guard<win_mutex> lock(mutex_);
601 43x impl.cancelled_ = true;
602
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 41 times.
43x if (impl.waiting_)
603 {
604 2x was_waiting = true;
605 2x impl.waiting_ = false;
606 2x op = &impl.pending_op_;
607 }
608 43x }
609
610
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 14 taken 41 times.
43x if (was_waiting)
611 {
612
1/2
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 9 not taken.
2x if (op->ec_out)
613 2x *op->ec_out = make_error_code(capy::error::canceled);
614
1/2
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 11 not taken.
2x if (op->signal_out)
615 2x *op->signal_out = 0;
616 2x op->cont_op.cont.h = op->h;
617
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
2x dispatch_coro(op->d, op->cont_op.cont).resume();
618 2x sched_.work_finished();
619 }
620 43x }
621
622 inline void
623 13x win_signals::start_wait(win_signal& impl, signal_op* op)
624 {
625 13x bool was_cancelled = false;
626
627 {
628 13x std::lock_guard<win_mutex> lock(mutex_);
629
630 // Check if cancel() was called before this wait started
631
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 9 taken 12 times.
13x if (impl.cancelled_)
632 {
633 1x was_cancelled = true;
634 1x impl.cancelled_ = false;
635
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
1x if (op->ec_out)
636 1x *op->ec_out = make_error_code(capy::error::canceled);
637
1/2
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
1x if (op->signal_out)
638 1x *op->signal_out = 0;
639 1x op->cont_op.cont.h = op->h;
640 }
641 else
642 {
643 // Check for queued signals first
644 12x signal_registration* reg = impl.signals_;
645
2/2
✓ Branch 14 → 10 taken 13 times.
✓ Branch 14 → 15 taken 7 times.
20x while (reg)
646 {
647
2/2
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 13 taken 8 times.
13x if (reg->undelivered > 0)
648 {
649 5x --reg->undelivered;
650 5x op->signal_number = reg->signal_number;
651 5x op->svc = nullptr; // No extra work_finished needed
652 // Post for immediate completion - post() handles work tracking
653
1/1
✓ Branch 11 → 12 taken 5 times.
5x post(op);
654 5x return;
655 }
656 8x reg = reg->next_in_set;
657 }
658
659 // No queued signals, wait for delivery
660 // We call work_started() to keep io_context alive while waiting.
661 // Set svc so signal_op::operator() will call work_finished().
662 7x impl.waiting_ = true;
663 7x op->svc = this;
664 7x sched_.work_started();
665 }
666 13x }
667
668 // Dispatch outside the lock to avoid deadlock if the resumed
669 // coroutine re-enters cancel()/add()/remove()
670
2/2
✓ Branch 20 → 22 taken 1 time.
✓ Branch 20 → 25 taken 7 times.
8x if (was_cancelled)
671
2/2
✓ Branch 22 → 23 taken 1 time.
✓ Branch 23 → 24 taken 1 time.
1x dispatch_coro(op->d, op->cont_op.cont).resume();
672 }
673
674 inline void
675 9x win_signals::deliver_signal(int signal_number)
676 {
677
2/4
✓ Branch 2 → 3 taken 9 times.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 9 times.
9x if (signal_number < 0 || signal_number >= max_signal_number)
678 return;
679
680
1/1
✓ Branch 5 → 6 taken 9 times.
9x signal_detail::signal_state* state = signal_detail::get_signal_state();
681
1/1
✓ Branch 6 → 7 taken 9 times.
9x std::lock_guard<std::mutex> lock(state->mutex);
682
683 // Deliver to all services. We hold state->mutex while iterating, and
684 // acquire each service's mutex_ inside (matching the lock order used by
685 // add_signal/remove_signal) to safely read and modify registration state.
686 9x win_signals* service = state->service_list;
687
2/2
✓ Branch 17 → 8 taken 9 times.
✓ Branch 17 → 18 taken 9 times.
18x while (service)
688 {
689 9x std::lock_guard<win_mutex> svc_lock(service->mutex_);
690
691 // Find registrations for this signal
692 9x signal_registration* reg = service->registrations_[signal_number];
693
2/2
✓ Branch 14 → 10 taken 10 times.
✓ Branch 14 → 15 taken 9 times.
19x while (reg)
694 {
695 10x win_signal* impl = reg->owner;
696
697
2/2
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 5 times.
10x if (impl->waiting_)
698 {
699 // Complete the pending wait
700 5x impl->waiting_ = false;
701 5x impl->pending_op_.signal_number = signal_number;
702
1/1
✓ Branch 11 → 13 taken 5 times.
5x service->post(&impl->pending_op_);
703 }
704 else
705 {
706 // No waiter yet; increment undelivered so start_wait() will
707 // find this signal immediately without blocking
708 5x ++reg->undelivered;
709 }
710
711 10x reg = reg->next_in_table;
712 }
713
714 9x service = service->next_;
715 9x }
716 9x }
717
718 inline void
719 win_signals::work_started() noexcept
720 {
721 sched_.work_started();
722 }
723
724 inline void
725 5x win_signals::work_finished() noexcept
726 {
727 5x sched_.work_finished();
728 5x }
729
730 inline void
731 10x win_signals::post(signal_op* op)
732 {
733 10x sched_.post(op);
734 10x }
735
736 inline void
737 442x win_signals::add_service(win_signals* service)
738 {
739
1/1
✓ Branch 2 → 3 taken 442 times.
442x signal_detail::signal_state* state = signal_detail::get_signal_state();
740
1/1
✓ Branch 3 → 4 taken 442 times.
442x std::lock_guard<std::mutex> lock(state->mutex);
741
742 442x service->next_ = state->service_list;
743 442x service->prev_ = nullptr;
744
2/2
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 439 times.
442x if (state->service_list)
745 3x state->service_list->prev_ = service;
746 442x state->service_list = service;
747 442x }
748
749 inline void
750 442x win_signals::remove_service(win_signals* service)
751 {
752
1/1
✓ Branch 2 → 3 taken 442 times.
442x signal_detail::signal_state* state = signal_detail::get_signal_state();
753
1/1
✓ Branch 3 → 4 taken 442 times.
442x std::lock_guard<std::mutex> lock(state->mutex);
754
755
4/6
✓ Branch 4 → 5 taken 439 times.
✓ Branch 4 → 7 taken 3 times.
✓ Branch 5 → 6 taken 439 times.
✗ Branch 5 → 7 not taken.
✓ Branch 6 → 7 taken 439 times.
✗ Branch 6 → 14 not taken.
442x if (service->next_ || service->prev_ || state->service_list == service)
756 {
757
1/2
✓ Branch 7 → 8 taken 442 times.
✗ Branch 7 → 9 not taken.
442x if (state->service_list == service)
758 442x state->service_list = service->next_;
759
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 442 times.
442x if (service->prev_)
760 service->prev_->next_ = service->next_;
761
2/2
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 439 times.
442x if (service->next_)
762 3x service->next_->prev_ = service->prev_;
763 442x service->next_ = nullptr;
764 442x service->prev_ = nullptr;
765 }
766 442x }
767
768 } // namespace boost::corosio::detail
769
770 #endif // BOOST_COROSIO_HAS_IOCP
771
772 #endif // BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_SIGNALS_HPP
773