mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Fix atexit unraisable exception message format (#7399)
* Fix atexit unraisable exception message format
Match PyErr_FormatUnraisable behavior: use
"Exception ignored in atexit callback {func!r}" as err_msg
and pass None as object instead of the callback function.
* Fix atexit unregister deadlock with reentrant __eq__
Release the lock during equality comparison in unregister so
that __eq__ can safely call atexit.unregister or atexit._clear.
Store callbacks in LIFO order (insert at front) and use
identity-based search after comparison to handle list mutations,
matching atexitmodule.c behavior.
Also pass None as err_msg when func.repr() fails, matching
CPython's PyErr_FormatUnraisable fallback.
This commit is contained in:
1 parent
3ef45abbce
commit
e97f1c8bdf
3 files changed
+52
-20
No files matched your search
Vendored
-7
@@ -47,7 +47,6 @@ class GeneralTest(unittest.TestCase):
|
||||
('func2', (), {}),
|
||||
('func1', (1, 2), {})])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_badargs(self):
|
||||
def func():
|
||||
pass
|
||||
@@ -55,14 +54,12 @@ class GeneralTest(unittest.TestCase):
|
||||
# func() has no parameter, but it's called with 2 parameters
|
||||
self.assert_raises_unraisable(TypeError, func, 1 ,2)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_raise(self):
|
||||
def raise_type_error():
|
||||
raise TypeError
|
||||
|
||||
self.assert_raises_unraisable(TypeError, raise_type_error)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_raise_unnormalized(self):
|
||||
# bpo-10756: Make sure that an unnormalized exception is handled
|
||||
# properly.
|
||||
@@ -71,7 +68,6 @@ class GeneralTest(unittest.TestCase):
|
||||
|
||||
self.assert_raises_unraisable(ZeroDivisionError, div_zero)
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_exit(self):
|
||||
self.assert_raises_unraisable(SystemExit, sys.exit)
|
||||
|
||||
@@ -122,7 +118,6 @@ class GeneralTest(unittest.TestCase):
|
||||
atexit._run_exitfuncs()
|
||||
self.assertEqual(l, [5])
|
||||
|
||||
@unittest.expectedFailure # TODO: RUSTPYTHON
|
||||
def test_atexit_with_unregistered_function(self):
|
||||
# See bpo-46025 for more info
|
||||
def func():
|
||||
@@ -140,7 +135,6 @@ class GeneralTest(unittest.TestCase):
|
||||
finally:
|
||||
atexit.unregister(func)
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; Hangs")
|
||||
def test_eq_unregister_clear(self):
|
||||
# Issue #112127: callback's __eq__ may call unregister or _clear
|
||||
class Evil:
|
||||
@@ -154,7 +148,6 @@ class GeneralTest(unittest.TestCase):
|
||||
atexit.unregister(Evil())
|
||||
atexit._clear()
|
||||
|
||||
@unittest.skip("TODO: RUSTPYTHON; Hangs")
|
||||
def test_eq_unregister(self):
|
||||
# Issue #112127: callback's __eq__ may call unregister
|
||||
def f1():
|
||||
|
||||
@@ -7,7 +7,11 @@ mod atexit {
|
||||
|
||||
#[pyfunction]
|
||||
fn register(func: PyObjectRef, args: FuncArgs, vm: &VirtualMachine) -> PyObjectRef {
|
||||
vm.state.atexit_funcs.lock().push((func.clone(), args));
|
||||
// Callbacks go in LIFO order (insert at front)
|
||||
vm.state
|
||||
.atexit_funcs
|
||||
.lock()
|
||||
.insert(0, Box::new((func.clone(), args)));
|
||||
func
|
||||
}
|
||||
|
||||
@@ -18,27 +22,62 @@ mod atexit {
|
||||
|
||||
#[pyfunction]
|
||||
fn unregister(func: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
|
||||
let mut funcs = vm.state.atexit_funcs.lock();
|
||||
|
||||
let mut i = 0;
|
||||
while i < funcs.len() {
|
||||
if vm.bool_eq(&funcs[i].0, &func)? {
|
||||
funcs.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
// Iterate backward (oldest to newest in LIFO list).
|
||||
// Release the lock during comparison so __eq__ can call atexit functions.
|
||||
let mut i = {
|
||||
let funcs = vm.state.atexit_funcs.lock();
|
||||
funcs.len() as isize - 1
|
||||
};
|
||||
while i >= 0 {
|
||||
let (cb, entry_ptr) = {
|
||||
let funcs = vm.state.atexit_funcs.lock();
|
||||
if i as usize >= funcs.len() {
|
||||
i = funcs.len() as isize;
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
let entry = &funcs[i as usize];
|
||||
(entry.0.clone(), &**entry as *const (PyObjectRef, FuncArgs))
|
||||
};
|
||||
// Lock released: __eq__ can safely call atexit functions
|
||||
let eq = vm.bool_eq(&func, &cb)?;
|
||||
if eq {
|
||||
// The entry may have moved during __eq__. Search backward by identity.
|
||||
let mut funcs = vm.state.atexit_funcs.lock();
|
||||
let mut j = (funcs.len() as isize - 1).min(i);
|
||||
while j >= 0 {
|
||||
if core::ptr::eq(&**funcs.get(j as usize).unwrap(), entry_ptr) {
|
||||
funcs.remove(j as usize);
|
||||
i = j;
|
||||
break;
|
||||
}
|
||||
j -= 1;
|
||||
}
|
||||
}
|
||||
{
|
||||
let funcs = vm.state.atexit_funcs.lock();
|
||||
if i as usize >= funcs.len() {
|
||||
i = funcs.len() as isize;
|
||||
}
|
||||
}
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
pub fn _run_exitfuncs(vm: &VirtualMachine) {
|
||||
let funcs: Vec<_> = core::mem::take(&mut *vm.state.atexit_funcs.lock());
|
||||
for (func, args) in funcs.into_iter().rev() {
|
||||
// Callbacks stored in LIFO order, iterate forward
|
||||
for entry in funcs.into_iter() {
|
||||
let (func, args) = *entry;
|
||||
if let Err(e) = func.call(args, vm) {
|
||||
let exit = e.fast_isinstance(vm.ctx.exceptions.system_exit);
|
||||
vm.run_unraisable(e, Some("Error in atexit._run_exitfuncs".to_owned()), func);
|
||||
let msg = func
|
||||
.repr(vm)
|
||||
.ok()
|
||||
.map(|r| format!("Exception ignored in atexit callback {}", r.as_wtf8()));
|
||||
vm.run_unraisable(e, msg, vm.ctx.none());
|
||||
if exit {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -585,7 +585,7 @@ pub struct PyGlobalState {
|
||||
pub stacksize: AtomicCell<usize>,
|
||||
pub thread_count: AtomicCell<usize>,
|
||||
pub hash_secret: HashSecret,
|
||||
pub atexit_funcs: PyMutex<Vec<(PyObjectRef, FuncArgs)>>,
|
||||
pub atexit_funcs: PyMutex<Vec<Box<(PyObjectRef, FuncArgs)>>>,
|
||||
pub codec_registry: CodecsRegistry,
|
||||
pub finalizing: AtomicBool,
|
||||
pub warnings: WarningsState,
|
||||
|
||||
Reference in new issue
Block a user