Skip to content

Commit 043d00f

Browse files
committed
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>
1 parent 211649d commit 043d00f

File tree

3 files changed

+31
-9
lines changed

3 files changed

+31
-9
lines changed

Lib/test/test_cmd_line.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,6 @@ def test_unmached_quote(self):
475475
self.assertRegex(err.decode('ascii', 'ignore'), 'SyntaxError')
476476
self.assertEqual(b'', out)
477477

478-
# TODO: RUSTPYTHON
479-
@unittest.expectedFailure
480478
def test_stdout_flush_at_shutdown(self):
481479
# Issue #5319: if stdout.flush() fails at shutdown, an error should
482480
# be printed out.

crates/vm/src/vm/interpreter.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ impl Interpreter {
401401
/// Note that calling `finalize` is not necessary by purpose though.
402402
pub fn finalize(self, exc: Option<PyBaseExceptionRef>) -> u32 {
403403
self.enter(|vm| {
404-
vm.flush_std();
404+
let mut flush_status = vm.flush_std();
405405

406406
// See if any exception leaked out:
407407
let exit_code = if let Some(exc) = exc {
@@ -439,9 +439,16 @@ impl Interpreter {
439439
// (while builtins is still available for __del__), then clear module dicts.
440440
vm.finalize_modules();
441441

442-
vm.flush_std();
442+
if vm.flush_std() < 0 && flush_status == 0 {
443+
flush_status = -1;
444+
}
443445

444-
exit_code
446+
// Match CPython: if exit_code is 0 and stdout flush failed, exit 120
447+
if exit_code == 0 && flush_status < 0 {
448+
120
449+
} else {
450+
exit_code
451+
}
445452
})
446453
}
447454
}

crates/vm/src/vm/vm_object.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,31 @@ impl VirtualMachine {
5252
}
5353
}
5454

55-
pub(crate) fn flush_std(&self) {
55+
/// Returns true if the file object's `closed` attribute is truthy.
56+
fn file_is_closed(&self, file: &PyObject) -> bool {
57+
file.get_attr("closed", self)
58+
.ok()
59+
.is_some_and(|v| v.try_to_bool(self).unwrap_or(false))
60+
}
61+
62+
pub(crate) fn flush_std(&self) -> i32 {
5663
let vm = self;
57-
if let Ok(stdout) = sys::get_stdout(vm) {
58-
let _ = vm.call_method(&stdout, identifier!(vm, flush).as_str(), ());
64+
let mut status = 0;
65+
if let Ok(stdout) = sys::get_stdout(vm)
66+
&& !vm.is_none(&stdout)
67+
&& !vm.file_is_closed(&stdout)
68+
&& let Err(e) = vm.call_method(&stdout, identifier!(vm, flush).as_str(), ())
69+
{
70+
vm.run_unraisable(e, None, stdout);
71+
status = -1;
5972
}
60-
if let Ok(stderr) = sys::get_stderr(vm) {
73+
if let Ok(stderr) = sys::get_stderr(vm)
74+
&& !vm.is_none(&stderr)
75+
&& !vm.file_is_closed(&stderr)
76+
{
6177
let _ = vm.call_method(&stderr, identifier!(vm, flush).as_str(), ());
6278
}
79+
status
6380
}
6481

6582
#[track_caller]

0 commit comments

Comments
 (0)