libs/corosio/src/corosio/src/detail/posix/signals.cpp

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