Skip to content

Commit 9be0543

Browse files
committed
Add import lock (IMP_LOCK) reinit after fork
The import lock is a ReentrantMutex that was never reinit'd after fork(). If a parent thread held it during fork, the child would deadlock on any import. Only reset if the owner is a dead thread; if the surviving thread held it, normal unlock still works.
1 parent 9b6d377 commit 9be0543

File tree

3 files changed

+49
-0
lines changed

3 files changed

+49
-0
lines changed

crates/common/src/lock.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,13 @@ pub type PyRwLockWriteGuard<'a, T> = RwLockWriteGuard<'a, RawRwLock, T>;
5757
pub type PyMappedRwLockWriteGuard<'a, T> = MappedRwLockWriteGuard<'a, RawRwLock, T>;
5858

5959
// can add fn const_{mutex,rw_lock}() if necessary, but we probably won't need to
60+
61+
/// Return the current thread's parking_lot thread ID.
62+
///
63+
/// This is the same ID stored in the `owner` field of `RawReentrantMutex`
64+
/// when the current thread holds it.
65+
#[cfg(all(unix, feature = "threading"))]
66+
pub fn current_thread_id() -> core::num::NonZeroUsize {
67+
use lock_api::GetThreadId;
68+
RawThreadId.nonzero_thread_id()
69+
}

crates/vm/src/stdlib/imp.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,41 @@ mod lock {
3333
fn lock_held(_vm: &VirtualMachine) -> bool {
3434
IMP_LOCK.is_locked()
3535
}
36+
37+
/// Reset import lock after fork() — only if held by a dead thread.
38+
///
39+
/// `IMP_LOCK` is a reentrant mutex. If the *current* (surviving) thread
40+
/// held it at fork time, the child must be able to release it normally.
41+
/// Only reset if a now-dead thread was the owner.
42+
///
43+
/// # Safety
44+
///
45+
/// Must only be called from single-threaded child after fork().
46+
#[cfg(unix)]
47+
pub(crate) unsafe fn reinit_after_fork() {
48+
use core::sync::atomic::{AtomicUsize, Ordering};
49+
50+
unsafe {
51+
// RawReentrantMutex layout: owner: AtomicUsize at offset 0
52+
let owner_ptr = &IMP_LOCK as *const RawRMutex as *const AtomicUsize;
53+
let owner = (*owner_ptr).load(Ordering::Relaxed);
54+
55+
if owner != 0 {
56+
let current = rustpython_common::lock::current_thread_id().get();
57+
if owner != current {
58+
// Held by a dead thread — reset to unlocked
59+
let ptr = &IMP_LOCK as *const RawRMutex as *mut u8;
60+
core::ptr::write_bytes(ptr, 0, core::mem::size_of::<RawRMutex>());
61+
}
62+
}
63+
}
64+
}
65+
}
66+
67+
/// Re-export for fork safety code in posix.rs
68+
#[cfg(all(unix, feature = "threading"))]
69+
pub(crate) unsafe fn reinit_imp_lock_after_fork() {
70+
unsafe { lock::reinit_after_fork() }
3671
}
3772

3873
#[cfg(not(feature = "threading"))]

crates/vm/src/stdlib/posix.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,6 +737,10 @@ pub mod module {
737737
force_unlock_mutex_after_fork(&vm.state.global_trace_func);
738738
force_unlock_mutex_after_fork(&vm.state.global_profile_func);
739739
crate::gc_state::gc_state().force_unlock_after_fork();
740+
741+
// Import lock (ReentrantMutex) — was previously not reinit'd
742+
#[cfg(feature = "threading")]
743+
crate::stdlib::imp::reinit_imp_lock_after_fork();
740744
}
741745

742746
// Mark all other threads as done before running Python callbacks

0 commit comments

Comments
 (0)