Skip to content

Commit 2613ee4

Browse files
Merge pull request #1189 from palaviv/signal
signal support
2 parents 6fc7027 + 5e5d46d commit 2613ee4

File tree

7 files changed

+244
-0
lines changed

7 files changed

+244
-0
lines changed

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/snippets/stdlib_signal.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import signal
2+
import time
3+
import sys
4+
from testutils import assert_raises
5+
6+
assert_raises(TypeError, lambda: signal.signal(signal.SIGINT, 2))
7+
8+
signals = []
9+
10+
def handler(signum, frame):
11+
signals.append(signum)
12+
13+
14+
signal.signal(signal.SIGILL, signal.SIG_IGN);
15+
assert signal.getsignal(signal.SIGILL) is signal.SIG_IGN
16+
17+
old_signal = signal.signal(signal.SIGILL, signal.SIG_DFL)
18+
assert old_signal is signal.SIG_IGN
19+
assert signal.getsignal(signal.SIGILL) is signal.SIG_DFL
20+
21+
22+
# unix
23+
if "win" not in sys.platform:
24+
signal.signal(signal.SIGALRM, handler)
25+
assert signal.getsignal(signal.SIGALRM) is handler
26+
27+
signal.alarm(1)
28+
time.sleep(2.0)
29+
assert signals == [signal.SIGALRM]
30+
31+
signal.signal(signal.SIGALRM, signal.SIG_IGN)
32+
signal.alarm(1)
33+
time.sleep(2.0)
34+
35+
assert signals == [signal.SIGALRM]
36+
37+
signal.signal(signal.SIGALRM, handler)
38+
signal.alarm(1)
39+
time.sleep(2.0)
40+
41+
assert signals == [signal.SIGALRM, signal.SIGALRM]
42+
43+
44+

vm/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ bitflags = "1.1"
6363
libc = "0.2"
6464
nix = "0.14.1"
6565
wtf8 = "0.0.3"
66+
arr_macro = "0.1.2"
6667

6768
flame = { version = "0.2", optional = true }
6869
flamer = { version = "0.3", optional = true }

vm/src/frame.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ use crate::vm::VirtualMachine;
2323
use indexmap::IndexMap;
2424
use itertools::Itertools;
2525

26+
#[cfg(not(target_arch = "wasm32"))]
27+
use crate::stdlib::signal::check_signals;
28+
2629
#[derive(Clone, Debug)]
2730
struct Block {
2831
/// The type of block.
@@ -163,6 +166,10 @@ impl Frame {
163166
/// Execute a single instruction.
164167
#[allow(clippy::cognitive_complexity)]
165168
fn execute_instruction(&self, vm: &VirtualMachine) -> FrameResult {
169+
#[cfg(not(target_arch = "wasm32"))]
170+
{
171+
check_signals(vm);
172+
}
166173
let instruction = self.fetch_instruction();
167174

168175
flame_guard!(format!("Frame::execute_instruction({:?})", instruction));

vm/src/stdlib/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ pub mod io;
3838
mod os;
3939
#[cfg(all(unix, not(any(target_os = "android", target_os = "redox"))))]
4040
mod pwd;
41+
#[cfg(not(target_arch = "wasm32"))]
42+
pub mod signal;
4143

4244
use crate::pyobject::PyObjectRef;
4345

@@ -92,6 +94,7 @@ pub fn get_module_inits() -> HashMap<String, StdlibInitFunc> {
9294
modules.insert("_io".to_string(), Box::new(io::make_module));
9395
modules.insert("_os".to_string(), Box::new(os::make_module));
9496
modules.insert("socket".to_string(), Box::new(socket::make_module));
97+
modules.insert("signal".to_string(), Box::new(signal::make_module));
9598
}
9699

97100
// Unix-only

vm/src/stdlib/signal.rs

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
use crate::obj::objint::PyIntRef;
2+
use crate::pyobject::{IdProtocol, PyObjectRef, PyResult};
3+
use crate::vm::VirtualMachine;
4+
5+
use std::sync::atomic::{AtomicBool, Ordering};
6+
7+
use num_traits::cast::ToPrimitive;
8+
9+
use arr_macro::arr;
10+
11+
#[cfg(unix)]
12+
use nix::unistd::alarm as sig_alarm;
13+
14+
use libc;
15+
16+
#[cfg(not(windows))]
17+
use libc::{SIG_DFL, SIG_ERR, SIG_IGN};
18+
19+
#[cfg(windows)]
20+
const SIG_DFL: libc::sighandler_t = 0;
21+
#[cfg(windows)]
22+
const SIG_IGN: libc::sighandler_t = 1;
23+
#[cfg(windows)]
24+
const SIG_ERR: libc::sighandler_t = !0;
25+
26+
const NSIG: usize = 64;
27+
28+
// We cannot use the NSIG const in the arr macro. This will fail compilation if NSIG is different.
29+
static mut TRIGGERS: [AtomicBool; NSIG] = arr![AtomicBool::new(false); 64];
30+
31+
extern "C" fn run_signal(signum: i32) {
32+
unsafe {
33+
TRIGGERS[signum as usize].store(true, Ordering::Relaxed);
34+
}
35+
}
36+
37+
fn signal(
38+
signalnum: PyIntRef,
39+
handler: PyObjectRef,
40+
vm: &VirtualMachine,
41+
) -> PyResult<Option<PyObjectRef>> {
42+
if !vm.isinstance(&handler, &vm.ctx.function_type())?
43+
&& !vm.isinstance(&handler, &vm.ctx.bound_method_type())?
44+
&& !vm.isinstance(&handler, &vm.ctx.builtin_function_or_method_type())?
45+
{
46+
return Err(vm.new_type_error("Hanlder must be callable".to_string()));
47+
}
48+
let signal_module = vm.import("signal", &vm.ctx.new_tuple(vec![]), 0)?;
49+
let sig_dfl = vm.get_attribute(signal_module.clone(), "SIG_DFL")?;
50+
let sig_ign = vm.get_attribute(signal_module, "SIG_IGN")?;
51+
let signalnum = signalnum.as_bigint().to_i32().unwrap();
52+
check_signals(vm);
53+
let sig_handler = if handler.is(&sig_dfl) {
54+
SIG_DFL
55+
} else if handler.is(&sig_ign) {
56+
SIG_IGN
57+
} else {
58+
run_signal as libc::sighandler_t
59+
};
60+
let old = unsafe { libc::signal(signalnum, sig_handler) };
61+
if old == SIG_ERR {
62+
return Err(vm.new_os_error("Failed to set signal".to_string()));
63+
}
64+
let old_handler = vm.signal_handlers.borrow_mut().insert(signalnum, handler);
65+
Ok(old_handler)
66+
}
67+
68+
fn getsignal(signalnum: PyIntRef, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
69+
let signalnum = signalnum.as_bigint().to_i32().unwrap();
70+
Ok(vm.signal_handlers.borrow_mut().get(&signalnum).cloned())
71+
}
72+
73+
#[cfg(unix)]
74+
fn alarm(time: PyIntRef, _vm: &VirtualMachine) -> u32 {
75+
let time = time.as_bigint().to_u32().unwrap();
76+
let prev_time = if time == 0 {
77+
sig_alarm::cancel()
78+
} else {
79+
sig_alarm::set(time)
80+
};
81+
prev_time.unwrap_or(0)
82+
}
83+
84+
#[allow(clippy::needless_range_loop)]
85+
pub fn check_signals(vm: &VirtualMachine) {
86+
for signum in 1..NSIG {
87+
let triggerd = unsafe { TRIGGERS[signum].swap(false, Ordering::Relaxed) };
88+
if triggerd {
89+
let handler = vm
90+
.signal_handlers
91+
.borrow()
92+
.get(&(signum as i32))
93+
.expect("Handler should be set")
94+
.clone();
95+
vm.invoke(handler, vec![vm.new_int(signum), vm.get_none()])
96+
.expect("Test");
97+
}
98+
}
99+
}
100+
101+
fn stub_func(_vm: &VirtualMachine) -> PyResult {
102+
panic!("Do not use directly");
103+
}
104+
105+
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
106+
let ctx = &vm.ctx;
107+
108+
let sig_dfl = ctx.new_rustfunc(stub_func);
109+
let sig_ign = ctx.new_rustfunc(stub_func);
110+
111+
let module = py_module!(vm, "signal", {
112+
"signal" => ctx.new_rustfunc(signal),
113+
"getsignal" => ctx.new_rustfunc(getsignal),
114+
"SIG_DFL" => sig_dfl,
115+
"SIG_IGN" => sig_ign,
116+
"SIGABRT" => ctx.new_int(libc::SIGABRT as u8),
117+
"SIGFPE" => ctx.new_int(libc::SIGFPE as u8),
118+
"SIGILL" => ctx.new_int(libc::SIGILL as u8),
119+
"SIGINT" => ctx.new_int(libc::SIGINT as u8),
120+
"SIGSEGV" => ctx.new_int(libc::SIGSEGV as u8),
121+
"SIGTERM" => ctx.new_int(libc::SIGTERM as u8),
122+
});
123+
extend_module_platform_specific(vm, module)
124+
}
125+
126+
#[cfg(unix)]
127+
fn extend_module_platform_specific(vm: &VirtualMachine, module: PyObjectRef) -> PyObjectRef {
128+
let ctx = &vm.ctx;
129+
130+
extend_module!(vm, module, {
131+
"alarm" => ctx.new_rustfunc(alarm),
132+
"SIGHUP" => ctx.new_int(libc::SIGHUP as u8),
133+
"SIGQUIT" => ctx.new_int(libc::SIGQUIT as u8),
134+
"SIGTRAP" => ctx.new_int(libc::SIGTRAP as u8),
135+
"SIGBUS" => ctx.new_int(libc::SIGBUS as u8),
136+
"SIGKILL" => ctx.new_int(libc::SIGKILL as u8),
137+
"SIGUSR1" => ctx.new_int(libc::SIGUSR1 as u8),
138+
"SIGUSR2" => ctx.new_int(libc::SIGUSR2 as u8),
139+
"SIGPIPE" => ctx.new_int(libc::SIGPIPE as u8),
140+
"SIGALRM" => ctx.new_int(libc::SIGALRM as u8),
141+
"SIGSTKFLT" => ctx.new_int(libc::SIGSTKFLT as u8),
142+
"SIGCHLD" => ctx.new_int(libc::SIGCHLD as u8),
143+
"SIGCONT" => ctx.new_int(libc::SIGCONT as u8),
144+
"SIGSTOP" => ctx.new_int(libc::SIGSTOP as u8),
145+
"SIGTSTP" => ctx.new_int(libc::SIGTSTP as u8),
146+
"SIGTTIN" => ctx.new_int(libc::SIGTTIN as u8),
147+
"SIGTTOU" => ctx.new_int(libc::SIGTTOU as u8),
148+
"SIGURG" => ctx.new_int(libc::SIGURG as u8),
149+
"SIGXCPU" => ctx.new_int(libc::SIGXCPU as u8),
150+
"SIGXFSZ" => ctx.new_int(libc::SIGXFSZ as u8),
151+
"SIGVTALRM" => ctx.new_int(libc::SIGVTALRM as u8),
152+
"SIGPROF" => ctx.new_int(libc::SIGPROF as u8),
153+
"SIGWINCH" => ctx.new_int(libc::SIGWINCH as u8),
154+
"SIGIO" => ctx.new_int(libc::SIGIO as u8),
155+
"SIGPWR" => ctx.new_int(libc::SIGPWR as u8),
156+
"SIGSYS" => ctx.new_int(libc::SIGSYS as u8),
157+
});
158+
159+
module
160+
}
161+
162+
#[cfg(not(unix))]
163+
fn extend_module_platform_specific(_vm: &VirtualMachine, module: PyObjectRef) -> PyObjectRef {
164+
module
165+
}

vm/src/vm.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub struct VirtualMachine {
6262
pub trace_func: RefCell<PyObjectRef>,
6363
pub use_tracing: RefCell<bool>,
6464
pub settings: PySettings,
65+
pub signal_handlers: RefCell<HashMap<i32, PyObjectRef>>,
6566
}
6667

6768
/// Struct containing all kind of settings for the python vm.
@@ -160,6 +161,7 @@ impl VirtualMachine {
160161
trace_func,
161162
use_tracing: RefCell::new(false),
162163
settings,
164+
signal_handlers: Default::default(),
163165
};
164166

165167
builtins::make_module(&vm, builtins.clone());

0 commit comments

Comments
 (0)