From 27d00ff73bc79a9f8b5e40b15b12ddaf18a761ae Mon Sep 17 00:00:00 2001 From: Weiteng Chen Date: Fri, 24 Apr 2026 17:14:06 -0700 Subject: [PATCH 1/3] fix --- litebox_platform_windows_userland/src/lib.rs | 4 +- litebox_shim_linux/src/syscalls/tests.rs | 69 ++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/litebox_platform_windows_userland/src/lib.rs b/litebox_platform_windows_userland/src/lib.rs index 396282544..8019f7909 100644 --- a/litebox_platform_windows_userland/src/lib.rs +++ b/litebox_platform_windows_userland/src/lib.rs @@ -1355,8 +1355,8 @@ impl litebox::platform::RawMutex for RawMutex { } // For windows, the OS kernel does not tell us how many threads were actually woken up, - // so we just return `n` - n as usize + // so we just return zero + 0 } fn block(&self, val: u32) -> Result<(), ImmediatelyWokenUp> { diff --git a/litebox_shim_linux/src/syscalls/tests.rs b/litebox_shim_linux/src/syscalls/tests.rs index 12781a244..c1a1ae7ab 100644 --- a/litebox_shim_linux/src/syscalls/tests.rs +++ b/litebox_shim_linux/src/syscalls/tests.rs @@ -622,3 +622,72 @@ fn test_unlinkat() { "Second directory should no longer exist after removal" ); } + +/// Regression test for a bug where readers can be permanently starved on +/// platforms where `wake_one` does not report whether it actually woke a thread +/// (e.g. Windows with `WakeByAddressSingle`). +#[test] +fn test_rwlock_readers_not_starved_after_writer_handoff() { + use core::sync::atomic::{AtomicBool, Ordering}; + + // Initialize the platform (reuses the global Once-based init). + let _task = init_platform(None); + + // We run the test many times to increase the probability of hitting the + // exact interleaving, since we rely on sleep-based synchronization. + for iteration in 0..200 { + let lock = alloc::sync::Arc::new(litebox::sync::RwLock::< + litebox_platform_multiplex::Platform, + u32, + >::new(0)); + let reader_done = alloc::sync::Arc::new(AtomicBool::new(false)); + let writer2_done = alloc::sync::Arc::new(AtomicBool::new(false)); + + // Step 1: W1 acquires the write lock on the main thread. + let mut w1_guard = lock.write(); + + // Step 2: Spawn a reader that will block (READERS_WAITING). + let lock_r = lock.clone(); + let rd = reader_done.clone(); + let reader_handle = std::thread::spawn(move || { + let r = lock_r.read(); + rd.store(true, Ordering::Release); + drop(r); + }); + + // Step 3: Spawn W2 that will block (WRITERS_WAITING + other_writers_waiting). + let lock_w2 = lock.clone(); + let wd = writer2_done.clone(); + let writer2_handle = std::thread::spawn(move || { + let mut w = lock_w2.write(); + *w += 1; + // Hold briefly so reader stays blocked during our unlock. + drop(w); + wd.store(true, Ordering::Release); + }); + + // Give both threads time to block and set their waiting bits. + std::thread::sleep(std::time::Duration::from_millis(10)); + + // Step 4: W1 unlocks. This triggers wake_writer_or_readers which + // should eventually lead to both W2 and R being served. + *w1_guard = 42; + drop(w1_guard); + + // Step 5: Wait for W2 to finish (it should acquire quickly). + writer2_handle.join().expect("writer2 panicked"); + + // Step 6: The reader must also complete. On the buggy path it + // deadlocks here because wake_writer_or_readers returned early + // without waking readers. + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); + while !reader_done.load(Ordering::Acquire) { + if std::time::Instant::now() > deadline { + panic!("iteration {iteration}: reader was never woken after writer handoff"); + } + std::thread::yield_now(); + } + + reader_handle.join().expect("reader panicked"); + } +} From 2365d4973630ea7a9c6a014b54209d0835586f2a Mon Sep 17 00:00:00 2001 From: weitengchen Date: Sat, 25 Apr 2026 00:18:20 +0000 Subject: [PATCH 2/3] fix fmt --- litebox_shim_linux/src/syscalls/tests.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/litebox_shim_linux/src/syscalls/tests.rs b/litebox_shim_linux/src/syscalls/tests.rs index c1a1ae7ab..b233ce080 100644 --- a/litebox_shim_linux/src/syscalls/tests.rs +++ b/litebox_shim_linux/src/syscalls/tests.rs @@ -682,9 +682,10 @@ fn test_rwlock_readers_not_starved_after_writer_handoff() { // without waking readers. let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); while !reader_done.load(Ordering::Acquire) { - if std::time::Instant::now() > deadline { - panic!("iteration {iteration}: reader was never woken after writer handoff"); - } + assert!( + std::time::Instant::now() <= deadline, + "iteration {iteration}: reader was never woken after writer handoff" + ); std::thread::yield_now(); } From 5dac8919430a2c931cfd10c963250bfa1f028bb6 Mon Sep 17 00:00:00 2001 From: weitengchen Date: Fri, 8 May 2026 18:37:12 +0000 Subject: [PATCH 3/3] update comments --- litebox/src/platform/mod.rs | 10 ++++++++-- litebox_platform_windows_userland/src/lib.rs | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/litebox/src/platform/mod.rs b/litebox/src/platform/mod.rs index 2a0b6a9df..7c682a0f0 100644 --- a/litebox/src/platform/mod.rs +++ b/litebox/src/platform/mod.rs @@ -298,18 +298,24 @@ pub trait RawMutex: Send + Sync + 'static { /// Wake up `n` threads blocked on on this raw mutex. /// /// Returns the number of waiters that were woken up. + /// Some platforms cannot observe this number and may return zero + /// even when one or more waiters were woken up, so callers must + /// not rely on zero meaning that no waiters were woken up. fn wake_many(&self, n: usize) -> usize; /// Wake up one thread blocked on this raw mutex. /// - /// Returns true if this actually woke up such a thread, or false if no thread was waiting on this raw mutex. + /// Returns true if this actually woke up such a thread. Returns false + /// if no thread was waiting on this raw mutex, or if the platform + /// cannot observe whether a thread was woken up. fn wake_one(&self) -> bool { self.wake_many(1) > 0 } /// Wake up all threads that are blocked on this raw mutex. /// - /// Returns the number of waiters that were woken up. + /// Returns the number of waiters that were woken up. This may be + /// zero on platforms that cannot observe this number. fn wake_all(&self) -> usize { self.wake_many(i32::MAX as usize) } diff --git a/litebox_platform_windows_userland/src/lib.rs b/litebox_platform_windows_userland/src/lib.rs index 8019f7909..82ebf0226 100644 --- a/litebox_platform_windows_userland/src/lib.rs +++ b/litebox_platform_windows_userland/src/lib.rs @@ -1355,7 +1355,7 @@ impl litebox::platform::RawMutex for RawMutex { } // For windows, the OS kernel does not tell us how many threads were actually woken up, - // so we just return zero + // so we return zero to indicate that the count is unknown. 0 }