Skip to content

Commit b1478a6

Browse files
authored
Feature: _thread.interrupt_main (#4082)
1 parent c345f8a commit b1478a6

File tree

4 files changed

+44
-25
lines changed

4 files changed

+44
-25
lines changed

Lib/test/test_threading.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,8 +1382,6 @@ def test__all__(self):
13821382

13831383

13841384
class InterruptMainTests(unittest.TestCase):
1385-
# TODO: RUSTPYTHON
1386-
@unittest.expectedFailure
13871385
def test_interrupt_main_subthread(self):
13881386
# Calling start_new_thread with a function that executes interrupt_main
13891387
# should raise KeyboardInterrupt upon completion.
@@ -1395,16 +1393,12 @@ def call_interrupt():
13951393
t.join()
13961394
t.join()
13971395

1398-
# TODO: RUSTPYTHON
1399-
@unittest.expectedFailure
14001396
def test_interrupt_main_mainthread(self):
14011397
# Make sure that if interrupt_main is called in main thread that
14021398
# KeyboardInterrupt is raised instantly.
14031399
with self.assertRaises(KeyboardInterrupt):
14041400
_thread.interrupt_main()
14051401

1406-
# TODO: RUSTPYTHON
1407-
@unittest.expectedFailure
14081402
def test_interrupt_main_noerror(self):
14091403
handler = signal.getsignal(signal.SIGINT)
14101404
try:

vm/src/signal.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,33 @@ pub(crate) fn set_triggered() {
5555
ANY_TRIGGERED.store(true, Ordering::Release);
5656
}
5757

58+
pub 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"))]
71+
pub 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+
5885
pub type UserSignal = Box<dyn FnOnce(&VirtualMachine) -> PyResult<()> + Send>;
5986

6087
#[derive(Clone, Debug)]

vm/src/stdlib/signal.rs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,23 @@ pub(crate) mod _signal {
3333
}
3434

3535
#[cfg(unix)]
36-
use nix::unistd::alarm as sig_alarm;
36+
pub use nix::unistd::alarm as sig_alarm;
3737

3838
#[cfg(not(windows))]
39-
use libc::SIG_ERR;
39+
pub use libc::SIG_ERR;
4040

4141
#[cfg(not(windows))]
4242
#[pyattr]
43-
use libc::{SIG_DFL, SIG_IGN};
43+
pub use libc::{SIG_DFL, SIG_IGN};
4444

4545
#[cfg(windows)]
4646
#[pyattr]
47-
const SIG_DFL: libc::sighandler_t = 0;
47+
pub const SIG_DFL: libc::sighandler_t = 0;
4848
#[cfg(windows)]
4949
#[pyattr]
50-
const SIG_IGN: libc::sighandler_t = 1;
50+
pub const SIG_IGN: libc::sighandler_t = 1;
5151
#[cfg(windows)]
52-
const SIG_ERR: libc::sighandler_t = !0;
52+
pub const SIG_ERR: libc::sighandler_t = !0;
5353

5454
#[cfg(all(unix, not(target_os = "redox")))]
5555
extern "C" {
@@ -60,7 +60,7 @@ pub(crate) mod _signal {
6060
use crate::signal::NSIG;
6161

6262
#[pyattr]
63-
use libc::{SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM};
63+
pub use libc::{SIGABRT, SIGFPE, SIGILL, SIGINT, SIGSEGV, SIGTERM};
6464

6565
#[cfg(unix)]
6666
#[pyattr]
@@ -112,7 +112,7 @@ pub(crate) mod _signal {
112112
handler: PyObjectRef,
113113
vm: &VirtualMachine,
114114
) -> PyResult<Option<PyObjectRef>> {
115-
assert_in_range(signalnum, vm)?;
115+
signal::assert_in_range(signalnum, vm)?;
116116
let signal_handlers = vm
117117
.signal_handlers
118118
.as_deref()
@@ -148,7 +148,7 @@ pub(crate) mod _signal {
148148

149149
#[pyfunction]
150150
fn getsignal(signalnum: i32, vm: &VirtualMachine) -> PyResult {
151-
assert_in_range(signalnum, vm)?;
151+
signal::assert_in_range(signalnum, vm)?;
152152
let signal_handlers = vm
153153
.signal_handlers
154154
.as_deref()
@@ -246,7 +246,7 @@ pub(crate) mod _signal {
246246
#[cfg(all(unix, not(target_os = "redox")))]
247247
#[pyfunction(name = "siginterrupt")]
248248
fn py_siginterrupt(signum: i32, flag: i32, vm: &VirtualMachine) -> PyResult<()> {
249-
assert_in_range(signum, vm)?;
249+
signal::assert_in_range(signum, vm)?;
250250
let res = unsafe { siginterrupt(signum, flag) };
251251
if res < 0 {
252252
Err(crate::stdlib::os::errno_err(vm))
@@ -255,7 +255,7 @@ pub(crate) mod _signal {
255255
}
256256
}
257257

258-
extern "C" fn run_signal(signum: i32) {
258+
pub extern "C" fn run_signal(signum: i32) {
259259
signal::TRIGGERS[signum as usize].store(true, Ordering::Relaxed);
260260
signal::set_triggered();
261261
let wakeup_fd = WAKEUP.load(Ordering::Relaxed);
@@ -271,12 +271,4 @@ pub(crate) mod _signal {
271271
// TODO: handle _res < 1, support warn_on_full_buffer
272272
}
273273
}
274-
275-
fn assert_in_range(signum: i32, vm: &VirtualMachine) -> PyResult<()> {
276-
if (1..NSIG as i32).contains(&signum) {
277-
Ok(())
278-
} else {
279-
Err(vm.new_value_error("signal number out of range".to_owned()))
280-
}
281-
}
282274
}

vm/src/stdlib/thread.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,12 @@ pub(crate) mod _thread {
304304
vm.state.thread_count.fetch_sub(1);
305305
}
306306

307+
#[cfg(not(target_arch = "wasm32"))]
308+
#[pyfunction]
309+
fn interrupt_main(signum: OptionalArg<i32>, vm: &VirtualMachine) -> PyResult<()> {
310+
crate::signal::set_interrupt_ex(signum.unwrap_or(libc::SIGINT), vm)
311+
}
312+
307313
#[pyfunction]
308314
fn exit(vm: &VirtualMachine) -> PyResult {
309315
Err(vm.new_exception_empty(vm.ctx.exceptions.system_exit.to_owned()))

0 commit comments

Comments
 (0)