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

89.5% Lines (290/324) 94.9% Functions (37/39) 70.9% Branches (122/172)
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/ex/executor_ref.hpp>
19 #include <boost/capy/error.hpp>
20 #include <system_error>
21
22 #include "src/detail/intrusive.hpp"
23 #include "src/detail/scheduler_op.hpp"
24
25 #include <coroutine>
26 #include <cstddef>
27 #include <mutex>
28 #include <stop_token>
29
30 #include <signal.h>
31
32 /*
33 POSIX Signal Implementation
34 ===========================
35
36 This file implements signal handling for POSIX systems using sigaction().
37 The implementation supports signal flags (SA_RESTART, etc.) and integrates
38 with any POSIX-compatible scheduler via the abstract scheduler interface.
39
40 Architecture Overview
41 ---------------------
42
43 Three layers manage signal registrations:
44
45 1. signal_state (global singleton)
46 - Tracks the global service list and per-signal registration counts
47 - Stores the flags used for first registration of each signal (for
48 conflict detection when multiple signal_sets register same signal)
49 - Owns the mutex that protects signal handler installation/removal
50
51 2. posix_signals_impl (one per execution_context)
52 - Maintains registrations_[] table indexed by signal number
53 - Each slot is a doubly-linked list of signal_registrations for that signal
54 - Also maintains impl_list_ of all posix_signal_impl objects it owns
55
56 3. posix_signal_impl (one per signal_set)
57 - Owns a singly-linked list (sorted by signal number) of signal_registrations
58 - Contains the pending_op_ used for wait operations
59
60 Signal Delivery Flow
61 --------------------
62
63 1. Signal arrives -> corosio_posix_signal_handler() (must be async-signal-safe)
64 -> deliver_signal()
65
66 2. deliver_signal() iterates all posix_signals_impl services:
67 - If a signal_set is waiting (impl->waiting_ == true), post the signal_op
68 to the scheduler for immediate completion
69 - Otherwise, increment reg->undelivered to queue the signal
70
71 3. When wait() is called via start_wait():
72 - First check for queued signals (undelivered > 0); if found, post
73 immediate completion without blocking
74 - Otherwise, set waiting_ = true and call on_work_started() to keep
75 the io_context alive
76
77 Locking Protocol
78 ----------------
79
80 Two mutex levels exist (MUST acquire in this order to avoid deadlock):
81 1. signal_state::mutex - protects handler registration and service list
82 2. posix_signals_impl::mutex_ - protects per-service registration tables
83
84 Async-Signal-Safety Limitation
85 ------------------------------
86
87 IMPORTANT: deliver_signal() is called from signal handler context and
88 acquires mutexes. This is NOT strictly async-signal-safe per POSIX.
89 The limitation:
90 - If a signal arrives while another thread holds state->mutex or
91 service->mutex_, and that same thread receives the signal, a
92 deadlock can occur (self-deadlock on non-recursive mutex).
93
94 This design trades strict async-signal-safety for implementation simplicity.
95 In practice, deadlocks are rare because:
96 - Mutexes are held only briefly during registration changes
97 - Most programs don't modify signal sets while signals are expected
98 - The window for signal arrival during mutex hold is small
99
100 A fully async-signal-safe implementation would require lock-free data
101 structures and atomic operations throughout, significantly increasing
102 complexity.
103
104 Flag Handling
105 -------------
106
107 - Flags are abstract values in the public API (signal_set::flags_t)
108 - flags_supported() validates that requested flags are available on
109 this platform; returns false if SA_NOCLDWAIT is unavailable and
110 no_child_wait is requested
111 - to_sigaction_flags() maps validated flags to actual SA_* constants
112 - First registration of a signal establishes the flags; subsequent
113 registrations must be compatible (same flags or dont_care)
114 - Requesting unavailable flags returns operation_not_supported
115
116 Work Tracking
117 -------------
118
119 When waiting for a signal:
120 - start_wait() calls sched_->on_work_started() to prevent io_context::run()
121 from returning while we wait
122 - signal_op::svc is set to point to the service
123 - signal_op::operator()() calls work_finished() after resuming the coroutine
124
125 If a signal was already queued (undelivered > 0), no work tracking is needed
126 because completion is posted immediately.
127 */
128
129 namespace boost::corosio {
130
131 namespace detail {
132
133 // Forward declarations
134 class posix_signals_impl;
135
136 // Maximum signal number supported (NSIG is typically 64 on Linux)
137 enum { max_signal_number = 64 };
138
139 //------------------------------------------------------------------------------
140 // signal_op - pending wait operation
141 //------------------------------------------------------------------------------
142
143 struct signal_op : scheduler_op
144 {
145 std::coroutine_handle<> h;
146 capy::executor_ref d;
147 std::error_code* ec_out = nullptr;
148 int* signal_out = nullptr;
149 int signal_number = 0;
150 posix_signals_impl* svc = nullptr; // For work_finished callback
151
152 void operator()() override;
153 void destroy() override;
154 };
155
156 //------------------------------------------------------------------------------
157 // signal_registration - per-signal registration tracking
158 //------------------------------------------------------------------------------
159
160 struct signal_registration
161 {
162 int signal_number = 0;
163 signal_set::flags_t flags = signal_set::none;
164 signal_set::implementation* owner = nullptr;
165 std::size_t undelivered = 0;
166 signal_registration* next_in_table = nullptr;
167 signal_registration* prev_in_table = nullptr;
168 signal_registration* next_in_set = nullptr;
169 };
170
171 //------------------------------------------------------------------------------
172 // posix_signal_impl - per-signal_set implementation
173 //------------------------------------------------------------------------------
174
175 class posix_signal_impl
176 : public signal_set::implementation
177 , public intrusive_list<posix_signal_impl>::node
178 {
179 friend class posix_signals_impl;
180
181 posix_signals_impl& svc_;
182 signal_registration* signals_ = nullptr;
183 signal_op pending_op_;
184 bool waiting_ = false;
185
186 public:
187 explicit posix_signal_impl(posix_signals_impl& svc) noexcept;
188
189 std::coroutine_handle<> wait(
190 std::coroutine_handle<>,
191 capy::executor_ref,
192 std::stop_token,
193 std::error_code*,
194 int*) override;
195
196 std::error_code add(int signal_number, signal_set::flags_t flags) override;
197 std::error_code remove(int signal_number) override;
198 std::error_code clear() override;
199 void cancel() override;
200 };
201
202 //------------------------------------------------------------------------------
203 // posix_signals_impl - concrete service implementation
204 //------------------------------------------------------------------------------
205
206 class posix_signals_impl : public posix_signals
207 {
208 public:
209 using key_type = posix_signals;
210
211 posix_signals_impl(capy::execution_context& ctx, scheduler& sched);
212 ~posix_signals_impl();
213
214 posix_signals_impl(posix_signals_impl const&) = delete;
215 posix_signals_impl& operator=(posix_signals_impl const&) = delete;
216
217 io_object::implementation* construct() override;
218
219 88 void destroy(io_object::implementation* p) override
220 {
221 88 auto& impl = static_cast<posix_signal_impl&>(*p);
222 88 impl.clear();
223 88 impl.cancel();
224 88 destroy_impl(impl);
225 88 }
226
227 void shutdown() override;
228
229 void destroy_impl(posix_signal_impl& impl);
230
231 std::error_code add_signal(
232 posix_signal_impl& impl,
233 int signal_number,
234 signal_set::flags_t flags);
235
236 std::error_code remove_signal(
237 posix_signal_impl& impl,
238 int signal_number);
239
240 std::error_code clear_signals(posix_signal_impl& impl);
241
242 void cancel_wait(posix_signal_impl& impl);
243 void start_wait(posix_signal_impl& impl, signal_op* op);
244
245 static void deliver_signal(int signal_number);
246
247 void work_started() noexcept;
248 void work_finished() noexcept;
249 void post(signal_op* op);
250
251 private:
252 static void add_service(posix_signals_impl* service);
253 static void remove_service(posix_signals_impl* service);
254
255 scheduler* sched_;
256 std::mutex mutex_;
257 intrusive_list<posix_signal_impl> impl_list_;
258
259 // Per-signal registration table
260 signal_registration* registrations_[max_signal_number];
261
262 // Registration counts for each signal
263 std::size_t registration_count_[max_signal_number];
264
265 // Linked list of all posix_signals_impl services for signal delivery
266 posix_signals_impl* next_ = nullptr;
267 posix_signals_impl* prev_ = nullptr;
268 };
269
270 //------------------------------------------------------------------------------
271 // Global signal state
272 //------------------------------------------------------------------------------
273
274 namespace {
275
276 struct signal_state
277 {
278 std::mutex mutex;
279 posix_signals_impl* service_list = nullptr;
280 std::size_t registration_count[max_signal_number] = {};
281 signal_set::flags_t registered_flags[max_signal_number] = {};
282 };
283
284 882 signal_state* get_signal_state()
285 {
286 static signal_state state;
287 882 return &state;
288 }
289
290 // Check if requested flags are supported on this platform.
291 // Returns true if all flags are supported, false otherwise.
292 94 bool flags_supported(signal_set::flags_t flags)
293 {
294 #ifndef SA_NOCLDWAIT
295 if (flags & signal_set::no_child_wait)
296 return false;
297 #endif
298 94 return true;
299 }
300
301 // Map abstract flags to sigaction() flags.
302 // Caller must ensure flags_supported() returns true first.
303 76 int to_sigaction_flags(signal_set::flags_t flags)
304 {
305 76 int sa_flags = 0;
306
2/2
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 58 times.
76 if (flags & signal_set::restart)
307 18 sa_flags |= SA_RESTART;
308
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
76 if (flags & signal_set::no_child_stop)
309 sa_flags |= SA_NOCLDSTOP;
310 #ifdef SA_NOCLDWAIT
311
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
76 if (flags & signal_set::no_child_wait)
312 sa_flags |= SA_NOCLDWAIT;
313 #endif
314
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 74 times.
76 if (flags & signal_set::no_defer)
315 2 sa_flags |= SA_NODEFER;
316
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
76 if (flags & signal_set::reset_handler)
317 sa_flags |= SA_RESETHAND;
318 76 return sa_flags;
319 }
320
321 // Check if two flag values are compatible
322 18 bool flags_compatible(
323 signal_set::flags_t existing,
324 signal_set::flags_t requested)
325 {
326 // dont_care is always compatible
327
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) ||
328 16 (requested & signal_set::dont_care))
329 6 return true;
330
331 // Mask out dont_care bit for comparison
332 12 constexpr auto mask = ~signal_set::dont_care;
333 12 return (existing & mask) == (requested & mask);
334 }
335
336 // C signal handler - must be async-signal-safe
337 20 extern "C" void corosio_posix_signal_handler(int signal_number)
338 {
339 20 posix_signals_impl::deliver_signal(signal_number);
340 // Note: With sigaction(), the handler persists automatically
341 // (unlike some signal() implementations that reset to SIG_DFL)
342 20 }
343
344 } // namespace
345
346 //------------------------------------------------------------------------------
347 // signal_op implementation
348 //------------------------------------------------------------------------------
349
350 void
351 22 signal_op::
352 operator()()
353 {
354
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (ec_out)
355 22 *ec_out = {};
356
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (signal_out)
357 22 *signal_out = signal_number;
358
359 // Capture svc before resuming (coro may destroy us)
360 22 auto* service = svc;
361 22 svc = nullptr;
362
363 22 d.post(h);
364
365 // Balance the on_work_started() from start_wait
366
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 10 times.
22 if (service)
367 12 service->work_finished();
368 22 }
369
370 void
371 signal_op::
372 destroy()
373 {
374 // No-op: signal_op is embedded in posix_signal_impl
375 }
376
377 //------------------------------------------------------------------------------
378 // posix_signal_impl implementation
379 //------------------------------------------------------------------------------
380
381 88 posix_signal_impl::
382 88 posix_signal_impl(posix_signals_impl& svc) noexcept
383 88 : svc_(svc)
384 {
385 88 }
386
387 std::coroutine_handle<>
388 26 posix_signal_impl::
389 wait(
390 std::coroutine_handle<> h,
391 capy::executor_ref d,
392 std::stop_token token,
393 std::error_code* ec,
394 int* signal_out)
395 {
396 26 pending_op_.h = h;
397 26 pending_op_.d = d;
398 26 pending_op_.ec_out = ec;
399 26 pending_op_.signal_out = signal_out;
400 26 pending_op_.signal_number = 0;
401
402
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
26 if (token.stop_requested())
403 {
404 if (ec)
405 *ec = make_error_code(capy::error::canceled);
406 if (signal_out)
407 *signal_out = 0;
408 d.post(h);
409 // completion is always posted to scheduler queue, never inline.
410 return std::noop_coroutine();
411 }
412
413 26 svc_.start_wait(*this, &pending_op_);
414 // completion is always posted to scheduler queue, never inline.
415 26 return std::noop_coroutine();
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 336 posix_signals_impl::
451 336 posix_signals_impl(capy::execution_context&, scheduler& sched)
452 336 : sched_(&sched)
453 {
454
2/2
✓ Branch 0 taken 21504 times.
✓ Branch 1 taken 336 times.
21840 for (int i = 0; i < max_signal_number; ++i)
455 {
456 21504 registrations_[i] = nullptr;
457 21504 registration_count_[i] = 0;
458 }
459
1/1
✓ Branch 1 taken 336 times.
336 add_service(this);
460 336 }
461
462 672 posix_signals_impl::
463 336 ~posix_signals_impl()
464 {
465 336 remove_service(this);
466 672 }
467
468 void
469 336 posix_signals_impl::
470 shutdown()
471 {
472
1/1
✓ Branch 1 taken 336 times.
336 std::lock_guard lock(mutex_);
473
474
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 336 times.
336 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 336 }
485
486 io_object::implementation*
487 88 posix_signals_impl::
488 construct()
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 336 posix_signals_impl::
819 add_service(posix_signals_impl* service)
820 {
821 336 signal_state* state = get_signal_state();
822
1/1
✓ Branch 1 taken 336 times.
336 std::lock_guard lock(state->mutex);
823
824 336 service->next_ = state->service_list;
825 336 service->prev_ = nullptr;
826
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 331 times.
336 if (state->service_list)
827 5 state->service_list->prev_ = service;
828 336 state->service_list = service;
829 336 }
830
831 void
832 336 posix_signals_impl::
833 remove_service(posix_signals_impl* service)
834 {
835 336 signal_state* state = get_signal_state();
836
1/1
✓ Branch 1 taken 336 times.
336 std::lock_guard lock(state->mutex);
837
838
4/6
✓ Branch 0 taken 331 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 331 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 331 times.
✗ Branch 5 not taken.
336 if (service->next_ || service->prev_ || state->service_list == service)
839 {
840
1/2
✓ Branch 0 taken 336 times.
✗ Branch 1 not taken.
336 if (state->service_list == service)
841 336 state->service_list = service->next_;
842
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 336 times.
336 if (service->prev_)
843 service->prev_->next_ = service->next_;
844
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 331 times.
336 if (service->next_)
845 5 service->next_->prev_ = service->prev_;
846 336 service->next_ = nullptr;
847 336 service->prev_ = nullptr;
848 }
849 336 }
850
851 //------------------------------------------------------------------------------
852 // get_signal_service - factory function
853 //------------------------------------------------------------------------------
854
855 posix_signals&
856 336 get_signal_service(capy::execution_context& ctx, scheduler& sched)
857 {
858 336 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 ~signal_set() = default;
869
870 88 signal_set::
871 88 signal_set(capy::execution_context& ctx)
872
1/1
✓ Branch 1 taken 88 times.
88 : io_object(create_handle<detail::posix_signals>(ctx))
873 {
874 88 }
875
876 2 signal_set::
877 2 signal_set(signal_set&& other) noexcept
878 2 : io_object(std::move(other))
879 {
880 2 }
881
882 signal_set&
883 4 signal_set::
884 operator=(signal_set&& other)
885 {
886
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (this != &other)
887 {
888
2/2
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
4 if (&context() != &other.context())
889 2 detail::throw_logic_error("signal_set::operator=: context mismatch");
890 2 h_ = std::move(other.h_);
891 }
892 2 return *this;
893 }
894
895 std::error_code
896 96 signal_set::
897 add(int signal_number, flags_t flags)
898 {
899 96 return get().add(signal_number, flags);
900 }
901
902 std::error_code
903 4 signal_set::
904 remove(int signal_number)
905 {
906 4 return get().remove(signal_number);
907 }
908
909 std::error_code
910 4 signal_set::
911 clear()
912 {
913 4 return get().clear();
914 }
915
916 void
917 12 signal_set::
918 cancel()
919 {
920 12 get().cancel();
921 12 }
922
923 } // namespace boost::corosio
924
925 #endif // BOOST_COROSIO_POSIX
926