From 982cbd4d25b5915e2dc8108028381157d809171b Mon Sep 17 00:00:00 2001 From: "Jeong, YunWon" <69878+youknowone@users.noreply.github.com> Date: Fri, 18 Sep 2026 11:03:46 +0900 Subject: [PATCH] Add os.wait3 and os.wait4 with EINTR retry (#8728) * Add os.wait3 and os.wait4 with EINTR retry Assisted-by: Grok:4.6 * Unskip multiprocessing Finalize thread-safety test Assisted-by: Grok:4.6 * Fix wait4 clippy lint and use libc wait4 Assisted-by: Grok:4.6 --- Lib/test/_test_multiprocessing.py | 1 - crates/host_env/src/posix.rs | 24 +++++++++++++ crates/vm/src/stdlib/posix.rs | 59 +++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 9cbe6dc64..f1d4cb360 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -5009,7 +5009,6 @@ class _TestFinalize(BaseTestCase): result = [obj for obj in iter(conn.recv, 'STOP')] self.assertEqual(result, ['a', 'b', 'd10', 'd03', 'd02', 'd01', 'e']) - @unittest.skip("TODO: RUSTPYTHON; SIGSEGV due to dict thread-safety issue under aggressive GC") @support.requires_resource('cpu') def test_thread_safety(self): # bpo-24484: _run_finalizers() should be thread-safe diff --git a/crates/host_env/src/posix.rs b/crates/host_env/src/posix.rs index 07c653777..aa8e86b92 100644 --- a/crates/host_env/src/posix.rs +++ b/crates/host_env/src/posix.rs @@ -971,6 +971,30 @@ pub fn waitpid(pid: libc::pid_t, status: &mut i32, opt: i32) -> std::io::Result< } } +/// `wait3(2)` is `wait4(-1, ...)`. +pub fn wait3(options: i32) -> std::io::Result<(libc::pid_t, i32, crate::resource::RUsage)> { + wait4(-1, options) +} + +/// `wait4(2)`. `rusage` is zeroed when `pid == 0` (WNOHANG, no child ready). +pub fn wait4( + pid: libc::pid_t, + options: i32, +) -> std::io::Result<(libc::pid_t, i32, crate::resource::RUsage)> { + let mut status = 0; + let mut ru = core::mem::MaybeUninit::::zeroed(); + let res = unsafe { libc::wait4(pid, &mut status, options, ru.as_mut_ptr()) }; + if res == -1 { + return Err(std::io::Error::last_os_error()); + } + let ru = if res == 0 { + unsafe { core::mem::zeroed() } + } else { + unsafe { ru.assume_init() } + }; + Ok((res, status, ru.into())) +} + pub fn kill(pid: i32, sig: i32) -> std::io::Result<()> { let ret = unsafe { libc::kill(pid, sig) }; if ret == -1 { diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index adbbc8bc4..1ed17a5c3 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -1824,6 +1824,65 @@ pub mod module { waitpid(-1, 0, vm) } + fn rusage_to_py( + ru: rustpython_host_env::resource::RUsage, + vm: &VirtualMachine, + ) -> PyResult { + let resource = vm.import("resource", 0)?; + let struct_rusage = resource.get_attr("struct_rusage", vm)?; + let tv = |t: rustpython_host_env::resource::timeval| { + t.tv_sec as f64 + (t.tv_usec as f64 / 1_000_000.0) + }; + let fields = vm.ctx.new_tuple(vec![ + vm.ctx.new_float(tv(ru.ru_utime)).into(), + vm.ctx.new_float(tv(ru.ru_stime)).into(), + vm.ctx.new_int(ru.ru_maxrss).into(), + vm.ctx.new_int(ru.ru_ixrss).into(), + vm.ctx.new_int(ru.ru_idrss).into(), + vm.ctx.new_int(ru.ru_isrss).into(), + vm.ctx.new_int(ru.ru_minflt).into(), + vm.ctx.new_int(ru.ru_majflt).into(), + vm.ctx.new_int(ru.ru_nswap).into(), + vm.ctx.new_int(ru.ru_inblock).into(), + vm.ctx.new_int(ru.ru_oublock).into(), + vm.ctx.new_int(ru.ru_msgsnd).into(), + vm.ctx.new_int(ru.ru_msgrcv).into(), + vm.ctx.new_int(ru.ru_nsignals).into(), + vm.ctx.new_int(ru.ru_nvcsw).into(), + vm.ctx.new_int(ru.ru_nivcsw).into(), + ]); + struct_rusage.call((fields,), vm) + } + + fn wait_with_rusage(vm: &VirtualMachine, wait: F) -> PyResult + where + F: Fn() -> std::io::Result<(libc::pid_t, i32, rustpython_host_env::resource::RUsage)>, + { + loop { + match vm.allow_threads(&wait) { + Err(err) if err.raw_os_error() == Some(libc::EINTR) => { + vm.check_signals()?; + continue; + } + Err(err) => return Err(err.into_pyexception(vm)), + Ok((pid, status, ru)) => { + let rusage = rusage_to_py(ru, vm)?; + return Ok(vm.new_tuple((pid, status, rusage))); + } + } + } + } + + #[pyfunction] + fn wait3(options: i32, vm: &VirtualMachine) -> PyResult { + wait_with_rusage(vm, || rustpython_host_env::posix::wait3(options)) + } + + #[pyfunction] + fn wait4(pid: libc::pid_t, options: i32, vm: &VirtualMachine) -> PyResult { + wait_with_rusage(vm, || rustpython_host_env::posix::wait4(pid, options)) + } + #[pyfunction] fn kill(pid: i32, signal: isize, vm: &VirtualMachine) -> PyResult<()> { rustpython_host_env::posix::kill(pid, signal as i32).map_err(|err| err.into_pyexception(vm))