seq_runtime/signal.rs
1//! Signal handling API for Seq
2//!
3//! Provides Unix signal handling with a safe, flag-based approach:
4//! - Signals are trapped and set atomic flags (no code runs in signal context)
5//! - User code polls for signals at safe points
6//! - Fits Seq's explicit, predictable style
7//!
8//! # Example
9//! ```seq
10//! signal.SIGINT signal.trap
11//! signal.SIGTERM signal.trap
12//!
13//! : main-loop ( -- )
14//! signal.SIGINT signal.received? if
15//! "Shutting down..." io.write-line
16//! return
17//! then
18//! do-work
19//! main-loop
20//! ;
21//! ```
22//!
23//! # Safety
24//!
25//! Signal handlers execute in an interrupt context with severe restrictions.
26//! This module uses only async-signal-safe operations (atomic flag setting).
27//! All Seq code execution happens outside the signal handler, when the user
28//! explicitly checks for received signals.
29//!
30//! # Thread Safety and Concurrent Access
31//!
32//! This module is designed to be safe for concurrent use from multiple strands:
33//!
34//! - **Handler installation** (`signal.trap`, `signal.default`, `signal.ignore`):
35//! Protected by a mutex to ensure only one strand modifies handlers at a time.
36//! Concurrent calls will serialize safely.
37//!
38//! - **Flag operations** (`signal.received?`, `signal.pending?`, `signal.clear`):
39//! Use lock-free atomic operations with appropriate memory ordering:
40//! - `signal.received?`: Atomic swap with Acquire ordering (read-modify-write)
41//! - `signal.pending?`: Atomic load with Acquire ordering (read-only)
42//! - `signal.clear`: Atomic store with Release ordering (write-only)
43//!
44//! Multiple strands can safely check the same signal. However, `signal.received?`
45//! clears the flag atomically, so if two strands both call it, only one will
46//! observe `true`. Use `signal.pending?` if you need non-destructive reads.
47//!
48//! - **Signal handler**: Executes outside the strand context (in OS interrupt
49//! context) and only performs a single atomic store. This is async-signal-safe.
50//!
51//! This module uses `sigaction()` instead of the deprecated `signal()` function
52//! for well-defined behavior in multithreaded environments.
53//!
54//! # Platform Support
55//!
56//! - Unix: Full signal support using sigaction()
57//! - Windows: Stub implementations (signals not supported, all operations no-op)
58//!
59//! # Module Layout
60//!
61//! Per-concern sub-modules:
62//! - `constants` — 9 `SIG*` constant getters (unix + non-unix stubs)
63//! - `handlers` — unix-only sigaction wrappers (`install`/`restore`/`ignore`)
64//! plus the async-signal-safe `flag_signal_handler`
65//! - `ops` — user-facing FFI ops (`trap`/`received?`/`pending?`/`default`/
66//! `ignore`/`clear`), both unix and non-unix stubs
67//!
68//! Shared state (`SIGNAL_FLAGS` and `MAX_SIGNAL`) stays on this aggregator so
69//! every sub-module points at the same flag table.
70
71use std::sync::atomic::AtomicBool;
72
73/// Maximum signal number we support (covers all standard Unix signals)
74pub(super) const MAX_SIGNAL: usize = 32;
75
76/// Atomic flags for each signal - set by signal handler, cleared by user code
77pub(super) static SIGNAL_FLAGS: [AtomicBool; MAX_SIGNAL] = [
78 AtomicBool::new(false),
79 AtomicBool::new(false),
80 AtomicBool::new(false),
81 AtomicBool::new(false),
82 AtomicBool::new(false),
83 AtomicBool::new(false),
84 AtomicBool::new(false),
85 AtomicBool::new(false),
86 AtomicBool::new(false),
87 AtomicBool::new(false),
88 AtomicBool::new(false),
89 AtomicBool::new(false),
90 AtomicBool::new(false),
91 AtomicBool::new(false),
92 AtomicBool::new(false),
93 AtomicBool::new(false),
94 AtomicBool::new(false),
95 AtomicBool::new(false),
96 AtomicBool::new(false),
97 AtomicBool::new(false),
98 AtomicBool::new(false),
99 AtomicBool::new(false),
100 AtomicBool::new(false),
101 AtomicBool::new(false),
102 AtomicBool::new(false),
103 AtomicBool::new(false),
104 AtomicBool::new(false),
105 AtomicBool::new(false),
106 AtomicBool::new(false),
107 AtomicBool::new(false),
108 AtomicBool::new(false),
109 AtomicBool::new(false),
110];
111
112mod constants;
113mod ops;
114
115#[cfg(unix)]
116mod handlers;
117
118pub use constants::*;
119pub use ops::*;
120
121#[cfg(test)]
122mod tests;