Skip to main content

bun_threading/
Mutex.rs

1//! This is a copy-pasta of std.Thread.Mutex with some changes.
2//! - No assert with unreachable
3//! - uses bun.Futex instead of std.Thread.Futex
4//! Synchronized with std as of Zig 0.14.1
5//!
6//! Mutex is a synchronization primitive which enforces atomic access to a shared region of code known as the "critical section".
7//! It does this by blocking ensuring only one thread is in the critical section at any given point in time by blocking the others.
8//! Mutex can be statically initialized and is at most `size_of::<u64>()` large.
9//! Use `lock()` or `try_lock()` to enter the critical section and `unlock()` to leave it.
10//!
11//! Example:
12//! ```ignore
13//! let m = Mutex::default();
14//!
15//! {
16//!     m.lock();
17//!     // ... critical section code
18//!     m.unlock();
19//! }
20//!
21//! if m.try_lock() {
22//!     // ... critical section code
23//!     m.unlock();
24//! }
25//! ```
26
27#[cfg(not(any(windows, target_vendor = "apple")))]
28use core::sync::atomic::AtomicU32;
29#[cfg(debug_assertions)]
30use core::sync::atomic::AtomicU64;
31#[cfg(any(debug_assertions, not(any(windows, target_vendor = "apple"))))]
32use core::sync::atomic::Ordering;
33
34#[cfg(not(any(windows, target_vendor = "apple")))]
35use crate::Futex;
36
37#[derive(Default)]
38pub struct Mutex {
39    // `pub(crate)` so `Condition` can reach `srwlock` / `locking_thread` for
40    // `SleepConditionVariableSRW` (mirrors Zig's same-module field access).
41    pub(crate) impl_: Impl,
42}
43
44impl Mutex {
45    /// Const-init an unlocked mutex (Zig: `.{}`). Required for `static` items.
46    pub const fn new() -> Self {
47        Self { impl_: Impl::new() }
48    }
49
50    /// Tries to acquire the mutex without blocking the caller's thread.
51    /// Returns `false` if the calling thread would have to block to acquire it.
52    /// Otherwise, returns `true` and the caller should `unlock()` the Mutex to release it.
53    pub fn try_lock(&self) -> bool {
54        self.impl_.try_lock()
55    }
56
57    /// Acquires the mutex, blocking the caller's thread until it can.
58    /// It is undefined behavior if the mutex is already held by the caller's thread.
59    /// Once acquired, call `unlock()` on the Mutex to release it.
60    pub fn lock(&self) {
61        self.impl_.lock()
62    }
63
64    /// Releases the mutex which was previously acquired with `lock()` or `try_lock()`.
65    /// It is undefined behavior if the mutex is unlocked from a different thread that it was locked from.
66    pub fn unlock(&self) {
67        // SAFETY: held by this thread (fn contract) and live for the whole call.
68        unsafe { Self::unlock_raw(self) }
69    }
70
71    /// [`unlock`](Self::unlock) for a release that lets another thread free the mutex
72    /// (`WaitGroup::finish_raw`): the releasing store is the last access to `*this`.
73    ///
74    /// # Safety
75    /// `this` must be held by this thread and stay live until the lock is released.
76    pub(crate) unsafe fn unlock_raw(this: *const Self) {
77        // SAFETY: the lock is still held, so `*this` is live (fn contract).
78        unsafe { Impl::unlock_raw(&raw const (*this).impl_) }
79    }
80
81    /// Debug-only check that the calling thread already holds this mutex.
82    /// Intended for `debug_assert!`-ing a "caller must hold the lock" contract
83    /// (e.g. `Watcher::flush_evictions`). In release builds the locking-thread
84    /// id is not tracked, so this just returns `true` to make the assert a
85    /// no-op there.
86    #[inline]
87    pub fn is_held_by_current_thread(&self) -> bool {
88        #[cfg(debug_assertions)]
89        {
90            self.impl_.locking_thread.load(Ordering::Relaxed) == current_thread_id()
91        }
92        #[cfg(not(debug_assertions))]
93        {
94            true
95        }
96    }
97
98    /// Acquires the mutex and returns an RAII guard that releases it on `Drop`.
99    ///
100    /// This is the idiomatic Rust spelling of Zig's `m.lock(); defer m.unlock();`
101    /// — prefer it over a bare [`lock`]/[`unlock`] pair so the critical section
102    /// is released on every return path (including `?`).
103    ///
104    /// The returned [`MutexGuard`] holds the mutex by raw pointer rather than a
105    /// borrowed `&'a Mutex`, so holding the guard does **not** keep a borrow of
106    /// the owning struct alive. This matches the Zig pattern where the mutex is
107    /// a plain field and the rest of `self` remains freely accessible while
108    /// locked. Caller must ensure the `Mutex` outlives the guard (trivially
109    /// true for `'static`/singleton mutexes and for guards that drop before the
110    /// owning `self` does).
111    #[inline]
112    #[must_use = "the mutex unlocks immediately if the guard is dropped"]
113    pub fn lock_guard(&self) -> MutexGuard {
114        self.lock();
115        MutexGuard {
116            mutex: bun_ptr::BackRef::new(self),
117            _not_send: core::marker::PhantomData,
118        }
119    }
120}
121
122/// RAII guard returned by [`Mutex::lock_guard`]. Unlocks on `Drop`.
123///
124/// Stores a [`BackRef<Mutex>`] (lifetime-erased `&Mutex`) so it does not hold
125/// a borrow of the mutex's owner — see [`Mutex::lock_guard`] for the rationale.
126/// The `BackRef` invariant (pointee outlives holder) is the caller contract on
127/// `lock_guard()`: the mutex outlives this guard (always true when the guard
128/// is a local that drops before the owning struct).
129pub struct MutexGuard {
130    mutex: bun_ptr::BackRef<Mutex>,
131    // Preserve the previous `!Send`/`!Sync` auto-trait surface (the field was
132    // `*const Mutex`): the Darwin `os_unfair_lock` / Windows `SRWLOCK` backends
133    // require unlock on the locking thread.
134    _not_send: core::marker::PhantomData<*const Mutex>,
135}
136
137impl Drop for MutexGuard {
138    #[inline]
139    fn drop(&mut self) {
140        self.mutex.unlock()
141    }
142}
143
144// Zig: `pub const deinit = void;` — no-op; Drop is implicit and there is nothing to free.
145
146// TODO(port): Zig also gates on `!builtin.single_threaded`; Rust has no direct equivalent.
147#[cfg(debug_assertions)]
148type Impl = DebugImpl;
149#[cfg(not(debug_assertions))]
150type Impl = ReleaseImpl;
151
152#[cfg(windows)]
153pub type ReleaseImpl = WindowsImpl;
154#[cfg(target_vendor = "apple")]
155pub type ReleaseImpl = DarwinImpl;
156#[cfg(not(any(windows, target_vendor = "apple")))]
157pub type ReleaseImpl = FutexImpl;
158
159#[cfg(windows)]
160#[allow(dead_code)]
161pub(crate) type ExternImpl = bun_sys::windows::SRWLOCK;
162#[cfg(not(any(windows, target_vendor = "apple")))]
163#[allow(dead_code)]
164pub(crate) type ExternImpl = u32;
165
166#[cfg(debug_assertions)]
167type ThreadId = u64;
168#[cfg(debug_assertions)]
169#[inline]
170fn current_thread_id() -> ThreadId {
171    crate::current_thread_id()
172}
173
174#[cfg(debug_assertions)]
175#[derive(Default)]
176pub(crate) struct DebugImpl {
177    /// 0 means it's not locked.
178    pub(crate) locking_thread: AtomicU64,
179    pub(crate) impl_: ReleaseImpl,
180}
181
182#[cfg(debug_assertions)]
183impl DebugImpl {
184    pub(crate) const fn new() -> Self {
185        Self {
186            locking_thread: AtomicU64::new(0),
187            impl_: ReleaseImpl::new(),
188        }
189    }
190
191    #[inline]
192    fn try_lock(&self) -> bool {
193        let locking = self.impl_.try_lock();
194        if locking {
195            // PORT NOTE: Zig uses .unordered; Rust's weakest is Relaxed.
196            self.locking_thread
197                .store(current_thread_id(), Ordering::Relaxed);
198        }
199        locking
200    }
201
202    #[inline]
203    fn lock(&self) {
204        let current_id = current_thread_id();
205        if self.locking_thread.load(Ordering::Relaxed) == current_id && current_id != 0 {
206            panic!("Deadlock detected");
207        }
208        self.impl_.lock();
209        self.locking_thread.store(current_id, Ordering::Relaxed);
210    }
211
212    /// See [`Mutex::unlock_raw`] for the contract.
213    #[inline]
214    unsafe fn unlock_raw(this: *const Self) {
215        // SAFETY: the lock is still held, so `*this` is live (fn contract).
216        unsafe {
217            debug_assert!((*this).locking_thread.load(Ordering::Relaxed) == current_thread_id());
218            (*this).locking_thread.store(0, Ordering::Relaxed);
219            ReleaseImpl::unlock_raw(&raw const (*this).impl_);
220        }
221    }
222}
223
224/// SRWLOCK on windows is almost always faster than Futex solution.
225/// It also implements an efficient Condition with requeue support for us.
226#[cfg(windows)]
227#[derive(Default)]
228pub struct WindowsImpl {
229    pub(crate) srwlock: core::cell::UnsafeCell<bun_sys::windows::SRWLOCK>,
230}
231
232#[cfg(windows)]
233unsafe impl Sync for WindowsImpl {}
234#[cfg(windows)]
235unsafe impl Send for WindowsImpl {}
236
237// `&UnsafeCell<SRWLOCK>` is ABI-identical to kernel32's `PSRWLOCK` (thin
238// non-null pointer to a `#[repr(C)]` word; `UnsafeCell` is
239// `#[repr(transparent)]`). The reference type encodes the only pointer-validity
240// precondition; acquire on an unowned lock blocks (recursive acquire deadlocks
241// — not UB), so `safe fn` discharges the link-time proof for the acquire pair.
242// `ReleaseSRWLockExclusive` keeps the raw-pointer `bun_sys` extern: MSDN
243// documents "results are undefined" when called without ownership (unlike
244// `os_unfair_lock_unlock`, which aborts), so that one retains its block.
245#[cfg(windows)]
246#[link(name = "kernel32")]
247unsafe extern "system" {
248    safe fn AcquireSRWLockExclusive(lock: &core::cell::UnsafeCell<bun_sys::windows::SRWLOCK>);
249    // Returns BOOLEAN (u8), not BOOL — compare against 0, not the i32 `FALSE`.
250    safe fn TryAcquireSRWLockExclusive(
251        lock: &core::cell::UnsafeCell<bun_sys::windows::SRWLOCK>,
252    ) -> u8;
253}
254
255#[cfg(windows)]
256impl WindowsImpl {
257    pub(crate) const fn new() -> Self {
258        Self {
259            srwlock: core::cell::UnsafeCell::new(bun_sys::windows::SRWLOCK_INIT),
260        }
261    }
262
263    fn try_lock(&self) -> bool {
264        TryAcquireSRWLockExclusive(&self.srwlock) != 0
265    }
266
267    fn lock(&self) {
268        AcquireSRWLockExclusive(&self.srwlock)
269    }
270
271    /// See [`Mutex::unlock_raw`] for the contract.
272    unsafe fn unlock_raw(this: *const Self) {
273        // SAFETY: held by this thread (fn contract), so `*this` is live until the release
274        // inside the call; releasing without ownership is documented UB on Windows.
275        unsafe {
276            let srwlock = core::cell::UnsafeCell::raw_get(&raw const (*this).srwlock);
277            bun_sys::windows::kernel32::ReleaseSRWLockExclusive(srwlock)
278        }
279    }
280}
281
282/// os_unfair_lock on darwin supports priority inheritance and is generally faster than Futex solutions.
283#[cfg(target_vendor = "apple")]
284#[derive(Default)]
285pub struct DarwinImpl {
286    oul: core::cell::UnsafeCell<OsUnfairLock>,
287}
288
289// SAFETY: `os_unfair_lock` is the kernel's cross-thread lock primitive; the
290// `UnsafeCell` only exists to hand the FFI a mutable pointer from `&self`.
291#[cfg(target_vendor = "apple")]
292unsafe impl Sync for DarwinImpl {}
293// SAFETY: see `Sync` above.
294#[cfg(target_vendor = "apple")]
295unsafe impl Send for DarwinImpl {}
296
297#[cfg(target_vendor = "apple")]
298#[repr(C)]
299#[derive(Default)]
300pub(crate) struct OsUnfairLock {
301    _opaque: u32,
302}
303
304// TODO(port): move to bun_sys (darwin libc externs)
305// `&UnsafeCell<OsUnfairLock>` is ABI-identical to `os_unfair_lock_t` (thin
306// non-null pointer to a `#[repr(C)]` u32; `UnsafeCell` is `#[repr(transparent)]`).
307// The type encodes the only pointer-validity precondition, and Apple's runtime
308// detects misuse (recursive lock / unowned unlock) by aborting — which is safe
309// — so `safe fn` discharges the link-time proof and callers need no `unsafe`.
310// `os_unfair_lock_unlock` takes the address instead: see `Mutex::unlock_raw`.
311#[cfg(target_vendor = "apple")]
312unsafe extern "C" {
313    safe fn os_unfair_lock_trylock(lock: &core::cell::UnsafeCell<OsUnfairLock>) -> bool;
314    safe fn os_unfair_lock_lock(lock: &core::cell::UnsafeCell<OsUnfairLock>);
315    fn os_unfair_lock_unlock(lock: *mut OsUnfairLock);
316}
317
318#[cfg(target_vendor = "apple")]
319impl DarwinImpl {
320    pub(crate) const fn new() -> Self {
321        Self {
322            oul: core::cell::UnsafeCell::new(OsUnfairLock { _opaque: 0 }),
323        }
324    }
325
326    fn try_lock(&self) -> bool {
327        os_unfair_lock_trylock(&self.oul)
328    }
329
330    fn lock(&self) {
331        os_unfair_lock_lock(&self.oul)
332    }
333
334    /// See [`Mutex::unlock_raw`] for the contract.
335    unsafe fn unlock_raw(this: *const Self) {
336        // SAFETY: held by this thread (fn contract), so `*this` is live until the release
337        // inside the call.
338        unsafe { os_unfair_lock_unlock(core::cell::UnsafeCell::raw_get(&raw const (*this).oul)) }
339    }
340}
341
342#[cfg(not(any(windows, target_vendor = "apple")))]
343#[derive(Default)]
344pub struct FutexImpl {
345    state: AtomicU32,
346}
347
348#[cfg(not(any(windows, target_vendor = "apple")))]
349impl FutexImpl {
350    pub(crate) const fn new() -> Self {
351        Self {
352            state: AtomicU32::new(0),
353        }
354    }
355
356    const UNLOCKED: u32 = 0b00;
357    const LOCKED: u32 = 0b01;
358    /// must contain the `LOCKED` bit for x86 optimization below
359    const CONTENDED: u32 = 0b11;
360
361    fn lock(&self) {
362        if !self.try_lock() {
363            self.lock_slow();
364        }
365    }
366
367    fn try_lock(&self) -> bool {
368        // On x86, use `lock bts` instead of `lock cmpxchg` as:
369        // - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048
370        // - `lock bts` is smaller instruction-wise which makes it better for inlining
371        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
372        {
373            let locked_bit: u32 = Self::LOCKED.trailing_zeros();
374            // PERF(port): Zig emits `lock bts` via atomic bitSet; fetch_or is the closest stable
375            // Rust atomic — profile if it shows up on a hot path and consider inline asm if needed.
376            return (self.state.fetch_or(1 << locked_bit, Ordering::Acquire) & (1 << locked_bit))
377                == 0;
378        }
379
380        // Acquire barrier ensures grabbing the lock happens before the critical section
381        // and that the previous lock holder's critical section happens before we grab the lock.
382        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
383        {
384            self.state
385                .compare_exchange_weak(
386                    Self::UNLOCKED,
387                    Self::LOCKED,
388                    Ordering::Acquire,
389                    Ordering::Relaxed,
390                )
391                .is_ok()
392        }
393    }
394
395    #[cold]
396    fn lock_slow(&self) {
397        // Avoid doing an atomic swap below if we already know the state is contended.
398        // An atomic swap unconditionally stores which marks the cache-line as modified unnecessarily.
399        if self.state.load(Ordering::Relaxed) == Self::CONTENDED {
400            Futex::wait_forever(&self.state, Self::CONTENDED);
401        }
402
403        // Try to acquire the lock while also telling the existing lock holder that there are threads waiting.
404        //
405        // Once we sleep on the Futex, we must acquire the mutex using `contended` rather than `locked`.
406        // If not, threads sleeping on the Futex wouldn't see the state change in unlock and potentially deadlock.
407        // The downside is that the last mutex unlocker will see `contended` and do an unnecessary Futex wake
408        // but this is better than having to wake all waiting threads on mutex unlock.
409        //
410        // Acquire barrier ensures grabbing the lock happens before the critical section
411        // and that the previous lock holder's critical section happens before we grab the lock.
412        while self.state.swap(Self::CONTENDED, Ordering::Acquire) != Self::UNLOCKED {
413            Futex::wait_forever(&self.state, Self::CONTENDED);
414        }
415    }
416
417    /// See [`Mutex::unlock_raw`] for the contract.
418    unsafe fn unlock_raw(this: *const Self) {
419        // Unlock the mutex and wake up a waiting thread if any.
420        //
421        // A waiting thread will acquire with `contended` instead of `locked`
422        // which ensures that it wakes up another thread on the next unlock().
423        //
424        // Release barrier ensures the critical section happens before we let go of the lock
425        // and that our critical section happens before the next lock holder grabs the lock.
426        //
427        // SAFETY: the lock is still held, so `*this` is live (fn contract).
428        let state_ptr = unsafe { &raw const (*this).state };
429        // SAFETY: as above; this swap is the release, and the last access to `*this`.
430        let state = unsafe { (*state_ptr).swap(Self::UNLOCKED, Ordering::Release) };
431        debug_assert!(state != Self::UNLOCKED);
432
433        if state == Self::CONTENDED {
434            Futex::wake_raw(state_ptr, 1);
435        }
436    }
437}
438
439// PORT NOTE: Zig had `pub const Type` inside each impl as an associated alias.
440// Inherent associated types are unstable in Rust; the per-platform alias is
441// already exposed as the module-level `ExternImpl` type above.
442
443// These have to be a size known to C.
444#[unsafe(no_mangle)]
445pub(crate) unsafe extern "C" fn Bun__lock(ptr: *mut ReleaseImpl) {
446    // SAFETY: C caller passes a valid, initialized ReleaseImpl pointer.
447    unsafe { (*ptr).lock() }
448}
449
450// These have to be a size known to C.
451#[unsafe(no_mangle)]
452pub(crate) unsafe extern "C" fn Bun__unlock(ptr: *mut ReleaseImpl) {
453    // SAFETY: C caller passes a valid, initialized ReleaseImpl pointer that this thread locked.
454    unsafe { ReleaseImpl::unlock_raw(ptr) }
455}
456
457#[unsafe(no_mangle)]
458pub(crate) static Bun__lock__size: usize = core::mem::size_of::<ReleaseImpl>();
459
460// ported from: src/threading/Mutex.zig