mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
fix: Flush stdout on shutdown matching CPython behavior (#7503)
* fix: flush stdout on interpreter shutdown matching CPython behavior When stdout flush fails during shutdown, report the error via run_unraisable and exit with code 120 (matching CPython's Py_FinalizeEx). Skip flushing already-closed or None streams. Stderr flush errors remain silently ignored per CPython behavior. Fixes #5521 Signed-off-by: majiayu000 <1835304752@qq.com> * refactor: replace magic number 120 with named constant EXITCODE_FLUSH_FAILURE Address review feedback on PR #7503: improve readability by extracting the CPython-compat exit code into a named constant. Signed-off-by: majiayu000 <1835304752@qq.com> --------- Signed-off-by: majiayu000 <1835304752@qq.com>
This commit is contained in:
1 parent
a293e11238
commit
44cd8d53a1
3 files changed
+35
-9
No files matched your search
Vendored
-2
@@ -475,8 +475,6 @@ class CmdLineTest(unittest.TestCase):
|
||||
self.assertRegex(err.decode('ascii', 'ignore'), 'SyntaxError')
|
||||
self.assertEqual(b'', out)
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
@unittest.expectedFailure
|
||||
def test_stdout_flush_at_shutdown(self):
|
||||
# Issue #5319: if stdout.flush() fails at shutdown, an error should
|
||||
# be printed out.
|
||||
|
||||
@@ -10,6 +10,10 @@ use core::sync::atomic::Ordering;
|
||||
|
||||
type InitFunc = Box<dyn FnOnce(&mut VirtualMachine)>;
|
||||
|
||||
/// Exit code used when stdout/stderr flush fails during interpreter shutdown.
|
||||
/// Matches CPython's behavior (see cpython/Python/pylifecycle.c).
|
||||
const EXITCODE_FLUSH_FAILURE: u32 = 120;
|
||||
|
||||
/// Configuration builder for constructing an Interpreter.
|
||||
///
|
||||
/// This is the preferred way to configure and create an interpreter with custom modules.
|
||||
@@ -401,7 +405,7 @@ impl Interpreter {
|
||||
/// Note that calling `finalize` is not necessary by purpose though.
|
||||
pub fn finalize(self, exc: Option<PyBaseExceptionRef>) -> u32 {
|
||||
self.enter(|vm| {
|
||||
vm.flush_std();
|
||||
let mut flush_status = vm.flush_std();
|
||||
|
||||
// See if any exception leaked out:
|
||||
let exit_code = if let Some(exc) = exc {
|
||||
@@ -439,9 +443,16 @@ impl Interpreter {
|
||||
// (while builtins is still available for __del__), then clear module dicts.
|
||||
vm.finalize_modules();
|
||||
|
||||
vm.flush_std();
|
||||
if vm.flush_std() < 0 && flush_status == 0 {
|
||||
flush_status = -1;
|
||||
}
|
||||
|
||||
exit_code
|
||||
// Match CPython: if exit_code is 0 and stdout flush failed, exit 120
|
||||
if exit_code == 0 && flush_status < 0 {
|
||||
EXITCODE_FLUSH_FAILURE
|
||||
} else {
|
||||
exit_code
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,14 +52,31 @@ impl VirtualMachine {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn flush_std(&self) {
|
||||
/// Returns true if the file object's `closed` attribute is truthy.
|
||||
fn file_is_closed(&self, file: &PyObject) -> bool {
|
||||
file.get_attr("closed", self)
|
||||
.ok()
|
||||
.is_some_and(|v| v.try_to_bool(self).unwrap_or(false))
|
||||
}
|
||||
|
||||
pub(crate) fn flush_std(&self) -> i32 {
|
||||
let vm = self;
|
||||
if let Ok(stdout) = sys::get_stdout(vm) {
|
||||
let _ = vm.call_method(&stdout, identifier!(vm, flush).as_str(), ());
|
||||
let mut status = 0;
|
||||
if let Ok(stdout) = sys::get_stdout(vm)
|
||||
&& !vm.is_none(&stdout)
|
||||
&& !vm.file_is_closed(&stdout)
|
||||
&& let Err(e) = vm.call_method(&stdout, identifier!(vm, flush).as_str(), ())
|
||||
{
|
||||
vm.run_unraisable(e, None, stdout);
|
||||
status = -1;
|
||||
}
|
||||
if let Ok(stderr) = sys::get_stderr(vm) {
|
||||
if let Ok(stderr) = sys::get_stderr(vm)
|
||||
&& !vm.is_none(&stderr)
|
||||
&& !vm.file_is_closed(&stderr)
|
||||
{
|
||||
let _ = vm.call_method(&stderr, identifier!(vm, flush).as_str(), ());
|
||||
}
|
||||
status
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
|
||||
Reference in new issue
Block a user