Skip to main content

rustpython_common/
lock.rs

1//! A module containing [`lock_api`]-based lock types that are or are not `Send + Sync`
2//! depending on whether the `threading` feature of this module is enabled.
3
4use lock_api::{
5    MappedMutexGuard, MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, MutexGuard, RwLock,
6    RwLockReadGuard, RwLockUpgradableReadGuard, RwLockWriteGuard,
7};
8
9cfg_select! {
10    feature = "threading" => {
11        pub use detaching::{BlockingWaitHook, set_blocking_wait_hook, set_world_stopped};
12        pub use parking_lot::{RawMutex, RawRwLock, RawThreadId};
13        pub use std::sync::OnceLock as OnceCell;
14        pub use core::cell::LazyCell;
15    }
16    _ => {
17        mod cell_lock;
18        pub use cell_lock::{RawCellMutex as RawMutex, RawCellRwLock as RawRwLock, SingleThreadId as RawThreadId};
19
20        pub use core::cell::{LazyCell, OnceCell};
21    }
22}
23
24// LazyLock: uses std::sync::LazyLock when std is available (even without
25// threading, because Rust test runner uses parallel threads).
26// Without std, uses a LazyCell wrapper (truly single-threaded only).
27cfg_select! {
28    any(feature = "threading", feature = "std") => {
29        pub use std::sync::LazyLock;
30    }
31    _ => {
32        pub struct LazyLock<T, F = fn() -> T>(core::cell::LazyCell<T, F>);
33        // SAFETY: This branch is only active when both "std" and "threading"
34        // features are absent — i.e., truly single-threaded no_std environments
35        // (e.g., embedded or bare-metal WASM). Without std, the Rust runtime
36        // cannot spawn threads, so Sync is trivially satisfied.
37        unsafe impl<T, F> Sync for LazyLock<T, F> {}
38
39        impl<T, F: FnOnce() -> T> LazyLock<T, F> {
40            pub const fn new(f: F) -> Self { Self(core::cell::LazyCell::new(f)) }
41            pub fn force(this: &Self) -> &T { core::cell::LazyCell::force(&this.0) }
42        }
43
44        impl<T, F: FnOnce() -> T> core::ops::Deref for LazyLock<T, F> {
45            type Target = T;
46            fn deref(&self) -> &T { &self.0 }
47        }
48    }
49}
50
51mod detaching;
52pub use detaching::RawDetachingRwLock;
53mod immutable_mutex;
54pub use immutable_mutex::*;
55mod thread_mutex;
56pub use thread_mutex::*;
57
58pub type PyMutex<T> = Mutex<RawMutex, T>;
59pub type PyMutexGuard<'a, T> = MutexGuard<'a, RawMutex, T>;
60pub type PyMappedMutexGuard<'a, T> = MappedMutexGuard<'a, RawMutex, T>;
61pub type PyImmutableMappedMutexGuard<'a, T> = ImmutableMappedMutexGuard<'a, RawMutex, T>;
62pub type PyThreadMutex<T> = ThreadMutex<RawMutex, RawThreadId, T>;
63pub type PyThreadMutexGuard<'a, T> = ThreadMutexGuard<'a, RawMutex, RawThreadId, T>;
64pub type PyMappedThreadMutexGuard<'a, T> = MappedThreadMutexGuard<'a, RawMutex, RawThreadId, T>;
65
66/// A `PyRwLock` for data a thread may hold locked across a blocking call.
67///
68/// Waiting for one of these leaves the interpreter first, so a thread blocked
69/// on it is a thread stop-the-world can park. That is only safe where a
70/// collection never takes the same lock — see [`RawDetachingRwLock`] — so this
71/// is opt-in per lock rather than what every `PyRwLock` does.
72pub type PyDetachingRwLock<T> = RwLock<RawDetachingRwLock, T>;
73pub type PyDetachingRwLockReadGuard<'a, T> = RwLockReadGuard<'a, RawDetachingRwLock, T>;
74pub type PyDetachingRwLockWriteGuard<'a, T> = RwLockWriteGuard<'a, RawDetachingRwLock, T>;
75pub type PyMappedDetachingRwLockReadGuard<'a, T> = MappedRwLockReadGuard<'a, RawDetachingRwLock, T>;
76pub type PyMappedDetachingRwLockWriteGuard<'a, T> =
77    MappedRwLockWriteGuard<'a, RawDetachingRwLock, T>;
78
79pub type PyRwLock<T> = RwLock<RawRwLock, T>;
80pub type PyRwLockUpgradableReadGuard<'a, T> = RwLockUpgradableReadGuard<'a, RawRwLock, T>;
81pub type PyRwLockReadGuard<'a, T> = RwLockReadGuard<'a, RawRwLock, T>;
82pub type PyMappedRwLockReadGuard<'a, T> = MappedRwLockReadGuard<'a, RawRwLock, T>;
83pub type PyRwLockWriteGuard<'a, T> = RwLockWriteGuard<'a, RawRwLock, T>;
84pub type PyMappedRwLockWriteGuard<'a, T> = MappedRwLockWriteGuard<'a, RawRwLock, T>;
85
86// can add fn const_{mutex,rw_lock}() if necessary, but we probably won't need to
87
88/// Reset a lock to its initial (unlocked) state by zeroing its bytes.
89///
90/// After `fork()`, any lock held by a now-dead thread would remain
91/// permanently locked. We zero the raw bytes (the unlocked state for all
92/// `parking_lot` raw lock types) instead of using the normal unlock path,
93/// which would interact with stale waiter queues.
94///
95/// # Safety
96///
97/// Must only be called from the single-threaded child process immediately
98/// after `fork()`, before any other thread is created.
99/// The type `T` must represent the unlocked state as all-zero bytes
100/// (true for `parking_lot::RawMutex`, `RawRwLock`, `RawReentrantMutex`, etc.).
101pub unsafe fn zero_reinit_after_fork<T>(lock: *const T) {
102    unsafe {
103        core::ptr::write_bytes(lock as *mut u8, 0, core::mem::size_of::<T>());
104    }
105}
106
107/// Reset a `PyMutex` after `fork()`. See [`zero_reinit_after_fork`].
108///
109/// # Safety
110///
111/// Must only be called from the single-threaded child process immediately
112/// after `fork()`, before any other thread is created.
113#[cfg(unix)]
114pub unsafe fn reinit_mutex_after_fork<T: ?Sized>(mutex: &PyMutex<T>) {
115    unsafe { zero_reinit_after_fork(mutex.raw()) }
116}
117
118/// Reset a `PyRwLock` after `fork()`. See [`zero_reinit_after_fork`].
119///
120/// # Safety
121///
122/// Must only be called from the single-threaded child process immediately
123/// after `fork()`, before any other thread is created.
124#[cfg(unix)]
125pub unsafe fn reinit_rwlock_after_fork<T: ?Sized>(rwlock: &PyRwLock<T>) {
126    unsafe { zero_reinit_after_fork(rwlock.raw()) }
127}
128
129/// Reset a `PyThreadMutex` to its initial (unlocked, unowned) state after `fork()`.
130///
131/// `PyThreadMutex` is used by buffered IO objects (`BufferedReader`,
132/// `BufferedWriter`, `TextIOWrapper`). If a dead parent thread held one of
133/// these locks during `fork()`, the child would deadlock on any IO operation.
134///
135/// # Safety
136///
137/// Must only be called from the single-threaded child process immediately
138/// after `fork()`, before any other thread is created.
139#[cfg(unix)]
140pub unsafe fn reinit_thread_mutex_after_fork<T: ?Sized>(mutex: &PyThreadMutex<T>) {
141    unsafe { mutex.raw().reinit_after_fork() }
142}