From b97e168c1a647aea8cf5afb4c407da0cd7c15bb5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 26 Dec 2025 11:04:23 +0900 Subject: [PATCH 1/2] fix waitpid --- Lib/test/test_signal.py | 2 -- crates/vm/src/stdlib/posix.rs | 14 +++++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index 58b3e55d321..bd9d3b011d7 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -127,8 +127,6 @@ def test_strsignal(self): self.assertIn("Terminated", signal.strsignal(signal.SIGTERM)) self.assertIn("Hangup", signal.strsignal(signal.SIGHUP)) - # TODO: RUSTPYTHON - @unittest.expectedFailure # Issue 3864, unknown if this affects earlier versions of freebsd also def test_interprocess_signal(self): dirname = os.path.dirname(__file__) diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 15c8745ded4..b903e41889d 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -1738,9 +1738,17 @@ pub mod module { #[pyfunction] fn waitpid(pid: libc::pid_t, opt: i32, vm: &VirtualMachine) -> PyResult<(libc::pid_t, i32)> { let mut status = 0; - let pid = unsafe { libc::waitpid(pid, &mut status, opt) }; - let pid = nix::Error::result(pid).map_err(|err| err.into_pyexception(vm))?; - Ok((pid, status)) + loop { + let res = unsafe { libc::waitpid(pid, &mut status, opt) }; + if res == -1 { + if nix::Error::last_raw() == libc::EINTR { + vm.check_signals()?; + continue; + } + return Err(nix::Error::last().into_pyexception(vm)); + } + return Ok((res, status)); + } } #[pyfunction] From d3c92f05705381c9d5d5f6fd33de8400fcb104cd Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 26 Dec 2025 10:49:05 +0900 Subject: [PATCH 2/2] signal timer --- Lib/test/test_signal.py | 10 --- crates/vm/src/stdlib/signal.rs | 116 +++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py index bd9d3b011d7..cb744b41e8b 100644 --- a/Lib/test/test_signal.py +++ b/Lib/test/test_signal.py @@ -818,8 +818,6 @@ def sig_prof(self, *args): self.hndl_called = True signal.setitimer(signal.ITIMER_PROF, 0) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_itimer_exc(self): # XXX I'm assuming -1 is an invalid itimer, but maybe some platform # defines it ? @@ -829,16 +827,12 @@ def test_itimer_exc(self): self.assertRaises(signal.ItimerError, signal.setitimer, signal.ITIMER_REAL, -1) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_itimer_real(self): self.itimer = signal.ITIMER_REAL signal.setitimer(self.itimer, 1.0) signal.pause() self.assertEqual(self.hndl_called, True) - # TODO: RUSTPYTHON - @unittest.expectedFailure # Issue 3864, unknown if this affects earlier versions of freebsd also @unittest.skipIf(sys.platform in ('netbsd5',), 'itimer not reliable (does not mix well with threading) on some BSDs.') @@ -859,8 +853,6 @@ def test_itimer_virtual(self): # and the handler should have been called self.assertEqual(self.hndl_called, True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_itimer_prof(self): self.itimer = signal.ITIMER_PROF signal.signal(signal.SIGPROF, self.sig_prof) @@ -878,8 +870,6 @@ def test_itimer_prof(self): # and the handler should have been called self.assertEqual(self.hndl_called, True) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_setitimer_tiny(self): # bpo-30807: C setitimer() takes a microsecond-resolution interval. # Check that float -> timeval conversion doesn't round diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index d34a681c2da..810ffabefe6 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -19,6 +19,11 @@ pub(crate) mod _signal { convert::{IntoPyException, TryFromBorrowedObject}, }; use crate::{PyObjectRef, PyResult, VirtualMachine, signal}; + #[cfg(unix)] + use crate::{ + builtins::PyTypeRef, + function::{ArgIntoFloat, OptionalArg}, + }; use std::sync::atomic::{self, Ordering}; #[cfg(any(unix, windows))] @@ -89,6 +94,18 @@ pub(crate) mod _signal { fn siginterrupt(sig: i32, flag: i32) -> i32; } + #[cfg(any(target_os = "linux", target_os = "android"))] + mod ffi { + unsafe extern "C" { + pub fn getitimer(which: libc::c_int, curr_value: *mut libc::itimerval) -> libc::c_int; + pub fn setitimer( + which: libc::c_int, + new_value: *const libc::itimerval, + old_value: *mut libc::itimerval, + ) -> libc::c_int; + } + } + #[pyattr] use crate::signal::NSIG; @@ -114,6 +131,31 @@ pub(crate) mod _signal { #[pyattr] use libc::{SIGPWR, SIGSTKFLT}; + // Interval timer constants + #[cfg(all(unix, not(target_os = "android")))] + #[pyattr] + use libc::{ITIMER_PROF, ITIMER_REAL, ITIMER_VIRTUAL}; + + #[cfg(target_os = "android")] + #[pyattr] + const ITIMER_REAL: libc::c_int = 0; + #[cfg(target_os = "android")] + #[pyattr] + const ITIMER_VIRTUAL: libc::c_int = 1; + #[cfg(target_os = "android")] + #[pyattr] + const ITIMER_PROF: libc::c_int = 2; + + #[cfg(unix)] + #[pyattr(name = "ItimerError", once)] + fn itimer_error(vm: &VirtualMachine) -> PyTypeRef { + vm.ctx.new_exception_type( + "signal", + "ItimerError", + Some(vec![vm.ctx.exceptions.os_error.to_owned()]), + ) + } + #[cfg(any(unix, windows))] pub(super) fn init_signal_handlers( module: &Py, @@ -216,6 +258,80 @@ pub(crate) mod _signal { prev_time.unwrap_or(0) } + #[cfg(unix)] + #[pyfunction] + fn pause(vm: &VirtualMachine) -> PyResult<()> { + unsafe { libc::pause() }; + signal::check_signals(vm)?; + Ok(()) + } + + #[cfg(unix)] + fn timeval_to_double(tv: &libc::timeval) -> f64 { + tv.tv_sec as f64 + (tv.tv_usec as f64 / 1_000_000.0) + } + + #[cfg(unix)] + fn double_to_timeval(val: f64) -> libc::timeval { + libc::timeval { + tv_sec: val.trunc() as _, + tv_usec: ((val.fract()) * 1_000_000.0) as _, + } + } + + #[cfg(unix)] + fn itimerval_to_tuple(it: &libc::itimerval) -> (f64, f64) { + ( + timeval_to_double(&it.it_value), + timeval_to_double(&it.it_interval), + ) + } + + #[cfg(unix)] + #[pyfunction] + fn setitimer( + which: i32, + seconds: ArgIntoFloat, + interval: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult<(f64, f64)> { + let seconds: f64 = seconds.into(); + let interval: f64 = interval.map(|v| v.into()).unwrap_or(0.0); + let new = libc::itimerval { + it_value: double_to_timeval(seconds), + it_interval: double_to_timeval(interval), + }; + let mut old = std::mem::MaybeUninit::::uninit(); + #[cfg(any(target_os = "linux", target_os = "android"))] + let ret = unsafe { ffi::setitimer(which, &new, old.as_mut_ptr()) }; + #[cfg(not(any(target_os = "linux", target_os = "android")))] + let ret = unsafe { libc::setitimer(which, &new, old.as_mut_ptr()) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + let itimer_error = itimer_error(vm); + return Err(vm.new_exception_msg(itimer_error, err.to_string())); + } + let old = unsafe { old.assume_init() }; + Ok(itimerval_to_tuple(&old)) + } + + #[cfg(unix)] + #[pyfunction] + fn getitimer(which: i32, vm: &VirtualMachine) -> PyResult<(f64, f64)> { + let mut old = std::mem::MaybeUninit::::uninit(); + #[cfg(any(target_os = "linux", target_os = "android"))] + let ret = unsafe { ffi::getitimer(which, old.as_mut_ptr()) }; + #[cfg(not(any(target_os = "linux", target_os = "android")))] + let ret = unsafe { libc::getitimer(which, old.as_mut_ptr()) }; + if ret != 0 { + let err = std::io::Error::last_os_error(); + let itimer_error = itimer_error(vm); + return Err(vm.new_exception_msg(itimer_error, err.to_string())); + } + let old = unsafe { old.assume_init() }; + Ok(itimerval_to_tuple(&old)) + } + #[pyfunction] fn default_int_handler( _signum: PyObjectRef,