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/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 std::coroutine_handle<> 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 828 signal_state* get_signal_state()
279 {
280 static signal_state state;
281 828 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 std::coroutine_handle<>
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 // completion is always posted to scheduler queue, never inline.
413 return std::noop_coroutine();
414 }
415
416 26 svc_.start_wait(*this, &pending_op_);
417 // completion is always posted to scheduler queue, never inline.
418 26 return std::noop_coroutine();
419 }
420
421 std::error_code
422 96 posix_signal_impl::
423 add(int signal_number, signal_set::flags_t flags)
424 {
425 96 return svc_.add_signal(*this, signal_number, flags);
426 }
427
428 std::error_code
429 4 posix_signal_impl::
430 remove(int signal_number)
431 {
432 4 return svc_.remove_signal(*this, signal_number);
433 }
434
435 std::error_code
436 92 posix_signal_impl::
437 clear()
438 {
439 92 return svc_.clear_signals(*this);
440 }
441
442 void
443 100 posix_signal_impl::
444 cancel()
445 {
446 100 svc_.cancel_wait(*this);
447 100 }
448
449 //------------------------------------------------------------------------------
450 // posix_signals_impl implementation
451 //------------------------------------------------------------------------------
452
453 309 posix_signals_impl::
454 309 posix_signals_impl(capy::execution_context&, scheduler& sched)
455 309 : sched_(&sched)
456 {
457
2/2
✓ Branch 0 taken 19776 times.
✓ Branch 1 taken 309 times.
20085 for (int i = 0; i < max_signal_number; ++i)
458 {
459 19776 registrations_[i] = nullptr;
460 19776 registration_count_[i] = 0;
461 }
462
1/1
✓ Branch 1 taken 309 times.
309 add_service(this);
463 309 }
464
465 618 posix_signals_impl::
466 309 ~posix_signals_impl()
467 {
468 309 remove_service(this);
469 618 }
470
471 void
472 309 posix_signals_impl::
473 shutdown()
474 {
475
1/1
✓ Branch 1 taken 309 times.
309 std::lock_guard lock(mutex_);
476
477
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 309 times.
309 for (auto* impl = impl_list_.pop_front(); impl != nullptr;
478 impl = impl_list_.pop_front())
479 {
480 while (auto* reg = impl->signals_)
481 {
482 impl->signals_ = reg->next_in_set;
483 delete reg;
484 }
485 delete impl;
486 }
487 309 }
488
489 signal_set::signal_set_impl&
490 88 posix_signals_impl::
491 create_impl()
492 {
493 88 auto* impl = new posix_signal_impl(*this);
494
495 {
496
1/1
✓ Branch 1 taken 88 times.
88 std::lock_guard lock(mutex_);
497 88 impl_list_.push_back(impl);
498 88 }
499
500 88 return *impl;
501 }
502
503 void
504 88 posix_signals_impl::
505 destroy_impl(posix_signal_impl& impl)
506 {
507 {
508
1/1
✓ Branch 1 taken 88 times.
88 std::lock_guard lock(mutex_);
509 88 impl_list_.remove(&impl);
510 88 }
511
512
1/2
✓ Branch 0 taken 88 times.
✗ Branch 1 not taken.
88 delete &impl;
513 88 }
514
515 std::error_code
516 96 posix_signals_impl::
517 add_signal(
518 posix_signal_impl& impl,
519 int signal_number,
520 signal_set::flags_t flags)
521 {
522
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)
523 2 return make_error_code(std::errc::invalid_argument);
524
525 // Validate that requested flags are supported on this platform
526 // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
527
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 94 times.
94 if (!flags_supported(flags))
528 return make_error_code(std::errc::operation_not_supported);
529
530 94 signal_state* state = get_signal_state();
531
1/1
✓ Branch 1 taken 94 times.
94 std::lock_guard state_lock(state->mutex);
532
1/1
✓ Branch 1 taken 94 times.
94 std::lock_guard lock(mutex_);
533
534 // Find insertion point (list is sorted by signal number)
535 94 signal_registration** insertion_point = &impl.signals_;
536 94 signal_registration* reg = impl.signals_;
537
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)
538 {
539 10 insertion_point = &reg->next_in_set;
540 10 reg = reg->next_in_set;
541 }
542
543 // Already registered in this set - check flag compatibility
544 // (same signal_set adding same signal twice with different flags)
545
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)
546 {
547
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 8 times.
10 if (!flags_compatible(reg->flags, flags))
548 2 return make_error_code(std::errc::invalid_argument);
549 8 return {};
550 }
551
552 // Check flag compatibility with global registration
553 // (different signal_set already registered this signal with different flags)
554
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 76 times.
84 if (state->registration_count[signal_number] > 0)
555 {
556
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 6 times.
8 if (!flags_compatible(state->registered_flags[signal_number], flags))
557 2 return make_error_code(std::errc::invalid_argument);
558 }
559
560
1/1
✓ Branch 1 taken 82 times.
82 auto* new_reg = new signal_registration;
561 82 new_reg->signal_number = signal_number;
562 82 new_reg->flags = flags;
563 82 new_reg->owner = &impl;
564 82 new_reg->undelivered = 0;
565
566 // Install signal handler on first global registration
567
2/2
✓ Branch 0 taken 76 times.
✓ Branch 1 taken 6 times.
82 if (state->registration_count[signal_number] == 0)
568 {
569 76 struct sigaction sa = {};
570 76 sa.sa_handler = corosio_posix_signal_handler;
571 76 sigemptyset(&sa.sa_mask);
572 76 sa.sa_flags = to_sigaction_flags(flags);
573
574
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 76 times.
76 if (::sigaction(signal_number, &sa, nullptr) < 0)
575 {
576 delete new_reg;
577 return make_error_code(std::errc::invalid_argument);
578 }
579
580 // Store the flags used for first registration
581 76 state->registered_flags[signal_number] = flags;
582 }
583
584 82 new_reg->next_in_set = reg;
585 82 *insertion_point = new_reg;
586
587 82 new_reg->next_in_table = registrations_[signal_number];
588 82 new_reg->prev_in_table = nullptr;
589
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 76 times.
82 if (registrations_[signal_number])
590 6 registrations_[signal_number]->prev_in_table = new_reg;
591 82 registrations_[signal_number] = new_reg;
592
593 82 ++state->registration_count[signal_number];
594 82 ++registration_count_[signal_number];
595
596 82 return {};
597 94 }
598
599 std::error_code
600 4 posix_signals_impl::
601 remove_signal(
602 posix_signal_impl& impl,
603 int signal_number)
604 {
605
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)
606 return make_error_code(std::errc::invalid_argument);
607
608 4 signal_state* state = get_signal_state();
609
1/1
✓ Branch 1 taken 4 times.
4 std::lock_guard state_lock(state->mutex);
610
1/1
✓ Branch 1 taken 4 times.
4 std::lock_guard lock(mutex_);
611
612 4 signal_registration** deletion_point = &impl.signals_;
613 4 signal_registration* reg = impl.signals_;
614
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)
615 {
616 deletion_point = &reg->next_in_set;
617 reg = reg->next_in_set;
618 }
619
620
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)
621 2 return {};
622
623 // Restore default handler on last global unregistration
624
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (state->registration_count[signal_number] == 1)
625 {
626 2 struct sigaction sa = {};
627 2 sa.sa_handler = SIG_DFL;
628 2 sigemptyset(&sa.sa_mask);
629 2 sa.sa_flags = 0;
630
631
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if (::sigaction(signal_number, &sa, nullptr) < 0)
632 return make_error_code(std::errc::invalid_argument);
633
634 // Clear stored flags
635 2 state->registered_flags[signal_number] = signal_set::none;
636 }
637
638 2 *deletion_point = reg->next_in_set;
639
640
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (registrations_[signal_number] == reg)
641 2 registrations_[signal_number] = reg->next_in_table;
642
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (reg->prev_in_table)
643 reg->prev_in_table->next_in_table = reg->next_in_table;
644
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (reg->next_in_table)
645 reg->next_in_table->prev_in_table = reg->prev_in_table;
646
647 2 --state->registration_count[signal_number];
648 2 --registration_count_[signal_number];
649
650
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 delete reg;
651 2 return {};
652 4 }
653
654 std::error_code
655 92 posix_signals_impl::
656 clear_signals(posix_signal_impl& impl)
657 {
658 92 signal_state* state = get_signal_state();
659
1/1
✓ Branch 1 taken 92 times.
92 std::lock_guard state_lock(state->mutex);
660
1/1
✓ Branch 1 taken 92 times.
92 std::lock_guard lock(mutex_);
661
662 92 std::error_code first_error;
663
664
2/2
✓ Branch 0 taken 80 times.
✓ Branch 1 taken 92 times.
172 while (signal_registration* reg = impl.signals_)
665 {
666 80 int signal_number = reg->signal_number;
667
668
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 6 times.
80 if (state->registration_count[signal_number] == 1)
669 {
670 74 struct sigaction sa = {};
671 74 sa.sa_handler = SIG_DFL;
672 74 sigemptyset(&sa.sa_mask);
673 74 sa.sa_flags = 0;
674
675
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)
676 first_error = make_error_code(std::errc::invalid_argument);
677
678 // Clear stored flags
679 74 state->registered_flags[signal_number] = signal_set::none;
680 }
681
682 80 impl.signals_ = reg->next_in_set;
683
684
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80 if (registrations_[signal_number] == reg)
685 80 registrations_[signal_number] = reg->next_in_table;
686
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 80 times.
80 if (reg->prev_in_table)
687 reg->prev_in_table->next_in_table = reg->next_in_table;
688
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 74 times.
80 if (reg->next_in_table)
689 6 reg->next_in_table->prev_in_table = reg->prev_in_table;
690
691 80 --state->registration_count[signal_number];
692 80 --registration_count_[signal_number];
693
694
1/2
✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
80 delete reg;
695 80 }
696
697
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 92 times.
92 if (first_error)
698 return first_error;
699 92 return {};
700 92 }
701
702 void
703 100 posix_signals_impl::
704 cancel_wait(posix_signal_impl& impl)
705 {
706 100 bool was_waiting = false;
707 100 signal_op* op = nullptr;
708
709 {
710
1/1
✓ Branch 1 taken 100 times.
100 std::lock_guard lock(mutex_);
711
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 96 times.
100 if (impl.waiting_)
712 {
713 4 was_waiting = true;
714 4 impl.waiting_ = false;
715 4 op = &impl.pending_op_;
716 }
717 100 }
718
719
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 96 times.
100 if (was_waiting)
720 {
721
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (op->ec_out)
722 4 *op->ec_out = make_error_code(capy::error::canceled);
723
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (op->signal_out)
724 4 *op->signal_out = 0;
725 4 op->d.post(op->h);
726 4 sched_->on_work_finished();
727 }
728 100 }
729
730 void
731 26 posix_signals_impl::
732 start_wait(posix_signal_impl& impl, signal_op* op)
733 {
734 {
735
1/1
✓ Branch 1 taken 26 times.
26 std::lock_guard lock(mutex_);
736
737 // Check for queued signals first (signal arrived before wait started)
738 26 signal_registration* reg = impl.signals_;
739
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 16 times.
44 while (reg)
740 {
741
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 18 times.
28 if (reg->undelivered > 0)
742 {
743 10 --reg->undelivered;
744 10 op->signal_number = reg->signal_number;
745 // svc=nullptr: no work_finished needed since we never called work_started
746 10 op->svc = nullptr;
747
1/1
✓ Branch 1 taken 10 times.
10 sched_->post(op);
748 10 return;
749 }
750 18 reg = reg->next_in_set;
751 }
752
753 // No queued signals - wait for delivery
754 16 impl.waiting_ = true;
755 // svc=this: signal_op::operator() will call work_finished() to balance this
756 16 op->svc = this;
757 16 sched_->on_work_started();
758 26 }
759 }
760
761 void
762 20 posix_signals_impl::
763 deliver_signal(int signal_number)
764 {
765
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)
766 return;
767
768 20 signal_state* state = get_signal_state();
769
1/1
✓ Branch 1 taken 20 times.
20 std::lock_guard lock(state->mutex);
770
771 20 posix_signals_impl* service = state->service_list;
772
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 20 times.
40 while (service)
773 {
774
1/1
✓ Branch 1 taken 20 times.
20 std::lock_guard svc_lock(service->mutex_);
775
776 20 signal_registration* reg = service->registrations_[signal_number];
777
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 20 times.
42 while (reg)
778 {
779 22 posix_signal_impl* impl = static_cast<posix_signal_impl*>(reg->owner);
780
781
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 10 times.
22 if (impl->waiting_)
782 {
783 12 impl->waiting_ = false;
784 12 impl->pending_op_.signal_number = signal_number;
785
1/1
✓ Branch 1 taken 12 times.
12 service->post(&impl->pending_op_);
786 }
787 else
788 {
789 10 ++reg->undelivered;
790 }
791
792 22 reg = reg->next_in_table;
793 }
794
795 20 service = service->next_;
796 20 }
797 20 }
798
799 void
800 posix_signals_impl::
801 work_started() noexcept
802 {
803 sched_->work_started();
804 }
805
806 void
807 12 posix_signals_impl::
808 work_finished() noexcept
809 {
810 12 sched_->work_finished();
811 12 }
812
813 void
814 12 posix_signals_impl::
815 post(signal_op* op)
816 {
817 12 sched_->post(op);
818 12 }
819
820 void
821 309 posix_signals_impl::
822 add_service(posix_signals_impl* service)
823 {
824 309 signal_state* state = get_signal_state();
825
1/1
✓ Branch 1 taken 309 times.
309 std::lock_guard lock(state->mutex);
826
827 309 service->next_ = state->service_list;
828 309 service->prev_ = nullptr;
829
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 304 times.
309 if (state->service_list)
830 5 state->service_list->prev_ = service;
831 309 state->service_list = service;
832 309 }
833
834 void
835 309 posix_signals_impl::
836 remove_service(posix_signals_impl* service)
837 {
838 309 signal_state* state = get_signal_state();
839
1/1
✓ Branch 1 taken 309 times.
309 std::lock_guard lock(state->mutex);
840
841
4/6
✓ Branch 0 taken 304 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 304 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 304 times.
✗ Branch 5 not taken.
309 if (service->next_ || service->prev_ || state->service_list == service)
842 {
843
1/2
✓ Branch 0 taken 309 times.
✗ Branch 1 not taken.
309 if (state->service_list == service)
844 309 state->service_list = service->next_;
845
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 309 times.
309 if (service->prev_)
846 service->prev_->next_ = service->next_;
847
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 304 times.
309 if (service->next_)
848 5 service->next_->prev_ = service->prev_;
849 309 service->next_ = nullptr;
850 309 service->prev_ = nullptr;
851 }
852 309 }
853
854 //------------------------------------------------------------------------------
855 // get_signal_service - factory function
856 //------------------------------------------------------------------------------
857
858 posix_signals&
859 309 get_signal_service(capy::execution_context& ctx, scheduler& sched)
860 {
861 309 return ctx.make_service<posix_signals_impl>(sched);
862 }
863
864 } // namespace detail
865
866 //------------------------------------------------------------------------------
867 // signal_set implementation
868 //------------------------------------------------------------------------------
869
870 90 signal_set::
871 90 ~signal_set()
872 {
873
2/2
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 4 times.
90 if (impl_)
874 86 impl_->release();
875 90 }
876
877 88 signal_set::
878 88 signal_set(capy::execution_context& ctx)
879 88 : io_object(ctx)
880 {
881 88 auto* svc = ctx.find_service<detail::posix_signals>();
882
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 88 times.
88 if (!svc)
883 detail::throw_logic_error("signal_set: signal service not initialized");
884
1/1
✓ Branch 1 taken 88 times.
88 impl_ = &svc->create_impl();
885 88 }
886
887 2 signal_set::
888 2 signal_set(signal_set&& other) noexcept
889 2 : io_object(std::move(other))
890 {
891 2 impl_ = other.impl_;
892 2 other.impl_ = nullptr;
893 2 }
894
895 signal_set&
896 4 signal_set::
897 operator=(signal_set&& other)
898 {
899
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (this != &other)
900 {
901
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (ctx_ != other.ctx_)
902 2 detail::throw_logic_error("signal_set::operator=: context mismatch");
903
904
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (impl_)
905 2 impl_->release();
906
907 2 impl_ = other.impl_;
908 2 other.impl_ = nullptr;
909 }
910 2 return *this;
911 }
912
913 std::error_code
914 96 signal_set::
915 add(int signal_number, flags_t flags)
916 {
917 96 return get().add(signal_number, flags);
918 }
919
920 std::error_code
921 4 signal_set::
922 remove(int signal_number)
923 {
924 4 return get().remove(signal_number);
925 }
926
927 std::error_code
928 4 signal_set::
929 clear()
930 {
931 4 return get().clear();
932 }
933
934 void
935 12 signal_set::
936 cancel()
937 {
938 12 get().cancel();
939 12 }
940
941 } // namespace boost::corosio
942
943 #endif // BOOST_COROSIO_POSIX
944