Merge pull request #1313 from RustPython/coolreader18/keyboardinterrupt

Raise a KeyboardInterrupt on a SIGINT
This commit is contained in:
Windel Bouwman authored and GitHub committed 2019-08-26 21:48:34 +02:00
commit 9168455b12
6 files changed
+209 -127

No files matched your search

+74 -55
View File
@@ -9,9 +9,9 @@ use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType}
use rustpython_parser::error::ParseErrorType;
use rustpython_vm::{
import,
obj::objstr,
obj::objstr::PyStringRef,
print_exception,
pyobject::{ItemProtocol, PyResult},
pyobject::{ItemProtocol, PyObjectRef, PyResult},
scope::Scope,
util, PySettings, VirtualMachine,
};
@@ -434,40 +434,32 @@ fn test_run_script() {
assert!(r.is_ok());
}
fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), CompileError> {
enum ShellExecResult {
Ok,
PyErr(PyObjectRef),
Continue,
}
fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> ShellExecResult {
match vm.compile(source, compile::Mode::Single, "<stdin>".to_string()) {
Ok(code) => {
match vm.run_code_obj(code, scope.clone()) {
Ok(value) => {
// Save non-None values as "_"
use rustpython_vm::pyobject::IdProtocol;
if !value.is(&vm.get_none()) {
if !vm.is_none(&value) {
let key = "_";
scope.globals.set_item(key, value, vm).unwrap();
}
ShellExecResult::Ok
}
Err(err) => {
print_exception(vm, &err);
}
Err(err) => ShellExecResult::PyErr(err),
}
Ok(())
}
// Don't inject syntax errors for line continuation
Err(
err @ CompileError {
error: CompileErrorType::Parse(ParseErrorType::EOF),
..
},
) => Err(err),
Err(err) => {
let exc = vm.new_syntax_error(&err);
print_exception(vm, &exc);
Err(err)
}
Err(CompileError {
error: CompileErrorType::Parse(ParseErrorType::EOF),
..
}) => ShellExecResult::Continue,
Err(err) => ShellExecResult::PyErr(vm.new_syntax_error(&err)),
}
}
@@ -486,12 +478,10 @@ fn get_history_path() -> PathBuf {
xdg_dirs.place_cache_file("repl_history.txt").unwrap()
}
fn get_prompt(vm: &VirtualMachine, prompt_name: &str) -> String {
fn get_prompt(vm: &VirtualMachine, prompt_name: &str) -> Option<PyStringRef> {
vm.get_attribute(vm.sys_module.clone(), prompt_name)
.and_then(|prompt| vm.to_str(&prompt))
.ok()
.as_ref()
.map(objstr::get_value)
.unwrap_or_else(String::new)
}
#[cfg(not(target_os = "redox"))]
@@ -521,15 +511,27 @@ fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
} else {
get_prompt(vm, "ps1")
};
match repl.readline(&prompt) {
let prompt = match prompt {
Some(ref s) => s.as_str(),
None => "",
};
let result = match repl.readline(prompt) {
Ok(line) => {
debug!("You entered {:?}", line);
input.push_str(&line);
input.push('\n');
repl.add_history_entry(line.trim_end());
let stop_continuing = line.is_empty();
if input.is_empty() {
input = line;
} else {
input.push_str(&line);
}
input.push_str("\n");
if continuing {
if line.is_empty() {
if stop_continuing {
continuing = false;
} else {
continue;
@@ -537,32 +539,39 @@ fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
}
match shell_exec(vm, &input, scope.clone()) {
Err(CompileError {
error: CompileErrorType::Parse(ParseErrorType::EOF),
..
}) => {
continuing = true;
continue;
}
_ => {
ShellExecResult::Ok => {
input = String::new();
Ok(())
}
ShellExecResult::Continue => {
continuing = true;
Ok(())
}
ShellExecResult::PyErr(err) => {
input = String::new();
Err(err)
}
}
}
Err(ReadlineError::Interrupted) => {
// TODO: Raise a real KeyboardInterrupt exception
println!("^C");
continuing = false;
continue;
let keyboard_interrupt = vm
.new_empty_exception(vm.ctx.exceptions.keyboard_interrupt.clone())
.unwrap();
Err(keyboard_interrupt)
}
Err(ReadlineError::Eof) => {
break;
}
Err(err) => {
println!("Error: {:?}", err);
eprintln!("Readline error: {:?}", err);
break;
}
};
if let Err(exc) = result {
print_exception(vm, &exc);
}
}
repl.save_history(repl_history_path_str).unwrap();
@@ -574,22 +583,32 @@ fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
use std::io::{self, BufRead, Write};
println!(
"Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
"Welcome to the magnificent Rust Python {} interpreter!",
crate_version!()
);
let stdin = io::stdin();
let stdout = io::stdout();
let mut stdout = stdout.lock();
fn print_prompt() {
let prompt = get_prompt(vm, "ps1");
let prompt = match prompt {
Some(ref s) => s.as_str(),
None => "",
};
print!("{}", prompt);
io::stdout().lock().flush().expect("flush failed");
}
print!("{}", get_prompt(vm, "ps1"));
stdout.flush().expect("flush failed");
let stdin = io::stdin();
print_prompt();
for line in stdin.lock().lines() {
let mut line = line.expect("line failed");
line.push('\n');
let _ = shell_exec(vm, &line, scope.clone());
print!("{}", get_prompt(vm, "ps1"));
stdout.flush().expect("flush failed");
line.push_str("\n");
match shell_exec(vm, &line, scope.clone()) {
ShellExecResult::Ok => {}
ShellExecResult::Continue => println!("Unexpected EOF"),
ShellExecResult::PyErr(exc) => print_exception(vm, &exc),
}
print_prompt();
}
Ok(())
+5 -3
View File
@@ -15,8 +15,8 @@ fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
let exc_args = vm.ctx.new_tuple(args.args[1..].to_vec());
vm.set_attr(&exc_self, "args", exc_args)?;
let traceback = vm.ctx.new_list(Vec::new());
vm.set_attr(&exc_self, "__traceback__", traceback)?;
// TODO: have an actual `traceback` object for __traceback__
vm.set_attr(&exc_self, "__traceback__", vm.ctx.new_list(vec![]))?;
vm.set_attr(&exc_self, "__cause__", vm.get_none())?;
vm.set_attr(&exc_self, "__context__", vm.get_none())?;
vm.set_attr(&exc_self, "__suppress_context__", vm.new_bool(false))?;
@@ -101,11 +101,13 @@ fn print_traceback_entry(vm: &VirtualMachine, tb_entry: &PyObjectRef) {
/// Print exception with traceback
pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) {
if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") {
println!("Traceback (most recent call last):");
if objtype::isinstance(&tb, &vm.ctx.list_type()) {
let mut tb_entries = objsequence::get_elements_list(&tb).to_vec();
tb_entries.reverse();
if !tb_entries.is_empty() {
println!("Traceback (most recent call last):");
}
for exc_location in tb_entries.iter() {
print_traceback_entry(vm, exc_location);
}
+2 -3
View File
@@ -167,9 +167,8 @@ impl Frame {
#[allow(clippy::cognitive_complexity)]
fn execute_instruction(&self, vm: &VirtualMachine) -> FrameResult {
#[cfg(not(target_arch = "wasm32"))]
{
check_signals(vm);
}
check_signals(vm)?;
let instruction = self.fetch_instruction();
flame_guard!(format!("Frame::execute_instruction({:?})", instruction));
+90 -58
View File
@@ -1,6 +1,6 @@
use crate::obj::objint::PyIntRef;
use crate::pyobject::{IdProtocol, PyObjectRef, PyResult};
use crate::vm::VirtualMachine;
use crate::pyobject::{PyObjectRef, PyResult, TryFromObject};
use crate::vm::{VirtualMachine, NSIG};
use std::sync::atomic::{AtomicBool, Ordering};
@@ -23,51 +23,65 @@ const SIG_IGN: libc::sighandler_t = 1;
#[cfg(windows)]
const SIG_ERR: libc::sighandler_t = !0;
const NSIG: usize = 64;
// We cannot use the NSIG const in the arr macro. This will fail compilation if NSIG is different.
static mut TRIGGERS: [AtomicBool; NSIG] = arr![AtomicBool::new(false); 64];
static TRIGGERS: [AtomicBool; NSIG] = arr![AtomicBool::new(false); 64];
static ANY_TRIGGERED: AtomicBool = AtomicBool::new(false);
extern "C" fn run_signal(signum: i32) {
unsafe {
TRIGGERS[signum as usize].store(true, Ordering::Relaxed);
ANY_TRIGGERED.store(true, Ordering::Relaxed);
TRIGGERS[signum as usize].store(true, Ordering::Relaxed);
}
fn assert_in_range(signum: i32, vm: &VirtualMachine) -> PyResult<()> {
if (1..NSIG as i32).contains(&signum) {
Ok(())
} else {
Err(vm.new_value_error("signal number out of range".to_owned()))
}
}
fn signal(
signalnum: PyIntRef,
handler: PyObjectRef,
vm: &VirtualMachine,
) -> PyResult<Option<PyObjectRef>> {
if !vm.isinstance(&handler, &vm.ctx.function_type())?
&& !vm.isinstance(&handler, &vm.ctx.bound_method_type())?
&& !vm.isinstance(&handler, &vm.ctx.builtin_function_or_method_type())?
{
return Err(vm.new_type_error("Hanlder must be callable".to_string()));
}
let signal_module = vm.import("signal", &vm.ctx.new_tuple(vec![]), 0)?;
let sig_dfl = vm.get_attribute(signal_module.clone(), "SIG_DFL")?;
let sig_ign = vm.get_attribute(signal_module, "SIG_IGN")?;
let signalnum = signalnum.as_bigint().to_i32().unwrap();
check_signals(vm);
let sig_handler = if handler.is(&sig_dfl) {
SIG_DFL
} else if handler.is(&sig_ign) {
SIG_IGN
} else {
run_signal as libc::sighandler_t
fn signal(signalnum: i32, handler: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
assert_in_range(signalnum, vm)?;
let sig_handler = match usize::try_from_object(vm, handler.clone()).ok() {
Some(SIG_DFL) => SIG_DFL,
Some(SIG_IGN) => SIG_IGN,
None if vm.is_callable(&handler) => run_signal as libc::sighandler_t,
_ => {
return Err(vm.new_type_error(
"signal handler must be signal.SIG_IGN, signal.SIG_DFL, or a callable object"
.to_owned(),
))
}
};
check_signals(vm)?;
let old = unsafe { libc::signal(signalnum, sig_handler) };
if old == SIG_ERR {
return Err(vm.new_os_error("Failed to set signal".to_string()));
}
let old_handler = vm.signal_handlers.borrow_mut().insert(signalnum, handler);
#[cfg(unix)]
{
extern "C" {
fn siginterrupt(sig: i32, flag: i32);
}
unsafe {
siginterrupt(signalnum, 1);
}
}
let mut old_handler = handler;
std::mem::swap(
&mut vm.signal_handlers.borrow_mut()[signalnum as usize],
&mut old_handler,
);
Ok(old_handler)
}
fn getsignal(signalnum: PyIntRef, vm: &VirtualMachine) -> PyResult<Option<PyObjectRef>> {
let signalnum = signalnum.as_bigint().to_i32().unwrap();
Ok(vm.signal_handlers.borrow_mut().get(&signalnum).cloned())
fn getsignal(signalnum: i32, vm: &VirtualMachine) -> PyResult<PyObjectRef> {
assert_in_range(signalnum, vm)?;
Ok(vm.signal_handlers.borrow()[signalnum as usize].clone())
}
#[cfg(unix)]
@@ -81,50 +95,72 @@ fn alarm(time: PyIntRef, _vm: &VirtualMachine) -> u32 {
prev_time.unwrap_or(0)
}
#[allow(clippy::needless_range_loop)]
pub fn check_signals(vm: &VirtualMachine) {
for signum in 1..NSIG {
let triggerd = unsafe { TRIGGERS[signum].swap(false, Ordering::Relaxed) };
#[cfg_attr(feature = "flame-it", flame)]
pub fn check_signals(vm: &VirtualMachine) -> PyResult<()> {
if !ANY_TRIGGERED.swap(false, Ordering::Relaxed) {
return Ok(());
}
for (signum, trigger) in TRIGGERS.iter().enumerate().skip(1) {
let triggerd = trigger.swap(false, Ordering::Relaxed);
if triggerd {
let handler = vm
.signal_handlers
.borrow()
.get(&(signum as i32))
.expect("Handler should be set")
.clone();
vm.invoke(&handler, vec![vm.new_int(signum), vm.get_none()])
.expect("Test");
let handler = &vm.signal_handlers.borrow()[signum];
if vm.is_callable(handler) {
vm.invoke(handler, vec![vm.new_int(signum), vm.get_none()])?;
}
}
}
Ok(())
}
fn stub_func(_vm: &VirtualMachine) -> PyResult {
panic!("Do not use directly");
fn default_int_handler(_signum: PyObjectRef, _arg: PyObjectRef, vm: &VirtualMachine) -> PyResult {
Err(vm.new_empty_exception(vm.ctx.exceptions.keyboard_interrupt.clone())?)
}
pub fn make_module(vm: &VirtualMachine) -> PyObjectRef {
let ctx = &vm.ctx;
let sig_dfl = ctx.new_rustfunc(stub_func);
let sig_ign = ctx.new_rustfunc(stub_func);
let int_handler = ctx.new_rustfunc(default_int_handler);
let sig_dfl = ctx.new_int(SIG_DFL as u8);
let sig_ign = ctx.new_int(SIG_IGN as u8);
let module = py_module!(vm, "signal", {
"signal" => ctx.new_rustfunc(signal),
"getsignal" => ctx.new_rustfunc(getsignal),
"SIG_DFL" => sig_dfl,
"SIG_IGN" => sig_ign,
"SIG_DFL" => sig_dfl.clone(),
"SIG_IGN" => sig_ign.clone(),
"SIGABRT" => ctx.new_int(libc::SIGABRT as u8),
"SIGFPE" => ctx.new_int(libc::SIGFPE as u8),
"SIGILL" => ctx.new_int(libc::SIGILL as u8),
"SIGINT" => ctx.new_int(libc::SIGINT as u8),
"SIGSEGV" => ctx.new_int(libc::SIGSEGV as u8),
"SIGTERM" => ctx.new_int(libc::SIGTERM as u8),
"default_int_handler" => int_handler.clone(),
});
extend_module_platform_specific(vm, module)
extend_module_platform_specific(vm, &module);
for signum in 1..NSIG {
let handler = unsafe { libc::signal(signum as i32, SIG_IGN) };
if handler != SIG_ERR {
unsafe { libc::signal(signum as i32, handler) };
}
let py_handler = if handler == SIG_DFL {
sig_dfl.clone()
} else if handler == SIG_IGN {
sig_ign.clone()
} else {
vm.get_none()
};
vm.signal_handlers.borrow_mut()[signum] = py_handler;
}
signal(libc::SIGINT, int_handler, vm).expect("Failed to set sigint handler");
module
}
#[cfg(unix)]
fn extend_module_platform_specific(vm: &VirtualMachine, module: PyObjectRef) -> PyObjectRef {
fn extend_module_platform_specific(vm: &VirtualMachine, module: &PyObjectRef) {
let ctx = &vm.ctx;
extend_module!(vm, module, {
@@ -161,11 +197,7 @@ fn extend_module_platform_specific(vm: &VirtualMachine, module: PyObjectRef) ->
"SIGSTKFLT" => ctx.new_int(libc::SIGSTKFLT as u8),
});
}
module
}
#[cfg(not(unix))]
fn extend_module_platform_specific(_vm: &VirtualMachine, module: PyObjectRef) -> PyObjectRef {
module
}
fn extend_module_platform_specific(_vm: &VirtualMachine, _module: &PyObjectRef) {}
+27 -6
View File
@@ -3,10 +3,10 @@
/// https://docs.python.org/3/library/time.html
use std::fmt;
use std::ops::Range;
use std::thread;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::function::{OptionalArg, PyFuncArgs};
use crate::obj::objfloat::PyFloatRef;
use crate::obj::objint::PyInt;
use crate::obj::objsequence::get_sequence_index;
use crate::obj::objsequence::PySliceableSequence;
@@ -22,14 +22,35 @@ use num_traits::cast::ToPrimitive;
use chrono::naive::NaiveDateTime;
use chrono::{Datelike, Timelike};
fn time_sleep(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult {
arg_check!(vm, args, required = [(seconds, Some(vm.ctx.float_type()))]);
let seconds = objfloat::get_value(seconds);
#[cfg(unix)]
fn time_sleep(seconds: PyFloatRef, vm: &VirtualMachine) -> PyResult<()> {
// this is basically std::thread::sleep, but that catches interrupts and we don't want to
let seconds = seconds.to_f64();
let secs = seconds.trunc() as u64;
let nsecs = (seconds.fract() * 1e9) as i64;
let mut ts = libc::timespec {
tv_sec: std::cmp::min(libc::time_t::max_value() as u64, secs) as libc::time_t,
tv_nsec: nsecs,
};
let res = unsafe { libc::nanosleep(&ts, &mut ts) };
let interrupted = res == -1 && nix::errno::errno() == libc::EINTR;
if interrupted {
crate::stdlib::signal::check_signals(vm)?;
}
Ok(())
}
#[cfg(not(unix))]
fn time_sleep(seconds: PyFloatRef, vm: &VirtualMachine) -> PyResult<()> {
let seconds = seconds.to_f64();
let secs: u64 = seconds.trunc() as u64;
let nanos: u32 = (seconds.fract() * 1e9) as u32;
let duration = Duration::new(secs, nanos);
thread::sleep(duration);
Ok(vm.get_none())
std::thread::sleep(duration);
Ok(())
}
fn duration_to_f64(d: Duration) -> f64 {
+11 -2
View File
@@ -39,6 +39,7 @@ use crate::pyobject::{
use crate::scope::Scope;
use crate::stdlib;
use crate::sysmodule;
use arr_macro::arr;
use num_bigint::BigInt;
#[cfg(feature = "rustpython-compiler")]
use rustpython_compiler::{compile, error::CompileError};
@@ -62,10 +63,12 @@ pub struct VirtualMachine {
pub profile_func: RefCell<PyObjectRef>,
pub trace_func: RefCell<PyObjectRef>,
pub use_tracing: RefCell<bool>,
pub signal_handlers: RefCell<[PyObjectRef; NSIG]>,
pub settings: PySettings,
pub signal_handlers: RefCell<HashMap<i32, PyObjectRef>>,
}
pub const NSIG: usize = 64;
/// Struct containing all kind of settings for the python vm.
pub struct PySettings {
/// -d command line switch
@@ -166,6 +169,8 @@ impl VirtualMachine {
let import_func = RefCell::new(ctx.none());
let profile_func = RefCell::new(ctx.none());
let trace_func = RefCell::new(ctx.none());
let signal_handlers = RefCell::new(arr![ctx.none(); 64]);
let vm = VirtualMachine {
builtins: builtins.clone(),
sys_module: sysmod.clone(),
@@ -179,8 +184,8 @@ impl VirtualMachine {
profile_func,
trace_func,
use_tracing: RefCell::new(false),
signal_handlers,
settings,
signal_handlers: Default::default(),
};
objmodule::init_module_dict(
@@ -198,6 +203,10 @@ impl VirtualMachine {
builtins::make_module(&vm, builtins.clone());
sysmodule::make_module(&vm, sysmod, builtins);
#[cfg(not(target_arch = "wasm32"))]
import::import_builtin(&vm, "signal").expect("Couldn't initialize signal module");
vm
}