TLA Line data Source code
1 : //
2 : // Copyright (c) 2026 Steve Gerbino
3 : // Copyright (c) 2026 Michael Vandeberg
4 : //
5 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 : //
8 : // Official repository: https://github.com/cppalliance/corosio
9 : //
10 :
11 : #ifndef BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
12 : #define BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
13 :
14 : #include <boost/corosio/detail/platform.hpp>
15 :
16 : #if BOOST_COROSIO_POSIX
17 :
18 : #include <boost/corosio/native/detail/posix/posix_signal.hpp>
19 :
20 : #include <boost/corosio/detail/config.hpp>
21 : #include <boost/capy/ex/execution_context.hpp>
22 : #include <boost/corosio/detail/scheduler.hpp>
23 : #include <boost/capy/error.hpp>
24 :
25 : #include <mutex>
26 :
27 : #include <errno.h>
28 : #include <fcntl.h>
29 : #include <signal.h>
30 : #include <unistd.h>
31 :
32 : /*
33 : POSIX Signal Service
34 : ====================
35 :
36 : Concrete signal service implementation for POSIX backends. Manages signal
37 : registrations via sigaction() and dispatches completions through the
38 : scheduler. One instance per execution_context, created by
39 : get_signal_service().
40 :
41 : See the block comment further down for the full architecture overview.
42 : */
43 :
44 : /*
45 : POSIX Signal Implementation
46 : ===========================
47 :
48 : This file implements signal handling for POSIX systems using sigaction().
49 : The implementation supports signal flags (SA_RESTART, etc.) and integrates
50 : with any POSIX-compatible scheduler via the abstract scheduler interface.
51 :
52 : Architecture Overview
53 : ---------------------
54 :
55 : Three layers manage signal registrations:
56 :
57 : 1. signal_state (global singleton)
58 : - Tracks the global service list and per-signal registration counts
59 : - Stores the flags used for first registration of each signal (for
60 : conflict detection when multiple signal_sets register same signal)
61 : - Owns the mutex that protects signal handler installation/removal
62 :
63 : 2. posix_signal_service (one per execution_context)
64 : - Maintains registrations_[] table indexed by signal number
65 : - Each slot is a doubly-linked list of signal_registrations for that signal
66 : - Also maintains impl_list_ of all posix_signal objects it owns
67 :
68 : 3. posix_signal (one per signal_set)
69 : - Owns a singly-linked list (sorted by signal number) of signal_registrations
70 : - Contains the pending_op_ used for wait operations
71 :
72 : Signal Delivery Flow
73 : --------------------
74 :
75 : Delivery uses the self-pipe trick so the signal handler itself performs
76 : only async-signal-safe work (mirrors Boost.Asio):
77 :
78 : 1. Signal arrives -> corosio_posix_signal_handler(). The handler only
79 : write()s the signal number to the global self-pipe (write_fd) and
80 : restores errno. No locks, no allocation, no scheduler dispatch.
81 :
82 : 2. The read end of the pipe is watched by one backend's event loop
83 : (registered via scheduler::register_signal_reader on the first
84 : registration). When it becomes readable the backend drains it
85 : (drain_signal_pipe) and calls deliver_signal() in normal context.
86 :
87 : 3. deliver_signal() iterates all posix_signal_service services:
88 : - If a signal_set is waiting (impl->waiting_ == true), post the signal_op
89 : to the scheduler for immediate completion
90 : - Otherwise, increment reg->undelivered to queue the signal
91 :
92 : 4. When wait() is called via start_wait():
93 : - First check for queued signals (undelivered > 0); if found, post
94 : immediate completion without blocking
95 : - Otherwise, set waiting_ = true and call work_started() to keep
96 : the io_context alive
97 :
98 : Locking Protocol
99 : ----------------
100 :
101 : Two mutex levels exist (MUST acquire in this order to avoid deadlock):
102 : 1. signal_state::mutex - protects handler registration and service list
103 : 2. posix_signal_service::mutex_ - protects per-service registration tables
104 :
105 : Async-Signal-Safety
106 : -------------------
107 :
108 : The C signal handler (corosio_posix_signal_handler) performs only
109 : async-signal-safe operations: it reads the single global write_fd and
110 : calls write(), saving/restoring errno. It never locks a mutex, allocates
111 : memory, or dispatches through the scheduler. All of that happens in
112 : deliver_signal(), which runs in normal thread context from the backend
113 : event loop after draining the self-pipe. There is therefore no
114 : self-deadlock risk if a signal arrives while a thread holds state->mutex
115 : or service->mutex_.
116 :
117 : Flag Handling
118 : -------------
119 :
120 : - Flags are abstract values in the public API (signal_set::flags_t)
121 : - flags_supported() validates that requested flags are available on
122 : this platform; returns false if SA_NOCLDWAIT is unavailable and
123 : no_child_wait is requested
124 : - to_sigaction_flags() maps validated flags to actual SA_* constants
125 : - First registration of a signal establishes the flags; subsequent
126 : registrations must be compatible (same flags or dont_care)
127 : - Requesting unavailable flags returns operation_not_supported
128 :
129 : Work Tracking
130 : -------------
131 :
132 : When waiting for a signal:
133 : - start_wait() calls sched_->work_started() to prevent io_context::run()
134 : from returning while we wait
135 : - signal_op::svc is set to point to the service
136 : - signal_op::operator()() calls work_finished() after resuming the coroutine
137 :
138 : If a signal was already queued (undelivered > 0), no work tracking is needed
139 : because completion is posted immediately.
140 : */
141 :
142 : namespace boost::corosio {
143 :
144 : namespace detail {
145 :
146 : /** Signal service for POSIX backends.
147 :
148 : Manages signal registrations via sigaction() and dispatches signal
149 : completions through the scheduler. One instance per execution_context.
150 : */
151 : class BOOST_COROSIO_DECL posix_signal_service final
152 : : public capy::execution_context::service
153 : , public io_object::io_service
154 : {
155 : public:
156 : using key_type = posix_signal_service;
157 :
158 : posix_signal_service(capy::execution_context& ctx, scheduler& sched);
159 : ~posix_signal_service() override;
160 :
161 : posix_signal_service(posix_signal_service const&) = delete;
162 : posix_signal_service& operator=(posix_signal_service const&) = delete;
163 :
164 : io_object::implementation* construct() override;
165 :
166 HIT 112 : void destroy(io_object::implementation* p) override
167 : {
168 112 : auto& impl = static_cast<posix_signal&>(*p);
169 112 : [[maybe_unused]] auto n = impl.clear();
170 112 : impl.cancel();
171 112 : destroy_impl(impl);
172 112 : }
173 :
174 : void shutdown() override;
175 :
176 : void destroy_impl(posix_signal& impl);
177 :
178 : std::error_code add_signal(
179 : posix_signal& impl, int signal_number, signal_set::flags_t flags);
180 :
181 : std::error_code remove_signal(posix_signal& impl, int signal_number);
182 :
183 : std::error_code clear_signals(posix_signal& impl);
184 :
185 : void cancel_wait(posix_signal& impl);
186 : void start_wait(posix_signal& impl, signal_op* op);
187 :
188 : static void deliver_signal(int signal_number);
189 :
190 : void work_started() noexcept;
191 : void work_finished() noexcept;
192 : void post(signal_op* op);
193 :
194 : private:
195 : static void add_service(posix_signal_service* service);
196 : static void remove_service(posix_signal_service* service);
197 :
198 : scheduler* sched_;
199 : std::mutex mutex_;
200 :
201 : // Registers the signal self-pipe's read end with sched_ exactly once per
202 : // service, so every io_context that waits on a signal can drain the pipe.
203 : // A once_flag (not a bool under mutex_) because registration must run
204 : // without holding mutex_ or the signal-state mutex — see add_signal.
205 : std::once_flag reader_once_;
206 :
207 : intrusive_list<posix_signal> impl_list_;
208 :
209 : // Per-signal registration table
210 : signal_registration* registrations_[max_signal_number];
211 :
212 : // Registration counts for each signal
213 : std::size_t registration_count_[max_signal_number];
214 :
215 : // Linked list of all posix_signal_service services for signal delivery
216 : posix_signal_service* next_ = nullptr;
217 : posix_signal_service* prev_ = nullptr;
218 : };
219 :
220 : /** Get or create the signal service for the given context.
221 :
222 : This function is called by the concrete scheduler during initialization
223 : to create the signal service with a reference to itself.
224 :
225 : @param ctx Reference to the owning execution_context.
226 : @param sched Reference to the scheduler for posting completions.
227 : @return Reference to the signal service.
228 : */
229 : posix_signal_service&
230 : get_signal_service(capy::execution_context& ctx, scheduler& sched);
231 :
232 : } // namespace detail
233 :
234 : } // namespace boost::corosio
235 :
236 : // ---------------------------------------------------------------------------
237 : // Inline implementation
238 : // ---------------------------------------------------------------------------
239 :
240 : namespace boost::corosio {
241 :
242 : namespace detail {
243 :
244 : namespace posix_signal_detail {
245 :
246 : struct signal_state
247 : {
248 : std::mutex mutex;
249 : posix_signal_service* service_list = nullptr;
250 : std::size_t registration_count[max_signal_number] = {};
251 : signal_set::flags_t registered_flags[max_signal_number] = {};
252 :
253 : // Self-pipe used to defer signal delivery out of handler context.
254 : // The C handler writes the signal number to write_fd (async-signal-
255 : // safe); a backend event loop drains read_fd and calls deliver_signal()
256 : // in normal context. Created once (on the first signal registration) and
257 : // kept for the process lifetime. Each posix_signal_service registers the
258 : // read end with its own scheduler (see reader_once_) so every running
259 : // io_context can drain it; multiple readers on one pipe are safe because
260 : // each signal is a fixed sizeof(int) record read atomically.
261 : int read_fd = -1;
262 : int write_fd = -1;
263 : };
264 :
265 : BOOST_COROSIO_DECL signal_state* get_signal_state();
266 :
267 : // Check if requested flags are supported on this platform.
268 : // Returns true if all flags are supported, false otherwise.
269 : inline bool
270 114 : flags_supported([[maybe_unused]] signal_set::flags_t flags)
271 : {
272 : #ifndef SA_NOCLDWAIT
273 : if (flags & signal_set::no_child_wait)
274 : return false;
275 : #endif
276 114 : return true;
277 : }
278 :
279 : // Map abstract flags to sigaction() flags.
280 : // Caller must ensure flags_supported() returns true first.
281 : inline int
282 96 : to_sigaction_flags(signal_set::flags_t flags)
283 : {
284 96 : int sa_flags = 0;
285 96 : if (flags & signal_set::restart)
286 18 : sa_flags |= SA_RESTART;
287 96 : if (flags & signal_set::no_child_stop)
288 MIS 0 : sa_flags |= SA_NOCLDSTOP;
289 : #ifdef SA_NOCLDWAIT
290 HIT 96 : if (flags & signal_set::no_child_wait)
291 MIS 0 : sa_flags |= SA_NOCLDWAIT;
292 : #endif
293 HIT 96 : if (flags & signal_set::no_defer)
294 2 : sa_flags |= SA_NODEFER;
295 96 : if (flags & signal_set::reset_handler)
296 MIS 0 : sa_flags |= SA_RESETHAND;
297 HIT 96 : return sa_flags;
298 : }
299 :
300 : // Check if two flag values are compatible
301 : inline bool
302 18 : flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
303 : {
304 : // dont_care is always compatible
305 34 : if ((existing & signal_set::dont_care) ||
306 16 : (requested & signal_set::dont_care))
307 6 : return true;
308 :
309 : // Mask out dont_care bit for comparison
310 12 : constexpr auto mask = ~signal_set::dont_care;
311 12 : return (existing & mask) == (requested & mask);
312 : }
313 :
314 : // Lazily create the global signal self-pipe. Idempotent; call under
315 : // state->mutex before installing the first signal handler so write_fd is
316 : // valid by the time the handler can fire. Both ends are non-blocking and
317 : // close-on-exec (mirrors the reactor self-pipe setup in select_scheduler).
318 : // Returns false and leaves the fds at -1 if creation fails.
319 : inline bool
320 114 : open_signal_pipe(signal_state* state)
321 : {
322 114 : if (state->read_fd >= 0)
323 112 : return true;
324 :
325 : int fds[2];
326 2 : if (::pipe(fds) < 0)
327 MIS 0 : return false;
328 :
329 HIT 6 : for (int i = 0; i < 2; ++i)
330 : {
331 4 : int fl = ::fcntl(fds[i], F_GETFL, 0);
332 8 : if (fl == -1 || ::fcntl(fds[i], F_SETFL, fl | O_NONBLOCK) == -1 ||
333 4 : ::fcntl(fds[i], F_SETFD, FD_CLOEXEC) == -1)
334 : {
335 MIS 0 : ::close(fds[0]);
336 0 : ::close(fds[1]);
337 0 : return false;
338 : }
339 : }
340 :
341 HIT 2 : state->read_fd = fds[0];
342 2 : state->write_fd = fds[1];
343 2 : return true;
344 : }
345 :
346 : // C signal handler. Async-signal-safe: it touches only the single global
347 : // write_fd (an int set before any handler is installed) and calls write(),
348 : // which POSIX lists as async-signal-safe. errno is saved and restored so an
349 : // interrupted foreground syscall is unaffected. A full pipe (write returns
350 : // EAGAIN) or a short write is intentionally dropped — the reactor still
351 : // coalesces because deliver_signal reports the signal to every waiting set.
352 : inline void
353 300 : corosio_posix_signal_handler(int signal_number)
354 : {
355 300 : int saved_errno = errno;
356 300 : signal_state* state = get_signal_state();
357 : [[maybe_unused]] ssize_t r =
358 300 : ::write(state->write_fd, &signal_number, sizeof(int));
359 300 : errno = saved_errno;
360 : // With sigaction(), the handler persists automatically (unlike some
361 : // signal() implementations that reset to SIG_DFL).
362 300 : }
363 :
364 : // Drain the signal self-pipe and deliver each pending signal. Runs in normal
365 : // thread context from the backend event loop, so deliver_signal()'s mutex
366 : // locking and scheduler post are safe here. Reads until EAGAIN (edge-
367 : // triggered backends require a full drain per readiness event).
368 : inline void
369 300 : drain_signal_pipe()
370 : {
371 300 : signal_state* state = get_signal_state();
372 : int signal_number;
373 600 : while (::read(state->read_fd, &signal_number, sizeof(int)) ==
374 : static_cast<ssize_t>(sizeof(int)))
375 : {
376 300 : posix_signal_service::deliver_signal(signal_number);
377 : }
378 300 : }
379 :
380 : } // namespace posix_signal_detail
381 :
382 : // signal_op implementation
383 :
384 : inline void
385 302 : signal_op::operator()()
386 : {
387 302 : if (ec_out)
388 302 : *ec_out = {};
389 302 : if (signal_out)
390 302 : *signal_out = signal_number;
391 :
392 : // Capture svc before resuming (coro may destroy us)
393 302 : auto* service = svc;
394 302 : svc = nullptr;
395 :
396 302 : cont.h = h;
397 302 : d.post(cont);
398 :
399 : // Balance the work_started() from start_wait
400 302 : if (service)
401 302 : service->work_finished();
402 302 : }
403 :
404 : inline void
405 MIS 0 : signal_op::destroy()
406 : {
407 : // No-op: signal_op is embedded in posix_signal
408 0 : }
409 :
410 : // posix_signal implementation
411 :
412 HIT 112 : inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
413 112 : : svc_(svc)
414 : {
415 112 : }
416 :
417 : inline std::coroutine_handle<>
418 310 : posix_signal::wait(
419 : std::coroutine_handle<> h,
420 : capy::executor_ref d,
421 : std::stop_token token,
422 : std::error_code* ec,
423 : int* signal_out)
424 : {
425 310 : pending_op_.h = h;
426 310 : pending_op_.d = d;
427 310 : pending_op_.ec_out = ec;
428 310 : pending_op_.signal_out = signal_out;
429 310 : pending_op_.signal_number = 0;
430 :
431 310 : if (token.stop_requested())
432 : {
433 2 : if (ec)
434 2 : *ec = make_error_code(capy::error::canceled);
435 2 : if (signal_out)
436 2 : *signal_out = 0;
437 2 : pending_op_.cont.h = h;
438 2 : d.post(pending_op_.cont);
439 : // completion is always posted to scheduler queue, never inline.
440 2 : return std::noop_coroutine();
441 : }
442 :
443 308 : svc_.start_wait(*this, &pending_op_);
444 : // completion is always posted to scheduler queue, never inline.
445 308 : return std::noop_coroutine();
446 : }
447 :
448 : inline std::error_code
449 118 : posix_signal::add(int signal_number, signal_set::flags_t flags)
450 : {
451 118 : return svc_.add_signal(*this, signal_number, flags);
452 : }
453 :
454 : inline std::error_code
455 6 : posix_signal::remove(int signal_number)
456 : {
457 6 : return svc_.remove_signal(*this, signal_number);
458 : }
459 :
460 : inline std::error_code
461 116 : posix_signal::clear()
462 : {
463 116 : return svc_.clear_signals(*this);
464 : }
465 :
466 : inline void
467 126 : posix_signal::cancel()
468 : {
469 126 : svc_.cancel_wait(*this);
470 126 : }
471 :
472 : // posix_signal_service implementation
473 :
474 1225 : inline posix_signal_service::posix_signal_service(
475 1225 : capy::execution_context&, scheduler& sched)
476 1225 : : sched_(&sched)
477 : {
478 79625 : for (int i = 0; i < max_signal_number; ++i)
479 : {
480 78400 : registrations_[i] = nullptr;
481 78400 : registration_count_[i] = 0;
482 : }
483 1225 : add_service(this);
484 1225 : }
485 :
486 2450 : inline posix_signal_service::~posix_signal_service()
487 : {
488 1225 : remove_service(this);
489 2450 : }
490 :
491 : inline void
492 1225 : posix_signal_service::shutdown()
493 : {
494 1225 : std::lock_guard lock(mutex_);
495 :
496 1225 : for (auto* impl = impl_list_.pop_front(); impl != nullptr;
497 MIS 0 : impl = impl_list_.pop_front())
498 : {
499 0 : while (auto* reg = impl->signals_)
500 : {
501 0 : impl->signals_ = reg->next_in_set;
502 0 : delete reg;
503 0 : }
504 0 : delete impl;
505 : }
506 HIT 1225 : }
507 :
508 : inline io_object::implementation*
509 112 : posix_signal_service::construct()
510 : {
511 112 : auto* impl = new posix_signal(*this);
512 :
513 : {
514 112 : std::lock_guard lock(mutex_);
515 112 : impl_list_.push_back(impl);
516 112 : }
517 :
518 112 : return impl;
519 : }
520 :
521 : inline void
522 112 : posix_signal_service::destroy_impl(posix_signal& impl)
523 : {
524 : {
525 112 : std::lock_guard lock(mutex_);
526 112 : impl_list_.remove(&impl);
527 112 : }
528 :
529 112 : delete &impl;
530 112 : }
531 :
532 : inline std::error_code
533 118 : posix_signal_service::add_signal(
534 : posix_signal& impl, int signal_number, signal_set::flags_t flags)
535 : {
536 118 : if (signal_number < 0 || signal_number >= max_signal_number)
537 4 : return make_error_code(std::errc::invalid_argument);
538 :
539 : // Validate that requested flags are supported on this platform
540 : // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
541 114 : if (!posix_signal_detail::flags_supported(flags))
542 MIS 0 : return make_error_code(std::errc::operation_not_supported);
543 :
544 : posix_signal_detail::signal_state* state =
545 HIT 114 : posix_signal_detail::get_signal_state();
546 :
547 : // Ensure the global self-pipe exists and this service's scheduler is
548 : // watching its read end, BEFORE taking the registration locks. The
549 : // reactor drain path locks the descriptor mutex and then the signal-state
550 : // and service mutexes; register_signal_reader locks the descriptor mutex
551 : // (via register_descriptor), so it must run holding neither of those or
552 : // the lock order would invert (a real deadlock, caught by TSan). call_once
553 : // makes the once-per-service registration safe when two signal_sets on
554 : // this context race add() from different threads.
555 : {
556 114 : std::lock_guard state_lock(state->mutex);
557 114 : if (!posix_signal_detail::open_signal_pipe(state))
558 MIS 0 : return make_error_code(std::errc::io_error);
559 HIT 114 : }
560 114 : std::call_once(reader_once_, [this, state] {
561 82 : sched_->register_signal_reader(state->read_fd);
562 82 : });
563 :
564 114 : std::lock_guard state_lock(state->mutex);
565 114 : std::lock_guard lock(mutex_);
566 :
567 : // Find insertion point (list is sorted by signal number)
568 114 : signal_registration** insertion_point = &impl.signals_;
569 114 : signal_registration* reg = impl.signals_;
570 128 : while (reg && reg->signal_number < signal_number)
571 : {
572 14 : insertion_point = ®->next_in_set;
573 14 : reg = reg->next_in_set;
574 : }
575 :
576 : // Already registered in this set - check flag compatibility
577 : // (same signal_set adding same signal twice with different flags)
578 114 : if (reg && reg->signal_number == signal_number)
579 : {
580 10 : if (!posix_signal_detail::flags_compatible(reg->flags, flags))
581 2 : return make_error_code(std::errc::invalid_argument);
582 8 : return {};
583 : }
584 :
585 : // Check flag compatibility with global registration
586 : // (different signal_set already registered this signal with different flags)
587 104 : if (state->registration_count[signal_number] > 0)
588 : {
589 8 : if (!posix_signal_detail::flags_compatible(
590 : state->registered_flags[signal_number], flags))
591 2 : return make_error_code(std::errc::invalid_argument);
592 : }
593 :
594 102 : auto* new_reg = new signal_registration;
595 102 : new_reg->signal_number = signal_number;
596 102 : new_reg->flags = flags;
597 102 : new_reg->owner = &impl;
598 102 : new_reg->undelivered = 0;
599 :
600 : // Install signal handler on first global registration
601 102 : if (state->registration_count[signal_number] == 0)
602 : {
603 96 : struct sigaction sa = {};
604 96 : sa.sa_handler = posix_signal_detail::corosio_posix_signal_handler;
605 96 : sigemptyset(&sa.sa_mask);
606 96 : sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
607 :
608 96 : if (::sigaction(signal_number, &sa, nullptr) < 0)
609 : {
610 MIS 0 : delete new_reg;
611 0 : return make_error_code(std::errc::invalid_argument);
612 : }
613 :
614 : // Store the flags used for first registration
615 HIT 96 : state->registered_flags[signal_number] = flags;
616 : }
617 :
618 102 : new_reg->next_in_set = reg;
619 102 : *insertion_point = new_reg;
620 :
621 102 : new_reg->next_in_table = registrations_[signal_number];
622 102 : new_reg->prev_in_table = nullptr;
623 102 : if (registrations_[signal_number])
624 6 : registrations_[signal_number]->prev_in_table = new_reg;
625 102 : registrations_[signal_number] = new_reg;
626 :
627 102 : ++state->registration_count[signal_number];
628 102 : ++registration_count_[signal_number];
629 :
630 102 : return {};
631 114 : }
632 :
633 : inline std::error_code
634 6 : posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
635 : {
636 6 : if (signal_number < 0 || signal_number >= max_signal_number)
637 2 : return make_error_code(std::errc::invalid_argument);
638 :
639 : posix_signal_detail::signal_state* state =
640 4 : posix_signal_detail::get_signal_state();
641 4 : std::lock_guard state_lock(state->mutex);
642 4 : std::lock_guard lock(mutex_);
643 :
644 4 : signal_registration** deletion_point = &impl.signals_;
645 4 : signal_registration* reg = impl.signals_;
646 4 : while (reg && reg->signal_number < signal_number)
647 : {
648 MIS 0 : deletion_point = ®->next_in_set;
649 0 : reg = reg->next_in_set;
650 : }
651 :
652 HIT 4 : if (!reg || reg->signal_number != signal_number)
653 2 : return {};
654 :
655 : // Restore default handler on last global unregistration
656 2 : if (state->registration_count[signal_number] == 1)
657 : {
658 2 : struct sigaction sa = {};
659 2 : sa.sa_handler = SIG_DFL;
660 2 : sigemptyset(&sa.sa_mask);
661 2 : sa.sa_flags = 0;
662 :
663 2 : if (::sigaction(signal_number, &sa, nullptr) < 0)
664 MIS 0 : return make_error_code(std::errc::invalid_argument);
665 :
666 : // Clear stored flags
667 HIT 2 : state->registered_flags[signal_number] = signal_set::none;
668 : }
669 :
670 2 : *deletion_point = reg->next_in_set;
671 :
672 2 : if (registrations_[signal_number] == reg)
673 2 : registrations_[signal_number] = reg->next_in_table;
674 2 : if (reg->prev_in_table)
675 MIS 0 : reg->prev_in_table->next_in_table = reg->next_in_table;
676 HIT 2 : if (reg->next_in_table)
677 MIS 0 : reg->next_in_table->prev_in_table = reg->prev_in_table;
678 :
679 HIT 2 : --state->registration_count[signal_number];
680 2 : --registration_count_[signal_number];
681 :
682 2 : delete reg;
683 2 : return {};
684 4 : }
685 :
686 : inline std::error_code
687 116 : posix_signal_service::clear_signals(posix_signal& impl)
688 : {
689 : posix_signal_detail::signal_state* state =
690 116 : posix_signal_detail::get_signal_state();
691 116 : std::lock_guard state_lock(state->mutex);
692 116 : std::lock_guard lock(mutex_);
693 :
694 116 : std::error_code first_error;
695 :
696 216 : while (signal_registration* reg = impl.signals_)
697 : {
698 100 : int signal_number = reg->signal_number;
699 :
700 100 : if (state->registration_count[signal_number] == 1)
701 : {
702 94 : struct sigaction sa = {};
703 94 : sa.sa_handler = SIG_DFL;
704 94 : sigemptyset(&sa.sa_mask);
705 94 : sa.sa_flags = 0;
706 :
707 94 : if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
708 MIS 0 : first_error = make_error_code(std::errc::invalid_argument);
709 :
710 : // Clear stored flags
711 HIT 94 : state->registered_flags[signal_number] = signal_set::none;
712 : }
713 :
714 100 : impl.signals_ = reg->next_in_set;
715 :
716 100 : if (registrations_[signal_number] == reg)
717 100 : registrations_[signal_number] = reg->next_in_table;
718 100 : if (reg->prev_in_table)
719 MIS 0 : reg->prev_in_table->next_in_table = reg->next_in_table;
720 HIT 100 : if (reg->next_in_table)
721 6 : reg->next_in_table->prev_in_table = reg->prev_in_table;
722 :
723 100 : --state->registration_count[signal_number];
724 100 : --registration_count_[signal_number];
725 :
726 100 : delete reg;
727 100 : }
728 :
729 116 : if (first_error)
730 MIS 0 : return first_error;
731 HIT 116 : return {};
732 116 : }
733 :
734 : inline void
735 126 : posix_signal_service::cancel_wait(posix_signal& impl)
736 : {
737 126 : bool was_waiting = false;
738 126 : signal_op* op = nullptr;
739 :
740 : {
741 126 : std::lock_guard lock(mutex_);
742 126 : impl.cancelled_ = true;
743 126 : if (impl.waiting_)
744 : {
745 4 : was_waiting = true;
746 4 : impl.waiting_ = false;
747 4 : op = &impl.pending_op_;
748 : }
749 126 : }
750 :
751 126 : if (was_waiting)
752 : {
753 4 : if (op->ec_out)
754 4 : *op->ec_out = make_error_code(capy::error::canceled);
755 4 : if (op->signal_out)
756 4 : *op->signal_out = 0;
757 4 : op->cont.h = op->h;
758 4 : op->d.post(op->cont);
759 4 : sched_->work_finished();
760 : }
761 126 : }
762 :
763 : inline void
764 308 : posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
765 : {
766 : {
767 308 : std::lock_guard lock(mutex_);
768 :
769 : // Check if cancel() was called before this wait started
770 308 : if (impl.cancelled_)
771 : {
772 2 : impl.cancelled_ = false;
773 2 : if (op->ec_out)
774 2 : *op->ec_out = make_error_code(capy::error::canceled);
775 2 : if (op->signal_out)
776 2 : *op->signal_out = 0;
777 2 : op->cont.h = op->h;
778 2 : op->d.post(op->cont);
779 2 : return;
780 : }
781 :
782 : // Check for queued signals first (signal arrived before wait started)
783 306 : signal_registration* reg = impl.signals_;
784 614 : while (reg)
785 : {
786 308 : if (reg->undelivered > 0)
787 : {
788 MIS 0 : --reg->undelivered;
789 0 : op->signal_number = reg->signal_number;
790 : // svc=nullptr: no work_finished needed since we never called work_started
791 0 : op->svc = nullptr;
792 0 : sched_->post(op);
793 0 : return;
794 : }
795 HIT 308 : reg = reg->next_in_set;
796 : }
797 :
798 : // No queued signals - wait for delivery
799 306 : impl.waiting_ = true;
800 : // svc=this: signal_op::operator() will call work_finished() to balance this
801 306 : op->svc = this;
802 306 : sched_->work_started();
803 308 : }
804 : }
805 :
806 : inline void
807 300 : posix_signal_service::deliver_signal(int signal_number)
808 : {
809 300 : if (signal_number < 0 || signal_number >= max_signal_number)
810 MIS 0 : return;
811 :
812 : posix_signal_detail::signal_state* state =
813 HIT 300 : posix_signal_detail::get_signal_state();
814 300 : std::lock_guard lock(state->mutex);
815 :
816 300 : posix_signal_service* service = state->service_list;
817 600 : while (service)
818 : {
819 300 : std::lock_guard svc_lock(service->mutex_);
820 :
821 300 : signal_registration* reg = service->registrations_[signal_number];
822 602 : while (reg)
823 : {
824 302 : posix_signal* impl = static_cast<posix_signal*>(reg->owner);
825 :
826 302 : if (impl->waiting_)
827 : {
828 302 : impl->waiting_ = false;
829 302 : impl->pending_op_.signal_number = signal_number;
830 302 : service->post(&impl->pending_op_);
831 : }
832 : else
833 : {
834 MIS 0 : ++reg->undelivered;
835 : }
836 :
837 HIT 302 : reg = reg->next_in_table;
838 : }
839 :
840 300 : service = service->next_;
841 300 : }
842 300 : }
843 :
844 : inline void
845 : posix_signal_service::work_started() noexcept
846 : {
847 : sched_->work_started();
848 : }
849 :
850 : inline void
851 302 : posix_signal_service::work_finished() noexcept
852 : {
853 302 : sched_->work_finished();
854 302 : }
855 :
856 : inline void
857 302 : posix_signal_service::post(signal_op* op)
858 : {
859 302 : sched_->post(op);
860 302 : }
861 :
862 : inline void
863 1225 : posix_signal_service::add_service(posix_signal_service* service)
864 : {
865 : posix_signal_detail::signal_state* state =
866 1225 : posix_signal_detail::get_signal_state();
867 1225 : std::lock_guard lock(state->mutex);
868 :
869 1225 : service->next_ = state->service_list;
870 1225 : service->prev_ = nullptr;
871 1225 : if (state->service_list)
872 5 : state->service_list->prev_ = service;
873 1225 : state->service_list = service;
874 1225 : }
875 :
876 : inline void
877 1225 : posix_signal_service::remove_service(posix_signal_service* service)
878 : {
879 : posix_signal_detail::signal_state* state =
880 1225 : posix_signal_detail::get_signal_state();
881 1225 : std::lock_guard lock(state->mutex);
882 :
883 1225 : if (service->next_ || service->prev_ || state->service_list == service)
884 : {
885 1225 : if (state->service_list == service)
886 1225 : state->service_list = service->next_;
887 1225 : if (service->prev_)
888 MIS 0 : service->prev_->next_ = service->next_;
889 HIT 1225 : if (service->next_)
890 5 : service->next_->prev_ = service->prev_;
891 1225 : service->next_ = nullptr;
892 1225 : service->prev_ = nullptr;
893 : }
894 1225 : }
895 :
896 : // get_signal_service - factory function
897 :
898 : inline posix_signal_service&
899 1225 : get_signal_service(capy::execution_context& ctx, scheduler& sched)
900 : {
901 1225 : return ctx.make_service<posix_signal_service>(sched);
902 : }
903 :
904 : } // namespace detail
905 : } // namespace boost::corosio
906 :
907 : #endif // BOOST_COROSIO_POSIX
908 :
909 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
|