Skip to content

Commit a4a1a15

Browse files
committed
Allow sending custom signals to a vm
1 parent 401789d commit a4a1a15

File tree

4 files changed

+105
-44
lines changed

4 files changed

+105
-44
lines changed

vm/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ mod pyobjectrc;
6767
pub mod readline;
6868
pub mod scope;
6969
mod sequence;
70-
mod signal;
70+
pub mod signal;
7171
pub mod sliceable;
7272
pub mod stdlib;
7373
pub mod suggestion;

vm/src/signal.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#![cfg_attr(target_os = "wasi", allow(dead_code))]
2-
use crate::{PyObjectRef, PyResult, VirtualMachine};
2+
use crate::{PyResult, VirtualMachine};
3+
use std::fmt;
34
use std::sync::atomic::{AtomicBool, Ordering};
5+
use std::sync::mpsc;
46

5-
pub const NSIG: usize = 64;
7+
pub(crate) const NSIG: usize = 64;
68
static ANY_TRIGGERED: AtomicBool = AtomicBool::new(false);
79
// hack to get around const array repeat expressions, rust issue #79270
810
#[allow(clippy::declare_interior_mutable_const)]
@@ -37,9 +39,56 @@ fn trigger_signals(vm: &VirtualMachine) -> PyResult<()> {
3739
}
3840
}
3941
}
42+
if let Some(signal_rx) = &vm.signal_rx {
43+
for f in signal_rx.rx.try_iter() {
44+
f(vm)?;
45+
}
46+
}
4047
Ok(())
4148
}
4249

4350
pub(crate) fn set_triggered() {
4451
ANY_TRIGGERED.store(true, Ordering::Release);
4552
}
53+
54+
pub type UserSignal = Box<dyn FnOnce(&VirtualMachine) -> PyResult<()> + Send>;
55+
56+
#[derive(Clone, Debug)]
57+
pub struct UserSignalSender {
58+
tx: mpsc::Sender<UserSignal>,
59+
}
60+
61+
#[derive(Debug)]
62+
pub struct UserSignalReceiver {
63+
rx: mpsc::Receiver<UserSignal>,
64+
}
65+
66+
impl UserSignalSender {
67+
pub fn send(&self, sig: UserSignal) -> Result<(), UserSignalSendError> {
68+
self.tx
69+
.send(sig)
70+
.map_err(|mpsc::SendError(sig)| UserSignalSendError(sig))?;
71+
set_triggered();
72+
Ok(())
73+
}
74+
}
75+
76+
pub struct UserSignalSendError(pub UserSignal);
77+
78+
impl fmt::Debug for UserSignalSendError {
79+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80+
f.debug_struct("UserSignalSendError")
81+
.finish_non_exhaustive()
82+
}
83+
}
84+
85+
impl fmt::Display for UserSignalSendError {
86+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
87+
f.write_str("sending a signal to a exited vm")
88+
}
89+
}
90+
91+
pub fn user_signal_channel() -> (UserSignalSender, UserSignalReceiver) {
92+
let (tx, rx) = mpsc::channel();
93+
(UserSignalSender { tx }, UserSignalReceiver { rx })
94+
}

vm/src/stdlib/signal.rs

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,9 @@
11
use crate::{PyObjectRef, VirtualMachine};
22

33
pub(crate) fn make_module(vm: &VirtualMachine) -> PyObjectRef {
4-
use crate::signal::NSIG;
5-
use _signal::{SIG_DFL, SIG_ERR, SIG_IGN};
6-
74
let module = _signal::make_module(vm);
85

9-
let sig_dfl = vm.new_pyobj(SIG_DFL as u8);
10-
let sig_ign = vm.new_pyobj(SIG_IGN as u8);
11-
12-
for signum in 1..NSIG {
13-
let handler = unsafe { libc::signal(signum as i32, SIG_IGN) };
14-
if handler != SIG_ERR {
15-
unsafe { libc::signal(signum as i32, handler) };
16-
}
17-
let py_handler = if handler == SIG_DFL {
18-
Some(sig_dfl.clone())
19-
} else if handler == SIG_IGN {
20-
Some(sig_ign.clone())
21-
} else {
22-
None
23-
};
24-
vm.signal_handlers.as_deref().unwrap().borrow_mut()[signum] = py_handler;
25-
}
26-
27-
let int_handler = module
28-
.clone()
29-
.get_attr("default_int_handler", vm)
30-
.expect("_signal does not have this attr?");
31-
_signal::signal(libc::SIGINT, int_handler, vm).expect("Failed to set sigint handler");
6+
_signal::init_signal_handlers(&module, vm);
327

338
module
349
}
@@ -61,35 +36,35 @@ pub(crate) mod _signal {
6136
use nix::unistd::alarm as sig_alarm;
6237

6338
#[cfg(not(windows))]
64-
pub use libc::SIG_ERR;
39+
use libc::SIG_ERR;
6540

6641
#[cfg(not(windows))]
6742
#[pyattr]
68-
pub use libc::{SIG_DFL, SIG_IGN};
43+
use libc::{SIG_DFL, SIG_IGN};
6944

7045
#[cfg(windows)]
7146
#[pyattr]
72-
pub const SIG_DFL: libc::sighandler_t = 0;
47+
const SIG_DFL: libc::sighandler_t = 0;
7348
#[cfg(windows)]
7449
#[pyattr]
75-
pub const SIG_IGN: libc::sighandler_t = 1;
50+
const SIG_IGN: libc::sighandler_t = 1;
7651
#[cfg(windows)]
77-
pub const SIG_ERR: libc::sighandler_t = !0;
52+
const SIG_ERR: libc::sighandler_t = !0;
7853

7954
#[cfg(all(unix, not(target_os = "redox")))]
8055
extern "C" {
8156
fn siginterrupt(sig: i32, flag: i32) -> i32;
8257
}
8358

8459
#[pyattr]
85-
pub use crate::signal::NSIG;
60+
use crate::signal::NSIG;
8661

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

9065
#[cfg(unix)]
9166
#[pyattr]
92-
pub use libc::{
67+
use libc::{
9368
SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGHUP, SIGIO, SIGKILL, SIGPIPE, SIGPROF, SIGQUIT,
9469
SIGSTOP, SIGSYS, SIGTRAP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM,
9570
SIGWINCH, SIGXCPU, SIGXFSZ,
@@ -103,10 +78,36 @@ pub(crate) mod _signal {
10378
target_os = "netbsd"
10479
)))]
10580
#[pyattr]
106-
pub use libc::{SIGPWR, SIGSTKFLT};
81+
use libc::{SIGPWR, SIGSTKFLT};
82+
83+
pub(super) fn init_signal_handlers(module: &PyObjectRef, vm: &VirtualMachine) {
84+
let sig_dfl = vm.new_pyobj(SIG_DFL as u8);
85+
let sig_ign = vm.new_pyobj(SIG_IGN as u8);
86+
87+
for signum in 1..NSIG {
88+
let handler = unsafe { libc::signal(signum as i32, SIG_IGN) };
89+
if handler != SIG_ERR {
90+
unsafe { libc::signal(signum as i32, handler) };
91+
}
92+
let py_handler = if handler == SIG_DFL {
93+
Some(sig_dfl.clone())
94+
} else if handler == SIG_IGN {
95+
Some(sig_ign.clone())
96+
} else {
97+
None
98+
};
99+
vm.signal_handlers.as_deref().unwrap().borrow_mut()[signum] = py_handler;
100+
}
101+
102+
let int_handler = module
103+
.clone()
104+
.get_attr("default_int_handler", vm)
105+
.expect("_signal does not have this attr?");
106+
signal(libc::SIGINT, int_handler, vm).expect("Failed to set sigint handler");
107+
}
107108

108109
#[pyfunction]
109-
pub(super) fn signal(
110+
pub fn signal(
110111
signalnum: i32,
111112
handler: PyObjectRef,
112113
vm: &VirtualMachine,

vm/src/vm.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ use crate::{
2424
import,
2525
protocol::{PyIterIter, PyIterReturn, PyMapping},
2626
scope::Scope,
27-
signal::NSIG,
28-
stdlib,
27+
signal, stdlib,
2928
types::PyComparisonOp,
3029
IdProtocol, ItemProtocol, PyArithmeticValue, PyContext, PyGenericObject, PyLease, PyMethod,
3130
PyObject, PyObjectRef, PyObjectWrap, PyRef, PyRefExact, PyResult, PyValue, TryFromObject,
@@ -58,7 +57,8 @@ pub struct VirtualMachine {
5857
pub trace_func: RefCell<PyObjectRef>,
5958
pub use_tracing: Cell<bool>,
6059
pub recursion_limit: Cell<usize>,
61-
pub signal_handlers: Option<Box<RefCell<[Option<PyObjectRef>; NSIG]>>>,
60+
pub(crate) signal_handlers: Option<Box<RefCell<[Option<PyObjectRef>; signal::NSIG]>>>,
61+
pub(crate) signal_rx: Option<signal::UserSignalReceiver>,
6262
pub repr_guards: RefCell<HashSet<usize>>,
6363
pub state: PyRc<PyGlobalState>,
6464
pub initialized: bool,
@@ -262,7 +262,11 @@ impl VirtualMachine {
262262
let trace_func = RefCell::new(ctx.none());
263263
// hack to get around const array repeat expressions, rust issue #79270
264264
const NONE: Option<PyObjectRef> = None;
265-
let signal_handlers = RefCell::new([NONE; NSIG]);
265+
// putting it in a const optimizes better, prevents linear initialization of the array
266+
#[allow(clippy::declare_interior_mutable_const)]
267+
const SIGNAL_HANDLERS: RefCell<[Option<PyObjectRef>; signal::NSIG]> =
268+
RefCell::new([NONE; signal::NSIG]);
269+
let signal_handlers = Some(Box::new(SIGNAL_HANDLERS));
266270

267271
let module_inits = stdlib::get_module_inits();
268272

@@ -285,7 +289,8 @@ impl VirtualMachine {
285289
trace_func,
286290
use_tracing: Cell::new(false),
287291
recursion_limit: Cell::new(if cfg!(debug_assertions) { 256 } else { 1000 }),
288-
signal_handlers: Some(Box::new(signal_handlers)),
292+
signal_handlers,
293+
signal_rx: None,
289294
repr_guards: RefCell::default(),
290295
state: PyRc::new(PyGlobalState {
291296
settings,
@@ -407,6 +412,11 @@ impl VirtualMachine {
407412
state.frozen.extend(frozen);
408413
}
409414

415+
/// Set the custom signal channel for the interpreter
416+
pub fn set_user_signal_channel(&mut self, signal_rx: signal::UserSignalReceiver) {
417+
self.signal_rx = Some(signal_rx);
418+
}
419+
410420
/// Start a new thread with access to the same interpreter.
411421
///
412422
/// # Note
@@ -463,6 +473,7 @@ impl VirtualMachine {
463473
use_tracing: Cell::new(false),
464474
recursion_limit: self.recursion_limit.clone(),
465475
signal_handlers: None,
476+
signal_rx: None,
466477
repr_guards: RefCell::default(),
467478
state: self.state.clone(),
468479
initialized: self.initialized,

0 commit comments

Comments
 (0)