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

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