Skip to content

Commit ee2dff6

Browse files
committed
Implement atexit functionality
1 parent a5c368a commit ee2dff6

File tree

9 files changed

+126
-27
lines changed

9 files changed

+126
-27
lines changed

Lib/atexit.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

Lib/logging/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,8 +2051,8 @@ def shutdown(handlerList=_handlerList):
20512051
#else, swallow
20522052

20532053
#Let's try and shutdown automatically on application exit...
2054-
# import atexit
2055-
# atexit.register(shutdown)
2054+
import atexit
2055+
atexit.register(shutdown)
20562056

20572057
# Null handler
20582058

src/main.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ fn main() {
5353

5454
let interp = Interpreter::new(settings, init);
5555

56-
interp.enter(move |vm| {
56+
let exitcode = interp.enter(move |vm| {
5757
let res = run_rustpython(vm, &matches);
5858

5959
#[cfg(feature = "flame-it")]
@@ -65,37 +65,48 @@ fn main() {
6565
}
6666

6767
// See if any exception leaked out:
68-
if let Err(err) = res {
69-
if objtype::isinstance(&err, &vm.ctx.exceptions.system_exit) {
68+
let exitcode = match res {
69+
Ok(()) => 0,
70+
Err(err) if objtype::isinstance(&err, &vm.ctx.exceptions.system_exit) => {
7071
let args = err.args();
7172
match args.borrow_value().len() {
72-
0 => return,
73+
0 => 0,
7374
1 => match_class!(match args.borrow_value()[0].clone() {
7475
i @ PyInt => {
7576
use num_traits::cast::ToPrimitive;
76-
process::exit(i.borrow_value().to_i32().unwrap_or(0));
77+
i.borrow_value().to_i32().unwrap_or(0)
7778
}
7879
arg => {
7980
if vm.is_none(&arg) {
80-
return;
81-
}
82-
if let Ok(s) = vm.to_str(&arg) {
83-
eprintln!("{}", s);
81+
0
82+
} else {
83+
if let Ok(s) = vm.to_str(&arg) {
84+
eprintln!("{}", s);
85+
}
86+
1
8487
}
8588
}
8689
}),
8790
_ => {
8891
if let Ok(r) = vm.to_repr(args.as_object()) {
8992
eprintln!("{}", r);
9093
}
94+
1
9195
}
9296
}
93-
} else {
97+
}
98+
Err(err) => {
9499
print_exception(&vm, err);
100+
1
95101
}
96-
process::exit(1);
97-
}
98-
})
102+
};
103+
104+
let _ = vm.run_atexit_funcs();
105+
106+
exitcode
107+
});
108+
109+
process::exit(exitcode);
99110
}
100111

101112
fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> {

vm/src/exceptions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,10 @@ pub fn print_exception(vm: &VirtualMachine, exc: PyBaseExceptionRef) {
162162
}
163163
};
164164
if let Ok(excepthook) = vm.get_attribute(vm.sys_module.clone(), "excepthook") {
165-
let (exc_type, exc_val, exc_tb) = split(exc, vm);
165+
let (exc_type, exc_val, exc_tb) = split(exc.clone(), vm);
166166
if let Err(eh_exc) = vm.invoke(&excepthook, vec![exc_type, exc_val, exc_tb]) {
167167
write_fallback(&eh_exc, "Error in sys.excepthook:");
168-
write_fallback(&eh_exc, "Original exception was:");
168+
write_fallback(&exc, "Original exception was:");
169169
}
170170
} else {
171171
write_fallback(&exc, "missing sys.excepthook");

vm/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
88
// for methods like vm.to_str(), not the typical use of 'to' as a method prefix
99
#![allow(clippy::wrong_self_convention, clippy::implicit_hasher)]
10+
// to allow `mod foo {}` in foo.rs; clippy thinks this is a mistake/misunderstanding of
11+
// how `mod` works, but we want this sometimes for pymodule declarations
12+
#![allow(clippy::module_inception)]
1013
#![doc(html_logo_url = "https://raw.githubusercontent.com/RustPython/RustPython/master/logo.png")]
1114
#![doc(html_root_url = "https://docs.rs/rustpython-vm/")]
1215
#![cfg_attr(

vm/src/stdlib/atexit.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
pub(crate) use atexit::make_module;
2+
3+
#[pymodule]
4+
mod atexit {
5+
use crate::function::PyFuncArgs;
6+
use crate::pyobject::{PyObjectRef, PyResult};
7+
use crate::VirtualMachine;
8+
9+
#[pyfunction]
10+
fn register(func: PyObjectRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyObjectRef {
11+
vm.state.atexit_funcs.lock().push((func.clone(), args));
12+
func
13+
}
14+
15+
#[pyfunction]
16+
fn _clear(vm: &VirtualMachine) {
17+
vm.state.atexit_funcs.lock().clear();
18+
}
19+
20+
#[pyfunction]
21+
fn unregister(func: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
22+
let mut funcs = vm.state.atexit_funcs.lock();
23+
24+
let mut i = 0;
25+
while i < funcs.len() {
26+
if vm.bool_eq(funcs[i].0.clone(), func.clone())? {
27+
funcs.remove(i);
28+
} else {
29+
i += 1;
30+
}
31+
}
32+
33+
Ok(())
34+
}
35+
36+
#[pyfunction]
37+
fn _run_exitfuncs(vm: &VirtualMachine) -> PyResult<()> {
38+
vm.run_atexit_funcs()
39+
}
40+
41+
#[pyfunction]
42+
fn _ncallbacks(vm: &VirtualMachine) -> usize {
43+
vm.state.atexit_funcs.lock().len()
44+
}
45+
}

vm/src/stdlib/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use std::collections::HashMap;
55
pub mod array;
66
#[cfg(feature = "rustpython-parser")]
77
pub(crate) mod ast;
8+
mod atexit;
89
mod binascii;
910
mod collections;
1011
mod csv;
@@ -73,6 +74,7 @@ pub fn get_module_inits() -> HashMap<String, StdlibInitFunc> {
7374
#[allow(unused_mut)]
7475
let mut modules = hashmap! {
7576
"array".to_owned() => Box::new(array::make_module) as StdlibInitFunc,
77+
"atexit".to_owned() => Box::new(atexit::make_module),
7678
"binascii".to_owned() => Box::new(binascii::make_module),
7779
"_collections".to_owned() => Box::new(collections::make_module),
7880
"_csv".to_owned() => Box::new(csv::make_module),

vm/src/sysmodule.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,34 @@ pub fn get_stderr(vm: &VirtualMachine) -> PyResult {
332332
.map_err(|_| vm.new_runtime_error("lost sys.stderr".to_owned()))
333333
}
334334

335+
/// Similar to PySys_WriteStderr in CPython.
336+
///
337+
/// # Usage
338+
///
339+
/// ```rust,ignore
340+
/// writeln!(sysmodule::PyStderr(vm), "foo bar baz :)");
341+
/// ```
342+
///
343+
/// Unlike writing to a `std::io::Write` with the `write[ln]!()` macro, there's no error condition here;
344+
/// this is intended to be a replacement for the `eprint[ln]!()` macro, so `write!()`-ing to PyStderr just
345+
/// returns `()`.
346+
pub struct PyStderr<'vm>(pub &'vm VirtualMachine);
347+
348+
impl PyStderr<'_> {
349+
pub fn write_fmt(&self, args: std::fmt::Arguments<'_>) {
350+
use py_io::Write;
351+
352+
let vm = self.0;
353+
if let Ok(stderr) = get_stderr(vm) {
354+
let mut stderr = py_io::PyWriter(stderr, vm);
355+
if let Ok(()) = stderr.write_fmt(args) {
356+
return;
357+
}
358+
}
359+
eprint!("{}", args)
360+
}
361+
}
362+
335363
fn sys_excepthook(
336364
exc_type: PyObjectRef,
337365
exc_val: PyObjectRef,

vm/src/vm.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustpython_compiler::{
1919

2020
use crate::builtins::{self, to_ascii};
2121
use crate::bytecode;
22-
use crate::common::{hash::HashSecret, rc::PyRc};
22+
use crate::common::{cell::PyMutex, hash::HashSecret, rc::PyRc};
2323
use crate::exceptions::{self, PyBaseException, PyBaseExceptionRef};
2424
use crate::frame::{ExecutionResult, Frame, FrameRef};
2525
use crate::frozen;
@@ -122,6 +122,7 @@ pub struct PyGlobalState {
122122
pub stacksize: AtomicCell<usize>,
123123
pub thread_count: AtomicCell<usize>,
124124
pub hash_secret: HashSecret,
125+
pub atexit_funcs: PyMutex<Vec<(PyObjectRef, PyFuncArgs)>>,
125126
}
126127

127128
pub const NSIG: usize = 64;
@@ -258,6 +259,7 @@ impl VirtualMachine {
258259
stacksize: AtomicCell::new(0),
259260
thread_count: AtomicCell::new(0),
260261
hash_secret,
262+
atexit_funcs: PyMutex::default(),
261263
}),
262264
initialized: false,
263265
};
@@ -344,6 +346,23 @@ impl VirtualMachine {
344346
}
345347
}
346348

349+
pub fn run_atexit_funcs(&self) -> PyResult<()> {
350+
let mut last_exc = None;
351+
for (func, args) in self.state.atexit_funcs.lock().drain(..).rev() {
352+
if let Err(e) = self.invoke(&func, args) {
353+
last_exc = Some(e.clone());
354+
if !objtype::isinstance(&e, &self.ctx.exceptions.system_exit) {
355+
writeln!(sysmodule::PyStderr(self), "Error in atexit._run_exitfuncs:");
356+
exceptions::print_exception(self, e);
357+
}
358+
}
359+
}
360+
match last_exc {
361+
None => Ok(()),
362+
Some(e) => Err(e),
363+
}
364+
}
365+
347366
pub fn run_code_obj(&self, code: PyCodeRef, scope: Scope) -> PyResult {
348367
let frame = Frame::new(code, scope, self).into_ref(self);
349368
self.run_frame_full(frame)

0 commit comments

Comments
 (0)