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