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

89.4% Lines (219/245) 100.0% List of functions (25/25) 68.0% Branches (104/153)
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 35x 100.0% 100.0% boost::corosio::detail::signal_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :287 10x 92.3% 70.0% 92.3% boost::corosio::detail::win_signal::win_signal(boost::corosio::detail::win_signals&) :317 35x 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*) :320 12x 60.0% 12.5% 47.1% boost::corosio::detail::win_signal::add(int, boost::corosio::signal_set::flags_t) :351 34x 100.0% 100.0% boost::corosio::detail::win_signal::remove(int) :357 2x 100.0% 100.0% boost::corosio::detail::win_signal::clear() :363 37x 100.0% 100.0% boost::corosio::detail::win_signal::cancel() :369 41x 100.0% 100.0% boost::corosio::detail::win_signals::win_signals(boost::capy::execution_context&) :378 369x 100.0% 100.0% 68.8% boost::corosio::detail::win_signals::~win_signals() :387 738x 100.0% 100.0% boost::corosio::detail::win_signals::shutdown() :393 369x 40.0% 12.5% 41.7% boost::corosio::detail::win_signals::construct() :411 35x 100.0% 100.0% boost::corosio::detail::win_signals::destroy(boost::corosio::io_object::implementation*) :424 35x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::destroy_impl(boost::corosio::detail::win_signal&) :433 35x 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) :444 34x 94.1% 80.0% 81.1% boost::corosio::detail::win_signals::remove_signal(boost::corosio::detail::win_signal&, int) :505 2x 77.8% 61.5% 83.9% boost::corosio::detail::win_signals::clear_signals(boost::corosio::detail::win_signal&) :551 37x 87.5% 63.6% 81.2% boost::corosio::detail::win_signals::cancel_wait(boost::corosio::detail::win_signal&) :592 41x 100.0% 80.0% 100.0% boost::corosio::detail::win_signals::start_wait(boost::corosio::detail::win_signal&, boost::corosio::detail::signal_op*) :619 12x 100.0% 100.0% 88.9% boost::corosio::detail::win_signals::deliver_signal(int) :650 9x 95.0% 84.6% 76.0% boost::corosio::detail::win_signals::work_finished() :700 5x 100.0% 100.0% boost::corosio::detail::win_signals::post(boost::corosio::detail::signal_op*) :706 10x 100.0% 100.0% boost::corosio::detail::win_signals::add_service(boost::corosio::detail::win_signals*) :712 369x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::remove_service(boost::corosio::detail::win_signals*) :725 369x 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 35x 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
2/2
✓ Branch 9 → 10 taken 10 times.
✓ Branch 10 → 11 taken 10 times.
10x dispatch_coro(op->d, op->h).resume();
308
309
2/2
✓ Branch 11 → 12 taken 5 times.
✓ Branch 11 → 13 taken 5 times.
10x if (service)
310 5x service->work_finished();
311 }
312
313 //
314 // win_signal
315 //
316
317 35x inline win_signal::win_signal(win_signals& svc) noexcept : svc_(svc) {}
318
319 inline std::coroutine_handle<>
320 12x 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 12x pending_op_.h = h;
328 12x pending_op_.d = d;
329 12x pending_op_.ec_out = ec;
330 12x pending_op_.signal_out = signal_out;
331 12x 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.
12x 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 12x svc_.start_wait(*this, &pending_op_);
346 // completion is always posted to scheduler queue, never inline.
347 12x return std::noop_coroutine();
348 }
349
350 inline std::error_code
351 34x win_signal::add(int signal_number, signal_set::flags_t flags)
352 {
353 34x return svc_.add_signal(*this, signal_number, flags);
354 }
355
356 inline std::error_code
357 2x win_signal::remove(int signal_number)
358 {
359 2x return svc_.remove_signal(*this, signal_number);
360 }
361
362 inline std::error_code
363 37x win_signal::clear()
364 {
365 37x return svc_.clear_signals(*this);
366 }
367
368 inline void
369 41x win_signal::cancel()
370 {
371 41x svc_.cancel_wait(*this);
372 41x }
373
374 //
375 // win_signals
376 //
377
378 369x inline win_signals::win_signals(capy::execution_context& ctx)
379
2/2
✓ Branch 4 → 5 taken 369 times.
✓ Branch 5 → 6 taken 369 times.
369x : sched_(ctx.use_service<win_scheduler>())
380 {
381
2/2
✓ Branch 9 → 8 taken 11808 times.
✓ Branch 9 → 10 taken 369 times.
12177x for (int i = 0; i < max_signal_number; ++i)
382 11808x registrations_[i] = nullptr;
383
384
1/1
✓ Branch 10 → 11 taken 369 times.
369x add_service(this);
385 369x }
386
387 738x inline win_signals::~win_signals()
388 {
389 369x remove_service(this);
390 738x }
391
392 inline void
393 369x win_signals::shutdown()
394 {
395 369x std::lock_guard<win_mutex> lock(mutex_);
396
397
1/2
✗ Branch 11 → 4 not taken.
✓ Branch 11 → 12 taken 369 times.
369x 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 369x }
409
410 inline io_object::implementation*
411 35x win_signals::construct()
412 {
413 35x auto* impl = new win_signal(*this);
414
415 {
416 35x std::lock_guard<win_mutex> lock(mutex_);
417 35x impl_list_.push_back(impl);
418 35x }
419
420 35x return impl;
421 }
422
423 inline void
424 35x win_signals::destroy(io_object::implementation* p)
425 {
426 35x auto& impl = static_cast<win_signal&>(*p);
427
1/1
✓ Branch 2 → 3 taken 35 times.
35x impl.clear();
428 35x impl.cancel();
429 35x destroy_impl(impl);
430 35x }
431
432 inline void
433 35x win_signals::destroy_impl(win_signal& impl)
434 {
435 {
436 35x std::lock_guard<win_mutex> lock(mutex_);
437 35x impl_list_.remove(&impl);
438 35x }
439
440
1/2
✓ Branch 5 → 6 taken 35 times.
✗ Branch 5 → 7 not taken.
35x delete &impl;
441 35x }
442
443 inline std::error_code
444 34x 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.
34x if (signal_number < 0 || signal_number >= max_signal_number)
448 1x return make_error_code(std::errc::invalid_argument);
449
450 // Windows only supports none and dont_care flags
451 33x 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.
33x if ((flags & ~supported) != signal_set::none)
453 1x return make_error_code(std::errc::operation_not_supported);
454
455
1/1
✓ Branch 9 → 10 taken 32 times.
32x signal_detail::signal_state* state = signal_detail::get_signal_state();
456
1/1
✓ Branch 10 → 11 taken 32 times.
32x std::lock_guard<std::mutex> state_lock(state->mutex);
457 32x std::lock_guard<win_mutex> lock(mutex_);
458
459 // Check if already registered in this set
460 32x signal_registration** insertion_point = &impl.signals_;
461 32x 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.
38x while (reg && reg->signal_number < signal_number)
463 {
464 6x insertion_point = &reg->next_in_set;
465 6x 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.
32x if (reg && reg->signal_number == signal_number)
469 1x return {}; // Already registered
470
471 // Create new registration
472
1/1
✓ Branch 19 → 20 taken 31 times.
31x auto* new_reg = new signal_registration;
473 31x new_reg->signal_number = signal_number;
474 31x new_reg->owner = &impl;
475 31x 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.
31x if (state->registration_count[signal_number] == 0)
479 {
480
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 27 taken 30 times.
30x 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 31x new_reg->next_in_set = reg;
490 31x *insertion_point = new_reg;
491
492 // Insert into service's registration table
493 31x new_reg->next_in_table = registrations_[signal_number];
494 31x new_reg->prev_in_table = nullptr;
495
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 30 times.
31x if (registrations_[signal_number])
496 1x registrations_[signal_number]->prev_in_table = new_reg;
497 31x registrations_[signal_number] = new_reg;
498
499 31x ++state->registration_count[signal_number];
500
501 31x return {};
502 32x }
503
504 inline std::error_code
505 2x 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.
2x 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.
2x signal_detail::signal_state* state = signal_detail::get_signal_state();
511
1/1
✓ Branch 6 → 7 taken 2 times.
2x std::lock_guard<std::mutex> state_lock(state->mutex);
512 2x std::lock_guard<win_mutex> lock(mutex_);
513
514 // Find the registration in the set
515 2x signal_registration** deletion_point = &impl.signals_;
516 2x 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.
2x 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.
2x if (!reg || reg->signal_number != signal_number)
524 1x 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.
1x if (state->registration_count[signal_number] == 1)
528 {
529
1/2
✗ Branch 17 → 18 not taken.
✓ Branch 17 → 19 taken 1 time.
1x 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 1x *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.
1x if (registrations_[signal_number] == reg)
538 1x registrations_[signal_number] = reg->next_in_table;
539
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 23 taken 1 time.
1x 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.
1x if (reg->next_in_table)
542 reg->next_in_table->prev_in_table = reg->prev_in_table;
543
544 1x --state->registration_count[signal_number];
545
546
1/2
✓ Branch 25 → 26 taken 1 time.
✗ Branch 25 → 27 not taken.
1x delete reg;
547 1x return {};
548 2x }
549
550 inline std::error_code
551 37x win_signals::clear_signals(win_signal& impl)
552 {
553
1/1
✓ Branch 2 → 3 taken 37 times.
37x signal_detail::signal_state* state = signal_detail::get_signal_state();
554
1/1
✓ Branch 3 → 4 taken 37 times.
37x std::lock_guard<std::mutex> state_lock(state->mutex);
555 37x std::lock_guard<win_mutex> lock(mutex_);
556
557 37x std::error_code first_error;
558
559
2/2
✓ Branch 6 → 7 taken 30 times.
✓ Branch 6 → 25 taken 37 times.
67x while (signal_registration* reg = impl.signals_)
560 {
561 30x 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.
30x 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.
29x 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 30x 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.
30x if (registrations_[signal_number] == reg)
575 30x registrations_[signal_number] = reg->next_in_table;
576
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 30 times.
30x 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.
30x if (reg->next_in_table)
579 1x reg->next_in_table->prev_in_table = reg->prev_in_table;
580
581 30x --state->registration_count[signal_number];
582
583
1/2
✓ Branch 22 → 23 taken 30 times.
✗ Branch 22 → 24 not taken.
30x delete reg;
584 30x }
585
586
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 37 times.
37x if (first_error)
587 return first_error;
588 37x return {};
589 37x }
590
591 inline void
592 41x win_signals::cancel_wait(win_signal& impl)
593 {
594 41x bool was_waiting = false;
595 41x signal_op* op = nullptr;
596
597 {
598 41x std::lock_guard<win_mutex> lock(mutex_);
599
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 39 times.
41x if (impl.waiting_)
600 {
601 2x was_waiting = true;
602 2x impl.waiting_ = false;
603 2x op = &impl.pending_op_;
604 }
605 41x }
606
607
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 14 taken 39 times.
41x if (was_waiting)
608 {
609
1/2
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 9 not taken.
2x if (op->ec_out)
610 2x *op->ec_out = make_error_code(capy::error::canceled);
611
1/2
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 11 not taken.
2x if (op->signal_out)
612 2x *op->signal_out = 0;
613
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
2x dispatch_coro(op->d, op->h).resume();
614 2x sched_.work_finished();
615 }
616 41x }
617
618 inline void
619 12x win_signals::start_wait(win_signal& impl, signal_op* op)
620 {
621 {
622 12x std::lock_guard<win_mutex> lock(mutex_);
623
624 // Check for queued signals first
625 12x signal_registration* reg = impl.signals_;
626
2/2
✓ Branch 8 → 4 taken 13 times.
✓ Branch 8 → 9 taken 7 times.
20x while (reg)
627 {
628
2/2
✓ Branch 4 → 5 taken 5 times.
✓ Branch 4 → 7 taken 8 times.
13x if (reg->undelivered > 0)
629 {
630 5x --reg->undelivered;
631 5x op->signal_number = reg->signal_number;
632 5x 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.
5x post(op);
635 5x return;
636 }
637 8x 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 7x impl.waiting_ = true;
644 7x op->svc = this;
645 7x sched_.work_started();
646 12x }
647 }
648
649 inline void
650 9x 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.
9x if (signal_number < 0 || signal_number >= max_signal_number)
653 return;
654
655
1/1
✓ Branch 5 → 6 taken 9 times.
9x signal_detail::signal_state* state = signal_detail::get_signal_state();
656
1/1
✓ Branch 6 → 7 taken 9 times.
9x 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 9x win_signals* service = state->service_list;
662
2/2
✓ Branch 17 → 8 taken 9 times.
✓ Branch 17 → 18 taken 9 times.
18x while (service)
663 {
664 9x std::lock_guard<win_mutex> svc_lock(service->mutex_);
665
666 // Find registrations for this signal
667 9x signal_registration* reg = service->registrations_[signal_number];
668
2/2
✓ Branch 14 → 10 taken 10 times.
✓ Branch 14 → 15 taken 9 times.
19x while (reg)
669 {
670 10x win_signal* impl = reg->owner;
671
672
2/2
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 5 times.
10x if (impl->waiting_)
673 {
674 // Complete the pending wait
675 5x impl->waiting_ = false;
676 5x impl->pending_op_.signal_number = signal_number;
677
1/1
✓ Branch 11 → 13 taken 5 times.
5x 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 5x ++reg->undelivered;
684 }
685
686 10x reg = reg->next_in_table;
687 }
688
689 9x service = service->next_;
690 9x }
691 9x }
692
693 inline void
694 win_signals::work_started() noexcept
695 {
696 sched_.work_started();
697 }
698
699 inline void
700 5x win_signals::work_finished() noexcept
701 {
702 5x sched_.work_finished();
703 5x }
704
705 inline void
706 10x win_signals::post(signal_op* op)
707 {
708 10x sched_.post(op);
709 10x }
710
711 inline void
712 369x win_signals::add_service(win_signals* service)
713 {
714
1/1
✓ Branch 2 → 3 taken 369 times.
369x signal_detail::signal_state* state = signal_detail::get_signal_state();
715
1/1
✓ Branch 3 → 4 taken 369 times.
369x std::lock_guard<std::mutex> lock(state->mutex);
716
717 369x service->next_ = state->service_list;
718 369x service->prev_ = nullptr;
719
2/2
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 366 times.
369x if (state->service_list)
720 3x state->service_list->prev_ = service;
721 369x state->service_list = service;
722 369x }
723
724 inline void
725 369x win_signals::remove_service(win_signals* service)
726 {
727
1/1
✓ Branch 2 → 3 taken 369 times.
369x signal_detail::signal_state* state = signal_detail::get_signal_state();
728
1/1
✓ Branch 3 → 4 taken 369 times.
369x std::lock_guard<std::mutex> lock(state->mutex);
729
730
4/6
✓ Branch 4 → 5 taken 366 times.
✓ Branch 4 → 7 taken 3 times.
✓ Branch 5 → 6 taken 366 times.
✗ Branch 5 → 7 not taken.
✓ Branch 6 → 7 taken 366 times.
✗ Branch 6 → 14 not taken.
369x if (service->next_ || service->prev_ || state->service_list == service)
731 {
732
1/2
✓ Branch 7 → 8 taken 369 times.
✗ Branch 7 → 9 not taken.
369x if (state->service_list == service)
733 369x state->service_list = service->next_;
734
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 369 times.
369x if (service->prev_)
735 service->prev_->next_ = service->next_;
736
2/2
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 366 times.
369x if (service->next_)
737 3x service->next_->prev_ = service->prev_;
738 369x service->next_ = nullptr;
739 369x service->prev_ = nullptr;
740 }
741 369x }
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