rustpython_vm/
signal.rs

1#![cfg_attr(target_os = "wasi", allow(dead_code))]
2use crate::{PyResult, VirtualMachine};
3use std::{
4    fmt,
5    sync::{
6        atomic::{AtomicBool, Ordering},
7        mpsc,
8    },
9};
10
11pub(crate) const NSIG: usize = 64;
12static ANY_TRIGGERED: AtomicBool = AtomicBool::new(false);
13// hack to get around const array repeat expressions, rust issue #79270
14#[allow(clippy::declare_interior_mutable_const)]
15const ATOMIC_FALSE: AtomicBool = AtomicBool::new(false);
16pub(crate) static TRIGGERS: [AtomicBool; NSIG] = [ATOMIC_FALSE; NSIG];
17
18#[cfg_attr(feature = "flame-it", flame)]
19#[inline(always)]
20pub fn check_signals(vm: &VirtualMachine) -> PyResult<()> {
21    if vm.signal_handlers.is_none() {
22        return Ok(());
23    }
24
25    if !ANY_TRIGGERED.swap(false, Ordering::Acquire) {
26        return Ok(());
27    }
28
29    trigger_signals(vm)
30}
31#[inline(never)]
32#[cold]
33fn trigger_signals(vm: &VirtualMachine) -> PyResult<()> {
34    // unwrap should never fail since we check above
35    let signal_handlers = vm.signal_handlers.as_ref().unwrap().borrow();
36    for (signum, trigger) in TRIGGERS.iter().enumerate().skip(1) {
37        let triggered = trigger.swap(false, Ordering::Relaxed);
38        if triggered {
39            if let Some(handler) = &signal_handlers[signum] {
40                if let Some(callable) = handler.to_callable() {
41                    callable.invoke((signum, vm.ctx.none()), vm)?;
42                }
43            }
44        }
45    }
46    if let Some(signal_rx) = &vm.signal_rx {
47        for f in signal_rx.rx.try_iter() {
48            f(vm)?;
49        }
50    }
51    Ok(())
52}
53
54pub(crate) fn set_triggered() {
55    ANY_TRIGGERED.store(true, Ordering::Release);
56}
57
58pub fn assert_in_range(signum: i32, vm: &VirtualMachine) -> PyResult<()> {
59    if (1..NSIG as i32).contains(&signum) {
60        Ok(())
61    } else {
62        Err(vm.new_value_error("signal number out of range".to_owned()))
63    }
64}
65
66/// Similar to `PyErr_SetInterruptEx` in CPython
67///
68/// Missing signal handler for the given signal number is silently ignored.
69#[allow(dead_code)]
70#[cfg(not(target_arch = "wasm32"))]
71pub fn set_interrupt_ex(signum: i32, vm: &VirtualMachine) -> PyResult<()> {
72    use crate::stdlib::signal::_signal::{run_signal, SIG_DFL, SIG_IGN};
73    assert_in_range(signum, vm)?;
74
75    match signum as usize {
76        SIG_DFL | SIG_IGN => Ok(()),
77        _ => {
78            // interrupt the main thread with given signal number
79            run_signal(signum);
80            Ok(())
81        }
82    }
83}
84
85pub type UserSignal = Box<dyn FnOnce(&VirtualMachine) -> PyResult<()> + Send>;
86
87#[derive(Clone, Debug)]
88pub struct UserSignalSender {
89    tx: mpsc::Sender<UserSignal>,
90}
91
92#[derive(Debug)]
93pub struct UserSignalReceiver {
94    rx: mpsc::Receiver<UserSignal>,
95}
96
97impl UserSignalSender {
98    pub fn send(&self, sig: UserSignal) -> Result<(), UserSignalSendError> {
99        self.tx
100            .send(sig)
101            .map_err(|mpsc::SendError(sig)| UserSignalSendError(sig))?;
102        set_triggered();
103        Ok(())
104    }
105}
106
107pub struct UserSignalSendError(pub UserSignal);
108
109impl fmt::Debug for UserSignalSendError {
110    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
111        f.debug_struct("UserSignalSendError")
112            .finish_non_exhaustive()
113    }
114}
115
116impl fmt::Display for UserSignalSendError {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        f.write_str("sending a signal to a exited vm")
119    }
120}
121
122pub fn user_signal_channel() -> (UserSignalSender, UserSignalReceiver) {
123    let (tx, rx) = mpsc::channel();
124    (UserSignalSender { tx }, UserSignalReceiver { rx })
125}