From 30d25ccb8bc91ca811773ee243e71e31772275d2 Mon Sep 17 00:00:00 2001 From: Alice Ryhl Date: Thu, 7 May 2026 09:31:12 +0200 Subject: [PATCH] sync: require that an `RwLock` has `max_readers != 0` (#8076) --- tokio/src/sync/rwlock.rs | 12 +++++++++++- tokio/tests/sync_rwlock.rs | 12 ++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tokio/src/sync/rwlock.rs b/tokio/src/sync/rwlock.rs index d94b65143..b6bf7faca 100644 --- a/tokio/src/sync/rwlock.rs +++ b/tokio/src/sync/rwlock.rs @@ -266,12 +266,13 @@ impl RwLock { /// /// # Panics /// - /// Panics if `max_reads` is more than `u32::MAX >> 3`. + /// Panics if `max_reads` is `0` or is bigger than `u32::MAX >> 3`. #[track_caller] pub fn with_max_readers(value: T, max_reads: u32) -> RwLock where T: Sized, { + assert_ne!(max_reads, 0, "a RwLock may not be created with 0 readers"); assert!( max_reads <= MAX_READS, "a RwLock may not be created with more than {MAX_READS} readers" @@ -367,11 +368,16 @@ impl RwLock { /// /// static LOCK: RwLock = RwLock::const_with_max_readers(5, 1024); /// ``` + /// + /// # Panics + /// + /// Panics if `max_reads` is `0` or is bigger than `u32::MAX >> 3`. #[cfg(not(all(loom, test)))] pub const fn const_with_max_readers(value: T, max_reads: u32) -> RwLock where T: Sized, { + assert!(max_reads != 0, "a RwLock may not be created with 0 readers"); assert!(max_reads <= MAX_READS); RwLock { @@ -771,6 +777,7 @@ impl RwLock { /// ``` pub async fn write(&self) -> RwLockWriteGuard<'_, T> { let acquire_fut = async { + debug_assert_ne!(self.mr, 0); self.s.acquire(self.mr as usize).await.unwrap_or_else(|_| { // The semaphore was closed. but, we never explicitly close it, and we have a // handle to it through the Arc, which means that this can never happen. @@ -906,6 +913,7 @@ impl RwLock { let resource_span = self.resource_span.clone(); let acquire_fut = async { + debug_assert_ne!(self.mr, 0); self.s.acquire(self.mr as usize).await.unwrap_or_else(|_| { // The semaphore was closed. but, we never explicitly close it, and we have a // handle to it through the Arc, which means that this can never happen. @@ -970,6 +978,7 @@ impl RwLock { /// } /// ``` pub fn try_write(&self) -> Result, TryLockError> { + debug_assert_ne!(self.mr, 0); match self.s.try_acquire(self.mr as usize) { Ok(permit) => permit, Err(TryAcquireError::NoPermits) => return Err(TryLockError(())), @@ -1028,6 +1037,7 @@ impl RwLock { /// } /// ``` pub fn try_write_owned(self: Arc) -> Result, TryLockError> { + debug_assert_ne!(self.mr, 0); match self.s.try_acquire(self.mr as usize) { Ok(permit) => permit, Err(TryAcquireError::NoPermits) => return Err(TryLockError(())), diff --git a/tokio/tests/sync_rwlock.rs b/tokio/tests/sync_rwlock.rs index 5a58b4971..2dc7b0a62 100644 --- a/tokio/tests/sync_rwlock.rs +++ b/tokio/tests/sync_rwlock.rs @@ -78,6 +78,18 @@ fn exhaust_reading() { let _g1 = assert_ready!(t1.poll()); } +#[test] +#[should_panic(expected = "a RwLock may not be created with 0 readers")] +fn zero_max_readers() { + RwLock::with_max_readers(100, 0); +} + +#[test] +#[should_panic(expected = "a RwLock may not be created with 0 readers")] +fn zero_max_readers_const() { + RwLock::const_with_max_readers(100, 0); +} + // When there is an active exclusive owner, subsequent exclusive access should not be possible #[test] fn write_exclusive_pending() {