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