Skip to main content

rustpython_vm/
signal.rs

1use core::{
2    cell::{Cell, RefCell},
3    fmt,
4    ops::{Deref, DerefMut, Index, IndexMut, Range},
5    sync::atomic::{AtomicBool, AtomicU8, Ordering},
6};
7use std::sync::mpsc;
8
9#[cfg(windows)]
10use core::sync::atomic::AtomicIsize;
11
12use crate::{PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject, VirtualMachine};
13
14pub(crate) const NSIG: usize = 64;
15
16#[cfg(not(feature = "threading"))]
17bitflagset::bitflag! {
18    /// Eval-breaker bits checked once per bytecode instruction.
19    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
20    #[repr(u8)]
21    enum EvalBreakerFlag {
22        Signal = 0,
23    }
24}
25
26#[cfg(feature = "threading")]
27bitflagset::bitflag! {
28    /// Eval-breaker bits checked once per bytecode instruction.
29    #[derive(Copy, Clone, Debug, PartialEq, Eq)]
30    #[repr(u8)]
31    enum EvalBreakerFlag {
32        Signal = 0,
33        Qsbr = 1,
34        Stop = 3,
35        Finalizing = 4,
36    }
37}
38
39bitflagset::bitflagset! {
40    #[derive(Copy, Clone, PartialEq, Eq)]
41    struct EvalBreakerBits(u8): EvalBreakerFlag
42}
43
44bitflagset::atomic_bitflagset!(struct EvalBreaker(AtomicU8) on EvalBreakerBits);
45
46/// Signal handlers and QSBR set bits with fetch_or (async-signal-safe,
47/// lock-free); consumers clear only their own bit with fetch_and.
48static EVAL_BREAKER: EvalBreaker = EvalBreaker::new();
49
50#[expect(
51    clippy::declare_interior_mutable_const,
52    reason = "workaround for const array repeat limitation (rust issue #79270)"
53)]
54const ATOMIC_FALSE: AtomicBool = AtomicBool::new(false);
55
56pub(crate) static TRIGGERS: [AtomicBool; NSIG] = [ATOMIC_FALSE; NSIG];
57
58#[cfg(windows)]
59static SIGINT_EVENT: AtomicIsize = AtomicIsize::new(0);
60
61thread_local! {
62    /// Prevent recursive signal handler invocation. When a Python signal
63    /// handler is running, new signals are deferred until it completes.
64    static IN_SIGNAL_HANDLER: Cell<bool> = const { Cell::new(false) };
65}
66
67struct SignalHandlerGuard;
68
69impl Drop for SignalHandlerGuard {
70    fn drop(&mut self) {
71        IN_SIGNAL_HANDLER.with(|h| h.set(false));
72    }
73}
74
75#[cfg_attr(feature = "flame-it", flame)]
76#[inline(always)]
77pub fn check_signals(vm: &VirtualMachine) -> PyResult<()> {
78    if vm.signal_handlers.get().is_none() {
79        return Ok(());
80    }
81
82    // Read-only check first: avoids cache-line invalidation on every
83    // instruction when no signal is pending (the common case).
84    if !EVAL_BREAKER.contains(&EvalBreakerFlag::Signal) {
85        return Ok(());
86    }
87
88    // Atomic RMW only when a signal is actually pending.
89    if !EVAL_BREAKER.remove(EvalBreakerFlag::Signal) {
90        return Ok(());
91    }
92
93    trigger_signals(vm)
94}
95
96#[inline(never)]
97#[cold]
98fn trigger_signals(vm: &VirtualMachine) -> PyResult<()> {
99    if IN_SIGNAL_HANDLER.with(|h| h.replace(true)) {
100        // Already inside a signal handler — defer pending signals
101        set_triggered();
102        return Ok(());
103    }
104    let _guard = SignalHandlerGuard;
105
106    let signal_handlers = vm
107        .signal_handlers
108        .get()
109        .expect("should never fail since we check above");
110
111    for (signum, trigger) in TRIGGERS.iter().enumerate().skip(1) {
112        let triggered = trigger.swap(false, Ordering::Relaxed);
113        if !triggered {
114            continue;
115        }
116
117        // SAFETY: TRIGGERS has the same length as the signal_handlers
118        let signum = unsafe { SignalNum::new_unchecked(signum as i32) };
119
120        // Read the handler out and drop the borrow before running it. A
121        // handler is free to call signal.signal(), which takes the same cell
122        // mutably, and a live read borrow turns that into a panic.
123        let handler = signal_handlers.borrow()[signum].clone();
124
125        if let Some(handler) = handler
126            && let Some(callable) = handler.to_callable()
127        {
128            callable.invoke((signum.as_i32(), vm.ctx.none()), vm)?;
129        }
130    }
131
132    if let Some(signal_rx) = &vm.signal_rx {
133        for f in signal_rx.rx.try_iter() {
134            f(vm)?;
135        }
136    }
137
138    Ok(())
139}
140
141pub(crate) fn set_triggered() {
142    // fetch_or (not store) so a signal handler never clobbers the QSBR bit;
143    // this compiles to a lock-free RMW, safe to call from a signal handler.
144    EVAL_BREAKER.insert(EvalBreakerFlag::Signal);
145}
146
147/// Any eval-breaker bit pending? One relaxed load; checked per instruction.
148#[inline(always)]
149pub(crate) fn eval_breaker_pending() -> bool {
150    !EVAL_BREAKER.is_empty()
151}
152
153/// Extra eval-breaker bits used only when more than one thread can run.
154#[cfg(feature = "threading")]
155mod mt {
156    use super::{EVAL_BREAKER, EvalBreakerFlag};
157
158    /// QSBR has retired allocations pending reclamation.
159    pub(crate) fn set_qsbr_bit() {
160        EVAL_BREAKER.insert(EvalBreakerFlag::Qsbr);
161    }
162
163    pub(crate) fn clear_qsbr_bit() {
164        EVAL_BREAKER.remove(EvalBreakerFlag::Qsbr);
165    }
166
167    pub(crate) fn qsbr_bit_set() -> bool {
168        EVAL_BREAKER.contains(&EvalBreakerFlag::Qsbr)
169    }
170
171    /// Record that some thread's `stop_requested` flag may be set. Shared by
172    /// every thread rather than being per-thread: the fast path checked once
173    /// per bytecode instruction becomes a single relaxed load of this word.
174    /// Sticky until `start_the_world`/`reset_after_fork` clears it.
175    pub(crate) fn set_stop_bit() {
176        EVAL_BREAKER.insert(EvalBreakerFlag::Stop);
177    }
178
179    /// Clear the shared stop-the-world bit. Only safe to call from
180    /// `start_the_world`/`reset_after_fork`.
181    pub(crate) fn clear_stop_bit() {
182        EVAL_BREAKER.remove(EvalBreakerFlag::Stop);
183    }
184
185    /// Record that finalization has begun (`Interpreter::finalize`). Set once
186    /// and never cleared.
187    pub(crate) fn set_finalizing_bit() {
188        EVAL_BREAKER.insert(EvalBreakerFlag::Finalizing);
189    }
190
191    /// Drop every process-wide eval-breaker bit. Tests that assert a single
192    /// thread's `stop_requested` must not trip `eval_breaker_pending` have to
193    /// start from a clean word: cargo's Windows runner shares the process
194    /// across `#[test]` functions, so a sibling can leave SIGNAL/QSBR/STOP.
195    #[cfg(test)]
196    pub(crate) fn clear_eval_breaker_for_test() {
197        EVAL_BREAKER.clear();
198    }
199}
200
201#[cfg(feature = "threading")]
202pub(crate) use mt::{
203    clear_qsbr_bit, clear_stop_bit, qsbr_bit_set, set_finalizing_bit, set_qsbr_bit, set_stop_bit,
204};
205
206#[cfg(all(test, feature = "threading"))]
207pub(crate) use mt::clear_eval_breaker_for_test;
208
209/// Reset all signal trigger state after fork in child process.
210/// Stale triggers from the parent must not fire in the child.
211#[cfg(all(unix, feature = "host_env"))]
212pub(crate) fn clear_after_fork() {
213    EVAL_BREAKER.remove(EvalBreakerFlag::Signal);
214    for trigger in &TRIGGERS {
215        trigger.store(false, Ordering::Relaxed);
216    }
217}
218
219/// A valid signal number.
220#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
221pub struct SignalNum(i32);
222
223impl SignalNum {
224    pub(crate) const VALID_RANGE: Range<i32> = 1..NSIG as i32;
225
226    /// Alias for:
227    /// ```rust
228    /// # use rustpython_vm::signal::SignalNum;
229    ///
230    /// unsafe { SignalNum::new_unchecked(libc::SIGINT) };
231    /// ```
232    #[cfg(any(unix, windows))]
233    #[allow(dead_code, reason = "Not used on all platforms")]
234    pub(crate) const SIGINT: Self = Self(libc::SIGINT);
235
236    /// Construct [`Self`] without any validation on the signalnum value.
237    ///
238    /// # Safety
239    ///
240    /// Caller's responsibility to ensure the signal num is valid.
241    #[must_use]
242    pub const unsafe fn new_unchecked(value: i32) -> Self {
243        Self(value)
244    }
245
246    /// Get the self as an [`i32`].
247    #[must_use]
248    pub const fn as_i32(&self) -> i32 {
249        self.0
250    }
251
252    /// Get the self as an [`usize`].
253    #[must_use]
254    pub const fn as_usize(&self) -> usize {
255        self.0 as usize
256    }
257}
258
259impl fmt::Display for SignalNum {
260    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261        fmt::Display::fmt(&self.0, f)
262    }
263}
264
265impl From<SignalNum> for i32 {
266    fn from(signalnum: SignalNum) -> Self {
267        signalnum.as_i32()
268    }
269}
270
271impl TryFrom<i32> for SignalNum {
272    type Error = String;
273
274    fn try_from(value: i32) -> Result<Self, Self::Error> {
275        let bounds = cfg_select! {
276            all(windows, feature = "host_env") => rustpython_host_env::signal::VALID_SIGNALS,
277            _ => Self::VALID_RANGE,
278        };
279
280        if bounds.contains(&value) {
281            Ok(Self(value))
282        } else {
283            Err("signal number out of range".into())
284        }
285    }
286}
287
288impl TryFromObject for SignalNum {
289    fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
290        Self::try_from(i32::try_from_borrowed_object(vm, &obj)?)
291            .map_err(|msg| vm.new_value_error(msg))
292    }
293}
294
295/// Similar to `PyErr_SetInterruptEx` in CPython
296///
297/// Missing signal handler for the given signal number is silently ignored.
298#[cfg(all(not(target_arch = "wasm32"), feature = "host_env"))]
299pub fn set_interrupt_ex(signum: SignalNum) -> PyResult<()> {
300    use crate::stdlib::_signal::_signal::{SIG_DFL, SIG_IGN, run_signal};
301
302    match signum.as_usize() {
303        SIG_DFL | SIG_IGN => Ok(()),
304        _ => {
305            // interrupt the main thread with given signal number
306            run_signal(signum.into());
307            Ok(())
308        }
309    }
310}
311
312pub type UserSignal = Box<dyn FnOnce(&VirtualMachine) -> PyResult<()> + Send>;
313
314#[derive(Clone, Debug)]
315pub struct UserSignalSender {
316    tx: mpsc::Sender<UserSignal>,
317}
318
319#[derive(Debug)]
320pub struct UserSignalReceiver {
321    rx: mpsc::Receiver<UserSignal>,
322}
323
324impl UserSignalSender {
325    pub fn send(&self, sig: UserSignal) -> Result<(), UserSignalSendError> {
326        self.tx
327            .send(sig)
328            .map_err(|mpsc::SendError(sig)| UserSignalSendError(sig))?;
329        set_triggered();
330        Ok(())
331    }
332}
333
334pub struct UserSignalSendError(pub UserSignal);
335
336impl fmt::Debug for UserSignalSendError {
337    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
338        f.debug_struct("UserSignalSendError")
339            .finish_non_exhaustive()
340    }
341}
342
343impl fmt::Display for UserSignalSendError {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        f.write_str("sending a signal to a exited vm")
346    }
347}
348
349#[must_use]
350pub fn user_signal_channel() -> (UserSignalSender, UserSignalReceiver) {
351    let (tx, rx) = mpsc::channel();
352    (UserSignalSender { tx }, UserSignalReceiver { rx })
353}
354
355#[cfg(windows)]
356pub fn set_sigint_event(handle: isize) {
357    SIGINT_EVENT.store(handle, Ordering::Release);
358}
359
360#[cfg(windows)]
361pub fn get_sigint_event() -> Option<isize> {
362    let handle = SIGINT_EVENT.load(Ordering::Acquire);
363    if handle == 0 { None } else { Some(handle) }
364}
365
366pub struct SignalHandlersInner([Option<PyObjectRef>; NSIG]);
367
368impl Default for SignalHandlersInner {
369    fn default() -> Self {
370        Self([const { None }; NSIG])
371    }
372}
373
374impl Index<SignalNum> for SignalHandlersInner {
375    type Output = Option<PyObjectRef>;
376
377    fn index(&self, index: SignalNum) -> &Self::Output {
378        &self.0[index.as_usize()]
379    }
380}
381
382impl IndexMut<SignalNum> for SignalHandlersInner {
383    fn index_mut(&mut self, index: SignalNum) -> &mut Self::Output {
384        &mut self.0[index.as_usize()]
385    }
386}
387
388pub struct SignalHandlers(Box<RefCell<SignalHandlersInner>>);
389
390impl Default for SignalHandlers {
391    fn default() -> Self {
392        Self(Box::new(RefCell::new(SignalHandlersInner::default())))
393    }
394}
395
396impl Deref for SignalHandlers {
397    type Target = Box<RefCell<SignalHandlersInner>>;
398
399    fn deref(&self) -> &Self::Target {
400        &self.0
401    }
402}
403
404impl DerefMut for SignalHandlers {
405    fn deref_mut(&mut self) -> &mut Self::Target {
406        &mut self.0
407    }
408}