Skip to content

Commit ca84d72

Browse files
committed
sys.unraisablehook
1 parent 9cbba17 commit ca84d72

File tree

9 files changed

+130
-82
lines changed

9 files changed

+130
-82
lines changed

Lib/test/_test_atexit.py

-10
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,19 @@ def func2(*args, **kwargs):
4545
('func2', (), {}),
4646
('func1', (1, 2), {})])
4747

48-
# TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute 'unraisablehook'
49-
@unittest.expectedFailure
5048
def test_badargs(self):
5149
def func():
5250
pass
5351

5452
# func() has no parameter, but it's called with 2 parameters
5553
self.assert_raises_unraisable(TypeError, func, 1 ,2)
5654

57-
# TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute 'unraisablehook'
58-
@unittest.expectedFailure
5955
def test_raise(self):
6056
def raise_type_error():
6157
raise TypeError
6258

6359
self.assert_raises_unraisable(TypeError, raise_type_error)
6460

65-
# TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute 'unraisablehook'
66-
@unittest.expectedFailure
6761
def test_raise_unnormalized(self):
6862
# bpo-10756: Make sure that an unnormalized exception is handled
6963
# properly.
@@ -72,8 +66,6 @@ def div_zero():
7266

7367
self.assert_raises_unraisable(ZeroDivisionError, div_zero)
7468

75-
# TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute 'unraisablehook'
76-
@unittest.expectedFailure
7769
def test_exit(self):
7870
self.assert_raises_unraisable(SystemExit, sys.exit)
7971

@@ -124,8 +116,6 @@ def test_bound_methods(self):
124116
atexit._run_exitfuncs()
125117
self.assertEqual(l, [5])
126118

127-
# TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute 'unraisablehook'
128-
@unittest.expectedFailure
129119
def test_atexit_with_unregistered_function(self):
130120
# See bpo-46025 for more info
131121
def func():

Lib/test/test_io.py

-6
Original file line numberDiff line numberDiff line change
@@ -1114,8 +1114,6 @@ def _with():
11141114
# a ValueError.
11151115
self.assertRaises(ValueError, _with)
11161116

1117-
# TODO: RUSTPYTHON, sys.unraisablehook
1118-
@unittest.expectedFailure
11191117
def test_error_through_destructor(self):
11201118
# Test that the exception state is not modified by a destructor,
11211119
# even if close() fails.
@@ -2121,8 +2119,6 @@ def reader_close():
21212119
# Silence destructor error
21222120
reader.close = lambda: None
21232121

2124-
# TODO: RUSTPYTHON, sys.unraisablehook
2125-
@unittest.expectedFailure
21262122
def test_writer_close_error_on_close(self):
21272123
def writer_close():
21282124
writer_non_existing
@@ -2952,8 +2948,6 @@ def flush(self):
29522948
support.gc_collect()
29532949
self.assertEqual(record, [1, 2, 3])
29542950

2955-
# TODO: RUSTPYTHON, sys.unraisablehook
2956-
@unittest.expectedFailure
29572951
def test_error_through_destructor(self):
29582952
# Test that the exception state is not modified by a destructor,
29592953
# even if close() fails.

Lib/test/test_signal.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1291,8 +1291,7 @@ def handler(signum, frame):
12911291
# Python handler
12921292
self.assertEqual(len(sigs), N, "Some signals were lost")
12931293

1294-
# TODO: RUSTPYTHON
1295-
@unittest.expectedFailure
1294+
@unittest.skip("TODO: RUSTPYTHON; hang")
12961295
@unittest.skipUnless(hasattr(signal, "SIGUSR1"),
12971296
"test needs SIGUSR1")
12981297
def test_stress_modifying_handlers(self):

Lib/test/test_thread.py

-2
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,6 @@ def task():
135135
time.sleep(POLL_SLEEP)
136136
self.assertEqual(thread._count(), orig)
137137

138-
# TODO: RUSTPYTHON, AttributeError: module 'sys' has no attribute 'unraisablehook'
139-
@unittest.expectedFailure
140138
def test_unraisable_exception(self):
141139
def task():
142140
started.release()

vm/src/pyobjectrc.rs

+19-34
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,27 @@
1111
//! PyRef<PyWeak> may looking like to be called as PyObjectWeak by the rule,
1212
//! but not to do to remember it is a PyRef object.
1313
14-
use crate::common::atomic::{OncePtr, PyAtomic, Radium};
15-
use crate::common::linked_list::{Link, LinkedList, Pointers};
16-
use crate::common::lock::{PyMutex, PyMutexGuard, PyRwLock};
17-
use crate::common::refcount::RefCount;
14+
use crate::common::{
15+
atomic::{OncePtr, PyAtomic, Radium},
16+
linked_list::{Link, LinkedList, Pointers},
17+
lock::{PyMutex, PyMutexGuard, PyRwLock},
18+
refcount::RefCount,
19+
};
1820
use crate::{
1921
_pyobject::{AsObject, PyObjectPayload, PyResult},
20-
builtins::{PyBaseExceptionRef, PyDictRef, PyTypeRef},
22+
builtins::{PyDictRef, PyTypeRef},
2123
vm::VirtualMachine,
2224
};
23-
use std::any::TypeId;
24-
use std::borrow::Borrow;
25-
use std::cell::UnsafeCell;
26-
use std::fmt;
27-
use std::marker::PhantomData;
28-
use std::mem::ManuallyDrop;
29-
use std::ops::Deref;
30-
use std::ptr::{self, NonNull};
25+
use std::{
26+
any::TypeId,
27+
borrow::Borrow,
28+
cell::UnsafeCell,
29+
fmt,
30+
marker::PhantomData,
31+
mem::ManuallyDrop,
32+
ops::Deref,
33+
ptr::{self, NonNull},
34+
};
3135

3236
// so, PyObjectRef is basically equivalent to `PyRc<PyInner<dyn PyObjectPayload>>`, except it's
3337
// only one pointer in width rather than 2. We do that by manually creating a vtable, and putting
@@ -732,7 +736,8 @@ impl PyObject {
732736
let ret = crate::vm::thread::with_vm(self, |vm| {
733737
self.0.ref_count.inc();
734738
if let Err(e) = slot_del(self, vm) {
735-
print_del_error(e, self, vm);
739+
let del_method = self.get_class_attr("__del__").unwrap();
740+
vm.run_unraisable(e, None, del_method);
736741
}
737742
self.0.ref_count.dec()
738743
});
@@ -804,26 +809,6 @@ impl Drop for PyObjectRef {
804809
}
805810
}
806811

807-
#[cold]
808-
fn print_del_error(e: PyBaseExceptionRef, zelf: &PyObject, vm: &VirtualMachine) {
809-
// exception in del will be ignored but printed
810-
print!("Exception ignored in: ",);
811-
let del_method = zelf.get_class_attr("__del__").unwrap();
812-
let repr = &del_method.repr(vm);
813-
match repr {
814-
Ok(v) => println!("{v}"),
815-
Err(_) => println!("{}", del_method.class().name()),
816-
}
817-
let tb_module = vm.import("traceback", None, 0).unwrap();
818-
// TODO: set exc traceback
819-
let print_stack = tb_module.get_attr("print_stack", vm).unwrap();
820-
vm.invoke(&print_stack, ()).unwrap();
821-
822-
if let Ok(repr) = e.as_object().repr(vm) {
823-
println!("{}", repr.as_str());
824-
}
825-
}
826-
827812
impl fmt::Debug for PyObjectRef {
828813
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
829814
// SAFETY: the vtable contains functions that accept payload types that always match up

vm/src/stdlib/atexit.rs

+7-14
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,17 @@ mod atexit {
3333
}
3434

3535
#[pyfunction]
36-
pub fn _run_exitfuncs(vm: &VirtualMachine) -> PyResult<()> {
37-
let mut last_exc = None;
38-
for (func, args) in vm.state.atexit_funcs.lock().drain(..).rev() {
36+
pub fn _run_exitfuncs(vm: &VirtualMachine) {
37+
let funcs: Vec<_> = std::mem::take(&mut *vm.state.atexit_funcs.lock());
38+
for (func, args) in funcs.into_iter().rev() {
3939
if let Err(e) = vm.invoke(&func, args) {
40-
last_exc = Some(e.clone());
41-
if !e.fast_isinstance(&vm.ctx.exceptions.system_exit) {
42-
writeln!(
43-
crate::stdlib::sys::PyStderr(vm),
44-
"Error in atexit._run_exitfuncs:"
45-
);
46-
vm.print_exception(e);
40+
let exit = e.fast_isinstance(&vm.ctx.exceptions.system_exit);
41+
vm.run_unraisable(e, Some("Error in atexit._run_exitfuncs".to_owned()), func);
42+
if exit {
43+
break;
4744
}
4845
}
4946
}
50-
match last_exc {
51-
None => Ok(()),
52-
Some(e) => Err(e),
53-
}
5447
}
5548

5649
#[pyfunction]

vm/src/stdlib/sys.rs

+75-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{convert::ToPyObject, PyObject, PyResult, VirtualMachine};
22

3-
pub(crate) use sys::{MAXSIZE, MULTIARCH};
3+
pub(crate) use sys::{UnraisableHookArgs, MAXSIZE, MULTIARCH};
44

55
#[pymodule]
66
mod sys {
@@ -16,7 +16,7 @@ mod sys {
1616
types::PyStructSequence,
1717
version,
1818
vm::{Settings, VirtualMachine},
19-
PyObjectRef, PyRef, PyRefExact, PyResult,
19+
AsObject, PyObjectRef, PyRef, PyRefExact, PyResult,
2020
};
2121
use num_traits::ToPrimitive;
2222
use std::{env, mem, path};
@@ -448,6 +448,66 @@ mod sys {
448448
.into_struct_sequence(vm))
449449
}
450450

451+
fn _unraisablehook(unraisable: UnraisableHookArgs, vm: &VirtualMachine) -> PyResult<()> {
452+
use super::PyStderr;
453+
454+
let stderr = PyStderr(vm);
455+
if !vm.is_none(&unraisable.object) {
456+
if !vm.is_none(&unraisable.err_msg) {
457+
write!(stderr, "{}: ", unraisable.err_msg.str(vm)?);
458+
} else {
459+
write!(stderr, "Exception ignored in: ");
460+
}
461+
// exception in del will be ignored but printed
462+
let repr = &unraisable.object.repr(vm);
463+
let str = match repr {
464+
Ok(v) => v.to_string(),
465+
Err(_) => format!(
466+
"<object {} repr() failed>",
467+
unraisable.object.class().name()
468+
),
469+
};
470+
writeln!(stderr, "{str}");
471+
} else if !vm.is_none(&unraisable.err_msg) {
472+
writeln!(stderr, "{}:", unraisable.err_msg.str(vm)?);
473+
}
474+
475+
// TODO: print received unraisable.exc_traceback
476+
let tb_module = vm.import("traceback", None, 0)?;
477+
let print_stack = tb_module.get_attr("print_stack", vm)?;
478+
vm.invoke(&print_stack, ())?;
479+
480+
if vm.is_none(unraisable.exc_type.as_object()) {
481+
// TODO: early return, but with what error?
482+
}
483+
assert!(unraisable
484+
.exc_type
485+
.fast_issubclass(&vm.ctx.exceptions.exception_type));
486+
487+
// TODO: print module name and qualname
488+
489+
if !vm.is_none(&unraisable.exc_value) {
490+
write!(stderr, ": ");
491+
if let Ok(str) = unraisable.exc_value.str(vm) {
492+
write!(stderr, "{}", str.as_str());
493+
} else {
494+
write!(stderr, "<exception str() failed>");
495+
}
496+
}
497+
writeln!(stderr);
498+
// TODO: call file.flush()
499+
500+
Ok(())
501+
}
502+
503+
#[pyfunction(name = "__unraisablehook__")]
504+
#[pyfunction]
505+
fn unraisablehook(unraisable: UnraisableHookArgs, vm: &VirtualMachine) {
506+
if let Err(e) = _unraisablehook(unraisable, vm) {
507+
println!("{}", e.as_object().repr(vm).unwrap().as_str());
508+
}
509+
}
510+
451511
#[pyattr]
452512
fn hash_info(vm: &VirtualMachine) -> PyTupleRef {
453513
PyHashInfo::INFO.into_struct_sequence(vm)
@@ -672,6 +732,19 @@ mod sys {
672732
#[cfg(windows)]
673733
#[pyimpl(with(PyStructSequence))]
674734
impl WindowsVersion {}
735+
736+
#[pyclass(noattr, module = "sys", name = "UnraisableHookArgs")]
737+
#[derive(Debug, PyStructSequence, TryIntoPyStructSequence)]
738+
pub struct UnraisableHookArgs {
739+
pub exc_type: PyTypeRef,
740+
pub exc_value: PyObjectRef,
741+
pub exc_traceback: PyObjectRef,
742+
pub err_msg: PyObjectRef,
743+
pub object: PyObjectRef,
744+
}
745+
746+
#[pyimpl(with(PyStructSequence))]
747+
impl UnraisableHookArgs {}
675748
}
676749

677750
pub(crate) fn init_module(vm: &VirtualMachine, module: &PyObject, builtins: &PyObject) {

vm/src/stdlib/thread.rs

+6-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ pub(crate) mod _thread {
88
builtins::{PyDictRef, PyStrRef, PyTupleRef, PyTypeRef},
99
convert::ToPyException,
1010
function::{ArgCallable, FuncArgs, KwArgs, OptionalArg},
11-
py_io,
1211
types::{Constructor, GetAttr, SetAttr},
1312
utils::Either,
1413
AsObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
@@ -17,7 +16,7 @@ pub(crate) mod _thread {
1716
lock_api::{RawMutex as RawMutexT, RawMutexTimed, RawReentrantMutex},
1817
RawMutex, RawThreadId,
1918
};
20-
use std::{cell::RefCell, fmt, io::Write, thread, time::Duration};
19+
use std::{cell::RefCell, fmt, thread, time::Duration};
2120
use thread_local::ThreadLocal;
2221

2322
// TIMEOUT_MAX_IN_MICROSECONDS is a value in microseconds
@@ -255,16 +254,11 @@ pub(crate) mod _thread {
255254
Ok(_obj) => {}
256255
Err(e) if e.fast_isinstance(&vm.ctx.exceptions.system_exit) => {}
257256
Err(exc) => {
258-
// TODO: sys.unraisablehook
259-
let stderr = std::io::stderr();
260-
let mut stderr = py_io::IoWriter(stderr.lock());
261-
let repr = func.as_ref().repr(vm).ok();
262-
let repr = repr
263-
.as_ref()
264-
.map_or("<object repr() failed>", |s| s.as_str());
265-
writeln!(*stderr, "Exception ignored in thread started by: {}", repr)
266-
.and_then(|()| vm.write_exception(&mut stderr, &exc))
267-
.ok();
257+
vm.run_unraisable(
258+
exc,
259+
Some("Exception ignored in thread started by".to_owned()),
260+
func.into(),
261+
);
268262
}
269263
}
270264
SENTINELS.with(|sents| {

vm/src/vm/mod.rs

+22
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,28 @@ impl VirtualMachine {
282282
self.run_frame_full(frame)
283283
}
284284

285+
#[cold]
286+
pub fn run_unraisable(&self, e: PyBaseExceptionRef, msg: Option<String>, object: PyObjectRef) {
287+
use crate::stdlib::sys::UnraisableHookArgs;
288+
289+
let sys_module = self.import("sys", None, 0).unwrap();
290+
let unraisablehook = sys_module.get_attr("unraisablehook", self).unwrap();
291+
292+
let exc_type = e.class().clone();
293+
let exc_traceback = e.traceback().to_pyobject(self); // TODO: actual traceback
294+
let exc_value = e.into();
295+
let args = UnraisableHookArgs {
296+
exc_type,
297+
exc_value,
298+
exc_traceback,
299+
err_msg: self.new_pyobj(msg),
300+
object,
301+
};
302+
if let Err(e) = self.invoke(&unraisablehook, (args,)) {
303+
println!("{}", e.as_object().repr(self).unwrap().as_str());
304+
}
305+
}
306+
285307
#[inline(always)]
286308
pub fn run_frame_full(&self, frame: FrameRef) -> PyResult {
287309
match self.run_frame(frame)? {

0 commit comments

Comments
 (0)