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

99.7% Lines (310/312) 100.0% List of functions (28/28) 85.0% Branches (164/193)
win_signals.hpp
f(x) Functions (28)
Function Calls Lines Branches Blocks
boost::corosio::detail::win_signals::reset_token_cancel(boost::corosio::detail::win_signal&) :230 447x 100.0% – 100.0% corosio_signal_handler :298 159x 100.0% – 100.0% boost::corosio::detail::signal_op::signal_op() :313 81x 100.0% – 100.0% boost::corosio::detail::signal_op::do_complete(void*, boost::corosio::detail::scheduler_op*, unsigned int, unsigned int) :316 161x 92.9% 70.0% 92.3% boost::corosio::detail::win_signal::win_signal(boost::corosio::detail::win_signals&) :347 81x 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*) :350 525x 100.0% 80.0% 100.0% boost::corosio::detail::win_signal::add(int, boost::corosio::signal_set::flags_t) :398 88x 100.0% – 100.0% boost::corosio::detail::win_signal::remove(int) :404 15x 100.0% – 100.0% boost::corosio::detail::win_signal::clear() :410 94x 100.0% – 100.0% boost::corosio::detail::win_signal::cancel() :416 88x 100.0% – 100.0% boost::corosio::detail::win_signals::win_signals(boost::capy::execution_context&) :425 73x 100.0% 100.0% 68.8% boost::corosio::detail::win_signals::~win_signals() :434 146x 100.0% – 100.0% boost::corosio::detail::win_signals::shutdown() :440 73x 100.0% 78.3% 96.4% boost::corosio::detail::win_signals::construct() :495 81x 100.0% – 100.0% boost::corosio::detail::win_signals::destroy(boost::corosio::io_object::implementation*) :508 79x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::destroy_impl(boost::corosio::detail::win_signal&) :518 79x 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) :529 88x 100.0% 92.0% 81.1% boost::corosio::detail::win_signals::remove_signal(boost::corosio::detail::win_signal&, int) :590 15x 100.0% 88.5% 96.8% boost::corosio::detail::win_signals::clear_signals(boost::corosio::detail::win_signal&) :636 94x 100.0% 90.9% 93.8% boost::corosio::detail::win_signals::cancel_wait(boost::corosio::detail::win_signal&) :677 88x 100.0% 80.0% 100.0% boost::corosio::detail::win_signals::cancel_wait_token(boost::corosio::detail::win_signal&) :706 280x 100.0% 75.0% 100.0% boost::corosio::detail::win_signal::token_canceller::operator()() const :737 280x 100.0% – 100.0% boost::corosio::detail::win_signals::start_wait(boost::corosio::detail::win_signal&, boost::corosio::detail::signal_op*) :743 447x 100.0% 81.0% 91.2% boost::corosio::detail::win_signals::deliver_signal(int) :809 159x 100.0% 84.6% 76.0% boost::corosio::detail::win_signals::work_finished() :861 12x 100.0% – 100.0% boost::corosio::detail::win_signals::post(boost::corosio::detail::signal_op*) :867 161x 100.0% – 100.0% boost::corosio::detail::win_signals::add_service(boost::corosio::detail::win_signals*) :873 73x 100.0% 100.0% 100.0% boost::corosio::detail::win_signals::remove_service(boost::corosio::detail::win_signals*) :886 73x 100.0% 92.9% 100.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2025 Vinnie Falco ([email protected])
3 // Copyright (c) 2026 Steve Gerbino
4 // Copyright (c) 2026 Michael Vandeberg
5 //
6 // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 //
9 // Official repository: https://github.com/cppalliance/corosio
10 //
11
12 #ifndef BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_SIGNALS_HPP
13 #define BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_SIGNALS_HPP
14
15 #include <boost/corosio/detail/platform.hpp>
16
17 #if BOOST_COROSIO_HAS_IOCP
18
19 #include <boost/corosio/native/detail/iocp/win_signal.hpp>
20
21 #include <boost/corosio/detail/config.hpp>
22 #include <boost/capy/ex/execution_context.hpp>
23 #include <boost/corosio/native/detail/iocp/win_mutex.hpp>
24 #include <boost/corosio/native/detail/iocp/win_scheduler.hpp>
25 #include <boost/corosio/detail/dispatch_coro.hpp>
26 #include <boost/capy/error.hpp>
27
28 #include <csignal>
29 #include <mutex>
30 #include <tuple>
31
32 #include <signal.h>
33
34 /*
35 Windows Signal Implementation - Header
36 ======================================
37
38 This header declares the internal types for Windows signal handling.
39
40 Key Differences from POSIX:
41 - Uses C runtime signal() instead of sigaction() (Windows has no sigaction)
42 - Only `none` and `dont_care` flags are supported; other flags return
43 `operation_not_supported` (Windows has no equivalent to SA_* flags)
44 - Windows resets handler to SIG_DFL after each signal, so we must re-register
45 - Only supports: SIGINT, SIGTERM, SIGABRT, SIGFPE, SIGILL, SIGSEGV
46 - max_signal_number is 32 (vs 64 on Linux)
47
48 The data structures mirror the POSIX implementation for consistency:
49 - signal_op, signal_registration, win_signal, win_signals
50
51 Threading note: Windows signal handling is synchronous (runs on faulting
52 thread), so we can safely acquire locks in the signal handler. This differs
53 from POSIX where the handler must be async-signal-safe.
54 */
55
56 /*
57 Windows Signal Handling Implementation
58 ======================================
59
60 This file implements POSIX-style signal handling on Windows, integrated with
61 the IOCP scheduler. Windows lacks native async signal support, so we use the
62 C standard library's signal() function and manually bridge signals into the
63 completion-based I/O model.
64
65 Architecture Overview
66 ---------------------
67
68 Three layers manage signal registrations:
69
70 1. signal_state (global singleton)
71 - Tracks the global service list and per-signal registration counts
72 - Owns the mutex that protects signal handler installation/removal
73 - Multiple execution_contexts share this; each gets a win_signals entry
74
75 2. win_signals (one per execution_context)
76 - Maintains registrations_[] table indexed by signal number
77 - Each slot is a doubly-linked list of all signal_registrations for that signal
78 - Also maintains impl_list_ of all win_signal objects it owns
79
80 3. win_signal (one per signal_set)
81 - Owns a singly-linked list (sorted by signal number) of signal_registrations
82 - Contains the pending_op_ used for wait operations
83
84 The signal_registration struct links these together:
85 - next_in_set / (implicit via sorted order): links registrations within one signal_set
86 - prev_in_table / next_in_table: links registrations for the same signal across sets
87
88 Signal Delivery Flow
89 --------------------
90
91 1. corosio_signal_handler() (C handler, must be async-signal-safe)
92 - Called by the OS when a signal arrives
93 - Delegates to deliver_signal() and re-registers itself (Windows resets to SIG_DFL)
94
95 2. deliver_signal() broadcasts to all win_signals services:
96 - If a signal_set is waiting (impl->waiting_ == true), complete it immediately
97 by posting the signal_op to the scheduler
98 - Otherwise, increment reg->undelivered to queue the signal for later
99
100 3. start_wait() checks for queued signals first:
101 - If undelivered > 0, consume one and post immediate completion
102 - Otherwise, set waiting_ = true and call work_started() to keep context alive
103
104 Locking Protocol
105 ----------------
106
107 Two mutex levels exist (must be acquired in this order to avoid deadlock):
108 1. signal_state::mutex - protects handler registration and service list
109 2. win_signals::mutex_ - protects per-service registration tables and wait state
110
111 deliver_signal() acquires both locks because it iterates the global service list
112 and modifies per-service state.
113
114 Work Tracking
115 -------------
116
117 When waiting for a signal:
118 - start_wait() calls sched_.work_started() to keep io_context::run() alive
119 - signal_op::svc is set to point to the service
120 - signal_op::operator()() calls work_finished() after resuming the coroutine
121
122 If a signal was already queued (undelivered > 0), no work tracking is needed
123 because completion is posted immediately.
124
125 Signal Flags
126 ------------
127
128 Windows only supports `none` and `dont_care` flags. Any other flags
129 (restart, no_child_stop, etc.) return `operation_not_supported`. The
130 C runtime signal() function has no equivalent to sigaction() flags
131 like SA_RESTART or SA_NOCLDSTOP.
132 */
133
134 namespace boost::corosio::detail {
135
136 class win_scheduler;
137
138 /** Windows signal management service.
139
140 This service owns all signal set implementations and coordinates
141 their lifecycle. It provides:
142
143 - Signal implementation allocation and deallocation
144 - Signal registration via the C runtime signal() function
145 - Global signal state management
146 - Graceful shutdown - destroys all implementations when io_context stops
147
148 @par Thread Safety
149 All public member functions are thread-safe.
150
151 @note Only available on Windows platforms.
152 */
153 class BOOST_COROSIO_DECL win_signals final
154 : public capy::execution_context::service
155 , public io_object::io_service
156 {
157 public:
158 using key_type = win_signals;
159
160 io_object::implementation* construct() override;
161 void destroy(io_object::implementation*) override;
162
163 /** Construct the signal service.
164
165 @param ctx Reference to the owning execution_context.
166 */
167 explicit win_signals(capy::execution_context& ctx);
168
169 /** Destroy the signal service. */
170 ~win_signals();
171
172 win_signals(win_signals const&) = delete;
173 win_signals& operator=(win_signals const&) = delete;
174
175 /** Shut down the service.
176
177 Destroys every implementation the service still owns and gives
178 each of their registrations back to the process-global table.
179 */
180 void shutdown() override;
181
182 /** Destroy a signal implementation. */
183 void destroy_impl(win_signal& impl);
184
185 /** Add a signal to a signal set.
186
187 @param impl The signal implementation to modify.
188 @param signal_number The signal to register.
189 @param flags The flags to apply (ignored on Windows).
190 @return Success, or an error.
191 */
192 std::error_code
193 add_signal(win_signal& impl, int signal_number, signal_set::flags_t flags);
194
195 /** Remove a signal from a signal set.
196
197 @param impl The signal implementation to modify.
198 @param signal_number The signal to unregister.
199 @return Success, or an error.
200 */
201 std::error_code remove_signal(win_signal& impl, int signal_number);
202
203 /** Remove all signals from a signal set.
204
205 @param impl The signal implementation to clear.
206 @return Success, or an error.
207 */
208 std::error_code clear_signals(win_signal& impl);
209
210 /** Cancel pending wait operations.
211
212 @param impl The signal implementation to cancel.
213 */
214 void cancel_wait(win_signal& impl);
215
216 /** Cancel an in-flight wait on behalf of a stop token.
217
218 Identical to @ref cancel_wait except that it does not set the
219 sticky `cancelled_` latch: a stop token scopes to one operation,
220 so a request arriving after the wait completed must do nothing.
221 */
222 void cancel_wait_token(win_signal& impl) noexcept;
223
224 /** Clear the per-operation stop flag before a new wait arms.
225
226 Lives here rather than on the implementation because the mutex is
227 the service's; the service is a friend of `win_signal`, not the
228 reverse.
229 */
230 447x void reset_token_cancel(win_signal& impl) noexcept
231 {
232 447x std::lock_guard<win_mutex> lock(mutex_);
233 447x impl.token_cancelled_ = false;
234 447x }
235
236 /** Start a wait operation.
237
238 @param impl The signal implementation.
239 @param op The operation to start.
240 */
241 void start_wait(win_signal& impl, signal_op* op);
242
243 /** Deliver a signal to all registered handlers.
244
245 Called from the signal handler.
246
247 @param signal_number The signal that occurred.
248 */
249 static void deliver_signal(int signal_number);
250
251 /** Notify scheduler of pending work. */
252 void work_started() noexcept;
253
254 /** Notify scheduler that work completed. */
255 void work_finished() noexcept;
256
257 /** Post an operation for completion. */
258 void post(signal_op* op);
259
260 private:
261 static void add_service(win_signals* service);
262 static void remove_service(win_signals* service);
263
264 win_scheduler& sched_;
265 BOOST_COROSIO_MSVC_WARNING_PUSH
266 BOOST_COROSIO_MSVC_WARNING_DISABLE(4251) // detail:: members, dll-interface
267 win_mutex mutex_;
268 intrusive_list<win_signal> impl_list_;
269 BOOST_COROSIO_MSVC_WARNING_POP
270
271 // Per-signal registration table for this service
272 signal_registration* registrations_[max_signal_number];
273
274 // Linked list of services for global signal delivery
275 win_signals* next_ = nullptr;
276 win_signals* prev_ = nullptr;
277 };
278
279 //
280 // Global signal state
281 //
282
283 namespace signal_detail {
284
285 struct signal_state
286 {
287 std::mutex mutex;
288 win_signals* service_list = nullptr;
289 std::size_t registration_count[max_signal_number] = {};
290 };
291
292 BOOST_COROSIO_DECL signal_state* get_signal_state();
293
294 // C signal handler. Note: On POSIX this would need to be async-signal-safe,
295 // but Windows signal handling is synchronous (runs on the faulting thread)
296 // so we can safely acquire locks here.
297 extern "C" inline void
298 159x corosio_signal_handler(int signal_number)
299 {
300 159x win_signals::deliver_signal(signal_number);
301
302 // Windows uses "one-shot" semantics: the handler reverts to SIG_DFL
303 // after each delivery. Re-register to maintain our handler.
304 159x ::signal(signal_number, corosio_signal_handler);
305 159x }
306
307 } // namespace signal_detail
308
309 //
310 // signal_op
311 //
312
313 81x inline signal_op::signal_op() noexcept : scheduler_op(&do_complete) {}
314
315 inline void
316 161x signal_op::do_complete(
317 void* owner,
318 scheduler_op* base,
319 std::uint32_t /*bytes*/,
320 std::uint32_t /*error*/)
321 {
322 161x auto* op = static_cast<signal_op*>(base);
323
324 // Destroy path - no-op: signal_op is embedded in win_signal
325
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 161 times.
161x if (!owner)
326 ✗ return;
327
328
1/2
✓ Branch 4 → 5 taken 161 times.
✗ Branch 4 → 7 not taken.
161x if (op->ec_out)
329 161x *op->ec_out = {};
330
1/2
✓ Branch 7 → 8 taken 161 times.
✗ Branch 7 → 9 not taken.
161x if (op->signal_out)
331 161x *op->signal_out = op->signal_number;
332
333 161x auto* service = op->svc;
334 161x op->svc = nullptr;
335
336 161x op->cont.h = op->h;
337
2/2
✓ Branch 9 → 10 taken 161 times.
✓ Branch 10 → 11 taken 161 times.
161x dispatch_coro(op->d, op->cont).resume();
338
339
2/2
✓ Branch 11 → 12 taken 12 times.
✓ Branch 11 → 13 taken 149 times.
161x if (service)
340 12x service->work_finished();
341 }
342
343 //
344 // win_signal
345 //
346
347 81x inline win_signal::win_signal(win_signals& svc) noexcept : svc_(svc) {}
348
349 inline std::coroutine_handle<>
350 525x win_signal::wait(
351 std::coroutine_handle<> h,
352 capy::executor_ref d,
353 std::stop_token token,
354 std::error_code* ec,
355 int* signal_out)
356 {
357 525x pending_op_.h = h;
358 525x pending_op_.d = d;
359 525x pending_op_.ec_out = ec;
360 525x pending_op_.signal_out = signal_out;
361 525x pending_op_.signal_number = 0;
362
363 // Disarm any callback left over from a previous wait before doing
364 // anything else, including the early return below: otherwise that
365 // path leaves this object owning a callback it no longer uses.
366 // Outside start_wait's lock on purpose: ~stop_callback blocks until a
367 // concurrently running callback returns, and that callback takes the
368 // service mutex.
369 525x stop_cb_.reset();
370
371 // Check for immediate cancellation
372
2/2
✓ Branch 4 → 5 taken 78 times.
✓ Branch 4 → 14 taken 447 times.
525x if (token.stop_requested())
373 {
374
1/2
✓ Branch 5 → 6 taken 78 times.
✗ Branch 5 → 7 not taken.
78x if (ec)
375 78x *ec = make_error_code(capy::error::canceled);
376
1/2
✓ Branch 7 → 8 taken 78 times.
✗ Branch 7 → 9 not taken.
78x if (signal_out)
377 78x *signal_out = 0;
378 78x pending_op_.cont.h = h;
379
2/2
✓ Branch 9 → 10 taken 78 times.
✓ Branch 10 → 11 taken 78 times.
78x dispatch_coro(d, pending_op_.cont).resume();
380 // resumed inline for an io_context executor, not posted to the
381 // scheduler queue.
382 78x return std::noop_coroutine();
383 }
384
385 // Clearing the flag before arming is load-bearing: reset_token_cancel
386 // must run immediately before emplace, not before the early return
387 // above.
388 447x svc_.reset_token_cancel(*this);
389
2/2
✓ Branch 16 → 17 taken 282 times.
✓ Branch 16 → 19 taken 165 times.
447x if (token.stop_possible())
390 282x stop_cb_.emplace(token, token_canceller{this});
391
392 447x svc_.start_wait(*this, &pending_op_);
393 // cancellation resumes inline here too; only signal delivery is posted.
394 447x return std::noop_coroutine();
395 }
396
397 inline std::error_code
398 88x win_signal::add(int signal_number, signal_set::flags_t flags)
399 {
400 88x return svc_.add_signal(*this, signal_number, flags);
401 }
402
403 inline std::error_code
404 15x win_signal::remove(int signal_number)
405 {
406 15x return svc_.remove_signal(*this, signal_number);
407 }
408
409 inline std::error_code
410 94x win_signal::clear()
411 {
412 94x return svc_.clear_signals(*this);
413 }
414
415 inline void
416 88x win_signal::cancel() noexcept
417 {
418 88x svc_.cancel_wait(*this);
419 88x }
420
421 //
422 // win_signals
423 //
424
425 73x inline win_signals::win_signals(capy::execution_context& ctx)
426
2/2
✓ Branch 4 → 5 taken 73 times.
✓ Branch 5 → 6 taken 73 times.
73x : sched_(ctx.use_service<win_scheduler>())
427 {
428
2/2
✓ Branch 9 → 8 taken 2336 times.
✓ Branch 9 → 10 taken 73 times.
2409x for (int i = 0; i < max_signal_number; ++i)
429 2336x registrations_[i] = nullptr;
430
431
1/1
✓ Branch 10 → 11 taken 73 times.
73x add_service(this);
432 73x }
433
434 146x inline win_signals::~win_signals()
435 {
436 73x remove_service(this);
437 146x }
438
439 inline void
440 73x win_signals::shutdown()
441 {
442 // Collected under the locks below and deleted after they are released:
443 // ~win_signal destroys an armed stop_cb_, and ~stop_callback blocks
444 // until a concurrently running token_canceller returns -- which takes
445 // mutex_. Deleting while still holding mutex_ would self-deadlock the
446 // same way disarm_stop() would if called inside the locked loop.
447 73x intrusive_list<win_signal> doomed;
448
449 {
450
1/1
✓ Branch 2 → 3 taken 67 times.
73x signal_detail::signal_state* state = signal_detail::get_signal_state();
451
1/1
✓ Branch 3 → 4 taken 73 times.
73x std::lock_guard<std::mutex> state_lock(state->mutex);
452 73x std::lock_guard<win_mutex> lock(mutex_);
453
454
3/3
✓ Branch 16 → 6 taken 2 times.
✓ Branch 16 → 17 taken 66 times.
✓ Branch 16 → 18 taken 7 times.
75x for (auto* impl = impl_list_.pop_front(); impl != nullptr;
455 2x impl = impl_list_.pop_front())
456 {
457
2/2
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 14 taken 2 times.
4x while (auto* reg = impl->signals_)
458 {
459 2x int const signal_number = reg->signal_number;
460
461 // The registration table outlives every io_context, so a set
462 // still registered here has to give its count and handler
463 // back the way clear() would: otherwise the handler stays
464 // installed for a signal no set owns any more. The per-node
465 // table unlink clear() also does is skipped in favour of the
466 // wholesale null-out below.
467
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 11 taken 1 time.
2x if (state->registration_count[signal_number] == 1)
468 1x std::ignore = ::signal(signal_number, SIG_DFL);
469
470 2x --state->registration_count[signal_number];
471
472 2x impl->signals_ = reg->next_in_set;
473
1/2
✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 13 not taken.
2x delete reg;
474 2x }
475 2x doomed.push_back(impl);
476 }
477
478 // Every live registration hung off an implementation in impl_list_,
479 // so the whole table goes stale at once and can be dropped wholesale
480 // rather than node by node. It has to be dropped: deliver_signal()
481 // walks this service until the destructor unlinks it from the global
482 // list.
483
4/4
✓ Branch 19 → 18 taken 2112 times.
✓ Branch 19 → 20 taken 66 times.
✓ Branch 20 → 19 taken 224 times.
✓ Branch 20 → 21 taken 7 times.
2409x for (int i = 0; i < max_signal_number; ++i)
484 2336x registrations_[i] = nullptr;
485 73x }
486
487
3/4
✓ Branch 26 → 23 taken 2 times.
✓ Branch 26 → 27 taken 66 times.
✗ Branch 27 → 24 not taken.
✓ Branch 27 → 28 taken 7 times.
75x for (auto* impl = doomed.pop_front(); impl != nullptr;
488 2x impl = doomed.pop_front())
489 {
490
1/4
✓ Branch 23 → 24 taken 2 times.
✗ Branch 23 → 25 not taken.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 26 not taken.
2x delete impl;
491 }
492 73x }
493
494 inline io_object::implementation*
495 81x win_signals::construct()
496 {
497 81x auto* impl = new win_signal(*this);
498
499 {
500 81x std::lock_guard<win_mutex> lock(mutex_);
501 81x impl_list_.push_back(impl);
502 81x }
503
504 81x return impl;
505 }
506
507 inline void
508 79x win_signals::destroy(io_object::implementation* p)
509 {
510 79x auto& impl = static_cast<win_signal&>(*p);
511
1/1
✓ Branch 2 → 3 taken 79 times.
79x impl.clear();
512 79x impl.disarm_stop();
513 79x impl.cancel();
514 79x destroy_impl(impl);
515 79x }
516
517 inline void
518 79x win_signals::destroy_impl(win_signal& impl)
519 {
520 {
521 79x std::lock_guard<win_mutex> lock(mutex_);
522 79x impl_list_.remove(&impl);
523 79x }
524
525
1/2
✓ Branch 5 → 6 taken 79 times.
✗ Branch 5 → 7 not taken.
79x delete &impl;
526 79x }
527
528 inline std::error_code
529 88x win_signals::add_signal(
530 win_signal& impl, int signal_number, signal_set::flags_t flags)
531 {
532
4/4
✓ Branch 2 → 3 taken 87 times.
✓ Branch 2 → 4 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 86 times.
88x if (signal_number < 0 || signal_number >= max_signal_number)
533 2x return make_error_code(std::errc::invalid_argument);
534
535 // Windows only supports none and dont_care flags
536 86x constexpr auto supported = signal_set::none | signal_set::dont_care;
537
2/2
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 9 taken 85 times.
86x if ((flags & ~supported) != signal_set::none)
538 1x return make_error_code(std::errc::operation_not_supported);
539
540
1/1
✓ Branch 9 → 10 taken 75 times.
85x signal_detail::signal_state* state = signal_detail::get_signal_state();
541
1/1
✓ Branch 10 → 11 taken 85 times.
85x std::lock_guard<std::mutex> state_lock(state->mutex);
542 85x std::lock_guard<win_mutex> lock(mutex_);
543
544 // Check if already registered in this set
545 85x signal_registration** insertion_point = &impl.signals_;
546 85x signal_registration* reg = impl.signals_;
547
4/4
✓ Branch 14 → 15 taken 14 times.
✓ Branch 14 → 16 taken 84 times.
✓ Branch 15 → 13 taken 13 times.
✓ Branch 15 → 16 taken 1 time.
98x while (reg && reg->signal_number < signal_number)
548 {
549 13x insertion_point = &reg->next_in_set;
550 13x reg = reg->next_in_set;
551 }
552
553
3/4
✓ Branch 16 → 17 taken 1 time.
✓ Branch 16 → 19 taken 84 times.
✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 19 not taken.
85x if (reg && reg->signal_number == signal_number)
554 1x return {}; // Already registered
555
556 // Create new registration
557
1/1
✓ Branch 19 → 20 taken 84 times.
84x auto* new_reg = new signal_registration;
558 84x new_reg->signal_number = signal_number;
559 84x new_reg->owner = &impl;
560 84x new_reg->undelivered = 0;
561
562 // Register signal handler if first registration
563
2/2
✓ Branch 21 → 22 taken 76 times.
✓ Branch 21 → 27 taken 8 times.
84x if (state->registration_count[signal_number] == 0)
564 {
565
2/2
✓ Branch 23 → 24 taken 2 times.
✓ Branch 23 → 27 taken 74 times.
76x if (::signal(signal_number, signal_detail::corosio_signal_handler) ==
566 SIG_ERR)
567 {
568
1/2
✓ Branch 24 → 25 taken 2 times.
✗ Branch 24 → 26 not taken.
2x delete new_reg;
569 2x return make_error_code(std::errc::invalid_argument);
570 }
571 }
572
573 // Insert into set's registration list (sorted by signal number)
574 82x new_reg->next_in_set = reg;
575 82x *insertion_point = new_reg;
576
577 // Insert into service's registration table
578 82x new_reg->next_in_table = registrations_[signal_number];
579 82x new_reg->prev_in_table = nullptr;
580
2/2
✓ Branch 27 → 28 taken 5 times.
✓ Branch 27 → 29 taken 77 times.
82x if (registrations_[signal_number])
581 5x registrations_[signal_number]->prev_in_table = new_reg;
582 82x registrations_[signal_number] = new_reg;
583
584 82x ++state->registration_count[signal_number];
585
586 82x return {};
587 85x }
588
589 inline std::error_code
590 15x win_signals::remove_signal(win_signal& impl, int signal_number)
591 {
592
3/4
✓ Branch 2 → 3 taken 14 times.
✓ Branch 2 → 4 taken 1 time.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 14 times.
15x if (signal_number < 0 || signal_number >= max_signal_number)
593 1x return make_error_code(std::errc::invalid_argument);
594
595
1/1
✓ Branch 5 → 6 taken 10 times.
14x signal_detail::signal_state* state = signal_detail::get_signal_state();
596
1/1
✓ Branch 6 → 7 taken 14 times.
14x std::lock_guard<std::mutex> state_lock(state->mutex);
597 14x std::lock_guard<win_mutex> lock(mutex_);
598
599 // Find the registration in the set
600 14x signal_registration** deletion_point = &impl.signals_;
601 14x signal_registration* reg = impl.signals_;
602
4/4
✓ Branch 10 → 11 taken 13 times.
✓ Branch 10 → 12 taken 2 times.
✓ Branch 11 → 9 taken 1 time.
✓ Branch 11 → 12 taken 12 times.
15x while (reg && reg->signal_number < signal_number)
603 {
604 1x deletion_point = &reg->next_in_set;
605 1x reg = reg->next_in_set;
606 }
607
608
3/4
✓ Branch 12 → 13 taken 12 times.
✓ Branch 12 → 14 taken 2 times.
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 12 times.
14x if (!reg || reg->signal_number != signal_number)
609 2x return {}; // Not found, no-op
610
611 // Restore default handler if last registration
612
2/2
✓ Branch 15 → 16 taken 10 times.
✓ Branch 15 → 19 taken 2 times.
12x if (state->registration_count[signal_number] == 1)
613 {
614
2/2
✓ Branch 17 → 18 taken 2 times.
✓ Branch 17 → 19 taken 8 times.
10x if (::signal(signal_number, SIG_DFL) == SIG_ERR)
615 2x return make_error_code(std::errc::invalid_argument);
616 }
617
618 // Remove from set's list
619 10x *deletion_point = reg->next_in_set;
620
621 // Remove from service's registration table
622
2/2
✓ Branch 19 → 20 taken 9 times.
✓ Branch 19 → 21 taken 1 time.
10x if (registrations_[signal_number] == reg)
623 9x registrations_[signal_number] = reg->next_in_table;
624
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 23 taken 9 times.
10x if (reg->prev_in_table)
625 1x reg->prev_in_table->next_in_table = reg->next_in_table;
626
2/2
✓ Branch 23 → 24 taken 1 time.
✓ Branch 23 → 25 taken 9 times.
10x if (reg->next_in_table)
627 1x reg->next_in_table->prev_in_table = reg->prev_in_table;
628
629 10x --state->registration_count[signal_number];
630
631
1/2
✓ Branch 25 → 26 taken 10 times.
✗ Branch 25 → 27 not taken.
10x delete reg;
632 10x return {};
633 14x }
634
635 inline std::error_code
636 94x win_signals::clear_signals(win_signal& impl)
637 {
638
1/1
✓ Branch 2 → 3 taken 78 times.
94x signal_detail::signal_state* state = signal_detail::get_signal_state();
639
1/1
✓ Branch 3 → 4 taken 94 times.
94x std::lock_guard<std::mutex> state_lock(state->mutex);
640 94x std::lock_guard<win_mutex> lock(mutex_);
641
642 94x std::error_code first_error;
643
644
2/2
✓ Branch 6 → 7 taken 70 times.
✓ Branch 6 → 25 taken 94 times.
164x while (signal_registration* reg = impl.signals_)
645 {
646 70x int signal_number = reg->signal_number;
647
648 // Restore default handler if last registration
649
2/2
✓ Branch 7 → 8 taken 65 times.
✓ Branch 7 → 16 taken 5 times.
70x if (state->registration_count[signal_number] == 1)
650 {
651
5/6
✓ Branch 9 → 10 taken 2 times.
✓ Branch 9 → 13 taken 63 times.
✓ Branch 11 → 12 taken 2 times.
✗ Branch 11 → 13 not taken.
✓ Branch 14 → 15 taken 2 times.
✓ Branch 14 → 16 taken 63 times.
65x if (::signal(signal_number, SIG_DFL) == SIG_ERR && !first_error)
652 2x first_error = make_error_code(std::errc::invalid_argument);
653 }
654
655 // Remove from set's list
656 70x impl.signals_ = reg->next_in_set;
657
658 // Remove from service's registration table
659
2/2
✓ Branch 16 → 17 taken 69 times.
✓ Branch 16 → 18 taken 1 time.
70x if (registrations_[signal_number] == reg)
660 69x registrations_[signal_number] = reg->next_in_table;
661
2/2
✓ Branch 18 → 19 taken 1 time.
✓ Branch 18 → 20 taken 69 times.
70x if (reg->prev_in_table)
662 1x reg->prev_in_table->next_in_table = reg->next_in_table;
663
2/2
✓ Branch 20 → 21 taken 2 times.
✓ Branch 20 → 22 taken 68 times.
70x if (reg->next_in_table)
664 2x reg->next_in_table->prev_in_table = reg->prev_in_table;
665
666 70x --state->registration_count[signal_number];
667
668
1/2
✓ Branch 22 → 23 taken 70 times.
✗ Branch 22 → 24 not taken.
70x delete reg;
669 70x }
670
671
2/2
✓ Branch 26 → 27 taken 2 times.
✓ Branch 26 → 28 taken 92 times.
94x if (first_error)
672 2x return first_error;
673 92x return {};
674 94x }
675
676 inline void
677 88x win_signals::cancel_wait(win_signal& impl)
678 {
679 88x bool was_waiting = false;
680 88x signal_op* op = nullptr;
681
682 {
683 88x std::lock_guard<win_mutex> lock(mutex_);
684 88x impl.cancelled_ = true;
685
2/2
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 5 taken 84 times.
88x if (impl.waiting_)
686 {
687 4x was_waiting = true;
688 4x impl.waiting_ = false;
689 4x op = &impl.pending_op_;
690 }
691 88x }
692
693
2/2
✓ Branch 6 → 7 taken 4 times.
✓ Branch 6 → 14 taken 84 times.
88x if (was_waiting)
694 {
695
1/2
✓ Branch 7 → 8 taken 4 times.
✗ Branch 7 → 9 not taken.
4x if (op->ec_out)
696 4x *op->ec_out = make_error_code(capy::error::canceled);
697
1/2
✓ Branch 9 → 10 taken 4 times.
✗ Branch 9 → 11 not taken.
4x if (op->signal_out)
698 4x *op->signal_out = 0;
699 4x op->cont.h = op->h;
700
2/2
✓ Branch 11 → 12 taken 4 times.
✓ Branch 12 → 13 taken 4 times.
4x dispatch_coro(op->d, op->cont).resume();
701 4x sched_.work_finished();
702 }
703 88x }
704
705 inline void
706 280x win_signals::cancel_wait_token(win_signal& impl) noexcept
707 {
708 280x bool was_waiting = false;
709 280x signal_op* op = nullptr;
710
711 {
712 280x std::lock_guard<win_mutex> lock(mutex_);
713 // Persist the request even when no wait is parked yet; start_wait
714 // consumes this flag.
715 280x impl.token_cancelled_ = true;
716
2/2
✓ Branch 3 → 4 taken 183 times.
✓ Branch 3 → 5 taken 97 times.
280x if (impl.waiting_)
717 {
718 183x was_waiting = true;
719 183x impl.waiting_ = false;
720 183x op = &impl.pending_op_;
721 }
722 280x }
723
724
2/2
✓ Branch 6 → 7 taken 183 times.
✓ Branch 6 → 14 taken 97 times.
280x if (was_waiting)
725 {
726
1/2
✓ Branch 7 → 8 taken 183 times.
✗ Branch 7 → 9 not taken.
183x if (op->ec_out)
727 183x *op->ec_out = make_error_code(capy::error::canceled);
728
1/2
✓ Branch 9 → 10 taken 183 times.
✗ Branch 9 → 11 not taken.
183x if (op->signal_out)
729 183x *op->signal_out = 0;
730 183x op->cont.h = op->h;
731 183x dispatch_coro(op->d, op->cont).resume();
732 183x sched_.work_finished();
733 }
734 280x }
735
736 inline void
737 280x win_signal::token_canceller::operator()() const noexcept
738 {
739 280x self->svc_.cancel_wait_token(*self);
740 280x }
741
742 inline void
743 447x win_signals::start_wait(win_signal& impl, signal_op* op)
744 {
745 447x bool was_cancelled = false;
746
747 {
748 447x std::lock_guard<win_mutex> lock(mutex_);
749
750 // Check if cancel() was called before this wait started
751
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 9 taken 446 times.
447x if (impl.cancelled_)
752 {
753 1x was_cancelled = true;
754 1x impl.cancelled_ = false;
755
1/2
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 6 not taken.
1x if (op->ec_out)
756 1x *op->ec_out = make_error_code(capy::error::canceled);
757
1/2
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 8 not taken.
1x if (op->signal_out)
758 1x *op->signal_out = 0;
759 1x op->cont.h = op->h;
760 }
761
2/2
✓ Branch 9 → 10 taken 96 times.
✓ Branch 9 → 15 taken 350 times.
446x else if (impl.token_cancelled_)
762 {
763 // A stop request that arrived between wait() arming the callback
764 // and this lock: complete now rather than parking forever. Filled
765 // in under the lock and dispatched below, outside it, like the
766 // cancelled_ branch above -- see the comment after this block.
767 96x was_cancelled = true;
768 96x impl.token_cancelled_ = false;
769
1/2
✓ Branch 10 → 11 taken 96 times.
✗ Branch 10 → 12 not taken.
96x if (op->ec_out)
770 96x *op->ec_out = make_error_code(capy::error::canceled);
771
1/2
✓ Branch 12 → 13 taken 96 times.
✗ Branch 12 → 14 not taken.
96x if (op->signal_out)
772 96x *op->signal_out = 0;
773 96x op->cont.h = op->h;
774 }
775 else
776 {
777 // Check for queued signals first
778 350x signal_registration* reg = impl.signals_;
779
2/2
✓ Branch 20 → 16 taken 353 times.
✓ Branch 20 → 21 taken 201 times.
554x while (reg)
780 {
781
2/2
✓ Branch 16 → 17 taken 149 times.
✓ Branch 16 → 19 taken 204 times.
353x if (reg->undelivered > 0)
782 {
783 149x --reg->undelivered;
784 149x op->signal_number = reg->signal_number;
785 149x op->svc = nullptr; // No extra work_finished needed
786 // Post for immediate completion - post() handles work tracking
787
1/1
✓ Branch 17 → 18 taken 149 times.
149x post(op);
788 149x return;
789 }
790 204x reg = reg->next_in_set;
791 }
792
793 // No queued signals, wait for delivery
794 // We call work_started() to keep io_context alive while waiting.
795 // Set svc so signal_op::operator() will call work_finished().
796 201x impl.waiting_ = true;
797 201x op->svc = this;
798 201x sched_.work_started();
799 }
800 447x }
801
802 // Dispatch outside the lock to avoid deadlock if the resumed
803 // coroutine re-enters cancel()/add()/remove()
804
2/2
✓ Branch 26 → 28 taken 97 times.
✓ Branch 26 → 31 taken 201 times.
298x if (was_cancelled)
805
2/2
✓ Branch 28 → 29 taken 97 times.
✓ Branch 29 → 30 taken 97 times.
97x dispatch_coro(op->d, op->cont).resume();
806 }
807
808 inline void
809 159x win_signals::deliver_signal(int signal_number)
810 {
811
2/4
✓ Branch 2 → 3 taken 159 times.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 159 times.
159x if (signal_number < 0 ||
812 signal_number >=
813 max_signal_number) // LCOV_EXCL_LINE OS never delivers out-of-range
814 − return; // LCOV_EXCL_LINE OS never delivers out-of-range
815
816
1/1
✓ Branch 5 → 6 taken 159 times.
159x signal_detail::signal_state* state = signal_detail::get_signal_state();
817
1/1
✓ Branch 6 → 7 taken 159 times.
159x std::lock_guard<std::mutex> lock(state->mutex);
818
819 // Deliver to all services. We hold state->mutex while iterating, and
820 // acquire each service's mutex_ inside (matching the lock order used by
821 // add_signal/remove_signal) to safely read and modify registration state.
822 159x win_signals* service = state->service_list;
823
2/2
✓ Branch 17 → 8 taken 159 times.
✓ Branch 17 → 18 taken 159 times.
318x while (service)
824 {
825 159x std::lock_guard<win_mutex> svc_lock(service->mutex_);
826
827 // Find registrations for this signal
828 159x signal_registration* reg = service->registrations_[signal_number];
829
2/2
✓ Branch 14 → 10 taken 161 times.
✓ Branch 14 → 15 taken 159 times.
320x while (reg)
830 {
831 161x win_signal* impl = reg->owner;
832
833
2/2
✓ Branch 10 → 11 taken 12 times.
✓ Branch 10 → 12 taken 149 times.
161x if (impl->waiting_)
834 {
835 // Complete the pending wait
836 12x impl->waiting_ = false;
837 12x impl->pending_op_.signal_number = signal_number;
838
1/1
✓ Branch 11 → 13 taken 12 times.
12x service->post(&impl->pending_op_);
839 }
840 else
841 {
842 // No waiter yet; increment undelivered so start_wait() will
843 // find this signal immediately without blocking
844 149x ++reg->undelivered;
845 }
846
847 161x reg = reg->next_in_table;
848 }
849
850 159x service = service->next_;
851 159x }
852 159x }
853
854 inline void
855 win_signals::work_started() noexcept
856 {
857 sched_.work_started();
858 }
859
860 inline void
861 12x win_signals::work_finished() noexcept
862 {
863 12x sched_.work_finished();
864 12x }
865
866 inline void
867 161x win_signals::post(signal_op* op)
868 {
869 161x sched_.post(op);
870 161x }
871
872 inline void
873 73x win_signals::add_service(win_signals* service)
874 {
875 73x signal_detail::signal_state* state = signal_detail::get_signal_state();
876
1/1
✓ Branch 3 → 4 taken 73 times.
73x std::lock_guard<std::mutex> lock(state->mutex);
877
878 73x service->next_ = state->service_list;
879 73x service->prev_ = nullptr;
880
2/2
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 69 times.
73x if (state->service_list)
881 4x state->service_list->prev_ = service;
882 73x state->service_list = service;
883 73x }
884
885 inline void
886 73x win_signals::remove_service(win_signals* service)
887 {
888
1/1
✓ Branch 2 → 3 taken 67 times.
73x signal_detail::signal_state* state = signal_detail::get_signal_state();
889
1/1
✓ Branch 3 → 4 taken 73 times.
73x std::lock_guard<std::mutex> lock(state->mutex);
890
891
5/6
✓ Branch 4 → 5 taken 70 times.
✓ Branch 4 → 7 taken 3 times.
✓ Branch 5 → 6 taken 69 times.
✓ Branch 5 → 7 taken 1 time.
✓ Branch 6 → 7 taken 69 times.
✗ Branch 6 → 14 not taken.
73x if (service->next_ || service->prev_ || state->service_list == service)
892 {
893
2/2
✓ Branch 7 → 8 taken 72 times.
✓ Branch 7 → 9 taken 1 time.
73x if (state->service_list == service)
894 72x state->service_list = service->next_;
895
2/2
✓ Branch 9 → 10 taken 1 time.
✓ Branch 9 → 11 taken 72 times.
73x if (service->prev_)
896 1x service->prev_->next_ = service->next_;
897
2/2
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 70 times.
73x if (service->next_)
898 3x service->next_->prev_ = service->prev_;
899 73x service->next_ = nullptr;
900 73x service->prev_ = nullptr;
901 }
902 73x }
903
904 } // namespace boost::corosio::detail
905
906 #endif // BOOST_COROSIO_HAS_IOCP
907
908 #endif // BOOST_COROSIO_NATIVE_DETAIL_IOCP_WIN_SIGNALS_HPP
909