Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Fix _at_fork_reinit to write INIT directly instead of calling unlock()
unlock() goes through unlock_slow() which accesses parking_lot's
global hash table to unpark waiters. After fork(), this hash table
contains stale entries from dead parent threads, making unlock_slow()
unsafe. Writing INIT directly bypasses parking_lot internals entirely.
  • Loading branch information
youknowone committed Mar 2, 2026
commit f00fcd2f13172e420a5997e113e3176efc47dbc4
20 changes: 6 additions & 14 deletions crates/vm/src/stdlib/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,9 @@ pub(crate) mod _thread {

#[pymethod]
fn _at_fork_reinit(&self, _vm: &VirtualMachine) -> PyResult<()> {
if self.mu.is_locked() {
unsafe {
self.mu.unlock();
};
}
// Casting to AtomicCell is as unsafe as CPython code.
// Using AtomicCell will prevent compiler optimizer move it to somewhere later unsafe place.
// It will be not under the cell anymore after init call.

// Reset the mutex to unlocked by directly writing the INIT value.
// Do NOT call unlock() here — after fork(), unlock_slow() would
// try to unpark stale waiters from dead parent threads.
let new_mut = RawMutex::INIT;
unsafe {
let old_mutex: &AtomicCell<RawMutex> = core::mem::transmute(&self.mu);
Expand Down Expand Up @@ -252,11 +246,9 @@ pub(crate) mod _thread {

#[pymethod]
fn _at_fork_reinit(&self, _vm: &VirtualMachine) -> PyResult<()> {
if self.mu.is_locked() {
unsafe {
self.mu.unlock();
};
}
// Reset the reentrant mutex to unlocked by directly writing INIT.
// Do NOT call unlock() — after fork(), the slow path would try
// to unpark stale waiters from dead parent threads.
self.count.store(0, core::sync::atomic::Ordering::Relaxed);
let new_mut = RawRMutex::INIT;
unsafe {
Expand Down