include/boost/corosio/native/detail/posix/posix_signal_service.hpp

90.2% Lines (284/315) 96.6% List of functions (28/29) 63.8% Branches (111/174)
f(x) Functions (29)
Function Calls Lines Branches Blocks
boost::corosio::detail::posix_signal_service::destroy(boost::corosio::io_object::implementation*) :161 94x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::flags_supported(boost::corosio::signal_set::flags_t) :247 98x 100.0% 100.0% boost::corosio::detail::posix_signal_detail::to_sigaction_flags(boost::corosio::signal_set::flags_t) :259 80x 76.9% 70.0% 72.0% boost::corosio::detail::posix_signal_detail::flags_compatible(boost::corosio::signal_set::flags_t, boost::corosio::signal_set::flags_t) :279 18x 100.0% 100.0% 100.0% boost::corosio::detail::posix_signal_detail::corosio_posix_signal_handler(int) :293 20x 100.0% 100.0% boost::corosio::detail::signal_op::operator()() :305 22x 100.0% 66.7% 100.0% boost::corosio::detail::signal_op::destroy() :324 0 0.0% 0.0% boost::corosio::detail::posix_signal::posix_signal(boost::corosio::detail::posix_signal_service&) :331 188x 100.0% 100.0% boost::corosio::detail::posix_signal::wait(std::__1::coroutine_handle<void>, boost::capy::executor_ref, std::__1::stop_token, std::__1::error_code*, int*) :337 26x 62.5% 16.7% 37.0% boost::corosio::detail::posix_signal::add(int, boost::corosio::signal_set::flags_t) :367 100x 100.0% 100.0% boost::corosio::detail::posix_signal::remove(int) :373 4x 100.0% 100.0% boost::corosio::detail::posix_signal::clear() :379 98x 100.0% 100.0% boost::corosio::detail::posix_signal::cancel() :385 106x 100.0% 100.0% boost::corosio::detail::posix_signal_service::posix_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :392 1212x 100.0% 75.0% 100.0% boost::corosio::detail::posix_signal_service::~posix_signal_service() :404 1818x 100.0% 50.0% 100.0% boost::corosio::detail::posix_signal_service::shutdown() :410 606x 40.0% 12.5% 25.0% boost::corosio::detail::posix_signal_service::construct() :427 94x 100.0% 100.0% boost::corosio::detail::posix_signal_service::destroy_impl(boost::corosio::detail::posix_signal&) :440 94x 100.0% 50.0% 100.0% boost::corosio::detail::posix_signal_service::add_signal(boost::corosio::detail::posix_signal&, int, boost::corosio::signal_set::flags_t) :451 100x 93.9% 72.5% 80.0% boost::corosio::detail::posix_signal_service::remove_signal(boost::corosio::detail::posix_signal&, int) :534 4x 82.9% 57.1% 71.0% boost::corosio::detail::posix_signal_service::clear_signals(boost::corosio::detail::posix_signal&) :587 98x 90.3% 59.1% 69.0% boost::corosio::detail::posix_signal_service::cancel_wait(boost::corosio::detail::posix_signal&) :635 106x 100.0% 75.0% 100.0% boost::corosio::detail::posix_signal_service::start_wait(boost::corosio::detail::posix_signal&, boost::corosio::detail::signal_op*) :662 26x 100.0% 83.3% 69.0% boost::corosio::detail::posix_signal_service::deliver_signal(int) :692 20x 95.5% 71.4% 75.0% boost::corosio::detail::posix_signal_service::work_finished() :736 12x 100.0% 100.0% boost::corosio::detail::posix_signal_service::post(boost::corosio::detail::signal_op*) :742 12x 100.0% 100.0% boost::corosio::detail::posix_signal_service::add_service(boost::corosio::detail::posix_signal_service*) :748 606x 100.0% 100.0% 100.0% boost::corosio::detail::posix_signal_service::remove_service(boost::corosio::detail::posix_signal_service*) :762 606x 93.3% 66.7% 90.0% boost::corosio::detail::get_signal_service(boost::capy::execution_context&, boost::corosio::detail::scheduler&) :784 606x 100.0% 100.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Steve Gerbino
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/corosio
8 //
9
10 #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
11 #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
12
13 #include <boost/corosio/detail/platform.hpp>
14
15 #if BOOST_COROSIO_POSIX
16
17 #include <boost/corosio/native/detail/posix/posix_signal.hpp>
18
19 #include <boost/corosio/detail/config.hpp>
20 #include <boost/capy/ex/execution_context.hpp>
21 #include <boost/corosio/detail/scheduler.hpp>
22 #include <boost/capy/error.hpp>
23
24 #include <mutex>
25
26 #include <signal.h>
27
28 /*
29 POSIX Signal Service
30 ====================
31
32 Concrete signal service implementation for POSIX backends. Manages signal
33 registrations via sigaction() and dispatches completions through the
34 scheduler. One instance per execution_context, created by
35 get_signal_service().
36
37 See the block comment further down for the full architecture overview.
38 */
39
40 /*
41 POSIX Signal Implementation
42 ===========================
43
44 This file implements signal handling for POSIX systems using sigaction().
45 The implementation supports signal flags (SA_RESTART, etc.) and integrates
46 with any POSIX-compatible scheduler via the abstract scheduler interface.
47
48 Architecture Overview
49 ---------------------
50
51 Three layers manage signal registrations:
52
53 1. signal_state (global singleton)
54 - Tracks the global service list and per-signal registration counts
55 - Stores the flags used for first registration of each signal (for
56 conflict detection when multiple signal_sets register same signal)
57 - Owns the mutex that protects signal handler installation/removal
58
59 2. posix_signal_service (one per execution_context)
60 - Maintains registrations_[] table indexed by signal number
61 - Each slot is a doubly-linked list of signal_registrations for that signal
62 - Also maintains impl_list_ of all posix_signal objects it owns
63
64 3. posix_signal (one per signal_set)
65 - Owns a singly-linked list (sorted by signal number) of signal_registrations
66 - Contains the pending_op_ used for wait operations
67
68 Signal Delivery Flow
69 --------------------
70
71 1. Signal arrives -> corosio_posix_signal_handler() (must be async-signal-safe)
72 -> deliver_signal()
73
74 2. deliver_signal() iterates all posix_signal_service services:
75 - If a signal_set is waiting (impl->waiting_ == true), post the signal_op
76 to the scheduler for immediate completion
77 - Otherwise, increment reg->undelivered to queue the signal
78
79 3. When wait() is called via start_wait():
80 - First check for queued signals (undelivered > 0); if found, post
81 immediate completion without blocking
82 - Otherwise, set waiting_ = true and call work_started() to keep
83 the io_context alive
84
85 Locking Protocol
86 ----------------
87
88 Two mutex levels exist (MUST acquire in this order to avoid deadlock):
89 1. signal_state::mutex - protects handler registration and service list
90 2. posix_signal_service::mutex_ - protects per-service registration tables
91
92 Async-Signal-Safety Limitation
93 ------------------------------
94
95 IMPORTANT: deliver_signal() is called from signal handler context and
96 acquires mutexes. This is NOT strictly async-signal-safe per POSIX.
97 The limitation:
98 - If a signal arrives while another thread holds state->mutex or
99 service->mutex_, and that same thread receives the signal, a
100 deadlock can occur (self-deadlock on non-recursive mutex).
101
102 This design trades strict async-signal-safety for implementation simplicity.
103 In practice, deadlocks are rare because:
104 - Mutexes are held only briefly during registration changes
105 - Most programs don't modify signal sets while signals are expected
106 - The window for signal arrival during mutex hold is small
107
108 A fully async-signal-safe implementation would require lock-free data
109 structures and atomic operations throughout, significantly increasing
110 complexity.
111
112 Flag Handling
113 -------------
114
115 - Flags are abstract values in the public API (signal_set::flags_t)
116 - flags_supported() validates that requested flags are available on
117 this platform; returns false if SA_NOCLDWAIT is unavailable and
118 no_child_wait is requested
119 - to_sigaction_flags() maps validated flags to actual SA_* constants
120 - First registration of a signal establishes the flags; subsequent
121 registrations must be compatible (same flags or dont_care)
122 - Requesting unavailable flags returns operation_not_supported
123
124 Work Tracking
125 -------------
126
127 When waiting for a signal:
128 - start_wait() calls sched_->work_started() to prevent io_context::run()
129 from returning while we wait
130 - signal_op::svc is set to point to the service
131 - signal_op::operator()() calls work_finished() after resuming the coroutine
132
133 If a signal was already queued (undelivered > 0), no work tracking is needed
134 because completion is posted immediately.
135 */
136
137 namespace boost::corosio {
138
139 namespace detail {
140
141 /** Signal service for POSIX backends.
142
143 Manages signal registrations via sigaction() and dispatches signal
144 completions through the scheduler. One instance per execution_context.
145 */
146 class BOOST_COROSIO_DECL posix_signal_service final
147 : public capy::execution_context::service
148 , public io_object::io_service
149 {
150 public:
151 using key_type = posix_signal_service;
152
153 posix_signal_service(capy::execution_context& ctx, scheduler& sched);
154 ~posix_signal_service() override;
155
156 posix_signal_service(posix_signal_service const&) = delete;
157 posix_signal_service& operator=(posix_signal_service const&) = delete;
158
159 io_object::implementation* construct() override;
160
161 94x void destroy(io_object::implementation* p) override
162 {
163 94x auto& impl = static_cast<posix_signal&>(*p);
164 94x [[maybe_unused]] auto n = impl.clear();
165 94x impl.cancel();
166 94x destroy_impl(impl);
167 94x }
168
169 void shutdown() override;
170
171 void destroy_impl(posix_signal& impl);
172
173 std::error_code add_signal(
174 posix_signal& impl, int signal_number, signal_set::flags_t flags);
175
176 std::error_code remove_signal(posix_signal& impl, int signal_number);
177
178 std::error_code clear_signals(posix_signal& impl);
179
180 void cancel_wait(posix_signal& impl);
181 void start_wait(posix_signal& impl, signal_op* op);
182
183 static void deliver_signal(int signal_number);
184
185 void work_started() noexcept;
186 void work_finished() noexcept;
187 void post(signal_op* op);
188
189 private:
190 static void add_service(posix_signal_service* service);
191 static void remove_service(posix_signal_service* service);
192
193 scheduler* sched_;
194 std::mutex mutex_;
195 intrusive_list<posix_signal> impl_list_;
196
197 // Per-signal registration table
198 signal_registration* registrations_[max_signal_number];
199
200 // Registration counts for each signal
201 std::size_t registration_count_[max_signal_number];
202
203 // Linked list of all posix_signal_service services for signal delivery
204 606x posix_signal_service* next_ = nullptr;
205 606x posix_signal_service* prev_ = nullptr;
206 };
207
208 /** Get or create the signal service for the given context.
209
210 This function is called by the concrete scheduler during initialization
211 to create the signal service with a reference to itself.
212
213 @param ctx Reference to the owning execution_context.
214 @param sched Reference to the scheduler for posting completions.
215 @return Reference to the signal service.
216 */
217 posix_signal_service&
218 get_signal_service(capy::execution_context& ctx, scheduler& sched);
219
220 } // namespace detail
221
222 } // namespace boost::corosio
223
224 // ---------------------------------------------------------------------------
225 // Inline implementation
226 // ---------------------------------------------------------------------------
227
228 namespace boost::corosio {
229
230 namespace detail {
231
232 namespace posix_signal_detail {
233
234 struct signal_state
235 {
236 std::mutex mutex;
237 posix_signal_service* service_list = nullptr;
238 std::size_t registration_count[max_signal_number] = {};
239 signal_set::flags_t registered_flags[max_signal_number] = {};
240 };
241
242 BOOST_COROSIO_DECL signal_state* get_signal_state();
243
244 // Check if requested flags are supported on this platform.
245 // Returns true if all flags are supported, false otherwise.
246 inline bool
247 98x flags_supported([[maybe_unused]] signal_set::flags_t flags)
248 {
249 #ifndef SA_NOCLDWAIT
250 if (flags & signal_set::no_child_wait)
251 return false;
252 #endif
253 98x return true;
254 }
255
256 // Map abstract flags to sigaction() flags.
257 // Caller must ensure flags_supported() returns true first.
258 inline int
259 80x to_sigaction_flags(signal_set::flags_t flags)
260 {
261 80x int sa_flags = 0;
262
2/2
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 18 times.
80x if (flags & signal_set::restart)
263 18x sa_flags |= SA_RESTART;
264
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80x if (flags & signal_set::no_child_stop)
265 sa_flags |= SA_NOCLDSTOP;
266 #ifdef SA_NOCLDWAIT
267
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80x if (flags & signal_set::no_child_wait)
268 sa_flags |= SA_NOCLDWAIT;
269 #endif
270
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 2 times.
80x if (flags & signal_set::no_defer)
271 2x sa_flags |= SA_NODEFER;
272
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80x if (flags & signal_set::reset_handler)
273 sa_flags |= SA_RESETHAND;
274 80x return sa_flags;
275 }
276
277 // Check if two flag values are compatible
278 inline bool
279 18x flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
280 {
281 // dont_care is always compatible
282
4/4
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 12 times.
18x if ((existing & signal_set::dont_care) ||
283 16x (requested & signal_set::dont_care))
284 6x return true;
285
286 // Mask out dont_care bit for comparison
287 12x constexpr auto mask = ~signal_set::dont_care;
288 12x return (existing & mask) == (requested & mask);
289 18x }
290
291 // C signal handler - must be async-signal-safe
292 inline void
293 20x corosio_posix_signal_handler(int signal_number)
294 {
295 20x posix_signal_service::deliver_signal(signal_number);
296 // Note: With sigaction(), the handler persists automatically
297 // (unlike some signal() implementations that reset to SIG_DFL)
298 20x }
299
300 } // namespace posix_signal_detail
301
302 // signal_op implementation
303
304 inline void
305 22x signal_op::operator()()
306 {
307
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22x if (ec_out)
308 22x *ec_out = {};
309
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 22 times.
22x if (signal_out)
310 22x *signal_out = signal_number;
311
312 // Capture svc before resuming (coro may destroy us)
313 22x auto* service = svc;
314 22x svc = nullptr;
315
316 22x d.post(h);
317
318 // Balance the work_started() from start_wait
319
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 12 times.
22x if (service)
320 12x service->work_finished();
321 22x }
322
323 inline void
324 signal_op::destroy()
325 {
326 // No-op: signal_op is embedded in posix_signal
327 }
328
329 // posix_signal implementation
330
331 188x inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
332 94x : svc_(svc)
333 188x {
334 94x }
335
336 inline std::coroutine_handle<>
337 26x posix_signal::wait(
338 std::coroutine_handle<> h,
339 capy::executor_ref d,
340 std::stop_token token,
341 std::error_code* ec,
342 int* signal_out)
343 {
344 26x pending_op_.h = h;
345 26x pending_op_.d = d;
346 26x pending_op_.ec_out = ec;
347 26x pending_op_.signal_out = signal_out;
348 26x pending_op_.signal_number = 0;
349
350
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26x if (token.stop_requested())
351 {
352 if (ec)
353 *ec = make_error_code(capy::error::canceled);
354 if (signal_out)
355 *signal_out = 0;
356 d.post(h);
357 // completion is always posted to scheduler queue, never inline.
358 return std::noop_coroutine();
359 }
360
361 26x svc_.start_wait(*this, &pending_op_);
362 // completion is always posted to scheduler queue, never inline.
363 26x return std::noop_coroutine();
364 26x }
365
366 inline std::error_code
367 100x posix_signal::add(int signal_number, signal_set::flags_t flags)
368 {
369 100x return svc_.add_signal(*this, signal_number, flags);
370 }
371
372 inline std::error_code
373 4x posix_signal::remove(int signal_number)
374 {
375 4x return svc_.remove_signal(*this, signal_number);
376 }
377
378 inline std::error_code
379 98x posix_signal::clear()
380 {
381 98x return svc_.clear_signals(*this);
382 }
383
384 inline void
385 106x posix_signal::cancel()
386 {
387 106x svc_.cancel_wait(*this);
388 106x }
389
390 // posix_signal_service implementation
391
392 2424x inline posix_signal_service::posix_signal_service(
393 capy::execution_context&, scheduler& sched)
394 606x : sched_(&sched)
395 1818x {
396
2/2
✓ Branch 0 taken 38784 times.
✓ Branch 1 taken 606 times.
39390x for (int i = 0; i < max_signal_number; ++i)
397 {
398 38784x registrations_[i] = nullptr;
399 38784x registration_count_[i] = 0;
400 38784x }
401
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606x add_service(this);
402 1212x }
403
404 1818x inline posix_signal_service::~posix_signal_service()
405 1212x {
406
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606x remove_service(this);
407 1818x }
408
409 inline void
410 606x posix_signal_service::shutdown()
411 {
412 606x std::lock_guard lock(mutex_);
413
414
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 606 times.
606x for (auto* impl = impl_list_.pop_front(); impl != nullptr;
415 impl = impl_list_.pop_front())
416 {
417 while (auto* reg = impl->signals_)
418 {
419 impl->signals_ = reg->next_in_set;
420 delete reg;
421 }
422 delete impl;
423 }
424 606x }
425
426 inline io_object::implementation*
427 94x posix_signal_service::construct()
428 {
429 94x auto* impl = new posix_signal(*this);
430
431 {
432 94x std::lock_guard lock(mutex_);
433 94x impl_list_.push_back(impl);
434 94x }
435
436 94x return impl;
437 }
438
439 inline void
440 94x posix_signal_service::destroy_impl(posix_signal& impl)
441 {
442 {
443 94x std::lock_guard lock(mutex_);
444 94x impl_list_.remove(&impl);
445 94x }
446
447
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94 times.
94x delete &impl;
448 94x }
449
450 inline std::error_code
451 100x posix_signal_service::add_signal(
452 posix_signal& impl, int signal_number, signal_set::flags_t flags)
453 {
454
3/4
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 98 times.
100x if (signal_number < 0 || signal_number >= max_signal_number)
455 2x return make_error_code(std::errc::invalid_argument);
456
457 // Validate that requested flags are supported on this platform
458 // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
459
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98x if (!posix_signal_detail::flags_supported(flags))
460 return make_error_code(std::errc::operation_not_supported);
461
462 98x posix_signal_detail::signal_state* state =
463 98x posix_signal_detail::get_signal_state();
464 98x std::lock_guard state_lock(state->mutex);
465
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98x std::lock_guard lock(mutex_);
466
467 // Find insertion point (list is sorted by signal number)
468 98x signal_registration** insertion_point = &impl.signals_;
469 98x signal_registration* reg = impl.signals_;
470
4/4
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 98 times.
108x while (reg && reg->signal_number < signal_number)
471 {
472 10x insertion_point = &reg->next_in_set;
473 10x reg = reg->next_in_set;
474 }
475
476 // Already registered in this set - check flag compatibility
477 // (same signal_set adding same signal twice with different flags)
478
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 86 times.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 2 times.
98x if (reg && reg->signal_number == signal_number)
479 {
480
3/4
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 2 times.
10x if (!posix_signal_detail::flags_compatible(reg->flags, flags))
481 2x return make_error_code(std::errc::invalid_argument);
482 8x return {};
483 }
484
485 // Check flag compatibility with global registration
486 // (different signal_set already registered this signal with different flags)
487
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 80 times.
88x if (state->registration_count[signal_number] > 0)
488 {
489
3/4
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 2 times.
8x if (!posix_signal_detail::flags_compatible(
490 8x state->registered_flags[signal_number], flags))
491 2x return make_error_code(std::errc::invalid_argument);
492 6x }
493
494
1/2
✓ Branch 0 taken 86 times.
✗ Branch 1 not taken.
86x auto* new_reg = new signal_registration;
495 86x new_reg->signal_number = signal_number;
496 86x new_reg->flags = flags;
497 86x new_reg->owner = &impl;
498 86x new_reg->undelivered = 0;
499
500 // Install signal handler on first global registration
501
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 6 times.
86x if (state->registration_count[signal_number] == 0)
502 {
503 80x struct sigaction sa = {};
504 80x sa.sa_handler = posix_signal_detail::corosio_posix_signal_handler;
505 80x sigemptyset(&sa.sa_mask);
506
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80x sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
507
508
2/4
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 80 times.
✗ Branch 3 not taken.
80x if (::sigaction(signal_number, &sa, nullptr) < 0)
509 {
510 delete new_reg;
511 return make_error_code(std::errc::invalid_argument);
512 }
513
514 // Store the flags used for first registration
515 80x state->registered_flags[signal_number] = flags;
516 80x }
517
518 86x new_reg->next_in_set = reg;
519 86x *insertion_point = new_reg;
520
521 86x new_reg->next_in_table = registrations_[signal_number];
522 86x new_reg->prev_in_table = nullptr;
523
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 80 times.
86x if (registrations_[signal_number])
524 6x registrations_[signal_number]->prev_in_table = new_reg;
525 86x registrations_[signal_number] = new_reg;
526
527 86x ++state->registration_count[signal_number];
528 86x ++registration_count_[signal_number];
529
530 86x return {};
531 100x }
532
533 inline std::error_code
534 4x posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
535 {
536
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4x if (signal_number < 0 || signal_number >= max_signal_number)
537 return make_error_code(std::errc::invalid_argument);
538
539 4x posix_signal_detail::signal_state* state =
540 4x posix_signal_detail::get_signal_state();
541 4x std::lock_guard state_lock(state->mutex);
542
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4x std::lock_guard lock(mutex_);
543
544 4x signal_registration** deletion_point = &impl.signals_;
545 4x signal_registration* reg = impl.signals_;
546
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4x while (reg && reg->signal_number < signal_number)
547 {
548 deletion_point = &reg->next_in_set;
549 reg = reg->next_in_set;
550 }
551
552
3/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
4x if (!reg || reg->signal_number != signal_number)
553 2x return {};
554
555 // Restore default handler on last global unregistration
556
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2x if (state->registration_count[signal_number] == 1)
557 {
558 2x struct sigaction sa = {};
559 2x sa.sa_handler = SIG_DFL;
560 2x sigemptyset(&sa.sa_mask);
561 2x sa.sa_flags = 0;
562
563
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2x if (::sigaction(signal_number, &sa, nullptr) < 0)
564 return make_error_code(std::errc::invalid_argument);
565
566 // Clear stored flags
567 2x state->registered_flags[signal_number] = signal_set::none;
568 2x }
569
570 2x *deletion_point = reg->next_in_set;
571
572
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2x if (registrations_[signal_number] == reg)
573 2x registrations_[signal_number] = reg->next_in_table;
574
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x if (reg->prev_in_table)
575 reg->prev_in_table->next_in_table = reg->next_in_table;
576
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2x if (reg->next_in_table)
577 reg->next_in_table->prev_in_table = reg->prev_in_table;
578
579 2x --state->registration_count[signal_number];
580 2x --registration_count_[signal_number];
581
582
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2x delete reg;
583 2x return {};
584 4x }
585
586 inline std::error_code
587 98x posix_signal_service::clear_signals(posix_signal& impl)
588 {
589 98x posix_signal_detail::signal_state* state =
590 98x posix_signal_detail::get_signal_state();
591 98x std::lock_guard state_lock(state->mutex);
592
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98x std::lock_guard lock(mutex_);
593
594 98x std::error_code first_error;
595
596
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 98 times.
182x while (signal_registration* reg = impl.signals_)
597 {
598 84x int signal_number = reg->signal_number;
599
600
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 78 times.
84x if (state->registration_count[signal_number] == 1)
601 {
602 78x struct sigaction sa = {};
603 78x sa.sa_handler = SIG_DFL;
604 78x sigemptyset(&sa.sa_mask);
605 78x sa.sa_flags = 0;
606
607
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 78 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 78 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
78x if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
608 first_error = make_error_code(std::errc::invalid_argument);
609
610 // Clear stored flags
611 78x state->registered_flags[signal_number] = signal_set::none;
612 78x }
613
614 84x impl.signals_ = reg->next_in_set;
615
616
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 84 times.
84x if (registrations_[signal_number] == reg)
617 84x registrations_[signal_number] = reg->next_in_table;
618
1/2
✓ Branch 0 taken 84 times.
✗ Branch 1 not taken.
84x if (reg->prev_in_table)
619 reg->prev_in_table->next_in_table = reg->next_in_table;
620
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 6 times.
84x if (reg->next_in_table)
621 6x reg->next_in_table->prev_in_table = reg->prev_in_table;
622
623 84x --state->registration_count[signal_number];
624 84x --registration_count_[signal_number];
625
626
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 84 times.
84x delete reg;
627 }
628
629
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 98 times.
98x if (first_error)
630 return first_error;
631 98x return {};
632 98x }
633
634 inline void
635 106x posix_signal_service::cancel_wait(posix_signal& impl)
636 {
637 106x bool was_waiting = false;
638 106x signal_op* op = nullptr;
639
640 {
641 106x std::lock_guard lock(mutex_);
642
2/2
✓ Branch 0 taken 102 times.
✓ Branch 1 taken 4 times.
106x if (impl.waiting_)
643 {
644 4x was_waiting = true;
645 4x impl.waiting_ = false;
646 4x op = &impl.pending_op_;
647 4x }
648 106x }
649
650
2/2
✓ Branch 0 taken 102 times.
✓ Branch 1 taken 4 times.
106x if (was_waiting)
651 {
652
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4x if (op->ec_out)
653 4x *op->ec_out = make_error_code(capy::error::canceled);
654
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4x if (op->signal_out)
655 4x *op->signal_out = 0;
656 4x op->d.post(op->h);
657 4x sched_->work_finished();
658 4x }
659 106x }
660
661 inline void
662 26x posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
663 {
664 {
665 26x std::lock_guard lock(mutex_);
666
667 // Check for queued signals first (signal arrived before wait started)
668 26x signal_registration* reg = impl.signals_;
669
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 16 times.
44x while (reg)
670 {
671
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 10 times.
28x if (reg->undelivered > 0)
672 {
673 10x --reg->undelivered;
674 10x op->signal_number = reg->signal_number;
675 // svc=nullptr: no work_finished needed since we never called work_started
676 10x op->svc = nullptr;
677
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10x sched_->post(op);
678 10x return;
679 }
680 18x reg = reg->next_in_set;
681 }
682
683 // No queued signals - wait for delivery
684 16x impl.waiting_ = true;
685 // svc=this: signal_op::operator() will call work_finished() to balance this
686 16x op->svc = this;
687 16x sched_->work_started();
688 26x }
689 26x }
690
691 inline void
692 20x posix_signal_service::deliver_signal(int signal_number)
693 {
694
2/4
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 20 times.
20x if (signal_number < 0 || signal_number >= max_signal_number)
695 return;
696
697 20x posix_signal_detail::signal_state* state =
698 20x posix_signal_detail::get_signal_state();
699 20x std::lock_guard lock(state->mutex);
700
701 20x posix_signal_service* service = state->service_list;
702
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 20 times.
40x while (service)
703 {
704
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20x std::lock_guard svc_lock(service->mutex_);
705
706 20x signal_registration* reg = service->registrations_[signal_number];
707
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 20 times.
42x while (reg)
708 {
709 22x posix_signal* impl = static_cast<posix_signal*>(reg->owner);
710
711
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 10 times.
22x if (impl->waiting_)
712 {
713 12x impl->waiting_ = false;
714 12x impl->pending_op_.signal_number = signal_number;
715
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12x service->post(&impl->pending_op_);
716 12x }
717 else
718 {
719 10x ++reg->undelivered;
720 }
721
722 22x reg = reg->next_in_table;
723 }
724
725 20x service = service->next_;
726 20x }
727 20x }
728
729 inline void
730 posix_signal_service::work_started() noexcept
731 {
732 sched_->work_started();
733 }
734
735 inline void
736 12x posix_signal_service::work_finished() noexcept
737 {
738 12x sched_->work_finished();
739 12x }
740
741 inline void
742 12x posix_signal_service::post(signal_op* op)
743 {
744 12x sched_->post(op);
745 12x }
746
747 inline void
748 606x posix_signal_service::add_service(posix_signal_service* service)
749 {
750 606x posix_signal_detail::signal_state* state =
751 606x posix_signal_detail::get_signal_state();
752 606x std::lock_guard lock(state->mutex);
753
754 606x service->next_ = state->service_list;
755 606x service->prev_ = nullptr;
756
2/2
✓ Branch 0 taken 601 times.
✓ Branch 1 taken 5 times.
606x if (state->service_list)
757 5x state->service_list->prev_ = service;
758 606x state->service_list = service;
759 606x }
760
761 inline void
762 606x posix_signal_service::remove_service(posix_signal_service* service)
763 {
764 606x posix_signal_detail::signal_state* state =
765 606x posix_signal_detail::get_signal_state();
766 606x std::lock_guard lock(state->mutex);
767
768
4/6
✓ Branch 0 taken 601 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 601 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 601 times.
606x if (service->next_ || service->prev_ || state->service_list == service)
769 {
770
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 606 times.
606x if (state->service_list == service)
771 606x state->service_list = service->next_;
772
1/2
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
606x if (service->prev_)
773 service->prev_->next_ = service->next_;
774
2/2
✓ Branch 0 taken 601 times.
✓ Branch 1 taken 5 times.
606x if (service->next_)
775 5x service->next_->prev_ = service->prev_;
776 606x service->next_ = nullptr;
777 606x service->prev_ = nullptr;
778 606x }
779 606x }
780
781 // get_signal_service - factory function
782
783 inline posix_signal_service&
784 606x get_signal_service(capy::execution_context& ctx, scheduler& sched)
785 {
786 606x return ctx.make_service<posix_signal_service>(sched);
787 }
788
789 } // namespace detail
790 } // namespace boost::corosio
791
792 #endif // BOOST_COROSIO_POSIX
793
794 #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
795