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
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-09-18 11:03:46 +09:00
1 parent ce470e3928
commit 982cbd4d25
3 files changed
+83 -1

No files matched your search

-1
View File
@@ -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
+24
View File
@@ -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::<libc::rusage>::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 {
+59
View File
@@ -1824,6 +1824,65 @@ pub mod module {
waitpid(-1, 0, vm)
}
fn rusage_to_py(
ru: rustpython_host_env::resource::RUsage,
vm: &VirtualMachine,
) -> PyResult<PyObjectRef> {
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<F>(vm: &VirtualMachine, wait: F) -> PyResult<PyTupleRef>
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<PyTupleRef> {
wait_with_rusage(vm, || rustpython_host_env::posix::wait3(options))
}
#[pyfunction]
fn wait4(pid: libc::pid_t, options: i32, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
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))