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

89.1% Lines (221/248) 100.0% List of functions (25/25) 68.0% Branches (104/153)
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 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.9% 70.0% 92.3% boost::corosio::detail::win_signal::win_signal(boost::corosio::detail::win_signals&) :318 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*) :321 12x 56.2% 12.5% 47.1% boost::corosio::detail::win_signal::add(int, boost::corosio::signal_set::flags_t) :353 34x 100.0% 100.0% boost::corosio::detail::win_signal::remove(int) :359 2x 100.0% 100.0% boost::corosio::detail::win_signal::clear() :365 37x 100.0% 100.0% boost::corosio::detail::win_signal::cancel() :371 41x 100.0% 100.0% boost::corosio::detail::win_signals::win_signals(boost::capy::execution_context&) :380 431x 100.0% 100.0% 68.8% boost::corosio::detail::win_signals::~win_signals() :389 862x 100.0% 100.0% boost::corosio::detail::win_signals::shutdown() :395 431x 40.0% 12.5% 41.7% boost::corosio::detail::win_signals::construct() :413 35x 100.0% 100.0% boost::corosio::detail::win_signals::destroy(boost::corosio::io_object::implementation*) :426 35x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::destroy_impl(boost::corosio::detail::win_signal&) :435 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) :446 34x 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 37x 87.5% 63.6% 81.2% boost::corosio::detail::win_signals::cancel_wait(boost::corosio::detail::win_signal&) :594 41x 100.0% 80.0% 100.0% boost::corosio::detail::win_signals::start_wait(boost::corosio::detail::win_signal&, boost::corosio::detail::signal_op*) :622 12x 100.0% 100.0% 88.9% boost::corosio::detail::win_signals::deliver_signal(int) :653 9x 95.0% 84.6% 76.0% boost::corosio::detail::win_signals::work_finished() :703 5x 100.0% 100.0% boost::corosio::detail::win_signals::post(boost::corosio::detail::signal_op*) :709 10x 100.0% 100.0% boost::corosio::detail::win_signals::add_service(boost::corosio::detail::win_signals*) :715 431x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::remove_service(boost::corosio::detail::win_signals*) :728 431x 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 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 35x inline win_signal::win_signal(win_signals& svc) noexcept : svc_(svc) {}
319
320 inline std::coroutine_handle<>
321 12x 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 12x pending_op_.h = h;
329 12x pending_op_.d = d;
330 12x pending_op_.ec_out = ec;
331 12x pending_op_.signal_out = signal_out;
332 12x pending_op_.signal_number = 0;
333
334 // Check for immediate cancellation
335
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 13 taken 12 times.
12x 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 12x svc_.start_wait(*this, &pending_op_);
348 // completion is always posted to scheduler queue, never inline.
349 12x return std::noop_coroutine();
350 }
351
352 inline std::error_code
353 34x win_signal::add(int signal_number, signal_set::flags_t flags)
354 {
355 34x 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 37x win_signal::clear()
366 {
367 37x return svc_.clear_signals(*this);
368 }
369
370 inline void
371 41x win_signal::cancel()
372 {
373 41x svc_.cancel_wait(*this);
374 41x }
375
376 //
377 // win_signals
378 //
379
380 431x inline win_signals::win_signals(capy::execution_context& ctx)
381
2/2
✓ Branch 4 → 5 taken 431 times.
✓ Branch 5 → 6 taken 431 times.
431x : sched_(ctx.use_service<win_scheduler>())
382 {
383
2/2
✓ Branch 9 → 8 taken 13792 times.
✓ Branch 9 → 10 taken 431 times.
14223x for (int i = 0; i < max_signal_number; ++i)
384 13792x registrations_[i] = nullptr;
385
386
1/1
✓ Branch 10 → 11 taken 431 times.
431x add_service(this);
387 431x }
388
389 862x inline win_signals::~win_signals()
390 {
391 431x remove_service(this);
392 862x }
393
394 inline void
395 431x win_signals::shutdown()
396 {
397 431x std::lock_guard<win_mutex> lock(mutex_);
398
399
1/2
✗ Branch 11 → 4 not taken.
✓ Branch 11 → 12 taken 431 times.
431x 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 431x }
411
412 inline io_object::implementation*
413 35x win_signals::construct()
414 {
415 35x auto* impl = new win_signal(*this);
416
417 {
418 35x std::lock_guard<win_mutex> lock(mutex_);
419 35x impl_list_.push_back(impl);
420 35x }
421
422 35x return impl;
423 }
424
425 inline void
426 35x win_signals::destroy(io_object::implementation* p)
427 {
428 35x auto& impl = static_cast<win_signal&>(*p);
429
1/1
✓ Branch 2 → 3 taken 35 times.
35x impl.clear();
430 35x impl.cancel();
431 35x destroy_impl(impl);
432 35x }
433
434 inline void
435 35x win_signals::destroy_impl(win_signal& impl)
436 {
437 {
438 35x std::lock_guard<win_mutex> lock(mutex_);
439 35x impl_list_.remove(&impl);
440 35x }
441
442
1/2
✓ Branch 5 → 6 taken 35 times.
✗ Branch 5 → 7 not taken.
35x delete &impl;
443 35x }
444
445 inline std::error_code
446 34x win_signals::add_signal(
447 win_signal& impl, int signal_number, signal_set::flags_t flags)
448 {
449
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)
450 1x return make_error_code(std::errc::invalid_argument);
451
452 // Windows only supports none and dont_care flags
453 33x constexpr auto supported = signal_set::none | signal_set::dont_care;
454
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 32 times.
33x 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 32 times.
32x signal_detail::signal_state* state = signal_detail::get_signal_state();
458
1/1
✓ Branch 10 → 11 taken 32 times.
32x std::lock_guard<std::mutex> state_lock(state->mutex);
459 32x std::lock_guard<win_mutex> lock(mutex_);
460
461 // Check if already registered in this set
462 32x signal_registration** insertion_point = &impl.signals_;
463 32x signal_registration* reg = impl.signals_;
464
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)
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 31 times.
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 19 not taken.
32x if (reg && reg->signal_number == signal_number)
471 1x return {}; // Already registered
472
473 // Create new registration
474
1/1
✓ Branch 19 → 20 taken 31 times.
31x auto* new_reg = new signal_registration;
475 31x new_reg->signal_number = signal_number;
476 31x new_reg->owner = &impl;
477 31x new_reg->undelivered = 0;
478
479 // Register signal handler if first registration
480
2/2
✓ Branch 21 → 22 taken 30 times.
✓ Branch 21 → 27 taken 1 time.
31x if (state->registration_count[signal_number] == 0)
481 {
482
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 27 taken 30 times.
30x 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 31x new_reg->next_in_set = reg;
492 31x *insertion_point = new_reg;
493
494 // Insert into service's registration table
495 31x new_reg->next_in_table = registrations_[signal_number];
496 31x new_reg->prev_in_table = nullptr;
497
2/2
✓ Branch 27 → 28 taken 1 time.
✓ Branch 27 → 29 taken 30 times.
31x if (registrations_[signal_number])
498 1x registrations_[signal_number]->prev_in_table = new_reg;
499 31x registrations_[signal_number] = new_reg;
500
501 31x ++state->registration_count[signal_number];
502
503 31x return {};
504 32x }
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 37x win_signals::clear_signals(win_signal& impl)
554 {
555
1/1
✓ Branch 2 → 3 taken 37 times.
37x signal_detail::signal_state* state = signal_detail::get_signal_state();
556
1/1
✓ Branch 3 → 4 taken 37 times.
37x std::lock_guard<std::mutex> state_lock(state->mutex);
557 37x std::lock_guard<win_mutex> lock(mutex_);
558
559 37x std::error_code first_error;
560
561
2/2
✓ Branch 6 → 7 taken 30 times.
✓ Branch 6 → 25 taken 37 times.
67x while (signal_registration* reg = impl.signals_)
562 {
563 30x int signal_number = reg->signal_number;
564
565 // Restore default handler if last registration
566
2/2
✓ Branch 7 → 8 taken 29 times.
✓ Branch 7 → 16 taken 1 time.
30x if (state->registration_count[signal_number] == 1)
567 {
568
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)
569 first_error = make_error_code(std::errc::invalid_argument);
570 }
571
572 // Remove from set's list
573 30x impl.signals_ = reg->next_in_set;
574
575 // Remove from service's registration table
576
1/2
✓ Branch 16 → 17 taken 30 times.
✗ Branch 16 → 18 not taken.
30x if (registrations_[signal_number] == reg)
577 30x registrations_[signal_number] = reg->next_in_table;
578
1/2
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 20 taken 30 times.
30x 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 29 times.
30x if (reg->next_in_table)
581 1x reg->next_in_table->prev_in_table = reg->prev_in_table;
582
583 30x --state->registration_count[signal_number];
584
585
1/2
✓ Branch 22 → 23 taken 30 times.
✗ Branch 22 → 24 not taken.
30x delete reg;
586 30x }
587
588
1/2
✗ Branch 26 → 27 not taken.
✓ Branch 26 → 28 taken 37 times.
37x if (first_error)
589 return first_error;
590 37x return {};
591 37x }
592
593 inline void
594 41x win_signals::cancel_wait(win_signal& impl)
595 {
596 41x bool was_waiting = false;
597 41x signal_op* op = nullptr;
598
599 {
600 41x std::lock_guard<win_mutex> lock(mutex_);
601
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 39 times.
41x if (impl.waiting_)
602 {
603 2x was_waiting = true;
604 2x impl.waiting_ = false;
605 2x op = &impl.pending_op_;
606 }
607 41x }
608
609
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 14 taken 39 times.
41x if (was_waiting)
610 {
611
1/2
✓ Branch 7 → 8 taken 2 times.
✗ Branch 7 → 9 not taken.
2x if (op->ec_out)
612 2x *op->ec_out = make_error_code(capy::error::canceled);
613
1/2
✓ Branch 9 → 10 taken 2 times.
✗ Branch 9 → 11 not taken.
2x if (op->signal_out)
614 2x *op->signal_out = 0;
615 2x op->cont_op.cont.h = op->h;
616
2/2
✓ Branch 11 → 12 taken 2 times.
✓ Branch 12 → 13 taken 2 times.
2x dispatch_coro(op->d, op->cont_op.cont).resume();
617 2x sched_.work_finished();
618 }
619 41x }
620
621 inline void
622 12x win_signals::start_wait(win_signal& impl, signal_op* op)
623 {
624 {
625 12x std::lock_guard<win_mutex> lock(mutex_);
626
627 // Check for queued signals first
628 12x signal_registration* reg = impl.signals_;
629
2/2
✓ Branch 8 → 4 taken 13 times.
✓ Branch 8 → 9 taken 7 times.
20x while (reg)
630 {
631
2/2
✓ Branch 4 → 5 taken 5 times.
✓ Branch 4 → 7 taken 8 times.
13x if (reg->undelivered > 0)
632 {
633 5x --reg->undelivered;
634 5x op->signal_number = reg->signal_number;
635 5x op->svc = nullptr; // No extra work_finished needed
636 // Post for immediate completion - post() handles work tracking
637
1/1
✓ Branch 5 → 6 taken 5 times.
5x post(op);
638 5x return;
639 }
640 8x reg = reg->next_in_set;
641 }
642
643 // No queued signals, wait for delivery
644 // We call work_started() to keep io_context alive while waiting.
645 // Set svc so signal_op::operator() will call work_finished().
646 7x impl.waiting_ = true;
647 7x op->svc = this;
648 7x sched_.work_started();
649 12x }
650 }
651
652 inline void
653 9x win_signals::deliver_signal(int signal_number)
654 {
655
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)
656 return;
657
658
1/1
✓ Branch 5 → 6 taken 9 times.
9x signal_detail::signal_state* state = signal_detail::get_signal_state();
659
1/1
✓ Branch 6 → 7 taken 9 times.
9x std::lock_guard<std::mutex> lock(state->mutex);
660
661 // Deliver to all services. We hold state->mutex while iterating, and
662 // acquire each service's mutex_ inside (matching the lock order used by
663 // add_signal/remove_signal) to safely read and modify registration state.
664 9x win_signals* service = state->service_list;
665
2/2
✓ Branch 17 → 8 taken 9 times.
✓ Branch 17 → 18 taken 9 times.
18x while (service)
666 {
667 9x std::lock_guard<win_mutex> svc_lock(service->mutex_);
668
669 // Find registrations for this signal
670 9x signal_registration* reg = service->registrations_[signal_number];
671
2/2
✓ Branch 14 → 10 taken 10 times.
✓ Branch 14 → 15 taken 9 times.
19x while (reg)
672 {
673 10x win_signal* impl = reg->owner;
674
675
2/2
✓ Branch 10 → 11 taken 5 times.
✓ Branch 10 → 12 taken 5 times.
10x if (impl->waiting_)
676 {
677 // Complete the pending wait
678 5x impl->waiting_ = false;
679 5x impl->pending_op_.signal_number = signal_number;
680
1/1
✓ Branch 11 → 13 taken 5 times.
5x service->post(&impl->pending_op_);
681 }
682 else
683 {
684 // No waiter yet; increment undelivered so start_wait() will
685 // find this signal immediately without blocking
686 5x ++reg->undelivered;
687 }
688
689 10x reg = reg->next_in_table;
690 }
691
692 9x service = service->next_;
693 9x }
694 9x }
695
696 inline void
697 win_signals::work_started() noexcept
698 {
699 sched_.work_started();
700 }
701
702 inline void
703 5x win_signals::work_finished() noexcept
704 {
705 5x sched_.work_finished();
706 5x }
707
708 inline void
709 10x win_signals::post(signal_op* op)
710 {
711 10x sched_.post(op);
712 10x }
713
714 inline void
715 431x win_signals::add_service(win_signals* service)
716 {
717
1/1
✓ Branch 2 → 3 taken 431 times.
431x signal_detail::signal_state* state = signal_detail::get_signal_state();
718
1/1
✓ Branch 3 → 4 taken 431 times.
431x std::lock_guard<std::mutex> lock(state->mutex);
719
720 431x service->next_ = state->service_list;
721 431x service->prev_ = nullptr;
722
2/2
✓ Branch 4 → 5 taken 3 times.
✓ Branch 4 → 6 taken 428 times.
431x if (state->service_list)
723 3x state->service_list->prev_ = service;
724 431x state->service_list = service;
725 431x }
726
727 inline void
728 431x win_signals::remove_service(win_signals* service)
729 {
730
1/1
✓ Branch 2 → 3 taken 431 times.
431x signal_detail::signal_state* state = signal_detail::get_signal_state();
731
1/1
✓ Branch 3 → 4 taken 431 times.
431x std::lock_guard<std::mutex> lock(state->mutex);
732
733
4/6
✓ Branch 4 → 5 taken 428 times.
✓ Branch 4 → 7 taken 3 times.
✓ Branch 5 → 6 taken 428 times.
✗ Branch 5 → 7 not taken.
✓ Branch 6 → 7 taken 428 times.
✗ Branch 6 → 14 not taken.
431x if (service->next_ || service->prev_ || state->service_list == service)
734 {
735
1/2
✓ Branch 7 → 8 taken 431 times.
✗ Branch 7 → 9 not taken.
431x if (state->service_list == service)
736 431x state->service_list = service->next_;
737
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 11 taken 431 times.
431x if (service->prev_)
738 service->prev_->next_ = service->next_;
739
2/2
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 428 times.
431x if (service->next_)
740 3x service->next_->prev_ = service->prev_;
741 431x service->next_ = nullptr;
742 431x service->prev_ = nullptr;
743 }
744 431x }
745
746 } // namespace boost::corosio::detail
747
748 #endif // BOOST_COROSIO_HAS_IOCP
749
750 #endif // BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_SIGNALS_HPP
751