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