Visitar URL original
Newtype SigalNum (#8096) · RustPython/RustPython@d758d60 · GitHub
Skip to content

Commit d758d60

Browse files
authored
Newtype SigalNum (#8096)
* Newtype SigalNum * impl Index<SigalNum> for SignalHandlersInner
1 parent c3e1f0f commit d758d60

5 files changed

Lines changed: 210 additions & 113 deletions

File tree

‎Lib/test/test_signal.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def test_setting_signal_handler_to_none_raises_error(self):
9393
self.assertRaises(TypeError, signal.signal,
9494
signal.SIGUSR1, None)
9595

96+
@unittest.expectedFailureIf(sys.platform == "android", "TODO: RUSTPYTHON; AssertionError: None is not an instance of <enum 'Handlers'>")
9697
def test_getsignal(self):
9798
hup = signal.signal(signal.SIGHUP, self.trivial_signal_handler)
9899
self.assertIsInstance(hup, signal.Handlers)
@@ -101,6 +102,7 @@ def test_getsignal(self):
101102
signal.signal(signal.SIGHUP, hup)
102103
self.assertEqual(signal.getsignal(signal.SIGHUP), hup)
103104

105+
@unittest.expectedFailureIf(sys.platform == "android", "TODO: RUSTPYTHON")
104106
def test_no_repr_is_called_on_signal_handler(self):
105107
# See https://github.com/python/cpython/issues/112559.
106108

@@ -778,6 +780,7 @@ def test_siginterrupt_off(self):
778780
self.assertFalse(interrupted)
779781

780782

783+
@unittest.skipIf(sys.platform == "android", "TODO: RUSTPYTHON; Error during teardown")
781784
@unittest.skipIf(sys.platform == "win32", "Not valid on Windows")
782785
@unittest.skipUnless(hasattr(signal, 'getitimer') and hasattr(signal, 'setitimer'),
783786
"needs signal.getitimer() and signal.setitimer()")
@@ -1264,6 +1267,7 @@ def decide_itimer_count(self):
12641267
"(> 10 ms.) on this platform (or system too busy)"
12651268
% (reso,))
12661269

1270+
@unittest.skipIf(sys.platform == "android", "TODO: RUSTPYTHON; TypeError: signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object")
12671271
@unittest.skipUnless(hasattr(signal, "setitimer"),
12681272
"test needs setitimer()")
12691273
def test_stress_delivery_dependent(self):
@@ -1310,6 +1314,7 @@ def second_handler(signum=None, frame=None):
13101314
# Python handler
13111315
self.assertEqual(len(sigs), N, "Some signals were lost")
13121316

1317+
@unittest.skipIf(sys.platform == "android", "TODO: RUSTPYTHON; TypeError: signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object")
13131318
@unittest.skipUnless(hasattr(signal, "setitimer"),
13141319
"test needs setitimer()")
13151320
def test_stress_delivery_simultaneous(self):
@@ -1409,6 +1414,7 @@ def cycle_handlers():
14091414

14101415
class RaiseSignalTest(unittest.TestCase):
14111416

1417+
@unittest.expectedFailureIf(sys.platform == "android", "TODO: RUSTPYTHON; AssertionError: KeyboardInterrupt not raised")
14121418
def test_sigint(self):
14131419
with self.assertRaises(KeyboardInterrupt):
14141420
signal.raise_signal(signal.SIGINT)
@@ -1425,6 +1431,7 @@ def test_invalid_argument(self):
14251431
else:
14261432
raise
14271433

1434+
@unittest.skipIf(sys.platform == "android", "TODO: RUSTPYTHON; TypeError: signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object")
14281435
def test_handler(self):
14291436
is_ok = False
14301437
def handler(a, b):
@@ -1455,6 +1462,7 @@ def __del__(self):
14551462

14561463
class PidfdSignalTest(unittest.TestCase):
14571464

1465+
@unittest.expectedFailureIf(sys.platform == "android", "TODO: RUSTPYTHON; AssertionError: KeyboardInterrupt not raised")
14581466
@unittest.skipUnless(
14591467
hasattr(signal, "pidfd_send_signal"),
14601468
"pidfd support not built in",

‎crates/host_env/src/signal.rs‎

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ mod ffi {
5555
}
5656
}
5757

58-
#[cfg(any(unix, windows))]
5958
/// # Safety
6059
///
6160
/// The caller must ensure `signalnum` is a valid platform signal number.
61+
#[cfg(any(unix, windows))]
6262
pub unsafe fn probe_handler(signalnum: i32) -> Option<sighandler_t> {
6363
let handler = unsafe { libc::signal(signalnum, libc::SIG_IGN) };
6464
if handler == libc::SIG_ERR as sighandler_t {
@@ -69,11 +69,11 @@ pub unsafe fn probe_handler(signalnum: i32) -> Option<sighandler_t> {
6969
}
7070
}
7171

72-
#[cfg(any(unix, windows))]
7372
/// # Safety
7473
///
7574
/// The caller must ensure `signalnum` is a valid platform signal number and
7675
/// `handler` is accepted by the platform signal ABI.
76+
#[cfg(any(unix, windows))]
7777
pub unsafe fn install_handler(signalnum: i32, handler: sighandler_t) -> io::Result<sighandler_t> {
7878
let old = unsafe { libc::signal(signalnum, handler) };
7979
if old == libc::SIG_ERR as sighandler_t {
@@ -158,7 +158,7 @@ pub fn pthread_sigmask(how: i32, set: &libc::sigset_t) -> io::Result<libc::sigse
158158
}
159159
}
160160

161-
#[cfg(target_os = "linux")]
161+
#[cfg(any(target_os = "android", target_os = "linux"))]
162162
pub fn pidfd_send_signal(pidfd: i32, sig: i32, flags: u32) -> io::Result<()> {
163163
let ret = unsafe {
164164
libc::syscall(
@@ -199,11 +199,6 @@ pub const CTRL_BREAK_EVENT: u32 = 1;
199199
#[cfg(windows)]
200200
pub const INVALID_SOCKET: libc::SOCKET = windows_sys::Win32::Networking::WinSock::INVALID_SOCKET;
201201

202-
#[cfg(windows)]
203-
pub fn is_valid_signal(signalnum: i32) -> bool {
204-
VALID_SIGNALS.contains(&signalnum)
205-
}
206-
207202
#[cfg(windows)]
208203
fn init_winsock() {
209204
static WSA_INIT: Once = Once::new();

‎crates/vm/src/signal.rs‎

Lines changed: 118 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
use crate::{PyObjectRef, PyResult, VirtualMachine};
21
use core::{
32
cell::{Cell, RefCell},
43
fmt,
5-
ops::{Deref, DerefMut},
4+
ops::{Deref, DerefMut, Index, IndexMut, Range},
65
sync::atomic::{AtomicBool, Ordering},
76
};
87
use std::sync::mpsc;
98

109
#[cfg(windows)]
1110
use core::sync::atomic::AtomicIsize;
1211

13-
static ANY_TRIGGERED: AtomicBool = AtomicBool::new(false);
12+
use crate::{PyObjectRef, PyResult, TryFromBorrowedObject, TryFromObject, VirtualMachine};
1413

1514
pub(crate) const NSIG: usize = 64;
1615

16+
static ANY_TRIGGERED: AtomicBool = AtomicBool::new(false);
17+
1718
#[expect(
1819
clippy::declare_interior_mutable_const,
1920
reason = "workaround for const array repeat limitation (rust issue #79270)"
@@ -70,22 +71,32 @@ fn trigger_signals(vm: &VirtualMachine) -> PyResult<()> {
7071
}
7172
let _guard = SignalHandlerGuard;
7273

73-
// unwrap should never fail since we check above
74-
let signal_handlers = vm.signal_handlers.get().unwrap().borrow();
74+
let signal_handlers = vm
75+
.signal_handlers
76+
.get()
77+
.expect("should never fail since we check above")
78+
.borrow();
79+
7580
for (signum, trigger) in TRIGGERS.iter().enumerate().skip(1) {
7681
let triggered = trigger.swap(false, Ordering::Relaxed);
82+
83+
// SAFETY: TRIGGERS has the same length as the signal_handlers
84+
let signum = unsafe { SignalNum::new_unchecked(signum as i32) };
85+
7786
if triggered
7887
&& let Some(handler) = &signal_handlers[signum]
7988
&& let Some(callable) = handler.to_callable()
8089
{
81-
callable.invoke((signum, vm.ctx.none()), vm)?;
90+
callable.invoke((signum.as_i32(), vm.ctx.none()), vm)?;
8291
}
8392
}
93+
8494
if let Some(signal_rx) = &vm.signal_rx {
8595
for f in signal_rx.rx.try_iter() {
8696
f(vm)?;
8797
}
8898
}
99+
89100
Ok(())
90101
}
91102

@@ -109,28 +120,94 @@ pub(crate) fn clear_after_fork() {
109120
}
110121
}
111122

112-
pub fn assert_in_range(signum: i32, vm: &VirtualMachine) -> PyResult<()> {
113-
if (1..NSIG as i32).contains(&signum) {
114-
Ok(())
115-
} else {
116-
Err(vm.new_value_error("signal number out of range"))
123+
/// A valid signal number.
124+
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
125+
pub struct SignalNum(i32);
126+
127+
impl SignalNum {
128+
pub(crate) const VALID_RANGE: Range<i32> = 1..NSIG as i32;
129+
130+
/// Alias for:
131+
/// ```rust
132+
/// # use rustpython_vm::signal::SignalNum;
133+
///
134+
/// unsafe { SignalNum::new_unchecked(libc::SIGINT) };
135+
/// ```
136+
#[cfg(any(unix, windows))]
137+
#[allow(dead_code, reason = "Not used on all platforms")]
138+
pub(crate) const SIGINT: Self = Self(libc::SIGINT);
139+
140+
/// Construct [`Self`] without any validation on the signalnum value.
141+
///
142+
/// # Safety
143+
///
144+
/// Caller's responsibility to ensure the signal num is valid.
145+
#[must_use]
146+
pub const unsafe fn new_unchecked(value: i32) -> Self {
147+
Self(value)
148+
}
149+
150+
/// Get the self as an [`i32`].
151+
#[must_use]
152+
pub const fn as_i32(&self) -> i32 {
153+
self.0
154+
}
155+
156+
/// Get the self as an [`usize`].
157+
#[must_use]
158+
pub const fn as_usize(&self) -> usize {
159+
self.0 as usize
160+
}
161+
}
162+
163+
impl fmt::Display for SignalNum {
164+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165+
fmt::Display::fmt(&self.0, f)
166+
}
167+
}
168+
169+
impl From<SignalNum> for i32 {
170+
fn from(signalnum: SignalNum) -> Self {
171+
signalnum.as_i32()
172+
}
173+
}
174+
175+
impl TryFrom<i32> for SignalNum {
176+
type Error = String;
177+
178+
fn try_from(value: i32) -> Result<Self, Self::Error> {
179+
let bounds = cfg_select! {
180+
all(windows, feature = "host_env") => rustpython_host_env::signal::VALID_SIGNALS,
181+
_ => Self::VALID_RANGE,
182+
};
183+
184+
if bounds.contains(&value) {
185+
Ok(Self(value))
186+
} else {
187+
Err("signal number out of range".into())
188+
}
189+
}
190+
}
191+
192+
impl TryFromObject for SignalNum {
193+
fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<Self> {
194+
Self::try_from(i32::try_from_borrowed_object(vm, &obj)?)
195+
.map_err(|msg| vm.new_value_error(msg))
117196
}
118197
}
119198

120199
/// Similar to `PyErr_SetInterruptEx` in CPython
121200
///
122201
/// Missing signal handler for the given signal number is silently ignored.
123-
#[allow(dead_code)]
124202
#[cfg(all(not(target_arch = "wasm32"), feature = "host_env"))]
125-
pub fn set_interrupt_ex(signum: i32, vm: &VirtualMachine) -> PyResult<()> {
203+
pub fn set_interrupt_ex(signum: SignalNum) -> PyResult<()> {
126204
use crate::stdlib::_signal::_signal::{SIG_DFL, SIG_IGN, run_signal};
127-
assert_in_range(signum, vm)?;
128205

129-
match signum as usize {
206+
match signum.as_usize() {
130207
SIG_DFL | SIG_IGN => Ok(()),
131208
_ => {
132209
// interrupt the main thread with given signal number
133-
run_signal(signum);
210+
run_signal(signum.into());
134211
Ok(())
135212
}
136213
}
@@ -190,16 +267,38 @@ pub fn get_sigint_event() -> Option<isize> {
190267
if handle == 0 { None } else { Some(handle) }
191268
}
192269

193-
pub struct SignalHandlers(Box<RefCell<[Option<PyObjectRef>; NSIG]>>);
270+
pub struct SignalHandlersInner([Option<PyObjectRef>; NSIG]);
271+
272+
impl Default for SignalHandlersInner {
273+
fn default() -> Self {
274+
Self([const { None }; NSIG])
275+
}
276+
}
277+
278+
impl Index<SignalNum> for SignalHandlersInner {
279+
type Output = Option<PyObjectRef>;
280+
281+
fn index(&self, index: SignalNum) -> &Self::Output {
282+
&self.0[index.as_usize()]
283+
}
284+
}
285+
286+
impl IndexMut<SignalNum> for SignalHandlersInner {
287+
fn index_mut(&mut self, index: SignalNum) -> &mut Self::Output {
288+
&mut self.0[index.as_usize()]
289+
}
290+
}
291+
292+
pub struct SignalHandlers(Box<RefCell<SignalHandlersInner>>);
194293

195294
impl Default for SignalHandlers {
196295
fn default() -> Self {
197-
Self(Box::new(const { RefCell::new([const { None }; NSIG]) }))
296+
Self(Box::new(RefCell::new(SignalHandlersInner::default())))
198297
}
199298
}
200299

201300
impl Deref for SignalHandlers {
202-
type Target = Box<RefCell<[Option<PyObjectRef>; NSIG]>>;
301+
type Target = Box<RefCell<SignalHandlersInner>>;
203302

204303
fn deref(&self) -> &Self::Target {
205304
&self.0

0 commit comments

Comments
 (0)