diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 21b789bac04..e1f2a712823 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -197,6 +197,15 @@ mod weakref_lock { core::hint::spin_loop(); } } + + /// Reset all weakref stripe locks after fork in child process. + /// Locks held by parent threads would cause infinite spin in the child. + #[cfg(unix)] + pub(crate) fn reset_all_after_fork() { + for lock in &LOCKS { + lock.store(0, Ordering::Release); + } + } } #[cfg(not(feature = "threading"))] @@ -212,6 +221,13 @@ mod weakref_lock { } } +/// Reset weakref stripe locks after fork. Must be called before any +/// Python code runs in the child process. +#[cfg(all(unix, feature = "threading"))] +pub(crate) fn reset_weakref_locks_after_fork() { + weakref_lock::reset_all_after_fork(); +} + // === WeakRefList: inline on every object (tp_weaklist) === pub(super) struct WeakRefList { diff --git a/crates/vm/src/signal.rs b/crates/vm/src/signal.rs index 4a1b84a1521..4aa245ad190 100644 --- a/crates/vm/src/signal.rs +++ b/crates/vm/src/signal.rs @@ -50,6 +50,16 @@ pub(crate) fn set_triggered() { ANY_TRIGGERED.store(true, Ordering::Release); } +/// Reset all signal trigger state after fork in child process. +/// Stale triggers from the parent must not fire in the child. +#[cfg(unix)] +pub(crate) fn clear_after_fork() { + ANY_TRIGGERED.store(false, Ordering::Release); + for trigger in &TRIGGERS { + trigger.store(false, Ordering::Relaxed); + } +} + pub fn assert_in_range(signum: i32, vm: &VirtualMachine) -> PyResult<()> { if (1..NSIG as i32).contains(&signum) { Ok(()) diff --git a/crates/vm/src/stdlib/posix.rs b/crates/vm/src/stdlib/posix.rs index 31b92137d57..5bbfef0f93b 100644 --- a/crates/vm/src/stdlib/posix.rs +++ b/crates/vm/src/stdlib/posix.rs @@ -660,8 +660,16 @@ pub mod module { } fn py_os_after_fork_child(vm: &VirtualMachine) { + // Reset low-level state before any Python code runs in the child. + // Signal triggers from the parent must not fire in the child. + crate::signal::clear_after_fork(); + crate::stdlib::signal::_signal::clear_wakeup_fd_after_fork(); + + // Reset weakref stripe locks that may have been held during fork. + #[cfg(feature = "threading")] + crate::object::reset_weakref_locks_after_fork(); + // Mark all other threads as done before running Python callbacks - // See _PyThread_AfterFork behavior #[cfg(feature = "threading")] crate::stdlib::thread::after_fork_child(vm); diff --git a/crates/vm/src/stdlib/signal.rs b/crates/vm/src/stdlib/signal.rs index 231fae75376..1a73c454b8f 100644 --- a/crates/vm/src/stdlib/signal.rs +++ b/crates/vm/src/stdlib/signal.rs @@ -679,6 +679,13 @@ pub(crate) mod _signal { } } + /// Reset wakeup fd after fork in child process. + /// The child must not write to the parent's wakeup fd. + #[cfg(unix)] + pub(crate) fn clear_wakeup_fd_after_fork() { + WAKEUP.store(INVALID_WAKEUP, Ordering::Relaxed); + } + pub(crate) fn module_exec( vm: &VirtualMachine, module: &Py,