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 125 : void destroy(io_object::implementation* p) override
167 : {
168 125 : auto& impl = static_cast<posix_signal&>(*p);
169 125 : [[maybe_unused]] auto n = impl.clear();
170 125 : impl.cancel();
171 125 : destroy_impl(impl);
172 125 : }
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::mutex reader_mutex_;
206 : bool reader_registered_ = false;
207 :
208 : intrusive_list<posix_signal> impl_list_;
209 :
210 : // Per-signal registration table
211 : signal_registration* registrations_[max_signal_number];
212 :
213 : // Registration counts for each signal
214 : std::size_t registration_count_[max_signal_number];
215 :
216 : // Linked list of all posix_signal_service services for signal delivery
217 : posix_signal_service* next_ = nullptr;
218 : posix_signal_service* prev_ = nullptr;
219 : };
220 :
221 : /** Get or create the signal service for the given context.
222 :
223 : This function is called by the concrete scheduler during initialization
224 : to create the signal service with a reference to itself.
225 :
226 : @param ctx Reference to the owning execution_context.
227 : @param sched Reference to the scheduler for posting completions.
228 : @return Reference to the signal service.
229 : */
230 : posix_signal_service&
231 : get_signal_service(capy::execution_context& ctx, scheduler& sched);
232 :
233 : } // namespace detail
234 :
235 : } // namespace boost::corosio
236 :
237 : // ---------------------------------------------------------------------------
238 : // Inline implementation
239 : // ---------------------------------------------------------------------------
240 :
241 : namespace boost::corosio {
242 :
243 : namespace detail {
244 :
245 : namespace posix_signal_detail {
246 :
247 : struct signal_state
248 : {
249 : std::mutex mutex;
250 : posix_signal_service* service_list = nullptr;
251 : std::size_t registration_count[max_signal_number] = {};
252 : signal_set::flags_t registered_flags[max_signal_number] = {};
253 :
254 : // Self-pipe used to defer signal delivery out of handler context.
255 : // The C handler writes the signal number to write_fd (async-signal-
256 : // safe); a backend event loop drains read_fd and calls deliver_signal()
257 : // in normal context. Created once (on the first signal registration) and
258 : // kept for the process lifetime. Each posix_signal_service registers the
259 : // read end with its own scheduler (see reader_once_) so every running
260 : // io_context can drain it; multiple readers on one pipe are safe because
261 : // each signal is a fixed sizeof(int) record read atomically.
262 : int read_fd = -1;
263 : int write_fd = -1;
264 : };
265 :
266 : BOOST_COROSIO_DECL signal_state* get_signal_state();
267 :
268 : // Check if requested flags are supported on this platform.
269 : // Returns true if all flags are supported, false otherwise.
270 : inline bool
271 138 : flags_supported([[maybe_unused]] signal_set::flags_t flags)
272 : {
273 : #ifndef SA_NOCLDWAIT
274 : if (flags & signal_set::no_child_wait)
275 : return false;
276 : #endif
277 138 : return true;
278 : }
279 :
280 : // Map abstract flags to sigaction() flags.
281 : // Caller must ensure flags_supported() returns true first.
282 : inline int
283 113 : to_sigaction_flags(signal_set::flags_t flags)
284 : {
285 113 : int sa_flags = 0;
286 113 : if (flags & signal_set::restart)
287 21 : sa_flags |= SA_RESTART;
288 113 : if (flags & signal_set::no_child_stop)
289 1 : sa_flags |= SA_NOCLDSTOP;
290 : #ifdef SA_NOCLDWAIT
291 113 : if (flags & signal_set::no_child_wait)
292 MIS 0 : sa_flags |= SA_NOCLDWAIT;
293 : #endif
294 HIT 113 : if (flags & signal_set::no_defer)
295 2 : sa_flags |= SA_NODEFER;
296 113 : if (flags & signal_set::reset_handler)
297 MIS 0 : sa_flags |= SA_RESETHAND;
298 HIT 113 : return sa_flags;
299 : }
300 :
301 : // Check if two flag values are compatible
302 : inline bool
303 25 : flags_compatible(signal_set::flags_t existing, signal_set::flags_t requested)
304 : {
305 : // dont_care is always compatible
306 48 : if ((existing & signal_set::dont_care) ||
307 23 : (requested & signal_set::dont_care))
308 7 : return true;
309 :
310 : // Mask out dont_care bit for comparison
311 18 : constexpr auto mask = ~signal_set::dont_care;
312 18 : return (existing & mask) == (requested & mask);
313 : }
314 :
315 : // Lazily create the global signal self-pipe. Idempotent; call under
316 : // state->mutex before installing the first signal handler so write_fd is
317 : // valid by the time the handler can fire. Both ends are non-blocking and
318 : // close-on-exec (mirrors the reactor self-pipe setup in select_scheduler).
319 : // Returns false and leaves the fds at -1 if creation fails.
320 : inline bool
321 138 : open_signal_pipe(signal_state* state)
322 : {
323 138 : if (state->read_fd >= 0)
324 135 : return true;
325 :
326 : int fds[2];
327 3 : if (::pipe(fds) < 0)
328 MIS 0 : return false;
329 :
330 HIT 9 : for (int i = 0; i < 2; ++i)
331 : {
332 6 : int fl = ::fcntl(fds[i], F_GETFL, 0);
333 12 : if (fl == -1 || ::fcntl(fds[i], F_SETFL, fl | O_NONBLOCK) == -1 ||
334 6 : ::fcntl(fds[i], F_SETFD, FD_CLOEXEC) == -1)
335 : {
336 MIS 0 : ::close(fds[0]);
337 0 : ::close(fds[1]);
338 0 : return false;
339 : }
340 : }
341 :
342 HIT 3 : state->read_fd = fds[0];
343 3 : state->write_fd = fds[1];
344 3 : return true;
345 : }
346 :
347 : // C signal handler. Async-signal-safe: it touches only the single global
348 : // write_fd (an int set before any handler is installed) and calls write(),
349 : // which POSIX lists as async-signal-safe. errno is saved and restored so an
350 : // interrupted foreground syscall is unaffected. A full pipe (write returns
351 : // EAGAIN) or a short write is intentionally dropped — the reactor still
352 : // coalesces because deliver_signal reports the signal to every waiting set.
353 : inline void
354 302 : corosio_posix_signal_handler(int signal_number)
355 : {
356 302 : int saved_errno = errno;
357 302 : signal_state* state = get_signal_state();
358 : [[maybe_unused]] ssize_t r =
359 302 : ::write(state->write_fd, &signal_number, sizeof(int));
360 302 : errno = saved_errno;
361 : // With sigaction(), the handler persists automatically (unlike some
362 : // signal() implementations that reset to SIG_DFL).
363 302 : }
364 :
365 : // Drain the signal self-pipe and deliver each pending signal. Runs in normal
366 : // thread context from the backend event loop, so deliver_signal()'s mutex
367 : // locking and scheduler post are safe here. Reads until EAGAIN (edge-
368 : // triggered backends require a full drain per readiness event).
369 : inline void
370 302 : drain_signal_pipe()
371 : {
372 302 : signal_state* state = get_signal_state();
373 : int signal_number;
374 604 : while (::read(state->read_fd, &signal_number, sizeof(int)) ==
375 : static_cast<ssize_t>(sizeof(int)))
376 : {
377 302 : posix_signal_service::deliver_signal(signal_number);
378 : }
379 302 : }
380 :
381 : } // namespace posix_signal_detail
382 :
383 : // signal_op implementation
384 :
385 : inline void
386 304 : signal_op::operator()()
387 : {
388 304 : if (ec_out)
389 304 : *ec_out = {};
390 304 : if (signal_out)
391 304 : *signal_out = signal_number;
392 :
393 : // Capture svc before resuming (coro may destroy us)
394 304 : auto* service = svc;
395 304 : svc = nullptr;
396 :
397 304 : cont.h = h;
398 304 : d.post(cont);
399 :
400 : // Balance the work_started() from start_wait
401 304 : if (service)
402 304 : service->work_finished();
403 304 : }
404 :
405 : inline void
406 MIS 0 : signal_op::destroy()
407 : {
408 : // No-op: signal_op is embedded in posix_signal
409 0 : }
410 :
411 : // posix_signal implementation
412 :
413 HIT 125 : inline posix_signal::posix_signal(posix_signal_service& svc) noexcept
414 125 : : svc_(svc)
415 : {
416 125 : }
417 :
418 : inline std::coroutine_handle<>
419 313 : posix_signal::wait(
420 : std::coroutine_handle<> h,
421 : capy::executor_ref d,
422 : std::stop_token token,
423 : std::error_code* ec,
424 : int* signal_out)
425 : {
426 313 : pending_op_.h = h;
427 313 : pending_op_.d = d;
428 313 : pending_op_.ec_out = ec;
429 313 : pending_op_.signal_out = signal_out;
430 313 : pending_op_.signal_number = 0;
431 :
432 313 : if (token.stop_requested())
433 : {
434 2 : if (ec)
435 2 : *ec = make_error_code(capy::error::canceled);
436 2 : if (signal_out)
437 2 : *signal_out = 0;
438 2 : pending_op_.cont.h = h;
439 2 : d.post(pending_op_.cont);
440 : // completion is always posted to scheduler queue, never inline.
441 2 : return std::noop_coroutine();
442 : }
443 :
444 311 : svc_.start_wait(*this, &pending_op_);
445 : // completion is always posted to scheduler queue, never inline.
446 311 : return std::noop_coroutine();
447 : }
448 :
449 : inline std::error_code
450 142 : posix_signal::add(int signal_number, signal_set::flags_t flags)
451 : {
452 142 : return svc_.add_signal(*this, signal_number, flags);
453 : }
454 :
455 : inline std::error_code
456 8 : posix_signal::remove(int signal_number)
457 : {
458 8 : return svc_.remove_signal(*this, signal_number);
459 : }
460 :
461 : inline std::error_code
462 130 : posix_signal::clear()
463 : {
464 130 : return svc_.clear_signals(*this);
465 : }
466 :
467 : inline void
468 140 : posix_signal::cancel() noexcept
469 : {
470 140 : svc_.cancel_wait(*this);
471 140 : }
472 :
473 : // posix_signal_service implementation
474 :
475 1603 : inline posix_signal_service::posix_signal_service(
476 1603 : capy::execution_context&, scheduler& sched)
477 1603 : : sched_(&sched)
478 : {
479 104195 : for (int i = 0; i < max_signal_number; ++i)
480 : {
481 102592 : registrations_[i] = nullptr;
482 102592 : registration_count_[i] = 0;
483 : }
484 1603 : add_service(this);
485 1603 : }
486 :
487 3206 : inline posix_signal_service::~posix_signal_service()
488 : {
489 1603 : remove_service(this);
490 3206 : }
491 :
492 : inline void
493 1603 : posix_signal_service::shutdown()
494 : {
495 1603 : std::lock_guard lock(mutex_);
496 :
497 1603 : for (auto* impl = impl_list_.pop_front(); impl != nullptr;
498 MIS 0 : impl = impl_list_.pop_front())
499 : {
500 0 : while (auto* reg = impl->signals_)
501 : {
502 0 : impl->signals_ = reg->next_in_set;
503 0 : delete reg;
504 0 : }
505 0 : delete impl;
506 : }
507 HIT 1603 : }
508 :
509 : inline io_object::implementation*
510 125 : posix_signal_service::construct()
511 : {
512 125 : auto* impl = new posix_signal(*this);
513 :
514 : {
515 125 : std::lock_guard lock(mutex_);
516 125 : impl_list_.push_back(impl);
517 125 : }
518 :
519 125 : return impl;
520 : }
521 :
522 : inline void
523 125 : posix_signal_service::destroy_impl(posix_signal& impl)
524 : {
525 : {
526 125 : std::lock_guard lock(mutex_);
527 125 : impl_list_.remove(&impl);
528 125 : }
529 :
530 125 : delete &impl;
531 125 : }
532 :
533 : inline std::error_code
534 142 : posix_signal_service::add_signal(
535 : posix_signal& impl, int signal_number, signal_set::flags_t flags)
536 : {
537 142 : if (signal_number < 0 || signal_number >= max_signal_number)
538 4 : return make_error_code(std::errc::invalid_argument);
539 :
540 : // Validate that requested flags are supported on this platform
541 : // (e.g., SA_NOCLDWAIT may not be available on all POSIX systems)
542 138 : if (!posix_signal_detail::flags_supported(flags))
543 MIS 0 : return make_error_code(std::errc::operation_not_supported);
544 :
545 : posix_signal_detail::signal_state* state =
546 HIT 138 : posix_signal_detail::get_signal_state();
547 :
548 : // Ensure the global self-pipe exists and this service's scheduler is
549 : // watching its read end, BEFORE taking the registration locks. The
550 : // reactor drain path locks the descriptor mutex and then the signal-state
551 : // and service mutexes; register_signal_reader locks the descriptor mutex
552 : // (via register_descriptor), so it must run holding neither of those or
553 : // the lock order would invert (a real deadlock, caught by TSan). call_once
554 : // makes the once-per-service registration safe when two signal_sets on
555 : // this context race add() from different threads.
556 : {
557 138 : std::lock_guard state_lock(state->mutex);
558 138 : if (!posix_signal_detail::open_signal_pipe(state))
559 MIS 0 : return make_error_code(std::errc::io_error);
560 HIT 138 : }
561 : {
562 : // Success-latched so a failed environmental registration
563 : // (epoll_ctl ENOMEM/ENOSPC) is retried by the next add()
564 : // instead of being lost; the code travels the return channel.
565 138 : std::lock_guard reg_lock(reader_mutex_);
566 138 : if (!reader_registered_)
567 : {
568 92 : if (auto ec = sched_->register_signal_reader(state->read_fd))
569 MIS 0 : return ec;
570 HIT 92 : reader_registered_ = true;
571 : }
572 138 : }
573 :
574 138 : std::lock_guard state_lock(state->mutex);
575 138 : std::lock_guard lock(mutex_);
576 :
577 : // Find insertion point (list is sorted by signal number)
578 138 : signal_registration** insertion_point = &impl.signals_;
579 138 : signal_registration* reg = impl.signals_;
580 159 : while (reg && reg->signal_number < signal_number)
581 : {
582 21 : insertion_point = ®->next_in_set;
583 21 : reg = reg->next_in_set;
584 : }
585 :
586 : // Already registered in this set - check flag compatibility
587 : // (same signal_set adding same signal twice with different flags)
588 138 : if (reg && reg->signal_number == signal_number)
589 : {
590 13 : if (!posix_signal_detail::flags_compatible(reg->flags, flags))
591 4 : return make_error_code(std::errc::invalid_argument);
592 9 : return {};
593 : }
594 :
595 : // Check flag compatibility with global registration
596 : // (different signal_set already registered this signal with different flags)
597 125 : if (state->registration_count[signal_number] > 0)
598 : {
599 12 : if (!posix_signal_detail::flags_compatible(
600 : state->registered_flags[signal_number], flags))
601 2 : return make_error_code(std::errc::invalid_argument);
602 : }
603 :
604 123 : auto* new_reg = new signal_registration;
605 123 : new_reg->signal_number = signal_number;
606 123 : new_reg->flags = flags;
607 123 : new_reg->owner = &impl;
608 123 : new_reg->undelivered = 0;
609 :
610 : // Install signal handler on first global registration
611 123 : if (state->registration_count[signal_number] == 0)
612 : {
613 113 : struct sigaction sa = {};
614 113 : sa.sa_handler = posix_signal_detail::corosio_posix_signal_handler;
615 113 : sigemptyset(&sa.sa_mask);
616 113 : sa.sa_flags = posix_signal_detail::to_sigaction_flags(flags);
617 :
618 113 : if (::sigaction(signal_number, &sa, nullptr) < 0)
619 : {
620 MIS 0 : delete new_reg;
621 0 : return make_error_code(std::errc::invalid_argument);
622 : }
623 :
624 : // Store the flags used for first registration
625 HIT 113 : state->registered_flags[signal_number] = flags;
626 : }
627 :
628 123 : new_reg->next_in_set = reg;
629 123 : *insertion_point = new_reg;
630 :
631 123 : new_reg->next_in_table = registrations_[signal_number];
632 123 : new_reg->prev_in_table = nullptr;
633 123 : if (registrations_[signal_number])
634 10 : registrations_[signal_number]->prev_in_table = new_reg;
635 123 : registrations_[signal_number] = new_reg;
636 :
637 123 : ++state->registration_count[signal_number];
638 123 : ++registration_count_[signal_number];
639 :
640 123 : return {};
641 138 : }
642 :
643 : inline std::error_code
644 8 : posix_signal_service::remove_signal(posix_signal& impl, int signal_number)
645 : {
646 8 : if (signal_number < 0 || signal_number >= max_signal_number)
647 2 : return make_error_code(std::errc::invalid_argument);
648 :
649 : posix_signal_detail::signal_state* state =
650 6 : posix_signal_detail::get_signal_state();
651 6 : std::lock_guard state_lock(state->mutex);
652 6 : std::lock_guard lock(mutex_);
653 :
654 6 : signal_registration** deletion_point = &impl.signals_;
655 6 : signal_registration* reg = impl.signals_;
656 6 : while (reg && reg->signal_number < signal_number)
657 : {
658 MIS 0 : deletion_point = ®->next_in_set;
659 0 : reg = reg->next_in_set;
660 : }
661 :
662 HIT 6 : if (!reg || reg->signal_number != signal_number)
663 3 : return {};
664 :
665 : // Restore default handler on last global unregistration
666 3 : if (state->registration_count[signal_number] == 1)
667 : {
668 3 : struct sigaction sa = {};
669 3 : sa.sa_handler = SIG_DFL;
670 3 : sigemptyset(&sa.sa_mask);
671 3 : sa.sa_flags = 0;
672 :
673 3 : if (::sigaction(signal_number, &sa, nullptr) < 0)
674 MIS 0 : return make_error_code(std::errc::invalid_argument);
675 :
676 : // Clear stored flags
677 HIT 3 : state->registered_flags[signal_number] = signal_set::none;
678 : }
679 :
680 3 : *deletion_point = reg->next_in_set;
681 :
682 3 : if (registrations_[signal_number] == reg)
683 3 : registrations_[signal_number] = reg->next_in_table;
684 3 : if (reg->prev_in_table)
685 MIS 0 : reg->prev_in_table->next_in_table = reg->next_in_table;
686 HIT 3 : if (reg->next_in_table)
687 MIS 0 : reg->next_in_table->prev_in_table = reg->prev_in_table;
688 :
689 HIT 3 : --state->registration_count[signal_number];
690 3 : --registration_count_[signal_number];
691 :
692 3 : delete reg;
693 3 : return {};
694 6 : }
695 :
696 : inline std::error_code
697 130 : posix_signal_service::clear_signals(posix_signal& impl)
698 : {
699 : posix_signal_detail::signal_state* state =
700 130 : posix_signal_detail::get_signal_state();
701 130 : std::lock_guard state_lock(state->mutex);
702 130 : std::lock_guard lock(mutex_);
703 :
704 130 : std::error_code first_error;
705 :
706 250 : while (signal_registration* reg = impl.signals_)
707 : {
708 120 : int signal_number = reg->signal_number;
709 :
710 120 : if (state->registration_count[signal_number] == 1)
711 : {
712 110 : struct sigaction sa = {};
713 110 : sa.sa_handler = SIG_DFL;
714 110 : sigemptyset(&sa.sa_mask);
715 110 : sa.sa_flags = 0;
716 :
717 110 : if (::sigaction(signal_number, &sa, nullptr) < 0 && !first_error)
718 MIS 0 : first_error = make_error_code(std::errc::invalid_argument);
719 :
720 : // Clear stored flags
721 HIT 110 : state->registered_flags[signal_number] = signal_set::none;
722 : }
723 :
724 120 : impl.signals_ = reg->next_in_set;
725 :
726 120 : if (registrations_[signal_number] == reg)
727 120 : registrations_[signal_number] = reg->next_in_table;
728 120 : if (reg->prev_in_table)
729 MIS 0 : reg->prev_in_table->next_in_table = reg->next_in_table;
730 HIT 120 : if (reg->next_in_table)
731 10 : reg->next_in_table->prev_in_table = reg->prev_in_table;
732 :
733 120 : --state->registration_count[signal_number];
734 120 : --registration_count_[signal_number];
735 :
736 120 : delete reg;
737 120 : }
738 :
739 130 : if (first_error)
740 MIS 0 : return first_error;
741 HIT 130 : return {};
742 130 : }
743 :
744 : inline void
745 140 : posix_signal_service::cancel_wait(posix_signal& impl)
746 : {
747 140 : bool was_waiting = false;
748 140 : signal_op* op = nullptr;
749 :
750 : {
751 140 : std::lock_guard lock(mutex_);
752 140 : impl.cancelled_ = true;
753 140 : if (impl.waiting_)
754 : {
755 5 : was_waiting = true;
756 5 : impl.waiting_ = false;
757 5 : op = &impl.pending_op_;
758 : }
759 140 : }
760 :
761 140 : if (was_waiting)
762 : {
763 5 : if (op->ec_out)
764 5 : *op->ec_out = make_error_code(capy::error::canceled);
765 5 : if (op->signal_out)
766 5 : *op->signal_out = 0;
767 5 : op->cont.h = op->h;
768 5 : op->d.post(op->cont);
769 5 : sched_->work_finished();
770 : }
771 140 : }
772 :
773 : inline void
774 311 : posix_signal_service::start_wait(posix_signal& impl, signal_op* op)
775 : {
776 : {
777 311 : std::lock_guard lock(mutex_);
778 :
779 : // Check if cancel() was called before this wait started
780 311 : if (impl.cancelled_)
781 : {
782 2 : impl.cancelled_ = false;
783 2 : if (op->ec_out)
784 2 : *op->ec_out = make_error_code(capy::error::canceled);
785 2 : if (op->signal_out)
786 2 : *op->signal_out = 0;
787 2 : op->cont.h = op->h;
788 2 : op->d.post(op->cont);
789 2 : return;
790 : }
791 :
792 : // Check for queued signals first (signal arrived before wait started)
793 309 : signal_registration* reg = impl.signals_;
794 622 : while (reg)
795 : {
796 313 : if (reg->undelivered > 0)
797 : {
798 MIS 0 : --reg->undelivered;
799 0 : op->signal_number = reg->signal_number;
800 : // svc=nullptr: no work_finished needed since we never called work_started
801 0 : op->svc = nullptr;
802 0 : sched_->post(op);
803 0 : return;
804 : }
805 HIT 313 : reg = reg->next_in_set;
806 : }
807 :
808 : // No queued signals - wait for delivery
809 309 : impl.waiting_ = true;
810 : // svc=this: signal_op::operator() will call work_finished() to balance this
811 309 : op->svc = this;
812 309 : sched_->work_started();
813 311 : }
814 : }
815 :
816 : inline void
817 302 : posix_signal_service::deliver_signal(int signal_number)
818 : {
819 302 : if (signal_number < 0 || signal_number >= max_signal_number)
820 MIS 0 : return;
821 :
822 : posix_signal_detail::signal_state* state =
823 HIT 302 : posix_signal_detail::get_signal_state();
824 302 : std::lock_guard lock(state->mutex);
825 :
826 302 : posix_signal_service* service = state->service_list;
827 604 : while (service)
828 : {
829 302 : std::lock_guard svc_lock(service->mutex_);
830 :
831 302 : signal_registration* reg = service->registrations_[signal_number];
832 606 : while (reg)
833 : {
834 304 : posix_signal* impl = static_cast<posix_signal*>(reg->owner);
835 :
836 304 : if (impl->waiting_)
837 : {
838 304 : impl->waiting_ = false;
839 304 : impl->pending_op_.signal_number = signal_number;
840 304 : service->post(&impl->pending_op_);
841 : }
842 : else
843 : {
844 MIS 0 : ++reg->undelivered;
845 : }
846 :
847 HIT 304 : reg = reg->next_in_table;
848 : }
849 :
850 302 : service = service->next_;
851 302 : }
852 302 : }
853 :
854 : inline void
855 : posix_signal_service::work_started() noexcept
856 : {
857 : sched_->work_started();
858 : }
859 :
860 : inline void
861 304 : posix_signal_service::work_finished() noexcept
862 : {
863 304 : sched_->work_finished();
864 304 : }
865 :
866 : inline void
867 304 : posix_signal_service::post(signal_op* op)
868 : {
869 304 : sched_->post(op);
870 304 : }
871 :
872 : inline void
873 1603 : posix_signal_service::add_service(posix_signal_service* service)
874 : {
875 : posix_signal_detail::signal_state* state =
876 1603 : posix_signal_detail::get_signal_state();
877 1603 : std::lock_guard lock(state->mutex);
878 :
879 1603 : service->next_ = state->service_list;
880 1603 : service->prev_ = nullptr;
881 1603 : if (state->service_list)
882 5 : state->service_list->prev_ = service;
883 1603 : state->service_list = service;
884 1603 : }
885 :
886 : inline void
887 1603 : posix_signal_service::remove_service(posix_signal_service* service)
888 : {
889 : posix_signal_detail::signal_state* state =
890 1603 : posix_signal_detail::get_signal_state();
891 1603 : std::lock_guard lock(state->mutex);
892 :
893 1603 : if (service->next_ || service->prev_ || state->service_list == service)
894 : {
895 1603 : if (state->service_list == service)
896 1603 : state->service_list = service->next_;
897 1603 : if (service->prev_)
898 MIS 0 : service->prev_->next_ = service->next_;
899 HIT 1603 : if (service->next_)
900 5 : service->next_->prev_ = service->prev_;
901 1603 : service->next_ = nullptr;
902 1603 : service->prev_ = nullptr;
903 : }
904 1603 : }
905 :
906 : // get_signal_service - factory function
907 :
908 : inline posix_signal_service&
909 1603 : get_signal_service(capy::execution_context& ctx, scheduler& sched)
910 : {
911 1603 : return ctx.make_service<posix_signal_service>(sched);
912 : }
913 :
914 : } // namespace detail
915 : } // namespace boost::corosio
916 :
917 : #endif // BOOST_COROSIO_POSIX
918 :
919 : #endif // BOOST_COROSIO_NATIVE_DETAIL_POSIX_POSIX_SIGNAL_SERVICE_HPP
|