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