use core::{
cell::{Cell, RefCell},
fmt,
ops::{Deref, DerefMut, Index, IndexMut, Range},
sync::atomic::{AtomicBool, AtomicU8, Ordering},
};
use std::sync::mpsc;
#[cfg(windows)]
use core::sync::atomic::AtomicIsize;
use crate::{PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject, VirtualMachine};
pub(crate) const NSIG: usize = 64;
#[cfg(not(feature = "threading"))]
bitflagset::bitflag! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
enum EvalBreakerFlag {
Signal = 0,
}
}
#[cfg(feature = "threading")]
bitflagset::bitflag! {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
enum EvalBreakerFlag {
Signal = 0,
Qsbr = 1,
Stop = 3,
Finalizing = 4,
}
}
bitflagset::bitflagset! {
#[derive(Copy, Clone, PartialEq, Eq)]
struct EvalBreakerBits(u8): EvalBreakerFlag
}
bitflagset::atomic_bitflagset!(struct EvalBreaker(AtomicU8) on EvalBreakerBits);
static EVAL_BREAKER: EvalBreaker = EvalBreaker::new();
#[expect(
clippy::declare_interior_mutable_const,
reason = "workaround for const array repeat limitation (rust issue #79270)"
)]
const ATOMIC_FALSE: AtomicBool = AtomicBool::new(false);
pub(crate) static TRIGGERS: [AtomicBool; NSIG] = [ATOMIC_FALSE; NSIG];
#[cfg(windows)]
static SIGINT_EVENT: AtomicIsize = AtomicIsize::new(0);
thread_local! {
static IN_SIGNAL_HANDLER: Cell<bool> = const { Cell::new(false) };
}
struct SignalHandlerGuard;
impl Drop for SignalHandlerGuard {
fn drop(&mut self) {
IN_SIGNAL_HANDLER.with(|h| h.set(false));
}
}
#[cfg_attr(feature = "flame-it", flame)]
#[inline(always)]
pub fn check_signals(vm: &VirtualMachine) -> PyResult<()> {
if vm.signal_handlers.get().is_none() {
return Ok(());
}
if !EVAL_BREAKER.contains(&EvalBreakerFlag::Signal) {
return Ok(());
}
if !EVAL_BREAKER.remove(EvalBreakerFlag::Signal) {
return Ok(());
}
trigger_signals(vm)
}
#[inline(never)]
#[cold]
fn trigger_signals(vm: &VirtualMachine) -> PyResult<()> {
if IN_SIGNAL_HANDLER.with(|h| h.replace(true)) {
set_triggered();
return Ok(());
}
let _guard = SignalHandlerGuard;
let signal_handlers = vm
.signal_handlers
.get()
.expect("should never fail since we check above");
for (signum, trigger) in TRIGGERS.iter().enumerate().skip(1) {
let triggered = trigger.swap(false, Ordering::Relaxed);
if !triggered {
continue;
}
let signum = unsafe { SignalNum::new_unchecked(signum as i32) };
let handler = signal_handlers.borrow()[signum].clone();
if let Some(handler) = handler
&& let Some(callable) = handler.to_callable()
{
callable.invoke((signum.as_i32(), vm.ctx.none()), vm)?;
}
}
if let Some(signal_rx) = &vm.signal_rx {
for f in signal_rx.rx.try_iter() {
f(vm)?;
}
}
Ok(())
}
pub(crate) fn set_triggered() {
EVAL_BREAKER.insert(EvalBreakerFlag::Signal);
}
#[inline(always)]
pub(crate) fn eval_breaker_pending() -> bool {
!EVAL_BREAKER.is_empty()
}
#[cfg(feature = "threading")]
mod mt {
use super::{EVAL_BREAKER, EvalBreakerFlag};
pub(crate) fn set_qsbr_bit() {
EVAL_BREAKER.insert(EvalBreakerFlag::Qsbr);
}
pub(crate) fn clear_qsbr_bit() {
EVAL_BREAKER.remove(EvalBreakerFlag::Qsbr);
}
pub(crate) fn qsbr_bit_set() -> bool {
EVAL_BREAKER.contains(&EvalBreakerFlag::Qsbr)
}
pub(crate) fn set_stop_bit() {
EVAL_BREAKER.insert(EvalBreakerFlag::Stop);
}
pub(crate) fn clear_stop_bit() {
EVAL_BREAKER.remove(EvalBreakerFlag::Stop);
}
pub(crate) fn set_finalizing_bit() {
EVAL_BREAKER.insert(EvalBreakerFlag::Finalizing);
}
#[cfg(test)]
pub(crate) fn clear_eval_breaker_for_test() {
EVAL_BREAKER.clear();
}
}
#[cfg(feature = "threading")]
pub(crate) use mt::{
clear_qsbr_bit, clear_stop_bit, qsbr_bit_set, set_finalizing_bit, set_qsbr_bit, set_stop_bit,
};
#[cfg(all(test, feature = "threading"))]
pub(crate) use mt::clear_eval_breaker_for_test;
#[cfg(all(unix, feature = "host_env"))]
pub(crate) fn clear_after_fork() {
EVAL_BREAKER.remove(EvalBreakerFlag::Signal);
for trigger in &TRIGGERS {
trigger.store(false, Ordering::Relaxed);
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct SignalNum(i32);
impl SignalNum {
pub(crate) const VALID_RANGE: Range<i32> = 1..NSIG as i32;
#[cfg(any(unix, windows))]
#[allow(dead_code, reason = "Not used on all platforms")]
pub(crate) const SIGINT: Self = Self(libc::SIGINT);
#[must_use]
pub const unsafe fn new_unchecked(value: i32) -> Self {
Self(value)
}
#[must_use]
pub const fn as_i32(&self) -> i32 {
self.0
}
#[must_use]
pub const fn as_usize(&self) -> usize {
self.0 as usize
}
}
impl fmt::Display for SignalNum {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl From<SignalNum> for i32 {
fn from(signalnum: SignalNum) -> Self {
signalnum.as_i32()
}
}
impl TryFrom<i32> for SignalNum {
type Error = String;
fn try_from(value: i32) -> Result<Self, Self::Error> {
let bounds = cfg_select! {
all(windows, feature = "host_env") => rustpython_host_env::signal::VALID_SIGNALS,
_ => Self::VALID_RANGE,
};
if bounds.contains(&value) {
Ok(Self(value))
} else {
Err("signal number out of range".into())
}
}
}
impl TryFromObject for SignalNum {
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
Self::try_from(i32::try_from_borrowed_object(vm, &obj)?)
.map_err(|msg| vm.new_value_error(msg))
}
}
#[cfg(all(not(target_arch = "wasm32"), feature = "host_env"))]
pub fn set_interrupt_ex(signum: SignalNum) -> PyResult<()> {
use crate::stdlib::_signal::_signal::{SIG_DFL, SIG_IGN, run_signal};
match signum.as_usize() {
SIG_DFL | SIG_IGN => Ok(()),
_ => {
run_signal(signum.into());
Ok(())
}
}
}
pub type UserSignal = Box<dyn FnOnce(&VirtualMachine) -> PyResult<()> + Send>;
#[derive(Clone, Debug)]
pub struct UserSignalSender {
tx: mpsc::Sender<UserSignal>,
}
#[derive(Debug)]
pub struct UserSignalReceiver {
rx: mpsc::Receiver<UserSignal>,
}
impl UserSignalSender {
pub fn send(&self, sig: UserSignal) -> Result<(), UserSignalSendError> {
self.tx
.send(sig)
.map_err(|mpsc::SendError(sig)| UserSignalSendError(sig))?;
set_triggered();
Ok(())
}
}
pub struct UserSignalSendError(pub UserSignal);
impl fmt::Debug for UserSignalSendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UserSignalSendError")
.finish_non_exhaustive()
}
}
impl fmt::Display for UserSignalSendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("sending a signal to a exited vm")
}
}
#[must_use]
pub fn user_signal_channel() -> (UserSignalSender, UserSignalReceiver) {
let (tx, rx) = mpsc::channel();
(UserSignalSender { tx }, UserSignalReceiver { rx })
}
#[cfg(windows)]
pub fn set_sigint_event(handle: isize) {
SIGINT_EVENT.store(handle, Ordering::Release);
}
#[cfg(windows)]
pub fn get_sigint_event() -> Option<isize> {
let handle = SIGINT_EVENT.load(Ordering::Acquire);
if handle == 0 { None } else { Some(handle) }
}
pub struct SignalHandlersInner([Option<PyObjectRef>; NSIG]);
impl Default for SignalHandlersInner {
fn default() -> Self {
Self([const { None }; NSIG])
}
}
impl Index<SignalNum> for SignalHandlersInner {
type Output = Option<PyObjectRef>;
fn index(&self, index: SignalNum) -> &Self::Output {
&self.0[index.as_usize()]
}
}
impl IndexMut<SignalNum> for SignalHandlersInner {
fn index_mut(&mut self, index: SignalNum) -> &mut Self::Output {
&mut self.0[index.as_usize()]
}
}
pub struct SignalHandlers(Box<RefCell<SignalHandlersInner>>);
impl Default for SignalHandlers {
fn default() -> Self {
Self(Box::new(RefCell::new(SignalHandlersInner::default())))
}
}
impl Deref for SignalHandlers {
type Target = Box<RefCell<SignalHandlersInner>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for SignalHandlers {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}