diff --git a/CHANGELOG.md b/CHANGELOG.md index 0543a04..e5b2bc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. ## Unreleased +### Bug fixes + +* Release the retained read access and owned lock reference when a waiter wake callback panics during `RwLock` guard downgrade. + ## v0.7.2 ### Improvements diff --git a/LICENSE b/LICENSE index 942ddce..bbdc147 100644 --- a/LICENSE +++ b/LICENSE @@ -258,13 +258,22 @@ The exact Tokio 1.42.0 revision is: https://github.com/tokio-rs/tokio/tree/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync -Asyncband adapted RwLock to its own semaphore and guard model. Acquisition has -no semaphore-close error, nonblocking methods return Option, and max_readers is -any NonZeroUsize instead of Tokio's restricted range. Mapped read guards use -separate borrowed and owned types rather than changing a type parameter on the -original read guard. All mapped guards use filter_map naming, and mutable mapped -guards carry explicit invariance. The local guard types release and downgrade -permits through Asyncband's usize-based semaphore implementation. +Asyncband retains semaphore-based fair scheduling and substantially rewrote +RwLock's guard lifecycle. The private rwlock/access.rs implementation owns +acquired permits in movable RAII tokens. Guards project data by transferring +these tokens, with no manual destruction suppression or raw Arc extraction. +Downgrading establishes a read token before releasing the other permits, so +unwinding from a wake callback also releases the retained permit and ownership. +The public documentation and examples were rewritten around access, projection, +and owned lifetimes. The standard ASF source headers cover these substantial +modifications contributed to Apache Asyncband; the incorporated Tokio-derived +portions remain under the MIT License below. + +Acquisition has no semaphore-close error, nonblocking methods return Option, +and max_readers accepts every NonZeroUsize. Mapped read guards use separate +borrowed and owned types. All mapped guards use filter_map naming, and mutable +mapped guards preserve invariance. The public guard types and FIFO ordering +remain unchanged by the RAII rewrite. Portions of the following files originated from Tokio 1.47.0's OnceCell. Each local path is followed by its upstream source path: diff --git a/asyncband/src/rwlock/access.rs b/asyncband/src/rwlock/access.rs new file mode 100644 index 0000000..c39dac0 --- /dev/null +++ b/asyncband/src/rwlock/access.rs @@ -0,0 +1,120 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//! Owns acquired permits independently of the guard's data projection. +//! +//! Tokens are created only after acquisition succeeds. Their optional owner is present until an +//! ownership transfer; taking it disarms the old token without suppressing Rust's drop machinery. +//! All supported owners are references or Arcs, so Option adds no storage to them. + +use std::sync::Arc; + +use crate::internal::semaphore::Semaphore; +use crate::rwlock::RwLock; + +pub trait Owner { + fn semaphore(&self) -> &Semaphore; +} + +impl Owner for &Semaphore { + fn semaphore(&self) -> &Semaphore { + self + } +} + +impl Owner for &RwLock { + fn semaphore(&self) -> &Semaphore { + &self.s + } +} + +impl Owner for Arc> { + fn semaphore(&self) -> &Semaphore { + &self.s + } +} + +pub struct ReadAccess { + owner: Option, +} + +impl ReadAccess { + /// Takes responsibility for one already-acquired permit. + pub fn new(owner: O) -> Self { + Self { owner: Some(owner) } + } + + pub fn owner(&self) -> &O { + self.owner.as_ref().unwrap() + } +} + +impl<'a, T: ?Sized> ReadAccess<&'a RwLock> { + /// A borrowed projection no longer needs the original value's type. + pub fn into_semaphore(mut self) -> ReadAccess<&'a Semaphore> { + ReadAccess::new(&self.owner.take().unwrap().s) + } +} + +impl Drop for ReadAccess { + fn drop(&mut self) { + if let Some(owner) = &self.owner { + owner.semaphore().release(1); + } + } +} + +pub struct WriteAccess { + owner: Option, + permits: usize, +} + +impl WriteAccess { + /// Takes responsibility for all permits of a lock, which must already be acquired. + pub fn new(owner: O, permits: usize) -> Self { + Self { + owner: Some(owner), + permits, + } + } + + pub fn owner(&self) -> &O { + self.owner.as_ref().unwrap() + } + + pub fn downgrade(mut self) -> ReadAccess { + let read = ReadAccess::new(self.owner.take().unwrap()); + // Keep the retained permit and owner in a live token before release can invoke wakers. + // If waking panics, unwinding drops this token instead of leaking a permit or an Arc. + read.owner().semaphore().release(self.permits - 1); + read + } +} + +impl<'a, T: ?Sized> WriteAccess<&'a RwLock> { + pub fn into_semaphore(mut self) -> WriteAccess<&'a Semaphore> { + WriteAccess::new(&self.owner.take().unwrap().s, self.permits) + } +} + +impl Drop for WriteAccess { + fn drop(&mut self) { + if let Some(owner) = &self.owner { + owner.semaphore().release(self.permits); + } + } +} diff --git a/asyncband/src/rwlock/mapped_read_guard.rs b/asyncband/src/rwlock/mapped_read_guard.rs index 1b06bd8..3fff47c 100644 --- a/asyncband/src/rwlock/mapped_read_guard.rs +++ b/asyncband/src/rwlock/mapped_read_guard.rs @@ -1,235 +1,109 @@ -// This file contains code derived from Tokio 1.42.0's RwLock implementation. +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Portions of the guard API originated from Tokio 1.42.0's RwLock implementation. // Copyright (c) Tokio Contributors -// The derived code remains licensed under the MIT License. -// The incorporated code has been modified for use in Apache Asyncband. +// The Tokio-derived portions remain licensed under the MIT License. +// Asyncband replaced guard-local destruction and manual ownership transfers with movable RAII +// access tokens. Projection moves the token, and downgrade establishes a read token before waking +// waiters. The public documentation and examples describe Asyncband's access and projection model. // Upstream sources: // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/read_guard.rs // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/write_guard_mapped.rs use std::fmt; -use std::marker::PhantomData; use std::ops::Deref; use std::ptr::NonNull; -use crate::internal::semaphore; +use crate::internal::semaphore::Semaphore; +use crate::rwlock::access::ReadAccess; -/// A borrowed read guard projected to one component of the protected value. -/// -/// [`RwLockReadGuard::map`](crate::rwlock::RwLockReadGuard::map) and -/// [`RwLockReadGuard::filter_map`](crate::rwlock::RwLockReadGuard::filter_map) create this guard. -/// It keeps the original read access active while exposing only the projected component. -/// -/// # Examples -/// -/// ``` -/// # #[tokio::main] -/// # async fn main() { -/// use asyncband::rwlock::RwLock; -/// use asyncband::rwlock::RwLockReadGuard; -/// -/// #[derive(Debug)] -/// struct User { -/// id: u32, -/// profile: UserProfile, -/// } -/// -/// #[derive(Debug)] -/// struct UserProfile { -/// email: String, -/// name: String, -/// } -/// -/// let user = User { -/// id: 1, -/// profile: UserProfile { -/// email: "user@example.com".to_owned(), -/// name: "Alice".to_owned(), -/// }, -/// }; -/// -/// let rwlock = RwLock::new(user); -/// let guard = rwlock.read().await; -/// let profile_guard = RwLockReadGuard::map(guard, |user| &user.profile); +/// Shared access to a projection of a locked value borrowed for the guard lifetime. /// -/// // Now we can only access the user's profile -/// assert_eq!(profile_guard.email, "user@example.com"); -/// # } -/// ``` +/// Use [`RwLockReadGuard::map`](crate::rwlock::RwLockReadGuard::map) to select a component. +/// Dropping the guard releases its access. #[must_use = "dropping the guard releases its read access immediately"] pub struct MappedRwLockReadGuard<'a, T: ?Sized> { - d: NonNull, - s: &'a semaphore::Semaphore, - variance: PhantomData T>, + data: NonNull, + access: ReadAccess<&'a Semaphore>, } -// SAFETY: MappedRwLockReadGuard is Send when T: Sync. We don't require T: Send because -// the guard RwLockReadGuard doesn't transfer ownership of T - it only holds a shared reference. -// When moved to another thread, the guard maintains the read lock and the new thread -// can safely access &T (which is allowed since T: Sync). The semaphore reference -// and NonNull pointer are both safe to transfer between threads. -unsafe impl Send for MappedRwLockReadGuard<'_, T> {} - -// SAFETY: `&MappedRwLockReadGuard` can be shared between threads if `T: Sync`. -// Accessing the guard only provides a `&T`, which is safe to share concurrently when `T: Sync`. -unsafe impl Sync for MappedRwLockReadGuard<'_, T> {} - -impl<'a, T: ?Sized> MappedRwLockReadGuard<'a, T> { - pub(crate) fn new(d: NonNull, s: &'a semaphore::Semaphore) -> Self { - Self { - d, - s, - variance: PhantomData, - } - } +pub fn new<'a, T: ?Sized>( + data: NonNull, + access: ReadAccess<&'a Semaphore>, +) -> MappedRwLockReadGuard<'a, T> { + MappedRwLockReadGuard { data, access } } -impl Drop for MappedRwLockReadGuard<'_, T> { - fn drop(&mut self) { - self.s.release(1); - } -} - -impl fmt::Debug for MappedRwLockReadGuard<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl fmt::Display for MappedRwLockReadGuard<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} +// SAFETY: Moving this guard transfers shared access without moving or dropping T. +unsafe impl Send for MappedRwLockReadGuard<'_, T> {} +// SAFETY: Sharing the guard only exposes &T; the access token keeps the lock held. +unsafe impl Sync for MappedRwLockReadGuard<'_, T> {} impl Deref for MappedRwLockReadGuard<'_, T> { type Target = T; - fn deref(&self) -> &Self::Target { - // SAFETY: we hold the read lock and the NonNull pointer is valid for the guard's lifetime - unsafe { self.d.as_ref() } + + fn deref(&self) -> &T { + // SAFETY: The access token holds shared access and keeps the projection valid. + unsafe { self.data.as_ref() } } } impl<'a, T: ?Sized> MappedRwLockReadGuard<'a, T> { - /// Projects this guard to a deeper shared component. - /// - /// The returned guard keeps the same read access active. Call this as - /// `MappedRwLockReadGuard::map(...)` so a method named `map` on `T` remains accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use asyncband::rwlock::MappedRwLockReadGuard; - /// use asyncband::rwlock::RwLock; - /// use asyncband::rwlock::RwLockReadGuard; - /// - /// #[derive(Debug)] - /// struct User { - /// id: u32, - /// profile: UserProfile, - /// } - /// - /// #[derive(Debug)] - /// struct UserProfile { - /// email: String, - /// name: String, - /// } - /// - /// let user = User { - /// id: 1, - /// profile: UserProfile { - /// email: "user@example.com".to_owned(), - /// name: "Alice".to_owned(), - /// }, - /// }; + /// Selects a component while retaining the same lock access. /// - /// let rwlock = RwLock::new(user); - /// let guard = rwlock.read().await; - /// // First map to the profile field - /// let profile_guard = RwLockReadGuard::map(guard, |user| &user.profile); - /// // Then map to the email field specifically - /// let email_guard = MappedRwLockReadGuard::map(profile_guard, |profile| &profile.email); - /// - /// assert_eq!(&*email_guard, "user@example.com"); - /// # } - /// ``` - pub fn map(orig: Self, f: F) -> MappedRwLockReadGuard<'a, U> + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `MappedRwLockReadGuard::map(guard, project)` to avoid shadowing methods of the + /// value. + pub fn map(orig: Self, project: F) -> MappedRwLockReadGuard<'a, U> where F: FnOnce(&T) -> &U, - U: ?Sized, { - // SAFETY: orig.d is a valid NonNull pointer that was created from a valid reference - // when the original MappedRwLockReadGuard was constructed. The guard guarantees shared - // access to the data through the rwlock, so dereferencing is safe. - let d = NonNull::from(f(unsafe { orig.d.as_ref() })); - let orig = std::mem::ManuallyDrop::new(orig); - MappedRwLockReadGuard::new(d, orig.s) + let data = NonNull::from(project(&*orig)); + new(data, orig.access) } - /// Attempts to project this guard to a deeper shared component. - /// - /// The original guard is returned when `f` returns `None`. Call this as - /// `MappedRwLockReadGuard::filter_map(...)` so a method with the same name on `T` remains - /// accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use asyncband::rwlock::MappedRwLockReadGuard; - /// use asyncband::rwlock::RwLock; - /// use asyncband::rwlock::RwLockReadGuard; - /// - /// #[derive(Debug)] - /// struct Person { - /// name: String, - /// email: Option, - /// } - /// - /// let person = Person { - /// name: "Alice".to_owned(), - /// email: Some("alice@example.com".to_owned()), - /// }; - /// - /// let rwlock = RwLock::new(person); - /// let guard = rwlock.read().await; - /// let name_guard = RwLockReadGuard::map(guard, |person| &person.name); - /// - /// // Try to map to the email if it exists - /// let person_guard = rwlock.read().await; - /// let email_result = MappedRwLockReadGuard::filter_map( - /// RwLockReadGuard::map(person_guard, |person| &person.email), - /// |email_opt| email_opt.as_ref(), - /// ); - /// - /// match email_result { - /// Ok(email_guard) => { - /// assert_eq!(&*email_guard, "alice@example.com"); - /// } - /// Err(_original_guard) => { - /// // Email was None, original guard is returned - /// println!("No email available"); - /// } - /// } - /// # } - /// ``` - pub fn filter_map(orig: Self, f: F) -> Result, Self> + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. + /// + /// A panic in the closure releases the guard. Call this as + /// `MappedRwLockReadGuard::filter_map(guard, project)`. + pub fn filter_map( + orig: Self, + project: F, + ) -> Result, Self> where F: FnOnce(&T) -> Option<&U>, - U: ?Sized, { - // SAFETY: orig.d is a valid NonNull pointer that was created from a valid reference - // when the original MappedRwLockReadGuard was constructed. The guard guarantees shared - // access to the data through the rwlock, so dereferencing is safe. - match f(unsafe { orig.d.as_ref() }) { - Some(d) => { - let d = NonNull::from(d); - let orig = std::mem::ManuallyDrop::new(orig); - Ok(MappedRwLockReadGuard::new(d, orig.s)) - } - None => Err(orig), - } + let Some(data) = project(&*orig).map(NonNull::from) else { + return Err(orig); + }; + Ok(new(data, orig.access)) + } +} + +impl fmt::Debug for MappedRwLockReadGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Display for MappedRwLockReadGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) } } diff --git a/asyncband/src/rwlock/mapped_write_guard.rs b/asyncband/src/rwlock/mapped_write_guard.rs index 4307842..a7e9dc1 100644 --- a/asyncband/src/rwlock/mapped_write_guard.rs +++ b/asyncband/src/rwlock/mapped_write_guard.rs @@ -1,315 +1,132 @@ -// This file contains code derived from Tokio 1.42.0's RwLock implementation. +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Portions of the guard API originated from Tokio 1.42.0's RwLock implementation. // Copyright (c) Tokio Contributors -// The derived code remains licensed under the MIT License. -// The incorporated code has been modified for use in Apache Asyncband. +// The Tokio-derived portions remain licensed under the MIT License. +// Asyncband replaced guard-local destruction and manual ownership transfers with movable RAII +// access tokens. Projection moves the token, and downgrade establishes a read token before waking +// waiters. The public documentation and examples describe Asyncband's access and projection model. // Upstream source: // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/write_guard_mapped.rs use std::fmt; use std::marker::PhantomData; -use std::mem::ManuallyDrop; use std::ops::Deref; use std::ops::DerefMut; use std::ptr::NonNull; -use crate::internal::semaphore; +use crate::internal::semaphore::Semaphore; use crate::rwlock::MappedRwLockReadGuard; +use crate::rwlock::access::WriteAccess; +use crate::rwlock::mapped_read_guard; -/// A borrowed write guard projected to one component of the protected value. +/// Exclusive access to a projection of a locked value borrowed for the guard lifetime. /// -/// [`RwLockWriteGuard::map`](crate::rwlock::RwLockWriteGuard::map) and -/// [`RwLockWriteGuard::filter_map`](crate::rwlock::RwLockWriteGuard::filter_map) create this guard. -/// It keeps the original write access active while exposing only the projected component. -/// -/// # Examples -/// -/// ``` -/// # #[tokio::main] -/// # async fn main() { -/// use asyncband::rwlock::RwLock; -/// use asyncband::rwlock::RwLockWriteGuard; -/// -/// #[derive(Debug)] -/// struct User { -/// id: u32, -/// profile: UserProfile, -/// } -/// -/// #[derive(Debug)] -/// struct UserProfile { -/// email: String, -/// name: String, -/// } -/// -/// let user = User { -/// id: 1, -/// profile: UserProfile { -/// email: "user@example.com".to_owned(), -/// name: "Alice".to_owned(), -/// }, -/// }; -/// -/// let rwlock = RwLock::new(user); -/// let mut guard = rwlock.write().await; -/// let mut profile_guard = RwLockWriteGuard::map(guard, |user| &mut user.profile); -/// -/// // Now we can only access and modify the user's profile -/// profile_guard.email = "newemail@example.com".to_owned(); -/// assert_eq!(profile_guard.email, "newemail@example.com"); -/// # } -/// ``` +/// Use [`RwLockWriteGuard::map`](crate::rwlock::RwLockWriteGuard::map) to select a component. +/// Dropping the guard releases its access. #[must_use = "dropping the guard releases its write access immediately"] pub struct MappedRwLockWriteGuard<'a, T: ?Sized> { - d: NonNull, - s: &'a semaphore::Semaphore, - permits_acquired: usize, - // Mutable access requires invariance over T. + data: NonNull, + access: WriteAccess<&'a Semaphore>, variance: PhantomData<&'a mut T>, } -// SAFETY: A `&MappedRwLockWriteGuard` can be safely shared between threads because it provides -// exclusive access to the data, and the `T: Send + Sync` bound prevents data races. -unsafe impl Sync for MappedRwLockWriteGuard<'_, T> {} - -// SAFETY: `MappedRwLockWriteGuard` owns the lock and can be safely sent to another thread. -// The `T: Send` bound ensures that the data can be safely accessed by the new thread, -// and the guard's lifetime guarantees that the data remains valid. -unsafe impl Send for MappedRwLockWriteGuard<'_, T> {} - -impl<'a, T: ?Sized> MappedRwLockWriteGuard<'a, T> { - pub(crate) fn new(d: NonNull, s: &'a semaphore::Semaphore, permits_acquired: usize) -> Self { - Self { - d, - s, - permits_acquired, - variance: PhantomData, - } - } -} - -impl Drop for MappedRwLockWriteGuard<'_, T> { - fn drop(&mut self) { - self.s.release(self.permits_acquired); - } -} - -impl fmt::Debug for MappedRwLockWriteGuard<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) +pub fn new<'a, T: ?Sized>( + data: NonNull, + access: WriteAccess<&'a Semaphore>, +) -> MappedRwLockWriteGuard<'a, T> { + MappedRwLockWriteGuard { + data, + access, + variance: PhantomData, } } -impl fmt::Display for MappedRwLockWriteGuard<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} +// SAFETY: Moving this guard transfers exclusive access without moving or dropping T. +unsafe impl Send for MappedRwLockWriteGuard<'_, T> {} +// SAFETY: Sharing the guard only exposes &T; the access token keeps the lock held. +unsafe impl Sync for MappedRwLockWriteGuard<'_, T> {} impl Deref for MappedRwLockWriteGuard<'_, T> { type Target = T; - fn deref(&self) -> &Self::Target { - // SAFETY: we hold the write lock and the NonNull pointer is valid for the guard's lifetime - unsafe { self.d.as_ref() } + + fn deref(&self) -> &T { + // SAFETY: The access token holds exclusive access and keeps the projection valid. + unsafe { self.data.as_ref() } } } impl DerefMut for MappedRwLockWriteGuard<'_, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - // SAFETY: we hold the write lock and the NonNull pointer is valid for the guard's lifetime - unsafe { self.d.as_mut() } + fn deref_mut(&mut self) -> &mut T { + // SAFETY: The write token excludes all other access, and self is exclusively borrowed. + unsafe { self.data.as_mut() } } } impl<'a, T: ?Sized> MappedRwLockWriteGuard<'a, T> { - /// Projects this guard to a deeper mutable component. - /// - /// The returned guard keeps the same write access active. Call this as - /// `MappedRwLockWriteGuard::map(...)` so a method named `map` on `T` remains accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use asyncband::rwlock::MappedRwLockWriteGuard; - /// use asyncband::rwlock::RwLock; - /// use asyncband::rwlock::RwLockWriteGuard; + /// Selects a component while retaining the same lock access. /// - /// #[derive(Debug)] - /// struct User { - /// id: u32, - /// profile: UserProfile, - /// } - /// - /// #[derive(Debug)] - /// struct UserProfile { - /// email: String, - /// name: String, - /// } - /// - /// let user = User { - /// id: 1, - /// profile: UserProfile { - /// email: "user@example.com".to_owned(), - /// name: "Alice".to_owned(), - /// }, - /// }; - /// - /// let rwlock = RwLock::new(user); - /// let mut guard = rwlock.write().await; - /// // First map to the profile field - /// let mut profile_guard = RwLockWriteGuard::map(guard, |user| &mut user.profile); - /// // Then map to the email field specifically - /// let mut email_guard = MappedRwLockWriteGuard::map(profile_guard, |profile| &mut profile.email); - /// - /// *email_guard = "newemail@example.com".to_owned(); - /// assert_eq!(&*email_guard, "newemail@example.com"); - /// # } - /// ``` - pub fn map(mut orig: Self, f: F) -> MappedRwLockWriteGuard<'a, U> + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `MappedRwLockWriteGuard::map(guard, project)` to avoid shadowing methods of the + /// value. + pub fn map(mut orig: Self, project: F) -> MappedRwLockWriteGuard<'a, U> where F: FnOnce(&mut T) -> &mut U, - U: ?Sized, { - // SAFETY: orig.d is a valid NonNull pointer that was created from a valid reference - // when the original MappedRwLockWriteGuard was constructed. The guard guarantees exclusive - // access to the data through the rwlock, so dereferencing is safe. - let d = NonNull::from(f(unsafe { orig.d.as_mut() })); - let permits_acquired = orig.permits_acquired; - let orig = ManuallyDrop::new(orig); - MappedRwLockWriteGuard::new(d, orig.s, permits_acquired) + let data = NonNull::from(project(&mut *orig)); + new(data, orig.access) } - /// Attempts to project this guard to a deeper mutable component. - /// - /// The original guard is returned when `f` returns `None`. Call this as - /// `MappedRwLockWriteGuard::filter_map(...)` so a method with the same name on `T` remains - /// accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use asyncband::rwlock::MappedRwLockWriteGuard; - /// use asyncband::rwlock::RwLock; - /// use asyncband::rwlock::RwLockWriteGuard; - /// - /// #[derive(Debug)] - /// struct Document { - /// title: String, - /// content: String, - /// metadata: Option, - /// } + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// #[derive(Debug)] - /// struct Metadata { - /// author: String, - /// version: Option, - /// } - /// - /// let doc = Document { - /// title: "My Document".to_owned(), - /// content: "Initial content".to_owned(), - /// metadata: Some(Metadata { - /// author: "Alice".to_owned(), - /// version: Some(1), - /// }), - /// }; - /// - /// let rwlock = RwLock::new(doc); - /// let mut guard = rwlock.write().await; - /// - /// // First map to the metadata field - /// let meta_guard = RwLockWriteGuard::map(guard, |doc| &mut doc.metadata); - /// - /// // Try to map to the version number if metadata and version both exist - /// let version_result = MappedRwLockWriteGuard::filter_map(meta_guard, |meta_opt| { - /// meta_opt.as_mut()?.version.as_mut() - /// }); - /// match version_result { - /// Ok(mut version_guard) => { - /// *version_guard += 1; // Increment version - /// assert_eq!(*version_guard, 2); - /// } - /// Err(_) => { - /// // Handle case where metadata or version doesn't exist - /// println!("No version to update"); - /// } - /// } - /// # } - /// ``` - pub fn filter_map(mut orig: Self, f: F) -> Result, Self> + /// A panic in the closure releases the guard. Call this as + /// `MappedRwLockWriteGuard::filter_map(guard, project)`. + pub fn filter_map( + mut orig: Self, + project: F, + ) -> Result, Self> where F: FnOnce(&mut T) -> Option<&mut U>, - U: ?Sized, { - // SAFETY: orig.d is a valid NonNull pointer that was created from a valid reference - // when the original MappedRwLockWriteGuard was constructed. The guard guarantees exclusive - // access to the data through the rwlock, so dereferencing is safe. - match f(unsafe { orig.d.as_mut() }) { - Some(d) => { - let d = NonNull::from(d); - let permits_acquired = orig.permits_acquired; - let orig = ManuallyDrop::new(orig); - Ok(MappedRwLockWriteGuard::new(d, orig.s, permits_acquired)) - } - None => Err(orig), - } + let Some(data) = project(&mut *orig).map(NonNull::from) else { + return Err(orig); + }; + Ok(new(data, orig.access)) } - /// Atomically downgrades the write lock to a read lock while preserving the mapping. - /// - /// This method changes the lock from exclusive mode to shared mode atomically, - /// preventing other writers from acquiring the lock in between. - /// - /// The returned `MappedRwLockReadGuard` preserves the original mapping to the specific - /// component of the data. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; + /// Retains shared access to the same projection while releasing exclusive access. /// - /// use asyncband::rwlock::RwLock; - /// use asyncband::rwlock::RwLockWriteGuard; - /// - /// #[derive(Debug)] - /// struct Counter { - /// value: i32, - /// name: String, - /// } - /// - /// let lock = Arc::new(RwLock::new(Counter { - /// value: 0, - /// name: "counter".to_owned(), - /// })); - /// - /// let write_guard = lock.write().await; - /// let mut value_write_guard = RwLockWriteGuard::map(write_guard, |counter| &mut counter.value); - /// *value_write_guard = 42; - /// - /// let value_read_guard = value_write_guard.downgrade(); - /// assert_eq!(*value_read_guard, 42); - /// - /// assert!(lock.try_write().is_none()); - /// - /// drop(value_read_guard); - /// assert!(lock.try_write().is_some()); - /// # } - /// ``` + /// There is no unlocked interval in which another writer can modify the value. Queued + /// requests retain their order, so a waiting writer can prevent later readers from joining. pub fn downgrade(self) -> MappedRwLockReadGuard<'a, T> { - // Prevent the original write guard from running its Drop implementation, - // which would release all permits. This must be done BEFORE any operation - // that might panic to ensure panic safety. - let guard = ManuallyDrop::new(self); + mapped_read_guard::new(self.data, self.access.downgrade()) + } +} - // Release max_readers - 1 permits to convert the write lock to a read lock. - guard.s.release(guard.permits_acquired - 1); +impl fmt::Debug for MappedRwLockWriteGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} - // Create the mapped read guard with 1 permit (standard for read locks) - MappedRwLockReadGuard::new(guard.d, guard.s) +impl fmt::Display for MappedRwLockWriteGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) } } diff --git a/asyncband/src/rwlock/mod.rs b/asyncband/src/rwlock/mod.rs index 346e38b..2fa1dc3 100644 --- a/asyncband/src/rwlock/mod.rs +++ b/asyncband/src/rwlock/mod.rs @@ -1,55 +1,116 @@ -// This file contains code derived from Tokio 1.42.0. +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Portions of the RwLock API originated from Tokio 1.42.0. // Copyright (c) Tokio Contributors -// The derived code remains licensed under the MIT License. -// The incorporated code has been modified for use in Apache Asyncband. +// The Tokio-derived portions remain licensed under the MIT License. +// Asyncband retains semaphore-based fair scheduling and has substantially rewritten the guard +// lifecycle around RAII access tokens, separating permit ownership from data projection. Borrowed +// and owned guards move tokens on projection and downgrade without manual destruction suppression. // Upstream source: // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock.rs -//! Shared read access or exclusive write access to a value. -//! -//! Any number of readers may hold the lock together. A writer waits for existing readers and then -//! holds the lock alone, allowing it to modify the protected value. +//! Shared and exclusive access to a value, with asynchronous waiting. //! -//! Requests are considered in arrival order. Once a writer is waiting ahead of a reader, that -//! reader waits until the writer has acquired and released the lock. This prevents a steady stream -//! of readers from starving writers. +//! A read guard allows inspection alongside other readers, up to the configured reader limit. +//! A write guard allows mutation and excludes every other guard. Both release their access on +//! drop, including during unwinding; a panic does not poison the lock. //! -//! Read guards dereference to `&T`; write guards dereference to `&mut T`. Dropping a guard releases -//! its access. The mapping APIs can narrow a guard to one component without unlocking in between. +//! Waiting requests are served in queue order. A queued writer blocks readers behind it, even +//! while earlier readers still hold the lock. Consequently, keeping a read guard while waiting +//! for a write guard, or for another read behind a queued writer, can deadlock. The `try_` methods +//! never wait or reserve a queue position. Dropping a pending acquisition cancels its request; +//! a subsequent acquisition starts again at the back of the queue. //! -//! # Examples +//! # Updating and inspecting //! //! ``` +//! use asyncband::rwlock::RwLock; +//! //! # #[tokio::main] //! # async fn main() { +//! let routes = RwLock::new(vec!["/health"]); +//! let mut edit = routes.write().await; +//! edit.push("/metrics"); +//! +//! // Downgrading retains access to the just-published value without an unlocked interval. +//! let snapshot = edit.downgrade(); +//! let other_reader = routes.read().await; +//! assert_eq!(*snapshot, *other_reader); +//! assert!(routes.try_write().is_none()); +//! # } +//! ``` +//! +//! # Selecting a component +//! +//! A mapped guard keeps the original lock held but exposes only the selected component. Mapping +//! can be repeated, and `filter_map` returns the original guard when the component is absent. +//! A projection closure that panics releases its guard during unwinding. +//! +//! ``` +//! use asyncband::rwlock::MappedRwLockWriteGuard; //! use asyncband::rwlock::RwLock; +//! use asyncband::rwlock::RwLockWriteGuard; //! -//! let lock = RwLock::new(5); +//! # #[tokio::main] +//! # async fn main() { +//! let queue = RwLock::new(vec![Some(String::from("pending"))]); +//! let slot = RwLockWriteGuard::map(queue.write().await, |items| &mut items[0]); +//! let mut message = MappedRwLockWriteGuard::filter_map(slot, Option::as_mut).unwrap(); +//! message.push_str(" review"); +//! let message = message.downgrade(); +//! assert_eq!(&*message, "pending review"); +//! # } +//! ``` +//! +//! # Keeping the lock alive //! -//! // many reader locks can be held at once -//! { -//! let r1 = lock.read().await; -//! let r2 = lock.read().await; -//! assert_eq!(*r1, 5); -//! assert_eq!(*r2, 5); -//! } // read locks are dropped at this point +//! Owned guards retain the `Arc` passed to acquisition, allowing the guard to outlive that call's +//! local scope. Projecting or downgrading an owned guard retains the same ownership. Values with +//! borrowed data still obey their original lifetime constraints. //! -//! // only one write lock may be held, however -//! { -//! let mut w = lock.write().await; -//! *w += 1; -//! assert_eq!(*w, 6); -//! } // write lock is dropped here +//! ``` +//! use std::sync::Arc; //! +//! use asyncband::rwlock::OwnedRwLockReadGuard; +//! use asyncband::rwlock::RwLock; +//! +//! # #[tokio::main] +//! # async fn main() { +//! let catalog = Arc::new(RwLock::new(vec![String::from("index")])); +//! let entry = OwnedRwLockReadGuard::map(catalog.read_owned().await, |items| &items[0]); +//! tokio::spawn(async move { +//! assert_eq!(&*entry, "index"); +//! }) +//! .await +//! .unwrap(); //! # } //! ``` use std::cell::UnsafeCell; use std::fmt; use std::num::NonZeroUsize; +use std::sync::Arc; +use self::access::ReadAccess; +use self::access::WriteAccess; use crate::internal::semaphore::Semaphore; +mod access; mod mapped_read_guard; mod mapped_write_guard; mod owned_mapped_read_guard; @@ -68,116 +129,143 @@ pub use self::owned_write_guard::OwnedRwLockWriteGuard; pub use self::read_guard::RwLockReadGuard; pub use self::write_guard::RwLockWriteGuard; -/// A reader-writer lock that allows multiple readers or a single writer at a time. +/// A value with fair, asynchronous shared or exclusive access. /// -/// See the [module level documentation](self) for more. +/// See the [module documentation](self) for ordering, cancellation, and guard projection. pub struct RwLock { - /// Maximum number of concurrent readers. - /// - /// This is ensured to be non-zero. max_readers: usize, - /// Semaphore to coordinate read and write access to T s: Semaphore, - /// The inner data. c: UnsafeCell, } +// SAFETY: Moving the lock transfers its value; no guard can outlive a borrowed lock. unsafe impl Send for RwLock {} +// SAFETY: Readers can share &T; writers can access T from another thread, exclusively. unsafe impl Sync for RwLock {} -impl From for RwLock { - fn from(t: T) -> Self { - Self::new(t) +impl RwLock { + /// Wraps a value with a reader limit of `usize::MAX >> 1`. + pub const fn new(value: T) -> Self { + Self::with_max_readers(value, NonZeroUsize::new(usize::MAX >> 1).unwrap()) } -} -impl Default for RwLock { - fn default() -> Self { - Self::new(T::default()) + /// Wraps a value with an explicit nonzero limit on simultaneously held read guards. + /// + /// A downgraded guard occupies one reader slot. A write guard excludes all reader slots, + /// regardless of the limit. Every `NonZeroUsize` is accepted. + pub const fn with_max_readers(value: T, max_readers: NonZeroUsize) -> Self { + Self { + max_readers: max_readers.get(), + s: Semaphore::new(max_readers.get()), + c: UnsafeCell::new(value), + } } -} -impl fmt::Debug for RwLock { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut d = f.debug_struct("RwLock"); - match self.try_read() { - Some(inner) => d.field("data", &&*inner), - None => d.field("data", &format_args!("")), - }; - d.finish() + /// Unwraps the value by consuming its lock. + pub fn into_inner(self) -> T { + self.c.into_inner() } } -impl RwLock { - /// Creates a new reader-writer lock in an unlocked state ready for use. - /// - /// # Examples - /// - /// ``` - /// use asyncband::rwlock::RwLock; +impl RwLock { + /// Borrows the value exclusively through an exclusive borrow of the lock itself. /// - /// let rwlock = RwLock::new(5); - /// ``` - pub const fn new(t: T) -> RwLock { - // large enough while not touch the edge - RwLock::with_max_readers(t, NonZeroUsize::new(usize::MAX >> 1).unwrap()) + /// This requires no acquisition because existing guards prevent borrowing the lock mutably. + pub fn get_mut(&mut self) -> &mut T { + self.c.get_mut() } - /// Creates a new reader-writer lock in an unlocked state, and allows a maximum of - /// `max_readers` concurrent readers. - /// - /// This method is typically used for debugging and testing purposes. + /// Waits for a reader slot and returns a guard borrowing this lock. /// - /// # Examples + /// An earlier queued writer must finish first. Cancelling this future releases its queue + /// position and any reserved permits. See [queue ordering](self) before acquiring recursively. + pub async fn read(&self) -> RwLockReadGuard<'_, T> { + self.s.acquire(1).await; + read_guard::new(ReadAccess::new(self)) + } + + /// Returns a read guard immediately, or `None` when a reader slot cannot be acquired. /// - /// ``` - /// use std::num::NonZeroUsize; + /// Readers cannot bypass an earlier queued writer. + pub fn try_read(&self) -> Option> { + self.s + .try_acquire(1) + .then(|| read_guard::new(ReadAccess::new(self))) + } + + /// Waits for exclusive access and returns a guard borrowing this lock. /// - /// use asyncband::rwlock::RwLock; + /// Cancelling this future releases its queue position and any reserved permits. + pub async fn write(&self) -> RwLockWriteGuard<'_, T> { + self.s.acquire(self.max_readers).await; + write_guard::new(WriteAccess::new(self, self.max_readers)) + } + + /// Returns a write guard immediately, or `None` when exclusive access is unavailable. + pub fn try_write(&self) -> Option> { + self.s + .try_acquire(self.max_readers) + .then(|| write_guard::new(WriteAccess::new(self, self.max_readers))) + } + + /// Waits for a reader slot and returns a guard retaining this `Arc`. /// - /// let max_readers = NonZeroUsize::new(1024).expect("max_readers must be non-zero"); - /// let rwlock = RwLock::with_max_readers(5, max_readers); - /// ``` - pub const fn with_max_readers(t: T, max_readers: NonZeroUsize) -> RwLock { - let max_readers = max_readers.get(); - let s = Semaphore::new(max_readers); - let c = UnsafeCell::new(t); - RwLock { max_readers, c, s } + /// Ordering and cancellation follow [`Self::read`]. The guard keeps the lock alive without + /// borrowing the caller's `Arc`. + pub async fn read_owned(self: Arc) -> OwnedRwLockReadGuard { + self.s.acquire(1).await; + owned_read_guard::new(ReadAccess::new(self)) } - /// Consumes the lock, returning the underlying data. + /// Returns an owned read guard immediately, or `None` when no reader slot is available. /// - /// # Examples + /// This consumes the passed `Arc` even when acquisition fails. + pub fn try_read_owned(self: Arc) -> Option> { + self.s + .try_acquire(1) + .then(|| owned_read_guard::new(ReadAccess::new(self))) + } + + /// Waits for exclusive access and returns a guard retaining this `Arc`. /// - /// ``` - /// use asyncband::rwlock::RwLock; + /// Ordering and cancellation follow [`Self::write`]. + pub async fn write_owned(self: Arc) -> OwnedRwLockWriteGuard { + let permits = self.max_readers; + self.s.acquire(permits).await; + owned_write_guard::new(WriteAccess::new(self, permits)) + } + + /// Returns an owned write guard immediately, or `None` when exclusive access is unavailable. /// - /// let lock = RwLock::new(1); - /// let n = lock.into_inner(); - /// assert_eq!(n, 1); - /// ``` - pub fn into_inner(self) -> T { - self.c.into_inner() + /// This consumes the passed `Arc` even when acquisition fails. + pub fn try_write_owned(self: Arc) -> Option> { + let permits = self.max_readers; + self.s + .try_acquire(permits) + .then(|| owned_write_guard::new(WriteAccess::new(self, permits))) } } -impl RwLock { - /// Returns a mutable reference to the underlying data. - /// - /// Since this call borrows the `RwLock` mutably, no actual locking needs to take place: the - /// mutable borrow statically guarantees no locks exist. - /// - /// # Examples - /// - /// ``` - /// use asyncband::rwlock::RwLock; - /// - /// let mut lock = RwLock::new(1); - /// let n = lock.get_mut(); - /// *n = 2; - /// ``` - pub fn get_mut(&mut self) -> &mut T { - self.c.get_mut() +impl From for RwLock { + fn from(value: T) -> Self { + Self::new(value) + } +} + +impl Default for RwLock { + fn default() -> Self { + Self::new(T::default()) + } +} + +impl fmt::Debug for RwLock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut debug = f.debug_struct("RwLock"); + match self.try_read() { + Some(value) => debug.field("data", &&*value), + None => debug.field("data", &format_args!("")), + }; + debug.finish() } } diff --git a/asyncband/src/rwlock/owned_mapped_read_guard.rs b/asyncband/src/rwlock/owned_mapped_read_guard.rs index ae96acd..02de5d0 100644 --- a/asyncband/src/rwlock/owned_mapped_read_guard.rs +++ b/asyncband/src/rwlock/owned_mapped_read_guard.rs @@ -1,270 +1,110 @@ -// This file contains code derived from Tokio 1.42.0's RwLock implementation. +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Portions of the guard API originated from Tokio 1.42.0's RwLock implementation. // Copyright (c) Tokio Contributors -// The derived code remains licensed under the MIT License. -// The incorporated code has been modified for use in Apache Asyncband. +// The Tokio-derived portions remain licensed under the MIT License. +// Asyncband replaced guard-local destruction and manual ownership transfers with movable RAII +// access tokens. Projection moves the token, and downgrade establishes a read token before waking +// waiters. The public documentation and examples describe Asyncband's access and projection model. // Upstream sources: // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/owned_read_guard.rs // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/owned_write_guard_mapped.rs use std::fmt; -use std::marker::PhantomData; -use std::mem::ManuallyDrop; use std::ops::Deref; use std::ptr::NonNull; use std::sync::Arc; use crate::rwlock::RwLock; +use crate::rwlock::access::ReadAccess; -/// An owned read guard projected to one component of the protected value. -/// -/// [`OwnedRwLockReadGuard::map`](crate::rwlock::OwnedRwLockReadGuard::map) and -/// [`OwnedRwLockReadGuard::filter_map`](crate::rwlock::OwnedRwLockReadGuard::filter_map) create -/// this guard. It keeps the lock alive and its read access active while exposing only the projected -/// component. -/// -/// # Examples -/// -/// ``` -/// # #[tokio::main] -/// # async fn main() { -/// use std::sync::Arc; -/// -/// use asyncband::rwlock::OwnedRwLockReadGuard; -/// use asyncband::rwlock::RwLock; -/// -/// #[derive(Debug)] -/// struct User { -/// id: u32, -/// profile: UserProfile, -/// } -/// -/// #[derive(Debug)] -/// struct UserProfile { -/// email: String, -/// name: String, -/// } -/// -/// let user = User { -/// id: 1, -/// profile: UserProfile { -/// email: "user@example.com".to_owned(), -/// name: "Alice".to_owned(), -/// }, -/// }; +/// Shared access to a projection of a locked value kept alive by an `Arc`. /// -/// let rwlock = Arc::new(RwLock::new(user)); -/// let guard = rwlock.read_owned().await; -/// let profile_guard = OwnedRwLockReadGuard::map(guard, |user| &user.profile); -/// -/// // Now we can only access the user's profile -/// assert_eq!(profile_guard.email, "user@example.com"); -/// # } -/// ``` +/// Use [`OwnedRwLockReadGuard::map`](crate::rwlock::OwnedRwLockReadGuard::map) to select a +/// component. Dropping the guard releases its access. #[must_use = "dropping the guard releases its read access immediately"] pub struct OwnedMappedRwLockReadGuard { - // This Arc acts as an ownership certificate, ensuring the RwLock remains valid - // and the lock is not released - lock: Arc>, - // This NonNull pointer precisely points to the subfield U, telling us which - // memory location we can operate on - d: NonNull, - variance: PhantomData U>, -} - -// SAFETY: Arc> is Send when T: Send + Sync, and we only provide shared access (&U) -// through deref(), so U: Sync is sufficient for safe cross-thread transfer. -unsafe impl Send for OwnedMappedRwLockReadGuard {} - -// SAFETY: OwnedMappedRwLockReadGuard can be safely shared between threads when T: Send + Sync and -// U: Sync. Multiple threads can hold &OwnedMappedRwLockReadGuard and call deref() concurrently, -// which only returns &U. -unsafe impl Sync for OwnedMappedRwLockReadGuard {} - -impl OwnedMappedRwLockReadGuard { - pub(crate) fn new(d: NonNull, lock: Arc>) -> Self { - Self { - d, - lock, - variance: PhantomData, - } - } -} -impl Drop for OwnedMappedRwLockReadGuard { - fn drop(&mut self) { - self.lock.s.release(1); - } + data: NonNull, + access: ReadAccess>>, } -impl fmt::Debug for OwnedMappedRwLockReadGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } +pub fn new( + data: NonNull, + access: ReadAccess>>, +) -> OwnedMappedRwLockReadGuard { + OwnedMappedRwLockReadGuard { data, access } } -impl fmt::Display for OwnedMappedRwLockReadGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} +// SAFETY: The Arc keeps T alive across threads; the projection only exposes shared access to U. +unsafe impl Send for OwnedMappedRwLockReadGuard {} +// SAFETY: A shared guard reference only exposes &U, and the Arc is safe to share. +unsafe impl Sync for OwnedMappedRwLockReadGuard {} impl Deref for OwnedMappedRwLockReadGuard { type Target = U; - fn deref(&self) -> &Self::Target { - // SAFETY: we hold the read lock and the NonNull pointer is valid for the guard's lifetime - unsafe { self.d.as_ref() } + + fn deref(&self) -> &U { + // SAFETY: The access token holds shared access and keeps the projection valid. + unsafe { self.data.as_ref() } } } impl OwnedMappedRwLockReadGuard { - /// Projects this guard to a deeper shared component. - /// - /// The returned guard keeps the same read access active. Call this as - /// `OwnedMappedRwLockReadGuard::map(...)` so a method named `map` on `U` remains accessible. + /// Selects a component while retaining the same lock access. /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::OwnedMappedRwLockReadGuard; - /// use asyncband::rwlock::OwnedRwLockReadGuard; - /// use asyncband::rwlock::RwLock; - /// - /// #[derive(Debug)] - /// struct ServerStats { - /// uptime: u64, - /// connection_info: ConnectionInfo, - /// } - /// - /// #[derive(Debug)] - /// struct ConnectionInfo { - /// active_connections: u32, - /// max_connections: u32, - /// } - /// - /// let stats = ServerStats { - /// uptime: 86400, // 1 day in seconds - /// connection_info: ConnectionInfo { - /// active_connections: 150, - /// max_connections: 1000, - /// }, - /// }; - /// - /// let rwlock = Arc::new(RwLock::new(stats)); - /// let guard = rwlock.read_owned().await; - /// // Map to connection info for cross-task monitoring - /// let conn_guard = OwnedRwLockReadGuard::map(guard, |stats| &stats.connection_info); - /// // Further map to active connections count - /// let active_guard = OwnedMappedRwLockReadGuard::map(conn_guard, |conn| &conn.active_connections); - /// - /// assert_eq!(*active_guard, 150); - /// # } - /// ``` - pub fn map(orig: Self, f: F) -> OwnedMappedRwLockReadGuard + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `OwnedMappedRwLockReadGuard::map(guard, project)` to avoid shadowing methods of + /// the value. + pub fn map(orig: Self, project: F) -> OwnedMappedRwLockReadGuard where F: FnOnce(&U) -> &V, - V: ?Sized, { - // SAFETY: orig.d is a valid NonNull pointer that was created from a valid reference - // when the original OwnedMappedRwLockReadGuard was constructed. The guard guarantees shared - // access to the data through the rwlock, so dereferencing is safe. - let d = NonNull::from(f(unsafe { orig.d.as_ref() })); - let orig = ManuallyDrop::new(orig); - - // SAFETY: The original guard is wrapped in `ManuallyDrop` and will not be dropped. - // This allows us to safely move the `Arc` out of it and transfer ownership to the new - // guard. - let lock = unsafe { std::ptr::read(&orig.lock) }; - - OwnedMappedRwLockReadGuard::new(d, lock) + let data = NonNull::from(project(&*orig)); + new(data, orig.access) } - /// Attempts to project this guard to a deeper shared component. - /// - /// The original guard is returned when `f` returns `None`. Call this as - /// `OwnedMappedRwLockReadGuard::filter_map(...)` so a method with the same name on `U` remains - /// accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::collections::HashMap; - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::OwnedMappedRwLockReadGuard; - /// use asyncband::rwlock::OwnedRwLockReadGuard; - /// use asyncband::rwlock::RwLock; - /// - /// #[derive(Debug)] - /// struct Cache { - /// entries: HashMap, - /// stats: CacheStats, - /// } - /// - /// #[derive(Debug)] - /// struct CacheEntry { - /// data: String, - /// metadata: Option, - /// } - /// - /// #[derive(Debug)] - /// struct CacheStats { - /// hits: u64, - /// } - /// - /// let mut entries = HashMap::new(); - /// entries.insert( - /// "key1".to_owned(), - /// CacheEntry { - /// data: "cached_data".to_owned(), - /// metadata: Some("important".to_owned()), - /// }, - /// ); - /// - /// let cache = Cache { - /// entries, - /// stats: CacheStats { hits: 42 }, - /// }; - /// - /// let rwlock = Arc::new(RwLock::new(cache)); - /// let guard = rwlock.read_owned().await; - /// - /// // Map to a specific cache entry for cross-task reading - /// let entry_guard = OwnedRwLockReadGuard::map(guard, |cache| cache.entries.get("key1").unwrap()); - /// - /// // Try to map to the metadata if it exists - /// let metadata_guard = - /// OwnedMappedRwLockReadGuard::filter_map(entry_guard, |entry| entry.metadata.as_ref()) - /// .expect("entry should have metadata"); - /// - /// assert_eq!(&*metadata_guard, "important"); - /// # } - /// ``` - pub fn filter_map(orig: Self, f: F) -> Result, Self> + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. + /// + /// A panic in the closure releases the guard. Call this as + /// `OwnedMappedRwLockReadGuard::filter_map(guard, project)`. + pub fn filter_map( + orig: Self, + project: F, + ) -> Result, Self> where F: FnOnce(&U) -> Option<&V>, - V: ?Sized, { - // SAFETY: orig.d is a valid NonNull pointer that was created from a valid reference - // when the original OwnedMappedRwLockReadGuard was constructed. The guard guarantees shared - // access to the data through the rwlock, so dereferencing is safe. - match f(unsafe { orig.d.as_ref() }) { - Some(d) => { - let d = NonNull::from(d); - let orig = ManuallyDrop::new(orig); + let Some(data) = project(&*orig).map(NonNull::from) else { + return Err(orig); + }; + Ok(new(data, orig.access)) + } +} - // SAFETY: The original guard is wrapped in `ManuallyDrop` and will not be dropped. - // This allows us to safely move the `Arc` out of it and transfer ownership to the - // new guard. - let lock = unsafe { std::ptr::read(&orig.lock) }; +impl fmt::Debug for OwnedMappedRwLockReadGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} - Ok(OwnedMappedRwLockReadGuard::new(d, lock)) - } - None => Err(orig), - } +impl fmt::Display for OwnedMappedRwLockReadGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) } } diff --git a/asyncband/src/rwlock/owned_mapped_write_guard.rs b/asyncband/src/rwlock/owned_mapped_write_guard.rs index 5734494..96daa19 100644 --- a/asyncband/src/rwlock/owned_mapped_write_guard.rs +++ b/asyncband/src/rwlock/owned_mapped_write_guard.rs @@ -1,13 +1,31 @@ -// This file contains code derived from Tokio 1.42.0's RwLock implementation. +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Portions of the guard API originated from Tokio 1.42.0's RwLock implementation. // Copyright (c) Tokio Contributors -// The derived code remains licensed under the MIT License. -// The incorporated code has been modified for use in Apache Asyncband. +// The Tokio-derived portions remain licensed under the MIT License. +// Asyncband replaced guard-local destruction and manual ownership transfers with movable RAII +// access tokens. Projection moves the token, and downgrade establishes a read token before waking +// waiters. The public documentation and examples describe Asyncband's access and projection model. // Upstream source: // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/owned_write_guard_mapped.rs use std::fmt; use std::marker::PhantomData; -use std::mem::ManuallyDrop; use std::ops::Deref; use std::ops::DerefMut; use std::ptr::NonNull; @@ -15,330 +33,101 @@ use std::sync::Arc; use crate::rwlock::OwnedMappedRwLockReadGuard; use crate::rwlock::RwLock; +use crate::rwlock::access::WriteAccess; +use crate::rwlock::owned_mapped_read_guard; -/// An owned write guard projected to one component of the protected value. +/// Exclusive access to a projection of a locked value kept alive by an `Arc`. /// -/// [`OwnedRwLockWriteGuard::map`](crate::rwlock::OwnedRwLockWriteGuard::map) and -/// [`OwnedRwLockWriteGuard::filter_map`](crate::rwlock::OwnedRwLockWriteGuard::filter_map) create -/// this guard. It keeps the lock alive and its write access active while exposing only the -/// projected component. -/// -/// # Examples -/// -/// ``` -/// # #[tokio::main] -/// # async fn main() { -/// use std::sync::Arc; -/// -/// use asyncband::rwlock::OwnedRwLockWriteGuard; -/// use asyncband::rwlock::RwLock; -/// -/// #[derive(Debug)] -/// struct User { -/// id: u32, -/// profile: UserProfile, -/// } -/// -/// #[derive(Debug)] -/// struct UserProfile { -/// email: String, -/// name: String, -/// } -/// -/// let user = User { -/// id: 1, -/// profile: UserProfile { -/// email: "user@example.com".to_owned(), -/// name: "Alice".to_owned(), -/// }, -/// }; -/// -/// let rwlock = Arc::new(RwLock::new(user)); -/// let mut guard = rwlock.write_owned().await; -/// let mut profile_guard = OwnedRwLockWriteGuard::map(guard, |user| &mut user.profile); -/// -/// // Now we can only access and modify the user's profile -/// profile_guard.email = "newemail@example.com".to_owned(); -/// assert_eq!(profile_guard.email, "newemail@example.com"); -/// # } -/// ``` +/// Use [`OwnedRwLockWriteGuard::map`](crate::rwlock::OwnedRwLockWriteGuard::map) to select a +/// component. Dropping the guard releases its access. #[must_use = "dropping the guard releases its write access immediately"] pub struct OwnedMappedRwLockWriteGuard { - d: NonNull, - lock: Arc>, - permits_acquired: usize, - // Mutable access requires invariance over U. + data: NonNull, + access: WriteAccess>>, variance: PhantomData<*mut U>, } -// SAFETY: Sharing &Guard across threads is safe when T: Send + Sync and U: Sync. -// Arc> requires T: Send + Sync for thread safety. -// &Guard only provides &U (via Deref), so U: Sync ensures safe concurrent access. -unsafe impl Sync for OwnedMappedRwLockWriteGuard {} - -// SAFETY: Sending Guard across threads is safe when T: Send + Sync and U: Send. -// Arc> requires T: Send + Sync to be Send. -// Guard transfers exclusive access to U, so U: Send ensures safe access from new thread. -unsafe impl Send for OwnedMappedRwLockWriteGuard {} - -impl OwnedMappedRwLockWriteGuard { - pub(crate) fn new(d: NonNull, lock: Arc>, permits_acquired: usize) -> Self { - Self { - d, - lock, - permits_acquired, - variance: PhantomData, - } - } -} -impl Drop for OwnedMappedRwLockWriteGuard { - fn drop(&mut self) { - self.lock.s.release(self.permits_acquired); - } -} - -impl fmt::Debug for OwnedMappedRwLockWriteGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) +pub fn new( + data: NonNull, + access: WriteAccess>>, +) -> OwnedMappedRwLockWriteGuard { + OwnedMappedRwLockWriteGuard { + data, + access, + variance: PhantomData, } } -impl fmt::Display for OwnedMappedRwLockWriteGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} +// SAFETY: The Arc keeps T alive across threads; the projection only exposes exclusive access to U. +unsafe impl Send for OwnedMappedRwLockWriteGuard {} +// SAFETY: A shared guard reference only exposes &U, and the Arc is safe to share. +unsafe impl Sync for OwnedMappedRwLockWriteGuard {} impl Deref for OwnedMappedRwLockWriteGuard { type Target = U; - fn deref(&self) -> &Self::Target { - // SAFETY: we hold the write lock and the NonNull pointer is valid for the guard's lifetime - unsafe { self.d.as_ref() } + + fn deref(&self) -> &U { + // SAFETY: The access token holds exclusive access and keeps the projection valid. + unsafe { self.data.as_ref() } } } impl DerefMut for OwnedMappedRwLockWriteGuard { - fn deref_mut(&mut self) -> &mut Self::Target { - // SAFETY: we hold the write lock and the NonNull pointer is valid for the guard's lifetime - unsafe { self.d.as_mut() } + fn deref_mut(&mut self) -> &mut U { + // SAFETY: The write token excludes all other access, and self is exclusively borrowed. + unsafe { self.data.as_mut() } } } impl OwnedMappedRwLockWriteGuard { - /// Projects this guard to a deeper mutable component. - /// - /// The returned guard keeps the same write access active. Call this as - /// `OwnedMappedRwLockWriteGuard::map(...)` so a method named `map` on `U` remains accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::OwnedMappedRwLockWriteGuard; - /// use asyncband::rwlock::OwnedRwLockWriteGuard; - /// use asyncband::rwlock::RwLock; - /// - /// #[derive(Debug)] - /// struct User { - /// id: u32, - /// profile: UserProfile, - /// } - /// - /// #[derive(Debug)] - /// struct UserProfile { - /// email: String, - /// name: String, - /// } - /// - /// let user = User { - /// id: 1, - /// profile: UserProfile { - /// email: "user@example.com".to_owned(), - /// name: "Alice".to_owned(), - /// }, - /// }; + /// Selects a component while retaining the same lock access. /// - /// let rwlock = Arc::new(RwLock::new(user)); - /// let mut guard = rwlock.write_owned().await; - /// // First map to the profile field - /// let mut profile_guard = OwnedRwLockWriteGuard::map(guard, |user| &mut user.profile); - /// // Then map to the email field specifically - /// let mut email_guard = - /// OwnedMappedRwLockWriteGuard::map(profile_guard, |profile| &mut profile.email); - /// - /// *email_guard = "newemail@example.com".to_owned(); - /// assert_eq!(&*email_guard, "newemail@example.com"); - /// # } - /// ``` - pub fn map(mut orig: Self, f: F) -> OwnedMappedRwLockWriteGuard + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `OwnedMappedRwLockWriteGuard::map(guard, project)` to avoid shadowing methods + /// of the value. + pub fn map(mut orig: Self, project: F) -> OwnedMappedRwLockWriteGuard where F: FnOnce(&mut U) -> &mut V, - V: ?Sized, { - // SAFETY: orig.d is a valid NonNull pointer that was created from a valid reference - // when the original OwnedMappedRwLockWriteGuard was constructed. The guard guarantees - // exclusive access to the data through the rwlock, so dereferencing is safe. - let d = NonNull::from(f(unsafe { orig.d.as_mut() })); - let orig = ManuallyDrop::new(orig); - - let permits_acquired = orig.permits_acquired; - // SAFETY: The original guard is wrapped in `ManuallyDrop` and will not be dropped. - // This allows us to safely move the `Arc` out of it and transfer ownership to the new - // guard. - let lock = unsafe { std::ptr::read(&orig.lock) }; - - OwnedMappedRwLockWriteGuard::new(d, lock, permits_acquired) + let data = NonNull::from(project(&mut *orig)); + new(data, orig.access) } - /// Attempts to project this guard to a deeper mutable component. - /// - /// The original guard is returned when `f` returns `None`. Call this as - /// `OwnedMappedRwLockWriteGuard::filter_map(...)` so a method with the same name on `U` remains - /// accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::OwnedMappedRwLockWriteGuard; - /// use asyncband::rwlock::OwnedRwLockWriteGuard; - /// use asyncband::rwlock::RwLock; - /// - /// #[derive(Debug)] - /// struct AppState { - /// user_count: u64, - /// metrics: Option, - /// } - /// - /// #[derive(Debug)] - /// struct Metrics { - /// requests_per_second: f64, - /// error_rate: f64, - /// } + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// let state = AppState { - /// user_count: 100, - /// metrics: Some(Metrics { - /// requests_per_second: 150.5, - /// error_rate: 0.01, - /// }), - /// }; - /// - /// let rwlock = Arc::new(RwLock::new(state)); - /// let guard = rwlock.write_owned().await; - /// - /// // First, map to the `metrics` field, which is an Option. - /// // This gives us an OwnedMappedRwLockWriteGuard> - /// let metrics_opt_guard = OwnedRwLockWriteGuard::map(guard, |state| &mut state.metrics); - /// - /// // Now, on the mapped guard, try to map into the Option. - /// // This is the correct usage of OwnedMappedRwLockWriteGuard::filter_map. - /// let metrics_result = - /// OwnedMappedRwLockWriteGuard::filter_map(metrics_opt_guard, |metrics_opt| { - /// metrics_opt.as_mut() - /// }); - /// - /// match metrics_result { - /// Ok(mut metrics_guard) => { - /// // Update metrics across tasks - /// metrics_guard.requests_per_second = 200.0; - /// metrics_guard.error_rate = 0.005; - /// assert_eq!(metrics_guard.requests_per_second, 200.0); - /// } - /// Err(_original_guard) => { - /// // Metrics not available, original guard is returned - /// println!("Metrics not enabled"); - /// } - /// } - /// # } - /// ``` - pub fn filter_map(mut orig: Self, f: F) -> Result, Self> + /// A panic in the closure releases the guard. Call this as + /// `OwnedMappedRwLockWriteGuard::filter_map(guard, project)`. + pub fn filter_map( + mut orig: Self, + project: F, + ) -> Result, Self> where F: FnOnce(&mut U) -> Option<&mut V>, - V: ?Sized, { - // SAFETY: orig.d is a valid NonNull pointer that was created from a valid reference - // when the original OwnedMappedRwLockWriteGuard was constructed. The guard guarantees - // exclusive access to the data through the rwlock, so dereferencing is safe. - match f(unsafe { orig.d.as_mut() }) { - Some(d) => { - let d = NonNull::from(d); - let orig = ManuallyDrop::new(orig); - - let permits_acquired = orig.permits_acquired; - // SAFETY: The original guard is wrapped in `ManuallyDrop` and will not be dropped. - // This allows us to safely move the `Arc` out of it and transfer ownership to the - // new guard. - let lock = unsafe { std::ptr::read(&orig.lock) }; - - Ok(OwnedMappedRwLockWriteGuard::new(d, lock, permits_acquired)) - } - None => Err(orig), - } + let Some(data) = project(&mut *orig).map(NonNull::from) else { + return Err(orig); + }; + Ok(new(data, orig.access)) } - /// Atomically downgrades the write lock to a read lock while preserving the mapping. - /// - /// This method changes the lock from exclusive mode to shared mode atomically, - /// preventing other writers from acquiring the lock in between. - /// - /// The returned `OwnedMappedRwLockReadGuard` preserves the original mapping and - /// has a `'static` lifetime. - /// - /// # Examples + /// Retains shared access to the same projection while releasing exclusive access. /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::OwnedRwLockWriteGuard; - /// use asyncband::rwlock::RwLock; - /// - /// #[derive(Debug)] - /// struct Database { - /// connection_count: u32, - /// status: String, - /// } - /// - /// let db = Arc::new(RwLock::new(Database { - /// connection_count: 0, - /// status: "idle".to_owned(), - /// })); - /// - /// let write_guard = db.clone().write_owned().await; - /// let mut count_write_guard = - /// OwnedRwLockWriteGuard::map(write_guard, |db| &mut db.connection_count); - /// *count_write_guard = 5; - /// - /// let count_read_guard = count_write_guard.downgrade(); - /// assert_eq!(*count_read_guard, 5); - /// - /// assert!(db.clone().try_write_owned().is_none()); - /// - /// drop(count_read_guard); - /// assert!(db.clone().try_write_owned().is_some()); - /// # } - /// ``` + /// There is no unlocked interval in which another writer can modify the value. Queued + /// requests retain their order, so a waiting writer can prevent later readers from joining. pub fn downgrade(self) -> OwnedMappedRwLockReadGuard { - // Prevent the original write guard from running its Drop implementation, - // which would release all permits. This must be done BEFORE any operation - // that might panic to ensure panic safety. - let guard = ManuallyDrop::new(self); + owned_mapped_read_guard::new(self.data, self.access.downgrade()) + } +} - // Release max_readers - 1 permits to convert the write lock to a read lock. - guard.lock.s.release(guard.permits_acquired - 1); +impl fmt::Debug for OwnedMappedRwLockWriteGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} - // SAFETY: The `guard` is wrapped in `ManuallyDrop`, so its destructor will not be run. - // We can safely move the `Arc` out of the guard, as the guard is not used after this. - // This is a standard way to transfer ownership from a `ManuallyDrop` wrapper. - let lock = unsafe { std::ptr::read(&guard.lock) }; - OwnedMappedRwLockReadGuard::new(guard.d, lock) +impl fmt::Display for OwnedMappedRwLockWriteGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) } } diff --git a/asyncband/src/rwlock/owned_read_guard.rs b/asyncband/src/rwlock/owned_read_guard.rs index 5c2d1be..92a602e 100644 --- a/asyncband/src/rwlock/owned_read_guard.rs +++ b/asyncband/src/rwlock/owned_read_guard.rs @@ -1,227 +1,101 @@ -// This file contains code derived from Tokio 1.42.0's RwLock implementation. +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Portions of the guard API originated from Tokio 1.42.0's RwLock implementation. // Copyright (c) Tokio Contributors -// The derived code remains licensed under the MIT License. -// The incorporated code has been modified for use in Apache Asyncband. +// The Tokio-derived portions remain licensed under the MIT License. +// Asyncband replaced guard-local destruction and manual ownership transfers with movable RAII +// access tokens. Projection moves the token, and downgrade establishes a read token before waking +// waiters. The public documentation and examples describe Asyncband's access and projection model. // Upstream source: // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/owned_read_guard.rs use std::fmt; use std::ops::Deref; +use std::ptr::NonNull; use std::sync::Arc; use crate::rwlock::OwnedMappedRwLockReadGuard; use crate::rwlock::RwLock; +use crate::rwlock::access::ReadAccess; +use crate::rwlock::owned_mapped_read_guard; -impl RwLock { - /// Waits for shared read access and returns a guard that owns this [`Arc`]. - /// - /// Other readers may hold the lock at the same time. Owning the `Arc` lets the guard be moved - /// wherever a `'static` value is required. - /// - /// A writer already waiting ahead of this request must acquire and release the lock first. - /// Holding a read guard, queuing a write request, and then waiting for another read guard in - /// the same task can therefore deadlock. - /// - /// # Cancel safety - /// - /// Pending lock requests complete in order. Cancelling this call loses its place among them. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let lock = Arc::new(RwLock::new(1)); - /// let lock_clone = lock.clone(); - /// - /// let n = lock.read_owned().await; - /// assert_eq!(*n, 1); - /// - /// tokio::spawn(async move { - /// // while the outer read lock is held, we acquire a read lock, too - /// let r = lock_clone.read_owned().await; - /// assert_eq!(*r, 1); - /// }) - /// .await - /// .unwrap(); - /// # } - /// ``` - pub async fn read_owned(self: Arc) -> OwnedRwLockReadGuard { - self.s.acquire(1).await; - OwnedRwLockReadGuard { lock: self } - } - - /// Acquires shared read access without waiting and returns a guard that owns this [`Arc`]. - /// - /// Returns `None` if read access is unavailable. - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let lock = Arc::new(RwLock::new(1)); - /// - /// let v = lock.clone().try_read_owned().unwrap(); - /// assert_eq!(*v, 1); - /// drop(v); - /// - /// let v = lock.try_write().unwrap(); - /// assert!(lock.clone().try_read_owned().is_none()); - /// ``` - pub fn try_read_owned(self: Arc) -> Option> { - if self.s.try_acquire(1) { - Some(OwnedRwLockReadGuard { lock: self }) - } else { - None - } - } -} - -/// An owned guard that provides shared access to a [`RwLock`]'s value. +/// Shared access to a locked value kept alive by an `Arc`. /// -/// [`RwLock::read_owned`] and [`RwLock::try_read_owned`] create this guard. It keeps the lock alive -/// without borrowing it and releases this reader's access when dropped. +/// Created by [`RwLock::read_owned`]. Dropping the guard releases its access. #[must_use = "dropping the guard releases its read access immediately"] pub struct OwnedRwLockReadGuard { - pub(super) lock: Arc>, -} - -impl Drop for OwnedRwLockReadGuard { - fn drop(&mut self) { - self.lock.s.release(1); - } -} - -impl fmt::Debug for OwnedRwLockReadGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } + access: ReadAccess>>, } -impl fmt::Display for OwnedRwLockReadGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } +pub fn new(access: ReadAccess>>) -> OwnedRwLockReadGuard { + OwnedRwLockReadGuard { access } } impl Deref for OwnedRwLockReadGuard { type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.c.get() } + + fn deref(&self) -> &T { + // SAFETY: The access token holds shared access and keeps the value valid. + unsafe { &*self.access.owner().c.get() } } } impl OwnedRwLockReadGuard { - /// Projects this guard to a shared component of the protected value. - /// - /// Call this as `OwnedRwLockReadGuard::map(...)` so a method named `map` on `T` remains - /// accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; + /// Selects a component while retaining the same lock access. /// - /// use asyncband::rwlock::OwnedRwLockReadGuard; - /// use asyncband::rwlock::RwLock; - /// - /// #[derive(Debug)] - /// struct Foo { - /// a: u32, - /// b: String, - /// } - /// - /// let rwlock = Arc::new(RwLock::new(Foo { - /// a: 1, - /// b: "hello".to_owned(), - /// })); - /// - /// let guard = rwlock.read_owned().await; - /// let mapped_guard = OwnedRwLockReadGuard::map(guard, |foo| &foo.a); - /// - /// assert_eq!(*mapped_guard, 1); - /// # } - /// ``` - pub fn map(orig: Self, f: F) -> OwnedMappedRwLockReadGuard + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `OwnedRwLockReadGuard::map(guard, project)` to avoid shadowing methods of the + /// value. + pub fn map(orig: Self, project: F) -> OwnedMappedRwLockReadGuard where F: FnOnce(&T) -> &U, - U: ?Sized, { - // SAFETY: orig.lock.c.get() is a valid pointer to T that was created when the lock was - // acquired. The guard guarantees shared access to the data through the rwlock, so - // dereferencing is safe. - let d = std::ptr::NonNull::from(f(unsafe { &*orig.lock.c.get() })); - let orig = std::mem::ManuallyDrop::new(orig); - - // Safely extract the Arc from the guard - let lock = unsafe { std::ptr::read(&orig.lock) }; - - OwnedMappedRwLockReadGuard::new(d, lock) + let data = NonNull::from(project(&*orig)); + owned_mapped_read_guard::new(data, orig.access) } - /// Attempts to project this guard to a shared component of the protected value. - /// - /// The original guard is returned when `f` returns `None`. Call this as - /// `OwnedRwLockReadGuard::filter_map(...)` so a method with the same name on `T` remains - /// accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::OwnedRwLockReadGuard; - /// use asyncband::rwlock::RwLock; - /// - /// #[derive(Debug)] - /// struct Foo { - /// a: u32, - /// b: String, - /// } - /// - /// let rwlock = Arc::new(RwLock::new(Foo { - /// a: 1, - /// b: "hello".to_owned(), - /// })); - /// - /// let guard = rwlock.read_owned().await; - /// let mapped_guard = - /// OwnedRwLockReadGuard::filter_map(guard, |foo| if foo.a > 0 { Some(&foo.b) } else { None }) - /// .expect("should have mapped"); - /// - /// assert_eq!(&*mapped_guard, "hello"); - /// # } - /// ``` - pub fn filter_map(orig: Self, f: F) -> Result, Self> + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. + /// + /// A panic in the closure releases the guard. Call this as + /// `OwnedRwLockReadGuard::filter_map(guard, project)`. + pub fn filter_map( + orig: Self, + project: F, + ) -> Result, Self> where F: FnOnce(&T) -> Option<&U>, - U: ?Sized, { - // SAFETY: orig.lock.c.get() is a valid pointer to T that was created when the lock was - // acquired. The guard guarantees shared access to the data through the rwlock, so - // dereferencing is safe. - match f(unsafe { &*orig.lock.c.get() }) { - Some(d) => { - let d = std::ptr::NonNull::from(d); - let orig = std::mem::ManuallyDrop::new(orig); + let Some(data) = project(&*orig).map(NonNull::from) else { + return Err(orig); + }; + Ok(owned_mapped_read_guard::new(data, orig.access)) + } +} - // Safely extract the Arc from the guard - let lock = unsafe { std::ptr::read(&orig.lock) }; +impl fmt::Debug for OwnedRwLockReadGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} - Ok(OwnedMappedRwLockReadGuard::new(d, lock)) - } - None => Err(orig), - } +impl fmt::Display for OwnedRwLockReadGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) } } diff --git a/asyncband/src/rwlock/owned_write_guard.rs b/asyncband/src/rwlock/owned_write_guard.rs index 8d7f558..8334fbe 100644 --- a/asyncband/src/rwlock/owned_write_guard.rs +++ b/asyncband/src/rwlock/owned_write_guard.rs @@ -1,12 +1,30 @@ -// This file contains code derived from Tokio 1.42.0's RwLock implementation. +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Portions of the guard API originated from Tokio 1.42.0's RwLock implementation. // Copyright (c) Tokio Contributors -// The derived code remains licensed under the MIT License. -// The incorporated code has been modified for use in Apache Asyncband. +// The Tokio-derived portions remain licensed under the MIT License. +// Asyncband replaced guard-local destruction and manual ownership transfers with movable RAII +// access tokens. Projection moves the token, and downgrade establishes a read token before waking +// waiters. The public documentation and examples describe Asyncband's access and projection model. // Upstream source: // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/owned_write_guard.rs use std::fmt; -use std::mem::ManuallyDrop; use std::ops::Deref; use std::ops::DerefMut; use std::ptr::NonNull; @@ -15,276 +33,87 @@ use std::sync::Arc; use crate::rwlock::OwnedMappedRwLockWriteGuard; use crate::rwlock::OwnedRwLockReadGuard; use crate::rwlock::RwLock; +use crate::rwlock::access::WriteAccess; +use crate::rwlock::owned_mapped_write_guard; +use crate::rwlock::owned_read_guard; -impl RwLock { - /// Waits for exclusive write access and returns a guard that owns this [`Arc`]. - /// - /// Owning the `Arc` lets the guard be moved wherever a `'static` value is required. - /// - /// # Cancel safety - /// - /// Pending lock requests complete in order. Cancelling this call loses its place among them. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let lock = Arc::new(RwLock::new(1)); - /// let mut n = lock.write_owned().await; - /// *n = 2; - /// # } - /// ``` - pub async fn write_owned(self: Arc) -> OwnedRwLockWriteGuard { - self.s.acquire(self.max_readers).await; - OwnedRwLockWriteGuard { - permits_acquired: self.max_readers, - lock: self, - } - } - - /// Acquires exclusive write access without waiting and returns a guard that owns this [`Arc`]. - /// - /// Returns `None` if write access is unavailable. - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let lock = Arc::new(RwLock::new(1)); - /// - /// let v = lock.try_read().unwrap(); - /// assert!(lock.clone().try_write_owned().is_none()); - /// drop(v); - /// - /// let mut v = lock.try_write_owned().unwrap(); - /// *v = 2; - /// ``` - pub fn try_write_owned(self: Arc) -> Option> { - if self.s.try_acquire(self.max_readers) { - Some(OwnedRwLockWriteGuard { - permits_acquired: self.max_readers, - lock: self, - }) - } else { - None - } - } -} - -/// An owned guard that provides exclusive access to a [`RwLock`]'s value. +/// Exclusive access to a locked value kept alive by an `Arc`. /// -/// [`RwLock::write_owned`] and [`RwLock::try_write_owned`] create this guard. It keeps the lock -/// alive without borrowing it and releases the lock when dropped. +/// Created by [`RwLock::write_owned`]. Dropping the guard releases its access. #[must_use = "dropping the guard releases its write access immediately"] pub struct OwnedRwLockWriteGuard { - pub(super) permits_acquired: usize, - pub(super) lock: Arc>, -} - -unsafe impl Send for OwnedRwLockWriteGuard {} -unsafe impl Sync for OwnedRwLockWriteGuard {} - -impl Drop for OwnedRwLockWriteGuard { - fn drop(&mut self) { - self.lock.s.release(self.permits_acquired); - } -} - -impl fmt::Debug for OwnedRwLockWriteGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } + access: WriteAccess>>, } -impl fmt::Display for OwnedRwLockWriteGuard { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } +pub fn new(access: WriteAccess>>) -> OwnedRwLockWriteGuard { + OwnedRwLockWriteGuard { access } } impl Deref for OwnedRwLockWriteGuard { type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.c.get() } + + fn deref(&self) -> &T { + // SAFETY: The access token holds exclusive access and keeps the value valid. + unsafe { &*self.access.owner().c.get() } } } impl DerefMut for OwnedRwLockWriteGuard { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *self.lock.c.get() } + fn deref_mut(&mut self) -> &mut T { + // SAFETY: The write token excludes all other access, and self is exclusively borrowed. + unsafe { &mut *self.access.owner().c.get() } } } impl OwnedRwLockWriteGuard { - /// Projects this guard to a mutable component of the protected value. - /// - /// Call this as `OwnedRwLockWriteGuard::map(...)` so a method named `map` on `T` remains - /// accessible. - /// - /// # Examples + /// Selects a component while retaining the same lock access. /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::OwnedRwLockWriteGuard; - /// use asyncband::rwlock::RwLock; - /// - /// #[derive(Debug)] - /// struct Foo { - /// a: u32, - /// b: String, - /// } - /// - /// let rwlock = Arc::new(RwLock::new(Foo { - /// a: 1, - /// b: "hello".to_owned(), - /// })); - /// - /// let mut guard = rwlock.write_owned().await; - /// let mut mapped_guard = OwnedRwLockWriteGuard::map(guard, |foo| &mut foo.b); - /// - /// mapped_guard.push_str(" world"); - /// assert_eq!(&*mapped_guard, "hello world"); - /// # } - /// ``` - pub fn map(orig: Self, f: F) -> OwnedMappedRwLockWriteGuard + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `OwnedRwLockWriteGuard::map(guard, project)` to avoid shadowing methods of the + /// value. + pub fn map(mut orig: Self, project: F) -> OwnedMappedRwLockWriteGuard where F: FnOnce(&mut T) -> &mut U, - U: ?Sized, { - // SAFETY: We have exclusive write access to the data through the rwlock. - // The data pointer is valid for the lifetime of the guard. - let d = NonNull::from(f(unsafe { &mut *orig.lock.c.get() })); - let orig = ManuallyDrop::new(orig); - - let permits_acquired = orig.permits_acquired; - // SAFETY: The original guard is wrapped in `ManuallyDrop` and will not be dropped. - // This allows us to safely move the `Arc` out of it and transfer ownership to the new - // guard. - let lock = unsafe { std::ptr::read(&orig.lock) }; - - OwnedMappedRwLockWriteGuard::new(d, lock, permits_acquired) + let data = NonNull::from(project(&mut *orig)); + owned_mapped_write_guard::new(data, orig.access) } - /// Attempts to project this guard to a mutable component of the protected value. - /// - /// The original guard is returned when `f` returns `None`. Call this as - /// `OwnedRwLockWriteGuard::filter_map(...)` so a method with the same name on `T` remains - /// accessible. - /// - /// # Examples + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::OwnedRwLockWriteGuard; - /// use asyncband::rwlock::RwLock; - /// - /// #[derive(Debug)] - /// struct Foo { - /// a: u32, - /// b: String, - /// } - /// - /// let rwlock = Arc::new(RwLock::new(Foo { - /// a: 1, - /// b: "hello".to_owned(), - /// })); - /// - /// let mut guard = rwlock.write_owned().await; - /// let mut mapped_guard = OwnedRwLockWriteGuard::filter_map(guard, |foo| { - /// if foo.b.len() > 3 { - /// Some(&mut foo.b) - /// } else { - /// None - /// } - /// }) - /// .expect("should have mapped"); - /// - /// mapped_guard.push_str(" world"); - /// assert_eq!(&*mapped_guard, "hello world"); - /// # } - /// ``` - pub fn filter_map(orig: Self, f: F) -> Result, Self> + /// A panic in the closure releases the guard. Call this as + /// `OwnedRwLockWriteGuard::filter_map(guard, project)`. + pub fn filter_map( + mut orig: Self, + project: F, + ) -> Result, Self> where F: FnOnce(&mut T) -> Option<&mut U>, - U: ?Sized, { - // SAFETY: We have exclusive write access to the data through the rwlock. - // The data pointer is valid for the lifetime of the guard. - let d = match f(unsafe { &mut *orig.lock.c.get() }) { - Some(d) => NonNull::from(d), - None => return Err(orig), + let Some(data) = project(&mut *orig).map(NonNull::from) else { + return Err(orig); }; - - let orig = ManuallyDrop::new(orig); - - let permits_acquired = orig.permits_acquired; - // SAFETY: The original guard is wrapped in `ManuallyDrop` and will not be dropped. - // This allows us to safely move the `Arc` out of it and transfer ownership to the new - // guard. - let lock = unsafe { std::ptr::read(&orig.lock) }; - - Ok(OwnedMappedRwLockWriteGuard::new(d, lock, permits_acquired)) + Ok(owned_mapped_write_guard::new(data, orig.access)) } - /// Atomically downgrades the write lock to a read lock. - /// - /// This method changes the lock from exclusive mode to shared mode atomically, - /// preventing other writers from acquiring the lock in between. + /// Retains shared access while releasing exclusive access. /// - /// The returned `OwnedRwLockReadGuard` has a `'static` lifetime, as it keeps - /// the `RwLock` alive by holding an `Arc`. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let lock = Arc::new(RwLock::new(1)); - /// - /// let mut write_guard = lock.clone().write_owned().await; - /// *write_guard = 42; - /// - /// let read_guard = write_guard.downgrade(); - /// assert_eq!(*read_guard, 42); - /// - /// assert!(lock.clone().try_write_owned().is_none()); - /// - /// drop(read_guard); - /// assert!(lock.clone().try_write_owned().is_some()); - /// # } - /// ``` + /// There is no unlocked interval in which another writer can modify the value. Queued + /// requests retain their order, so a waiting writer can prevent later readers from joining. pub fn downgrade(self) -> OwnedRwLockReadGuard { - // Prevent the original write guard from running its Drop implementation, - // which would release all permits. This must be done BEFORE any operation - // that might panic to ensure panic safety. - let guard = ManuallyDrop::new(self); + owned_read_guard::new(self.access.downgrade()) + } +} - // Release max_readers - 1 permits to convert the write lock to a read lock. - // The remaining 1 permit is kept for the read lock. - guard.lock.s.release(guard.permits_acquired - 1); +impl fmt::Debug for OwnedRwLockWriteGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} - // SAFETY: The `guard` is wrapped in `ManuallyDrop`, so its destructor will not be run. - // We can safely move the `Arc` out of the guard, as the guard is not used after this. - // This is a standard way to transfer ownership from a `ManuallyDrop` wrapper. - let lock = unsafe { std::ptr::read(&guard.lock) }; - OwnedRwLockReadGuard { lock } +impl fmt::Display for OwnedRwLockWriteGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) } } diff --git a/asyncband/src/rwlock/read_guard.rs b/asyncband/src/rwlock/read_guard.rs index 48739cf..4dd1424 100644 --- a/asyncband/src/rwlock/read_guard.rs +++ b/asyncband/src/rwlock/read_guard.rs @@ -1,196 +1,104 @@ -// This file contains code derived from Tokio 1.42.0's RwLock implementation. +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Portions of the guard API originated from Tokio 1.42.0's RwLock implementation. // Copyright (c) Tokio Contributors -// The derived code remains licensed under the MIT License. -// The incorporated code has been modified for use in Apache Asyncband. +// The Tokio-derived portions remain licensed under the MIT License. +// Asyncband replaced guard-local destruction and manual ownership transfers with movable RAII +// access tokens. Projection moves the token, and downgrade establishes a read token before waking +// waiters. The public documentation and examples describe Asyncband's access and projection model. // Upstream source: // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/read_guard.rs use std::fmt; -use std::mem::ManuallyDrop; use std::ops::Deref; use std::ptr::NonNull; use crate::rwlock::MappedRwLockReadGuard; use crate::rwlock::RwLock; +use crate::rwlock::access::ReadAccess; +use crate::rwlock::mapped_read_guard; -impl RwLock { - /// Waits for shared read access and returns a borrowed guard. - /// - /// Other readers may hold the lock at the same time. A writer already waiting ahead of this - /// request must acquire and release the lock first. - /// - /// Holding a read guard, queuing a write request, and then waiting for another read guard in - /// the same task can therefore deadlock. - /// - /// # Cancel safety - /// - /// Pending lock requests complete in order. Cancelling this call loses its place among them. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let lock = Arc::new(RwLock::new(1)); - /// let lock_clone = lock.clone(); - /// - /// let n = lock.read().await; - /// assert_eq!(*n, 1); - /// - /// tokio::spawn(async move { - /// // while the outer read lock is held, we acquire a read lock, too - /// let r = lock_clone.read().await; - /// assert_eq!(*r, 1); - /// }) - /// .await - /// .unwrap(); - /// # } - /// ``` - pub async fn read(&self) -> RwLockReadGuard<'_, T> { - self.s.acquire(1).await; - RwLockReadGuard { lock: self } - } - - /// Acquires shared read access without waiting, or returns `None` if it is unavailable. - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let lock = Arc::new(RwLock::new(1)); - /// - /// let v = lock.try_read().unwrap(); - /// assert_eq!(*v, 1); - /// drop(v); - /// - /// let v = lock.try_write().unwrap(); - /// assert!(lock.try_read().is_none()); - /// ``` - pub fn try_read(&self) -> Option> { - if self.s.try_acquire(1) { - Some(RwLockReadGuard { lock: self }) - } else { - None - } - } -} - -/// A borrowed guard that provides shared access to a [`RwLock`]'s value. +/// Shared access to a locked value borrowed for the guard lifetime. /// -/// [`RwLock::read`] and [`RwLock::try_read`] create this guard. Dropping it releases this reader's -/// access. +/// Created by [`RwLock::read`]. Dropping the guard releases its access. #[must_use = "dropping the guard releases its read access immediately"] pub struct RwLockReadGuard<'a, T: ?Sized> { - pub(super) lock: &'a RwLock, -} - -unsafe impl Send for RwLockReadGuard<'_, T> {} -unsafe impl Sync for RwLockReadGuard<'_, T> {} - -impl Drop for RwLockReadGuard<'_, T> { - fn drop(&mut self) { - self.lock.s.release(1); - } + access: ReadAccess<&'a RwLock>, } -impl fmt::Debug for RwLockReadGuard<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } +pub fn new<'a, T: ?Sized>(access: ReadAccess<&'a RwLock>) -> RwLockReadGuard<'a, T> { + RwLockReadGuard { access } } -impl fmt::Display for RwLockReadGuard<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} +// SAFETY: Moving this guard transfers shared access without moving or dropping T. +unsafe impl Send for RwLockReadGuard<'_, T> {} +// SAFETY: Sharing the guard only exposes &T; the access token keeps the lock held. +unsafe impl Sync for RwLockReadGuard<'_, T> {} impl Deref for RwLockReadGuard<'_, T> { type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.c.get() } + + fn deref(&self) -> &T { + // SAFETY: The access token holds shared access and keeps the value valid. + unsafe { &*self.access.owner().c.get() } } } impl<'a, T: ?Sized> RwLockReadGuard<'a, T> { - /// Projects this guard to a shared component of the protected value. - /// - /// Call this as `RwLockReadGuard::map(...)` so a method named `map` on `T` remains accessible. + /// Selects a component while retaining the same lock access. /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use asyncband::rwlock::RwLock; - /// use asyncband::rwlock::RwLockReadGuard; - /// - /// #[derive(Debug, Clone)] - /// struct Foo(String); - /// - /// let rwlock = RwLock::new(Foo("hello".to_owned())); - /// - /// let guard = rwlock.read().await; - /// let mapped_guard = RwLockReadGuard::map(guard, |f| &f.0); - /// - /// assert_eq!(&*mapped_guard, "hello"); - /// # } - /// ``` - pub fn map(orig: Self, f: F) -> MappedRwLockReadGuard<'a, U> + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `RwLockReadGuard::map(guard, project)` to avoid shadowing methods of the value. + pub fn map(orig: Self, project: F) -> MappedRwLockReadGuard<'a, U> where F: FnOnce(&T) -> &U, - U: ?Sized, { - let d = NonNull::from(f(&*orig)); - let orig = ManuallyDrop::new(orig); - MappedRwLockReadGuard::new(d, &orig.lock.s) + let data = NonNull::from(project(&*orig)); + mapped_read_guard::new(data, orig.access.into_semaphore()) } - /// Attempts to project this guard to a shared component of the protected value. - /// - /// The original guard is returned when `f` returns `None`. Call this as - /// `RwLockReadGuard::filter_map(...)` so a method with the same name on `T` remains accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use asyncband::rwlock::RwLock; - /// use asyncband::rwlock::RwLockReadGuard; - /// - /// #[derive(Debug, Clone)] - /// struct Foo(String); - /// - /// let rwlock = RwLock::new(Foo("hello".to_owned())); - /// - /// let guard = rwlock.read().await; - /// let mapped_guard = - /// RwLockReadGuard::filter_map(guard, |f| if f.0.len() > 3 { Some(&f.0) } else { None }) - /// .expect("should have mapped"); - /// - /// assert_eq!(&*mapped_guard, "hello"); - /// # } - /// ``` - pub fn filter_map(orig: Self, f: F) -> Result, Self> + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. + /// + /// A panic in the closure releases the guard. Call this as `RwLockReadGuard::filter_map(guard, + /// project)`. + pub fn filter_map( + orig: Self, + project: F, + ) -> Result, Self> where F: FnOnce(&T) -> Option<&U>, - U: ?Sized, { - match f(&*orig) { - Some(d) => { - let d = NonNull::from(d); - let orig = ManuallyDrop::new(orig); - Ok(MappedRwLockReadGuard::new(d, &orig.lock.s)) - } - None => Err(orig), - } + let Some(data) = project(&*orig).map(NonNull::from) else { + return Err(orig); + }; + Ok(mapped_read_guard::new(data, orig.access.into_semaphore())) + } +} + +impl fmt::Debug for RwLockReadGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Display for RwLockReadGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) } } diff --git a/asyncband/src/rwlock/write_guard.rs b/asyncband/src/rwlock/write_guard.rs index 896ed60..7fd1659 100644 --- a/asyncband/src/rwlock/write_guard.rs +++ b/asyncband/src/rwlock/write_guard.rs @@ -1,12 +1,30 @@ -// This file contains code derived from Tokio 1.42.0's RwLock implementation. +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Portions of the guard API originated from Tokio 1.42.0's RwLock implementation. // Copyright (c) Tokio Contributors -// The derived code remains licensed under the MIT License. -// The incorporated code has been modified for use in Apache Asyncband. +// The Tokio-derived portions remain licensed under the MIT License. +// Asyncband replaced guard-local destruction and manual ownership transfers with movable RAII +// access tokens. Projection moves the token, and downgrade establishes a read token before waking +// waiters. The public documentation and examples describe Asyncband's access and projection model. // Upstream source: // https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/write_guard.rs use std::fmt; -use std::mem::ManuallyDrop; use std::ops::Deref; use std::ops::DerefMut; use std::ptr::NonNull; @@ -14,247 +32,92 @@ use std::ptr::NonNull; use crate::rwlock::MappedRwLockWriteGuard; use crate::rwlock::RwLock; use crate::rwlock::RwLockReadGuard; +use crate::rwlock::access::WriteAccess; +use crate::rwlock::mapped_write_guard; +use crate::rwlock::read_guard; -impl RwLock { - /// Waits for exclusive write access and returns a borrowed guard. - /// - /// # Cancel safety - /// - /// Pending lock requests complete in order. Cancelling this call loses its place among them. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use asyncband::rwlock::RwLock; - /// - /// let lock = RwLock::new(1); - /// let mut n = lock.write().await; - /// *n = 2; - /// # } - /// ``` - pub async fn write(&self) -> RwLockWriteGuard<'_, T> { - self.s.acquire(self.max_readers).await; - RwLockWriteGuard { - permits_acquired: self.max_readers, - lock: self, - } - } - - /// Acquires exclusive write access without waiting, or returns `None` if it is unavailable. - /// - /// # Examples - /// - /// ``` - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let lock = Arc::new(RwLock::new(1)); - /// - /// let v = lock.try_read().unwrap(); - /// assert!(lock.try_write().is_none()); - /// drop(v); - /// - /// let mut v = lock.try_write().unwrap(); - /// *v = 2; - /// ``` - pub fn try_write(&self) -> Option> { - if self.s.try_acquire(self.max_readers) { - Some(RwLockWriteGuard { - permits_acquired: self.max_readers, - lock: self, - }) - } else { - None - } - } -} - -/// A borrowed guard that provides exclusive access to a [`RwLock`]'s value. +/// Exclusive access to a locked value borrowed for the guard lifetime. /// -/// [`RwLock::write`] and [`RwLock::try_write`] create this guard. Dropping it releases the lock. +/// Created by [`RwLock::write`]. Dropping the guard releases its access. #[must_use = "dropping the guard releases its write access immediately"] pub struct RwLockWriteGuard<'a, T: ?Sized> { - pub(super) permits_acquired: usize, - pub(super) lock: &'a RwLock, + access: WriteAccess<&'a RwLock>, } -unsafe impl Send for RwLockWriteGuard<'_, T> {} -unsafe impl Sync for RwLockWriteGuard<'_, T> {} - -impl Drop for RwLockWriteGuard<'_, T> { - fn drop(&mut self) { - self.lock.s.release(self.permits_acquired); - } +pub fn new<'a, T: ?Sized>(access: WriteAccess<&'a RwLock>) -> RwLockWriteGuard<'a, T> { + RwLockWriteGuard { access } } -impl fmt::Debug for RwLockWriteGuard<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&**self, f) - } -} - -impl fmt::Display for RwLockWriteGuard<'_, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&**self, f) - } -} +// SAFETY: Moving this guard transfers exclusive access without moving or dropping T. +unsafe impl Send for RwLockWriteGuard<'_, T> {} +// SAFETY: Sharing the guard only exposes &T; the access token keeps the lock held. +unsafe impl Sync for RwLockWriteGuard<'_, T> {} impl Deref for RwLockWriteGuard<'_, T> { type Target = T; - fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.c.get() } + + fn deref(&self) -> &T { + // SAFETY: The access token holds exclusive access and keeps the value valid. + unsafe { &*self.access.owner().c.get() } } } impl DerefMut for RwLockWriteGuard<'_, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *self.lock.c.get() } + fn deref_mut(&mut self) -> &mut T { + // SAFETY: The write token excludes all other access, and self is exclusively borrowed. + unsafe { &mut *self.access.owner().c.get() } } } impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> { - /// Projects this guard to a mutable component of the protected value. - /// - /// Call this as `RwLockWriteGuard::map(...)` so a method named `map` on `T` remains accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use asyncband::rwlock::RwLock; - /// use asyncband::rwlock::RwLockWriteGuard; - /// - /// #[derive(Debug)] - /// struct Foo { - /// a: u32, - /// b: String, - /// } + /// Selects a component while retaining the same lock access. /// - /// let rwlock = RwLock::new(Foo { - /// a: 1, - /// b: "hello".to_owned(), - /// }); - /// - /// let mut guard = rwlock.write().await; - /// let mut mapped_guard = RwLockWriteGuard::map(guard, |foo| &mut foo.a); - /// - /// *mapped_guard = 42; - /// assert_eq!(*mapped_guard, 42); - /// # } - /// ``` - pub fn map(orig: Self, f: F) -> MappedRwLockWriteGuard<'a, U> + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `RwLockWriteGuard::map(guard, project)` to avoid shadowing methods of the + /// value. + pub fn map(mut orig: Self, project: F) -> MappedRwLockWriteGuard<'a, U> where F: FnOnce(&mut T) -> &mut U, - U: ?Sized, { - let d = NonNull::from(f(unsafe { &mut *orig.lock.c.get() })); - let permits_acquired = orig.permits_acquired; - let orig = ManuallyDrop::new(orig); - MappedRwLockWriteGuard::new(d, &orig.lock.s, permits_acquired) + let data = NonNull::from(project(&mut *orig)); + mapped_write_guard::new(data, orig.access.into_semaphore()) } - /// Attempts to project this guard to a mutable component of the protected value. - /// - /// The original guard is returned when `f` returns `None`. Call this as - /// `RwLockWriteGuard::filter_map(...)` so a method with the same name on `T` remains - /// accessible. - /// - /// # Examples - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use asyncband::rwlock::RwLock; - /// use asyncband::rwlock::RwLockWriteGuard; - /// - /// #[derive(Debug)] - /// struct Foo { - /// a: u32, - /// b: String, - /// } - /// - /// let rwlock = RwLock::new(Foo { - /// a: 11, - /// b: "ok".to_owned(), - /// }); - /// - /// let mut guard = rwlock.write().await; - /// let mut mapped_guard = - /// RwLockWriteGuard::filter_map( - /// guard, - /// |foo| { - /// if foo.a > 10 { Some(&mut foo.a) } else { None } - /// }, - /// ) - /// .expect("should have mapped"); - /// - /// *mapped_guard = 12; - /// assert_eq!(*mapped_guard, 12); - /// # } - /// ``` - pub fn filter_map(orig: Self, f: F) -> Result, Self> + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. + /// + /// A panic in the closure releases the guard. Call this as `RwLockWriteGuard::filter_map(guard, + /// project)`. + pub fn filter_map( + mut orig: Self, + project: F, + ) -> Result, Self> where F: FnOnce(&mut T) -> Option<&mut U>, - U: ?Sized, { - match f(unsafe { &mut *orig.lock.c.get() }) { - Some(d) => { - let d = NonNull::from(d); - let permits_acquired = orig.permits_acquired; - let orig = ManuallyDrop::new(orig); - Ok(MappedRwLockWriteGuard::new( - d, - &orig.lock.s, - permits_acquired, - )) - } - None => Err(orig), - } + let Some(data) = project(&mut *orig).map(NonNull::from) else { + return Err(orig); + }; + Ok(mapped_write_guard::new(data, orig.access.into_semaphore())) } - /// Atomically downgrades the write lock to a read lock. - /// - /// This method changes the lock from exclusive mode to shared mode atomically, - /// preventing other writers from acquiring the lock in between. - /// - /// This is more efficient than dropping the write guard and acquiring a new read guard. - /// - /// # Examples + /// Retains shared access while releasing exclusive access. /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let lock = Arc::new(RwLock::new(1)); - /// - /// let mut write_guard = lock.write().await; - /// *write_guard = 2; - /// - /// let read_guard = write_guard.downgrade(); - /// assert_eq!(*read_guard, 2); - /// - /// assert!(lock.try_write().is_none()); - /// - /// drop(read_guard); - /// assert!(lock.try_write().is_some()); - /// # } - /// ``` + /// There is no unlocked interval in which another writer can modify the value. Queued + /// requests retain their order, so a waiting writer can prevent later readers from joining. pub fn downgrade(self) -> RwLockReadGuard<'a, T> { - // Prevent the original write guard from running its Drop implementation, - // which would release all permits. This must be done BEFORE any operation - // that might panic to ensure panic safety. - let guard = ManuallyDrop::new(self); + read_guard::new(self.access.downgrade()) + } +} - // Release max_readers - 1 permits to convert the write lock to a read lock. - // The remaining 1 permit is kept for the read lock. - guard.lock.s.release(guard.permits_acquired - 1); - RwLockReadGuard { lock: guard.lock } +impl fmt::Debug for RwLockWriteGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Display for RwLockWriteGuard<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) } } diff --git a/licenserc.toml b/licenserc.toml index eec57c5..fc6d404 100644 --- a/licenserc.toml +++ b/licenserc.toml @@ -29,15 +29,6 @@ excludes = [ "asyncband/src/pool/mod.rs", "asyncband/src/pool/state.rs", "asyncband/src/pool/unbounded.rs", - "asyncband/src/rwlock/mapped_read_guard.rs", - "asyncband/src/rwlock/mapped_write_guard.rs", - "asyncband/src/rwlock/mod.rs", - "asyncband/src/rwlock/owned_mapped_read_guard.rs", - "asyncband/src/rwlock/owned_mapped_write_guard.rs", - "asyncband/src/rwlock/owned_read_guard.rs", - "asyncband/src/rwlock/owned_write_guard.rs", - "asyncband/src/rwlock/read_guard.rs", - "asyncband/src/rwlock/write_guard.rs", "tests-integration/tests/pool_recycle_cancelled_test.rs", "tests-integration/tests/pool_replenish_test.rs", ] diff --git a/tests-integration/tests/rwlock_test.rs b/tests-integration/tests/rwlock_test.rs index 1f95bd6..da6fdd7 100644 --- a/tests-integration/tests/rwlock_test.rs +++ b/tests-integration/tests/rwlock_test.rs @@ -296,3 +296,65 @@ async fn queued_writer_precedes_a_later_reader() { let reader_guard = assert_ready!(poll_once(later_reader.as_mut())); assert_eq!(*reader_guard, 100); } + +#[test] +fn cancelling_a_writer_returns_reserved_reader_slots() { + let lock = RwLock::with_max_readers(0, NonZeroUsize::new(2).unwrap()); + let first = lock.try_read().unwrap(); + let mut writer = Box::pin(lock.write()); + let mut reader = Box::pin(lock.read()); + assert_pending!(poll_once(writer.as_mut())); + assert_pending!(poll_once(reader.as_mut())); + assert!(lock.try_read().is_none()); + + drop(writer); + let second = assert_ready!(poll_once(reader.as_mut())); + assert!(lock.try_read().is_none()); + drop(first); + drop(second); + assert!(lock.try_write().is_some()); +} + +#[test] +fn cancelling_granted_owned_requests_releases_access_and_ownership() { + let lock = Arc::new(RwLock::new(0)); + let writer = lock.try_write().unwrap(); + let mut reader = Box::pin(lock.clone().read_owned()); + assert_pending!(poll_once(reader.as_mut())); + drop(writer); + // Cancel after the semaphore granted access, before a guard is constructed by the future. + drop(reader); + assert_eq!(Arc::strong_count(&lock), 1); + + let reader = lock.try_read().unwrap(); + let mut writer = Box::pin(lock.clone().write_owned()); + assert_pending!(poll_once(writer.as_mut())); + drop(reader); + drop(writer); + assert_eq!(Arc::strong_count(&lock), 1); + assert!(lock.try_write().is_some()); +} + +#[test] +fn downgrade_preserves_queue_order_at_reader_limits() { + for limit in [1, 3, usize::MAX] { + let lock = RwLock::with_max_readers(0, NonZeroUsize::new(limit).unwrap()); + let writer = lock.try_write().unwrap(); + let mut next_writer = Box::pin(lock.write()); + let mut reader = Box::pin(lock.read()); + assert_pending!(poll_once(next_writer.as_mut())); + assert_pending!(poll_once(reader.as_mut())); + + let held_reader = writer.downgrade(); + assert_pending!(poll_once(next_writer.as_mut())); + assert_pending!(poll_once(reader.as_mut())); + assert!(lock.try_read().is_none()); + drop(held_reader); + + let next_writer = assert_ready!(poll_once(next_writer.as_mut())); + assert_pending!(poll_once(reader.as_mut())); + drop(next_writer); + drop(assert_ready!(poll_once(reader.as_mut()))); + assert!(lock.try_write().is_some()); + } +} diff --git a/tests-integration/tests/traits_test.rs b/tests-integration/tests/traits_test.rs index bf10a0e..2220bef 100644 --- a/tests-integration/tests/traits_test.rs +++ b/tests-integration/tests/traits_test.rs @@ -34,6 +34,9 @@ use asyncband::oneshot; use asyncband::pool; use asyncband::pool::ManageObject; use asyncband::pool::ObjectStatus; +use asyncband::rwlock::MappedRwLockReadGuard; +use asyncband::rwlock::MappedRwLockWriteGuard; +use asyncband::rwlock::OwnedMappedRwLockWriteGuard; use asyncband::rwlock::OwnedRwLockReadGuard; use asyncband::rwlock::RwLock; use asyncband::rwlock::RwLockReadGuard; @@ -122,6 +125,9 @@ fn movable_public_types_are_send() { fn assert_send_value(_: T) {} assert_send::>>(); + assert_send::>>(); + assert_send::>>(); + assert_send::>>(); assert_send::>(); assert_send::>(); assert_send::>>(); diff --git a/tests-integration/tests/unsafe_paths_test.rs b/tests-integration/tests/unsafe_paths_test.rs index 044492c..9b54501 100644 --- a/tests-integration/tests/unsafe_paths_test.rs +++ b/tests-integration/tests/unsafe_paths_test.rs @@ -170,3 +170,161 @@ fn lazy_cell_resumes_a_pinned_attempt_in_place() { let mut force = pin!(LazyCell::force_pin(lazy.as_ref())); assert_eq!(poll_once(force.as_mut()), Poll::Ready(&42)); } + +#[test] +fn rwlock_projection_panics_release_access() { + use std::panic::AssertUnwindSafe; + use std::panic::catch_unwind; + + let lock = Arc::new(RwLock::new(vec![1, 2])); + + // Exercise every guard representation. Some closures mutate before unwinding: the value + // must remain accessible afterward, and the lock must neither leak access nor be poisoned. + macro_rules! check { + ($project:expr) => { + assert!( + catch_unwind(AssertUnwindSafe(|| { + drop($project); + })) + .is_err() + ); + assert!(lock.try_write().is_some()); + assert_eq!(Arc::strong_count(&lock), 1); + }; + } + + check!(RwLockReadGuard::map::<(), _>( + lock.try_read().unwrap(), + |_| panic!("projection") + )); + check!(RwLockWriteGuard::filter_map::<(), _>( + lock.try_write().unwrap(), + |values| { + values.push(3); + panic!("projection"); + } + )); + check!(OwnedRwLockReadGuard::filter_map::<(), _>( + lock.clone().try_read_owned().unwrap(), + |_| panic!("projection") + )); + check!(OwnedRwLockWriteGuard::map::<(), _>( + lock.clone().try_write_owned().unwrap(), + |_| panic!("projection") + )); + + let read = RwLockReadGuard::map(lock.try_read().unwrap(), Vec::as_slice); + check!(MappedRwLockReadGuard::filter_map::<(), _>( + read, + |_| panic!("projection") + )); + let write = RwLockWriteGuard::map(lock.try_write().unwrap(), Vec::as_mut_slice); + check!(MappedRwLockWriteGuard::map::<(), _>(write, |_| panic!( + "projection" + ))); + let read = OwnedRwLockReadGuard::map(lock.clone().try_read_owned().unwrap(), Vec::as_slice); + check!(OwnedMappedRwLockReadGuard::map::<(), _>(read, |_| panic!( + "projection" + ))); + let write = + OwnedRwLockWriteGuard::map(lock.clone().try_write_owned().unwrap(), Vec::as_mut_slice); + check!(OwnedMappedRwLockWriteGuard::filter_map::<(), _>( + write, + |_| panic!("projection") + )); + + assert_eq!(*lock.try_read().unwrap(), [1, 2, 3]); +} + +#[test] +fn rwlock_failed_projection_keeps_access_and_mutations() { + let lock = Arc::new(RwLock::new(vec![Some(1)])); + let guard = lock.try_write().unwrap(); + let guard = RwLockWriteGuard::filter_map(guard, |values| { + values.push(None); + None::<&mut i32> + }) + .unwrap_err(); + assert!(lock.try_read().is_none()); + let mut slot = RwLockWriteGuard::map(guard, |values| &mut values[1]); + slot = MappedRwLockWriteGuard::filter_map(slot, Option::as_mut).unwrap_err(); + *slot = Some(2); + let slot = slot.downgrade(); + let slot = MappedRwLockReadGuard::filter_map(slot, |_| None::<&i32>).unwrap_err(); + assert_eq!(*slot, Some(2)); + assert!(lock.try_write().is_none()); + drop(slot); + + let guard = lock.clone().try_write_owned().unwrap(); + let slot = OwnedRwLockWriteGuard::map(guard, |values| &mut values[1]); + let mut slot = OwnedMappedRwLockWriteGuard::filter_map(slot, |_| None::<&mut i32>).unwrap_err(); + *slot = Some(3); + let slot = slot.downgrade(); + let slot = OwnedMappedRwLockReadGuard::filter_map(slot, |_| None::<&i32>).unwrap_err(); + let weak = Arc::downgrade(&lock); + drop(lock); + assert_eq!(*slot, Some(3)); + drop(slot); + assert!(weak.upgrade().is_none()); +} + +#[test] +fn rwlock_downgrade_unwind_releases_retained_access() { + use std::num::NonZeroUsize; + use std::panic::AssertUnwindSafe; + use std::panic::catch_unwind; + use std::task::Wake; + + struct PanicOnWake; + impl Wake for PanicOnWake { + fn wake(self: Arc) { + panic!("wake during downgrade"); + } + } + + fn check(lock: &RwLock<(usize, usize)>, downgrade: impl FnOnce()) { + let mut reader = Box::pin(lock.read()); + let waker = Waker::from(Arc::new(PanicOnWake)); + assert!( + reader + .as_mut() + .poll(&mut Context::from_waker(&waker)) + .is_pending() + ); + assert!(catch_unwind(AssertUnwindSafe(downgrade)).is_err()); + // The queued reader received its permit before waking. After it releases that permit, + // no read access from the failed downgrade may remain. + let Poll::Ready(reader) = poll_once(reader.as_mut()) else { + panic!("reader was not granted access"); + }; + drop(reader); + assert!(lock.try_write().is_some()); + } + + for limit in [2, usize::MAX] { + let lock = Arc::new(RwLock::with_max_readers( + (1, 2), + NonZeroUsize::new(limit).unwrap(), + )); + let guard = lock.try_write().unwrap(); + check(&lock, || { + drop(guard.downgrade()); + }); + let guard = RwLockWriteGuard::map(lock.try_write().unwrap(), |value| &mut value.0); + check(&lock, || { + drop(guard.downgrade()); + }); + let guard = lock.clone().try_write_owned().unwrap(); + check(&lock, || { + drop(guard.downgrade()); + }); + assert_eq!(Arc::strong_count(&lock), 1); + let guard = OwnedRwLockWriteGuard::map(lock.clone().try_write_owned().unwrap(), |value| { + &mut value.1 + }); + check(&lock, || { + drop(guard.downgrade()); + }); + assert_eq!(Arc::strong_count(&lock), 1); + } +}