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

92.7% Lines (241/260) 100.0% List of functions (25/25) 73.0% Branches (119/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 40x 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 40x 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 14x 100.0% 75.0% 100.0% boost::corosio::detail::win_signal::add(int, boost::corosio::signal_set::flags_t) :353 39x 100.0% 100.0% boost::corosio::detail::win_signal::remove(int) :359 3x 100.0% 100.0% boost::corosio::detail::win_signal::clear() :365 42x 100.0% 100.0% boost::corosio::detail::win_signal::cancel() :371 47x 100.0% 100.0% boost::corosio::detail::win_signals::win_signals(boost::capy::execution_context&) :380 594x 100.0% 100.0% 68.8% boost::corosio::detail::win_signals::~win_signals() :389 1188x 100.0% 100.0% boost::corosio::detail::win_signals::shutdown() :395 594x 40.0% 12.5% 41.7% boost::corosio::detail::win_signals::construct() :413 40x 100.0% 100.0% boost::corosio::detail::win_signals::destroy(boost::corosio::io_object::implementation*) :426 40x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::destroy_impl(boost::corosio::detail::win_signal&) :435 40x 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 39x 94.1% 84.0% 81.1% boost::corosio::detail::win_signals::remove_signal(boost::corosio::detail::win_signal&, int) :507 3x 81.5% 65.4% 87.1% boost::corosio::detail::win_signals::clear_signals(boost::corosio::detail::win_signal&) :553 42x 87.5% 63.6% 81.2% boost::corosio::detail::win_signals::cancel_wait(boost::corosio::detail::win_signal&) :594 47x 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 594x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::remove_service(boost::corosio::detail::win_signals*) :750 594x 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 40x 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 40x inline win_signal::win_signal(win_signals& svc) noexcept : svc_(svc) {}
319
320 inline std::coroutine_handle<>
321 14x 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 14x pending_op_.h = h;
329 14x pending_op_.d = d;
330 14x pending_op_.ec_out = ec;
331 14x pending_op_.signal_out = signal_out;
332 14x pending_op_.signal_number = 0;
333
334 // Check for immediate cancellation
335
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 13 taken 13 times.
14x if (token.stop_requested())
336 {
337
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
1x if (ec)
338 1x *ec = make_error_code(capy::error::canceled);
339
1/2
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
1x if (signal_out)
340 1x *signal_out = 0;
341 1x pending_op_.cont_op.cont.h = h;
342
2/2
✓ Branch 8 → 9 taken 1 time.
✓ Branch 9 → 10 taken 1 time.
1x dispatch_coro(d, pending_op_.cont_op.cont).resume();
343 // completion is always posted to scheduler queue, never inline.
344 1x 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 39x win_signal::add(int signal_number, signal_set::flags_t flags)
354 {
355 39x return svc_.add_signal(*this, signal_number, flags);
356 }
357
358 inline std::error_code
359 3x win_signal::remove(int signal_number)
360 {
361 3x return svc_.remove_signal(*this, signal_number);
362 }
363
364 inline std::error_code
365 42x win_signal::clear()
366 {
367 42x return svc_.clear_signals(*this);
368 }
369
370 inline void
371 47x win_signal::cancel()
372 {
373 47x svc_.cancel_wait(*this);
374 47x }
375
376 //
377 // win_signals
378 //
379
380 594x inline win_signals::win_signals(capy::execution_context& ctx)
381
2/2
✓ Branch 4 → 5 taken 594 times.
✓ Branch 5 → 6 taken 594 times.
594x : sched_(ctx.use_service<win_scheduler>())
382 {
383
2/2
✓ Branch 9 → 8 taken 19008 times.
✓ Branch 9 → 10 taken 594 times.
19602x for (int i = 0; i < max_signal_number; ++i)
384 19008x registrations_[i] = nullptr;
385
386
1/1
✓ Branch 10 → 11 taken 594 times.
594x add_service(this);
387 594x }
388
389 1188x inline win_signals::~win_signals()
390 {
391 594x remove_service(this);
392 1188x }
393
394 inline void
395 594x win_signals::shutdown()
396 {
397 594x std::lock_guard<win_mutex> lock(mutex_);
398
399
1/2
✗ Branch 11 → 4 not taken.
✓ Branch 11 → 12 taken 594 times.
594x 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 594x }
411
412 inline io_object::implementation*
413 40x win_signals::construct()
414 {
415 40x auto* impl = new win_signal(*this);
416
417 {
418 40x std::lock_guard<win_mutex> lock(mutex_);
419 40x impl_list_.push_back(impl);
420 40x }
421
422 40x return impl;
423 }
424
425 inline void
426 40x win_signals::destroy(io_object::implementation* p)
427 {
428 40x auto& impl = static_cast<win_signal&>(*p);
429
1/1
✓ Branch 2 → 3 taken 40 times.
40x impl.clear();
430 40x impl.cancel();
431 40x destroy_impl(impl);
432 40x }
433
434 inline void
435 40x win_signals::destroy_impl(win_signal& impl)
436 {
437 {
438 40x std::lock_guard<win_mutex> lock(mutex_);
439 40x impl_list_.remove(&impl);
440 40x }
441
442
1/2
✓ Branch 5 → 6 taken 40 times.
✗ Branch 5 → 7 not taken.
40x delete &impl;
443 40x }
444
445 inline std::error_code
446 39x win_signals::add_signal(
447 win_signal& impl, int signal_number, signal_set::flags_t flags)
448 {
449
4/4
✓ Branch 2 → 3 taken 38 times.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 37 times.
39x if (signal_number < 0 || signal_number >= max_signal_number)
450 2x return make_error_code(std::errc::invalid_argument);
451
452 // Windows only supports none and dont_care flags
453 37x constexpr auto supported = signal_set::none | signal_set::dont_care;
454
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 36 times.
37x 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 36 times.
36x signal_detail::signal_state* state = signal_detail::get_signal_state();
458
1/1
✓ Branch 10 → 11 taken 36 times.
36x std::lock_guard<std::mutex> state_lock(state->mutex);
459 36x std::lock_guard<win_mutex> lock(mutex_);
460
461 // Check if already registered in this set
462 36x signal_registration** insertion_point = &impl.signals_;
463 36x signal_registration* reg = impl.signals_;
464
4/4
✓ Branch 14 → 15 taken 8 times.
✓ Branch 14 → 16 taken 35 times.
✓ Branch 15 → 13 taken 7 times.
✓ Branch 15 → 16 taken 1 time.
43x while (reg && reg->signal_number < signal_number)
465 {
466 7x insertion_point = &reg->next_in_set;
467 7x reg = reg->next_in_set;
468 }
469
470
3/4
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 19 taken 35 times.
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 19 not taken.
36x if (reg && reg->signal_number == signal_number)
471 1x return {}; // Already registered
472
473 // Create new registration
474
1/1
✓ Branch 19 → 20 taken 35 times.
35x auto* new_reg = new signal_registration;
475 35x new_reg->signal_number = signal_number;
476 35x new_reg->owner = &impl;
477 35x new_reg->undelivered = 0;
478
479 // Register signal handler if first registration
480
2/2
✓ Branch 21 → 22 taken 34 times.
✓ Branch 21 → 27 taken 1 time.
35x if (state->registration_count[signal_number] == 0)
481 {
482
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 27 taken 34 times.
34x 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 35x new_reg->next_in_set = reg;
492 35x *insertion_point = new_reg;
493
494 // Insert into service's registration table
495 35x new_reg->next_in_table = registrations_[signal_number];
496 35x new_reg->prev_in_table = nullptr;
497
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 34 times.
35x if (registrations_[signal_number])
498 1x registrations_[signal_number]->prev_in_table = new_reg;
499 35x registrations_[signal_number] = new_reg;
500
501 35x ++state->registration_count[signal_number];
502
503 35x return {};
504 36x }
505
506 inline std::error_code
507 3x win_signals::remove_signal(win_signal& impl, int signal_number)
508 {
509
3/4
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 2 times.
3x if (signal_number < 0 || signal_number >= max_signal_number)
510 1x 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 42x win_signals::clear_signals(win_signal& impl)
554 {
555
1/1
✓ Branch 2 → 3 taken 42 times.
42x signal_detail::signal_state* state = signal_detail::get_signal_state();
556
1/1
✓ Branch 3 → 4 taken 42 times.
42x std::lock_guard<std::mutex> state_lock(state->mutex);
557 42x std::lock_guard<win_mutex> lock(mutex_);
558
559 42x std::error_code first_error;
560
561
2/2
✓ Branch 6 → 7 taken 34 times.
✓ Branch 6 → 25 taken 42 times.
76x while (signal_registration* reg = impl.signals_)
562 {
563 34x int signal_number = reg->signal_number;
564
565 // Restore default handler if last registration
566
2/2
✓ Branch 7 → 8 taken 33 times.
✓ Branch 7 → 16 taken 1 time.
34x if (state->registration_count[signal_number] == 1)
567 {
568
2/6
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 13 taken 33 times.
✗ Branch 11 → 12 not taken.
✗ Branch 11 → 13 not taken.
✗ Branch 14 → 15 not taken.
✓ Branch 14 → 16 taken 33 times.
33x 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 34x impl.signals_ = reg->next_in_set;
574
575 // Remove from service's registration table
576
1/2
✓ Branch 16 → 17 taken 34 times.
✗ Branch 16 → 18 not taken.
34x if (registrations_[signal_number] == reg)
577 34x registrations_[signal_number] = reg->next_in_table;
578
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 34 times.
34x 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 33 times.
34x if (reg->next_in_table)
581 1x reg->next_in_table->prev_in_table = reg->prev_in_table;
582
583 34x --state->registration_count[signal_number];
584
585
1/2
✓ Branch 22 → 23 taken 34 times.
✗ Branch 22 → 24 not taken.
34x delete reg;
586 34x }
587
588
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 42 times.
42x if (first_error)
589 return first_error;
590 42x return {};
591 42x }
592
593 inline void
594 47x win_signals::cancel_wait(win_signal& impl)
595 {
596 47x bool was_waiting = false;
597 47x signal_op* op = nullptr;
598
599 {
600 47x std::lock_guard<win_mutex> lock(mutex_);
601 47x impl.cancelled_ = true;
602
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 45 times.
47x if (impl.waiting_)
603 {
604 2x was_waiting = true;
605 2x impl.waiting_ = false;
606 2x op = &impl.pending_op_;
607 }
608 47x }
609
610
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 14 taken 45 times.
47x 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 47x }
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 594x win_signals::add_service(win_signals* service)
738 {
739
1/1
✓ Branch 2 → 3 taken 594 times.
594x signal_detail::signal_state* state = signal_detail::get_signal_state();
740
1/1
✓ Branch 3 → 4 taken 594 times.
594x std::lock_guard<std::mutex> lock(state->mutex);
741
742 594x service->next_ = state->service_list;
743 594x service->prev_ = nullptr;
744
2/2
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 590 times.
594x if (state->service_list)
745 4x state->service_list->prev_ = service;
746 594x state->service_list = service;
747 594x }
748
749 inline void
750 594x win_signals::remove_service(win_signals* service)
751 {
752
1/1
✓ Branch 2 → 3 taken 594 times.
594x signal_detail::signal_state* state = signal_detail::get_signal_state();
753
1/1
✓ Branch 3 → 4 taken 594 times.
594x std::lock_guard<std::mutex> lock(state->mutex);
754
755
4/6
✓ Branch 4 → 5 taken 590 times.
✓ Branch 4 → 7 taken 4 times.
✓ Branch 5 → 6 taken 590 times.
✗ Branch 5 → 7 not taken.
✓ Branch 6 → 7 taken 590 times.
✗ Branch 6 → 14 not taken.
594x if (service->next_ || service->prev_ || state->service_list == service)
756 {
757
1/2
✓ Branch 7 → 8 taken 594 times.
✗ Branch 7 → 9 not taken.
594x if (state->service_list == service)
758 594x state->service_list = service->next_;
759
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 594 times.
594x if (service->prev_)
760 service->prev_->next_ = service->next_;
761
2/2
✓ Branch 11 → 12 taken 4 times.
✓ Branch 11 → 13 taken 590 times.
594x if (service->next_)
762 4x service->next_->prev_ = service->prev_;
763 594x service->next_ = nullptr;
764 594x service->prev_ = nullptr;
765 }
766 594x }
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