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

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