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

89.6% Lines (301/336) 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/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::signal_set_impl* 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::signal_set_impl
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 void release() override;
190
191 std::coroutine_handle<> wait(
192 std::coroutine_handle<>,
193 capy::executor_ref,
194 std::stop_token,
195 std::error_code*,
196 int*) override;
197
198 std::error_code add(int signal_number, signal_set::flags_t flags) override;
199 std::error_code remove(int signal_number) override;
200 std::error_code clear() override;
201 void cancel() override;
202 };
203
204 //------------------------------------------------------------------------------
205 // posix_signals_impl - concrete service implementation
206 //------------------------------------------------------------------------------
207
208 class posix_signals_impl : public posix_signals
209 {
210 public:
211 using key_type = posix_signals;
212
213 posix_signals_impl(capy::execution_context& ctx, scheduler& sched);
214 ~posix_signals_impl();
215
216 posix_signals_impl(posix_signals_impl const&) = delete;
217 posix_signals_impl& operator=(posix_signals_impl const&) = delete;
218
219 void shutdown() override;
220 signal_set::signal_set_impl& create_impl() override;
221
222 void destroy_impl(posix_signal_impl& impl);
223
224 std::error_code add_signal(
225 posix_signal_impl& impl,
226 int signal_number,
227 signal_set::flags_t flags);
228
229 std::error_code remove_signal(
230 posix_signal_impl& impl,
231 int signal_number);
232
233 std::error_code clear_signals(posix_signal_impl& impl);
234
235 void cancel_wait(posix_signal_impl& impl);
236 void start_wait(posix_signal_impl& impl, signal_op* op);
237
238 static void deliver_signal(int signal_number);
239
240 void work_started() noexcept;
241 void work_finished() noexcept;
242 void post(signal_op* op);
243
244 private:
245 static void add_service(posix_signals_impl* service);
246 static void remove_service(posix_signals_impl* service);
247
248 scheduler* sched_;
249 std::mutex mutex_;
250 intrusive_list<posix_signal_impl> impl_list_;
251
252 // Per-signal registration table
253 signal_registration* registrations_[max_signal_number];
254
255 // Registration counts for each signal
256 std::size_t registration_count_[max_signal_number];
257
258 // Linked list of all posix_signals_impl services for signal delivery
259 posix_signals_impl* next_ = nullptr;
260 posix_signals_impl* prev_ = nullptr;
261 };
262
263 //------------------------------------------------------------------------------
264 // Global signal state
265 //------------------------------------------------------------------------------
266
267 namespace {
268
269 struct signal_state
270 {
271 std::mutex mutex;
272 posix_signals_impl* service_list = nullptr;
273 std::size_t registration_count[max_signal_number] = {};
274 signal_set::flags_t registered_flags[max_signal_number] = {};
275 };
276
277 882 signal_state* get_signal_state()
278 {
279 static signal_state state;
280 882 return &state;
281 }
282
283 // Check if requested flags are supported on this platform.
284 // Returns true if all flags are supported, false otherwise.
285 94 bool flags_supported(signal_set::flags_t flags)
286 {
287 #ifndef SA_NOCLDWAIT
288 if (flags & signal_set::no_child_wait)
289 return false;
290 #endif
291 94 return true;
292 }
293
294 // Map abstract flags to sigaction() flags.
295 // Caller must ensure flags_supported() returns true first.
296 76 int to_sigaction_flags(signal_set::flags_t flags)
297 {
298 76 int sa_flags = 0;
299
2/2
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 58 times.
76 if (flags & signal_set::restart)
300 18 sa_flags |= SA_RESTART;
301
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
76 if (flags & signal_set::no_child_stop)
302 sa_flags |= SA_NOCLDSTOP;
303 #ifdef SA_NOCLDWAIT
304
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
76 if (flags & signal_set::no_child_wait)
305 sa_flags |= SA_NOCLDWAIT;
306 #endif
307
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 74 times.
76 if (flags & signal_set::no_defer)
308 2 sa_flags |= SA_NODEFER;
309
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
76 if (flags & signal_set::reset_handler)
310 sa_flags |= SA_RESETHAND;
311 76 return sa_flags;
312 }
313
314 // Check if two flag values are compatible
315 18 bool flags_compatible(
316 signal_set::flags_t existing,
317 signal_set::flags_t requested)
318 {
319 // dont_care is always compatible
320
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) ||
321 16 (requested & signal_set::dont_care))
322 6 return true;
323
324 // Mask out dont_care bit for comparison
325 12 constexpr auto mask = ~signal_set::dont_care;
326 12 return (existing & mask) == (requested & mask);
327 }
328
329 // C signal handler - must be async-signal-safe
330 20 extern "C" void corosio_posix_signal_handler(int signal_number)
331 {
332 20 posix_signals_impl::deliver_signal(signal_number);
333 // Note: With sigaction(), the handler persists automatically
334 // (unlike some signal() implementations that reset to SIG_DFL)
335 20 }
336
337 } // namespace
338
339 //------------------------------------------------------------------------------
340 // signal_op implementation
341 //------------------------------------------------------------------------------
342
343 void
344 22 signal_op::
345 operator()()
346 {
347
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (ec_out)
348 22 *ec_out = {};
349
1/2
✓ Branch 0 taken 22 times.
✗ Branch 1 not taken.
22 if (signal_out)
350 22 *signal_out = signal_number;
351
352 // Capture svc before resuming (coro may destroy us)
353 22 auto* service = svc;
354 22 svc = nullptr;
355
356 22 d.post(h);
357
358 // Balance the on_work_started() from start_wait
359
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 10 times.
22 if (service)
360 12 service->work_finished();
361 22 }
362
363 void
364 signal_op::
365 destroy()
366 {
367 // No-op: signal_op is embedded in posix_signal_impl
368 }
369
370 //------------------------------------------------------------------------------
371 // posix_signal_impl implementation
372 //------------------------------------------------------------------------------
373
374 88 posix_signal_impl::
375 88 posix_signal_impl(posix_signals_impl& svc) noexcept
376 88 : svc_(svc)
377 {
378 88 }
379
380 void
381 88 posix_signal_impl::
382 release()
383 {
384 88 clear();
385 88 cancel();
386 88 svc_.destroy_impl(*this);
387 88 }
388
389 std::coroutine_handle<>
390 26 posix_signal_impl::
391 wait(
392 std::coroutine_handle<> h,
393 capy::executor_ref d,
394 std::stop_token token,
395 std::error_code* ec,
396 int* signal_out)
397 {
398 26 pending_op_.h = h;
399 26 pending_op_.d = d;
400 26 pending_op_.ec_out = ec;
401 26 pending_op_.signal_out = signal_out;
402 26 pending_op_.signal_number = 0;
403
404
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
26 if (token.stop_requested())
405 {
406 if (ec)
407 *ec = make_error_code(capy::error::canceled);
408 if (signal_out)
409 *signal_out = 0;
410 d.post(h);
411 // completion is always posted to scheduler queue, never inline.
412 return std::noop_coroutine();
413 }
414
415 26 svc_.start_wait(*this, &pending_op_);
416 // completion is always posted to scheduler queue, never inline.
417 26 return std::noop_coroutine();
418 }
419
420 std::error_code
421 96 posix_signal_impl::
422 add(int signal_number, signal_set::flags_t flags)
423 {
424 96 return svc_.add_signal(*this, signal_number, flags);
425 }
426
427 std::error_code
428 4 posix_signal_impl::
429 remove(int signal_number)
430 {
431 4 return svc_.remove_signal(*this, signal_number);
432 }
433
434 std::error_code
435 92 posix_signal_impl::
436 clear()
437 {
438 92 return svc_.clear_signals(*this);
439 }
440
441 void
442 100 posix_signal_impl::
443 cancel()
444 {
445 100 svc_.cancel_wait(*this);
446 100 }
447
448 //------------------------------------------------------------------------------
449 // posix_signals_impl implementation
450 //------------------------------------------------------------------------------
451
452 336 posix_signals_impl::
453 336 posix_signals_impl(capy::execution_context&, scheduler& sched)
454 336 : sched_(&sched)
455 {
456
2/2
✓ Branch 0 taken 21504 times.
✓ Branch 1 taken 336 times.
21840 for (int i = 0; i < max_signal_number; ++i)
457 {
458 21504 registrations_[i] = nullptr;
459 21504 registration_count_[i] = 0;
460 }
461
1/1
✓ Branch 1 taken 336 times.
336 add_service(this);
462 336 }
463
464 672 posix_signals_impl::
465 336 ~posix_signals_impl()
466 {
467 336 remove_service(this);
468 672 }
469
470 void
471 336 posix_signals_impl::
472 shutdown()
473 {
474
1/1
✓ Branch 1 taken 336 times.
336 std::lock_guard lock(mutex_);
475
476
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 336 times.
336 for (auto* impl = impl_list_.pop_front(); impl != nullptr;
477 impl = impl_list_.pop_front())
478 {
479 while (auto* reg = impl->signals_)
480 {
481 impl->signals_ = reg->next_in_set;
482 delete reg;
483 }
484 delete impl;
485 }
486 336 }
487
488 signal_set::signal_set_impl&
489 88 posix_signals_impl::
490 create_impl()
491 {
492 88 auto* impl = new posix_signal_impl(*this);
493
494 {
495
1/1
✓ Branch 1 taken 88 times.
88 std::lock_guard lock(mutex_);
496 88 impl_list_.push_back(impl);
497 88 }
498
499 88 return *impl;
500 }
501
502 void
503 88 posix_signals_impl::
504 destroy_impl(posix_signal_impl& impl)
505 {
506 {
507
1/1
✓ Branch 1 taken 88 times.
88 std::lock_guard lock(mutex_);
508 88 impl_list_.remove(&impl);
509 88 }
510
511
1/2
✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
88 delete &impl;
512 88 }
513
514 std::error_code
515 96 posix_signals_impl::
516 add_signal(
517 posix_signal_impl& impl,
518 int signal_number,
519 signal_set::flags_t flags)
520 {
521
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)
522 2 return make_error_code(std::errc::invalid_argument);
523
524 // Validate that requested flags are supported on this platform
525 // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
526
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 94 times.
94 if (!flags_supported(flags))
527 return make_error_code(std::errc::operation_not_supported);
528
529 94 signal_state* state = get_signal_state();
530
1/1
✓ Branch 1 taken 94 times.
94 std::lock_guard state_lock(state->mutex);
531
1/1
✓ Branch 1 taken 94 times.
94 std::lock_guard lock(mutex_);
532
533 // Find insertion point (list is sorted by signal number)
534 94 signal_registration** insertion_point = &impl.signals_;
535 94 signal_registration* reg = impl.signals_;
536
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)
537 {
538 10 insertion_point = &reg->next_in_set;
539 10 reg = reg->next_in_set;
540 }
541
542 // Already registered in this set - check flag compatibility
543 // (same signal_set adding same signal twice with different flags)
544
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)
545 {
546
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 8 times.
10 if (!flags_compatible(reg->flags, flags))
547 2 return make_error_code(std::errc::invalid_argument);
548 8 return {};
549 }
550
551 // Check flag compatibility with global registration
552 // (different signal_set already registered this signal with different flags)
553
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 76 times.
84 if (state->registration_count[signal_number] > 0)
554 {
555
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 6 times.
8 if (!flags_compatible(state->registered_flags[signal_number], flags))
556 2 return make_error_code(std::errc::invalid_argument);
557 }
558
559
1/1
✓ Branch 1 taken 82 times.
82 auto* new_reg = new signal_registration;
560 82 new_reg->signal_number = signal_number;
561 82 new_reg->flags = flags;
562 82 new_reg->owner = &impl;
563 82 new_reg->undelivered = 0;
564
565 // Install signal handler on first global registration
566
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 6 times.
82 if (state->registration_count[signal_number] == 0)
567 {
568 76 struct sigaction sa = {};
569 76 sa.sa_handler = corosio_posix_signal_handler;
570 76 sigemptyset(&sa.sa_mask);
571 76 sa.sa_flags = to_sigaction_flags(flags);
572
573
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
76 if (::sigaction(signal_number, &sa, nullptr) < 0)
574 {
575 delete new_reg;
576 return make_error_code(std::errc::invalid_argument);
577 }
578
579 // Store the flags used for first registration
580 76 state->registered_flags[signal_number] = flags;
581 }
582
583 82 new_reg->next_in_set = reg;
584 82 *insertion_point = new_reg;
585
586 82 new_reg->next_in_table = registrations_[signal_number];
587 82 new_reg->prev_in_table = nullptr;
588
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 76 times.
82 if (registrations_[signal_number])
589 6 registrations_[signal_number]->prev_in_table = new_reg;
590 82 registrations_[signal_number] = new_reg;
591
592 82 ++state->registration_count[signal_number];
593 82 ++registration_count_[signal_number];
594
595 82 return {};
596 94 }
597
598 std::error_code
599 4 posix_signals_impl::
600 remove_signal(
601 posix_signal_impl& impl,
602 int signal_number)
603 {
604
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)
605 return make_error_code(std::errc::invalid_argument);
606
607 4 signal_state* state = get_signal_state();
608
1/1
✓ Branch 1 taken 4 times.
4 std::lock_guard state_lock(state->mutex);
609
1/1
✓ Branch 1 taken 4 times.
4 std::lock_guard lock(mutex_);
610
611 4 signal_registration** deletion_point = &impl.signals_;
612 4 signal_registration* reg = impl.signals_;
613
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)
614 {
615 deletion_point = &reg->next_in_set;
616 reg = reg->next_in_set;
617 }
618
619
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)
620 2 return {};
621
622 // Restore default handler on last global unregistration
623
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (state->registration_count[signal_number] == 1)
624 {
625 2 struct sigaction sa = {};
626 2 sa.sa_handler = SIG_DFL;
627 2 sigemptyset(&sa.sa_mask);
628 2 sa.sa_flags = 0;
629
630
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (::sigaction(signal_number, &sa, nullptr) < 0)
631 return make_error_code(std::errc::invalid_argument);
632
633 // Clear stored flags
634 2 state->registered_flags[signal_number] = signal_set::none;
635 }
636
637 2 *deletion_point = reg->next_in_set;
638
639
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (registrations_[signal_number] == reg)
640 2 registrations_[signal_number] = reg->next_in_table;
641
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (reg->prev_in_table)
642 reg->prev_in_table->next_in_table = reg->next_in_table;
643
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (reg->next_in_table)
644 reg->next_in_table->prev_in_table = reg->prev_in_table;
645
646 2 --state->registration_count[signal_number];
647 2 --registration_count_[signal_number];
648
649
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 delete reg;
650 2 return {};
651 4 }
652
653 std::error_code
654 92 posix_signals_impl::
655 clear_signals(posix_signal_impl& impl)
656 {
657 92 signal_state* state = get_signal_state();
658
1/1
✓ Branch 1 taken 92 times.
92 std::lock_guard state_lock(state->mutex);
659
1/1
✓ Branch 1 taken 92 times.
92 std::lock_guard lock(mutex_);
660
661 92 std::error_code first_error;
662
663
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 92 times.
172 while (signal_registration* reg = impl.signals_)
664 {
665 80 int signal_number = reg->signal_number;
666
667
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 6 times.
80 if (state->registration_count[signal_number] == 1)
668 {
669 74 struct sigaction sa = {};
670 74 sa.sa_handler = SIG_DFL;
671 74 sigemptyset(&sa.sa_mask);
672 74 sa.sa_flags = 0;
673
674
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)
675 first_error = make_error_code(std::errc::invalid_argument);
676
677 // Clear stored flags
678 74 state->registered_flags[signal_number] = signal_set::none;
679 }
680
681 80 impl.signals_ = reg->next_in_set;
682
683
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80 if (registrations_[signal_number] == reg)
684 80 registrations_[signal_number] = reg->next_in_table;
685
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 80 times.
80 if (reg->prev_in_table)
686 reg->prev_in_table->next_in_table = reg->next_in_table;
687
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 74 times.
80 if (reg->next_in_table)
688 6 reg->next_in_table->prev_in_table = reg->prev_in_table;
689
690 80 --state->registration_count[signal_number];
691 80 --registration_count_[signal_number];
692
693
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80 delete reg;
694 80 }
695
696
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 92 times.
92 if (first_error)
697 return first_error;
698 92 return {};
699 92 }
700
701 void
702 100 posix_signals_impl::
703 cancel_wait(posix_signal_impl& impl)
704 {
705 100 bool was_waiting = false;
706 100 signal_op* op = nullptr;
707
708 {
709
1/1
✓ Branch 1 taken 100 times.
100 std::lock_guard lock(mutex_);
710
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 96 times.
100 if (impl.waiting_)
711 {
712 4 was_waiting = true;
713 4 impl.waiting_ = false;
714 4 op = &impl.pending_op_;
715 }
716 100 }
717
718
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 96 times.
100 if (was_waiting)
719 {
720
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (op->ec_out)
721 4 *op->ec_out = make_error_code(capy::error::canceled);
722
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (op->signal_out)
723 4 *op->signal_out = 0;
724 4 op->d.post(op->h);
725 4 sched_->on_work_finished();
726 }
727 100 }
728
729 void
730 26 posix_signals_impl::
731 start_wait(posix_signal_impl& impl, signal_op* op)
732 {
733 {
734
1/1
✓ Branch 1 taken 26 times.
26 std::lock_guard lock(mutex_);
735
736 // Check for queued signals first (signal arrived before wait started)
737 26 signal_registration* reg = impl.signals_;
738
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 16 times.
44 while (reg)
739 {
740
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 18 times.
28 if (reg->undelivered > 0)
741 {
742 10 --reg->undelivered;
743 10 op->signal_number = reg->signal_number;
744 // svc=nullptr: no work_finished needed since we never called work_started
745 10 op->svc = nullptr;
746
1/1
✓ Branch 1 taken 10 times.
10 sched_->post(op);
747 10 return;
748 }
749 18 reg = reg->next_in_set;
750 }
751
752 // No queued signals - wait for delivery
753 16 impl.waiting_ = true;
754 // svc=this: signal_op::operator() will call work_finished() to balance this
755 16 op->svc = this;
756 16 sched_->on_work_started();
757 26 }
758 }
759
760 void
761 20 posix_signals_impl::
762 deliver_signal(int signal_number)
763 {
764
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)
765 return;
766
767 20 signal_state* state = get_signal_state();
768
1/1
✓ Branch 1 taken 20 times.
20 std::lock_guard lock(state->mutex);
769
770 20 posix_signals_impl* service = state->service_list;
771
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 20 times.
40 while (service)
772 {
773
1/1
✓ Branch 1 taken 20 times.
20 std::lock_guard svc_lock(service->mutex_);
774
775 20 signal_registration* reg = service->registrations_[signal_number];
776
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 20 times.
42 while (reg)
777 {
778 22 posix_signal_impl* impl = static_cast<posix_signal_impl*>(reg->owner);
779
780
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 10 times.
22 if (impl->waiting_)
781 {
782 12 impl->waiting_ = false;
783 12 impl->pending_op_.signal_number = signal_number;
784
1/1
✓ Branch 1 taken 12 times.
12 service->post(&impl->pending_op_);
785 }
786 else
787 {
788 10 ++reg->undelivered;
789 }
790
791 22 reg = reg->next_in_table;
792 }
793
794 20 service = service->next_;
795 20 }
796 20 }
797
798 void
799 posix_signals_impl::
800 work_started() noexcept
801 {
802 sched_->work_started();
803 }
804
805 void
806 12 posix_signals_impl::
807 work_finished() noexcept
808 {
809 12 sched_->work_finished();
810 12 }
811
812 void
813 12 posix_signals_impl::
814 post(signal_op* op)
815 {
816 12 sched_->post(op);
817 12 }
818
819 void
820 336 posix_signals_impl::
821 add_service(posix_signals_impl* service)
822 {
823 336 signal_state* state = get_signal_state();
824
1/1
✓ Branch 1 taken 336 times.
336 std::lock_guard lock(state->mutex);
825
826 336 service->next_ = state->service_list;
827 336 service->prev_ = nullptr;
828
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 331 times.
336 if (state->service_list)
829 5 state->service_list->prev_ = service;
830 336 state->service_list = service;
831 336 }
832
833 void
834 336 posix_signals_impl::
835 remove_service(posix_signals_impl* service)
836 {
837 336 signal_state* state = get_signal_state();
838
1/1
✓ Branch 1 taken 336 times.
336 std::lock_guard lock(state->mutex);
839
840
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)
841 {
842
1/2
✓ Branch 0 taken 336 times.
✗ Branch 1 not taken.
336 if (state->service_list == service)
843 336 state->service_list = service->next_;
844
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 336 times.
336 if (service->prev_)
845 service->prev_->next_ = service->next_;
846
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 331 times.
336 if (service->next_)
847 5 service->next_->prev_ = service->prev_;
848 336 service->next_ = nullptr;
849 336 service->prev_ = nullptr;
850 }
851 336 }
852
853 //------------------------------------------------------------------------------
854 // get_signal_service - factory function
855 //------------------------------------------------------------------------------
856
857 posix_signals&
858 336 get_signal_service(capy::execution_context& ctx, scheduler& sched)
859 {
860 336 return ctx.make_service<posix_signals_impl>(sched);
861 }
862
863 } // namespace detail
864
865 //------------------------------------------------------------------------------
866 // signal_set implementation
867 //------------------------------------------------------------------------------
868
869 90 signal_set::
870 90 ~signal_set()
871 {
872
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 4 times.
90 if (impl_)
873 86 impl_->release();
874 90 }
875
876 88 signal_set::
877 88 signal_set(capy::execution_context& ctx)
878 88 : io_object(ctx)
879 {
880 88 auto* svc = ctx.find_service<detail::posix_signals>();
881
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 if (!svc)
882 detail::throw_logic_error("signal_set: signal service not initialized");
883
1/1
✓ Branch 1 taken 88 times.
88 impl_ = &svc->create_impl();
884 88 }
885
886 2 signal_set::
887 2 signal_set(signal_set&& other) noexcept
888 2 : io_object(std::move(other))
889 {
890 2 impl_ = other.impl_;
891 2 other.impl_ = nullptr;
892 2 }
893
894 signal_set&
895 4 signal_set::
896 operator=(signal_set&& other)
897 {
898
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (this != &other)
899 {
900
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (ctx_ != other.ctx_)
901 2 detail::throw_logic_error("signal_set::operator=: context mismatch");
902
903
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (impl_)
904 2 impl_->release();
905
906 2 impl_ = other.impl_;
907 2 other.impl_ = nullptr;
908 }
909 2 return *this;
910 }
911
912 std::error_code
913 96 signal_set::
914 add(int signal_number, flags_t flags)
915 {
916 96 return get().add(signal_number, flags);
917 }
918
919 std::error_code
920 4 signal_set::
921 remove(int signal_number)
922 {
923 4 return get().remove(signal_number);
924 }
925
926 std::error_code
927 4 signal_set::
928 clear()
929 {
930 4 return get().clear();
931 }
932
933 void
934 12 signal_set::
935 cancel()
936 {
937 12 get().cancel();
938 12 }
939
940 } // namespace boost::corosio
941
942 #endif // BOOST_COROSIO_POSIX
943