diff --git a/LICENSE b/LICENSE index 546e801..db4c32c 100644 --- a/LICENSE +++ b/LICENSE @@ -262,13 +262,24 @@ 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 asyncband/src/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. The tokens release and downgrade permits through Asyncband's +usize-based semaphore implementation. 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 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 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..0da892e --- /dev/null +++ b/asyncband/src/rwlock/access.rs @@ -0,0 +1,130 @@ +// 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. + +//! Tokens that own acquired `RwLock` permits and release them on drop. +//! +//! Guards hold a token instead of implementing `Drop`, so projecting or downgrading a guard moves +//! the token into the new guard. + +use std::sync::Arc; + +use crate::internal::semaphore::Semaphore; +use crate::rwlock::RwLock; + +/// Keeps the lock's semaphore alive while a token holds permits from it. +pub trait Owner { + /// Returns the semaphore that the token's permits belong to. + 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 + } +} + +/// Owns one read permit. +pub struct ReadAccess { + // `None` only after the owner moved to a replacement token. + owner: Option, +} + +impl ReadAccess { + /// Takes over one permit already acquired from `owner`. + pub fn new(owner: O) -> Self { + Self { owner: Some(owner) } + } + + /// Returns the owner that the permit was acquired from. + pub fn owner(&self) -> &O { + self.owner.as_ref().expect("token still has its owner") + } +} + +impl<'a, T: ?Sized> ReadAccess<&'a RwLock> { + /// Drops the value type so that mapped guards need not name it. + pub fn into_semaphore(mut self) -> ReadAccess<&'a Semaphore> { + let lock = self.owner.take().expect("token still has its owner"); + ReadAccess::new(&lock.s) + } +} + +impl Drop for ReadAccess { + fn drop(&mut self) { + if let Some(owner) = &self.owner { + owner.semaphore().release(1); + } + } +} + +/// Owns every permit of a lock, which together grant write access. +pub struct WriteAccess { + // `None` only after the owner moved to a replacement token. + owner: Option, + permits_acquired: usize, +} + +impl WriteAccess { + /// Takes over `permits_acquired` permits already acquired from `owner`. + pub fn new(owner: O, permits_acquired: usize) -> Self { + Self { + owner: Some(owner), + permits_acquired, + } + } + + /// Returns the owner that the permits were acquired from. + pub fn owner(&self) -> &O { + self.owner.as_ref().expect("token still has its owner") + } + + /// Releases all permits but one, which the returned token keeps. + pub fn downgrade(mut self) -> ReadAccess { + let owner = self.owner.take().expect("token still has its owner"); + let read = ReadAccess::new(owner); + read.owner().semaphore().release(self.permits_acquired - 1); + read + } +} + +impl<'a, T: ?Sized> WriteAccess<&'a RwLock> { + /// Drops the value type so that mapped guards need not name it. + pub fn into_semaphore(mut self) -> WriteAccess<&'a Semaphore> { + let lock = self.owner.take().expect("token still has its owner"); + WriteAccess::new(&lock.s, self.permits_acquired) + } +} + +impl Drop for WriteAccess { + fn drop(&mut self) { + if let Some(owner) = &self.owner { + owner.semaphore().release(self.permits_acquired); + } + } +} diff --git a/asyncband/src/rwlock/mapped_read_guard.rs b/asyncband/src/rwlock/mapped_read_guard.rs index 1b06bd8..f10a1b9 100644 --- a/asyncband/src/rwlock/mapped_read_guard.rs +++ b/asyncband/src/rwlock/mapped_read_guard.rs @@ -1,7 +1,26 @@ -// 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 @@ -11,54 +30,17 @@ 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(), -/// }, -/// }; +/// Shared access to a projection of a locked value borrowed for the guard lifetime. /// -/// let rwlock = RwLock::new(user); -/// let guard = rwlock.read().await; -/// let profile_guard = RwLockReadGuard::map(guard, |user| &user.profile); -/// -/// // 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, + access: ReadAccess<&'a Semaphore>, variance: PhantomData T>, } @@ -74,21 +56,15 @@ unsafe impl Send for MappedRwLockReadGuard<'_, T> {} unsafe impl Sync for MappedRwLockReadGuard<'_, T> {} impl<'a, T: ?Sized> MappedRwLockReadGuard<'a, T> { - pub(crate) fn new(d: NonNull, s: &'a semaphore::Semaphore) -> Self { + pub(crate) fn new(d: NonNull, access: ReadAccess<&'a Semaphore>) -> Self { Self { d, - s, + access, variance: PhantomData, } } } -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) @@ -110,50 +86,11 @@ impl Deref for MappedRwLockReadGuard<'_, T> { } 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 + /// Selects a component while retaining the same lock access. /// - /// ``` - /// # #[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(), - /// }, - /// }; - /// - /// 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"); - /// # } - /// ``` + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `MappedRwLockReadGuard::map(guard, f)` to avoid shadowing methods of the + /// value. pub fn map(orig: Self, f: F) -> MappedRwLockReadGuard<'a, U> where F: FnOnce(&T) -> &U, @@ -163,58 +100,14 @@ impl<'a, T: ?Sized> MappedRwLockReadGuard<'a, T> { // 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) + MappedRwLockReadGuard::new(d, 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(), - /// ); + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// 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"); - /// } - /// } - /// # } - /// ``` + /// A panic in the closure releases the guard. Call this as + /// `MappedRwLockReadGuard::filter_map(guard, f)`. pub fn filter_map(orig: Self, f: F) -> Result, Self> where F: FnOnce(&T) -> Option<&U>, @@ -226,8 +119,7 @@ impl<'a, T: ?Sized> MappedRwLockReadGuard<'a, T> { 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)) + Ok(MappedRwLockReadGuard::new(d, orig.access)) } None => Err(orig), } diff --git a/asyncband/src/rwlock/mapped_write_guard.rs b/asyncband/src/rwlock/mapped_write_guard.rs index 4307842..0a5c6d3 100644 --- a/asyncband/src/rwlock/mapped_write_guard.rs +++ b/asyncband/src/rwlock/mapped_write_guard.rs @@ -1,68 +1,47 @@ -// 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; -/// 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, + access: WriteAccess<&'a Semaphore>, // Mutable access requires invariance over T. variance: PhantomData<&'a mut T>, } @@ -77,22 +56,15 @@ unsafe impl Sync for MappedRwLockWriteGuard<'_, T> {} 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 { + pub(crate) fn new(d: NonNull, access: WriteAccess<&'a Semaphore>) -> Self { Self { d, - s, - permits_acquired, + access, 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) @@ -121,51 +93,11 @@ impl DerefMut for MappedRwLockWriteGuard<'_, T> { } impl<'a, T: ?Sized> MappedRwLockWriteGuard<'a, T> { - /// Projects this guard to a deeper mutable component. + /// Selects a component while retaining the same lock access. /// - /// 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; - /// - /// #[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"); - /// # } - /// ``` + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `MappedRwLockWriteGuard::map(guard, f)` to avoid shadowing methods of the + /// value. pub fn map(mut orig: Self, f: F) -> MappedRwLockWriteGuard<'a, U> where F: FnOnce(&mut T) -> &mut U, @@ -175,70 +107,14 @@ impl<'a, T: ?Sized> MappedRwLockWriteGuard<'a, T> { // 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) + MappedRwLockWriteGuard::new(d, 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 + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// ``` - /// # #[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, - /// } - /// - /// #[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"); - /// } - /// } - /// # } - /// ``` + /// A panic in the closure releases the guard. Call this as + /// `MappedRwLockWriteGuard::filter_map(guard, f)`. pub fn filter_map(mut orig: Self, f: F) -> Result, Self> where F: FnOnce(&mut T) -> Option<&mut U>, @@ -250,66 +126,17 @@ impl<'a, T: ?Sized> MappedRwLockWriteGuard<'a, T> { 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)) + Ok(MappedRwLockWriteGuard::new(d, orig.access)) } None => Err(orig), } } - /// 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 + /// Retains shared access to the same projection while releasing exclusive access. /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; - /// - /// 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); - - // Release max_readers - 1 permits to convert the write lock to a read lock. - guard.s.release(guard.permits_acquired - 1); - - // Create the mapped read guard with 1 permit (standard for read locks) - MappedRwLockReadGuard::new(guard.d, guard.s) + MappedRwLockReadGuard::new(self.d, self.access.downgrade()) } } diff --git a/asyncband/src/rwlock/mod.rs b/asyncband/src/rwlock/mod.rs index 13ec0a2..7358712 100644 --- a/asyncband/src/rwlock/mod.rs +++ b/asyncband/src/rwlock/mod.rs @@ -1,46 +1,103 @@ -// 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. +//! Shared and exclusive access to a value, with asynchronous waiting. //! -//! 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. +//! 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. //! -//! 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. +//! 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. //! -//! 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. -//! -//! # 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 +//! +//! 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. //! -//! // 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 +//! ``` +//! use std::sync::Arc; //! -//! // 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 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(); //! # } //! ``` @@ -50,6 +107,7 @@ use std::num::NonZeroUsize; use crate::internal::semaphore::Semaphore; +mod access; mod mapped_read_guard; mod mapped_write_guard; mod owned_mapped_read_guard; @@ -68,13 +126,10 @@ 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, s: Semaphore, c: UnsafeCell, @@ -107,35 +162,16 @@ impl fmt::Debug for RwLock { } impl RwLock { - /// Creates a new reader-writer lock in an unlocked state ready for use. - /// - /// # Examples - /// - /// ``` - /// use asyncband::rwlock::RwLock; - /// - /// let rwlock = RwLock::new(5); - /// ``` + /// Wraps a value with a reader limit of `usize::MAX >> 1`. pub const fn new(t: T) -> RwLock { // Effectively unlimited, while keeping permit arithmetic far from usize::MAX. RwLock::with_max_readers(t, NonZeroUsize::new(usize::MAX >> 1).unwrap()) } - /// 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. + /// Wraps a value with an explicit nonzero limit on simultaneously held read guards. /// - /// # Examples - /// - /// ``` - /// use std::num::NonZeroUsize; - /// - /// use asyncband::rwlock::RwLock; - /// - /// let max_readers = NonZeroUsize::new(1024).expect("max_readers must be non-zero"); - /// let rwlock = RwLock::with_max_readers(5, max_readers); - /// ``` + /// 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(t: T, max_readers: NonZeroUsize) -> RwLock { let max_readers = max_readers.get(); let s = Semaphore::new(max_readers); @@ -143,37 +179,16 @@ impl RwLock { RwLock { max_readers, c, s } } - /// Consumes the lock, returning the underlying data. - /// - /// # Examples - /// - /// ``` - /// use asyncband::rwlock::RwLock; - /// - /// let lock = RwLock::new(1); - /// let n = lock.into_inner(); - /// assert_eq!(n, 1); - /// ``` + /// Unwraps the value by consuming its lock. pub fn into_inner(self) -> T { self.c.into_inner() } } impl RwLock { - /// Returns a mutable reference to the underlying data. + /// Borrows the value exclusively through an exclusive borrow of the lock itself. /// - /// 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; - /// ``` + /// This requires no acquisition because existing guards prevent borrowing the lock mutably. pub fn get_mut(&mut self) -> &mut T { self.c.get_mut() } diff --git a/asyncband/src/rwlock/owned_mapped_read_guard.rs b/asyncband/src/rwlock/owned_mapped_read_guard.rs index e9a7365..9ca2d45 100644 --- a/asyncband/src/rwlock/owned_mapped_read_guard.rs +++ b/asyncband/src/rwlock/owned_mapped_read_guard.rs @@ -1,68 +1,46 @@ -// 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(), -/// }, -/// }; -/// -/// let rwlock = Arc::new(RwLock::new(user)); -/// let guard = rwlock.read_owned().await; -/// let profile_guard = OwnedRwLockReadGuard::map(guard, |user| &user.profile); +/// Shared access to a projection of a locked value kept alive by an `Arc`. /// -/// // 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 { - lock: Arc>, + access: ReadAccess>>, d: NonNull, variance: PhantomData U>, } @@ -77,19 +55,14 @@ unsafe impl Send for OwnedMappedRwLoc unsafe impl Sync for OwnedMappedRwLockReadGuard {} impl OwnedMappedRwLockReadGuard { - pub(crate) fn new(d: NonNull, lock: Arc>) -> Self { + pub(crate) fn new(d: NonNull, access: ReadAccess>>) -> Self { Self { d, - lock, + access, variance: PhantomData, } } } -impl Drop for OwnedMappedRwLockReadGuard { - fn drop(&mut self) { - self.lock.s.release(1); - } -} impl fmt::Debug for OwnedMappedRwLockReadGuard { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -112,52 +85,11 @@ impl Deref for OwnedMappedRwLockReadGuard { } 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. - /// - /// # 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, - /// }, - /// }; + /// Selects a component while retaining the same lock access. /// - /// 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); - /// # } - /// ``` + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `OwnedMappedRwLockReadGuard::map(guard, f)` to avoid shadowing methods of + /// the value. pub fn map(orig: Self, f: F) -> OwnedMappedRwLockReadGuard where F: FnOnce(&U) -> &V, @@ -167,79 +99,14 @@ impl OwnedMappedRwLockReadGuard { // 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) + OwnedMappedRwLockReadGuard::new(d, 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, - /// } + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// #[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"); - /// # } - /// ``` + /// A panic in the closure releases the guard. Call this as + /// `OwnedMappedRwLockReadGuard::filter_map(guard, f)`. pub fn filter_map(orig: Self, f: F) -> Result, Self> where F: FnOnce(&U) -> Option<&V>, @@ -251,14 +118,7 @@ impl OwnedMappedRwLockReadGuard { match f(unsafe { orig.d.as_ref() }) { Some(d) => { let d = NonNull::from(d); - 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) }; - - Ok(OwnedMappedRwLockReadGuard::new(d, lock)) + Ok(OwnedMappedRwLockReadGuard::new(d, orig.access)) } None => Err(orig), } diff --git a/asyncband/src/rwlock/owned_mapped_write_guard.rs b/asyncband/src/rwlock/owned_mapped_write_guard.rs index 5734494..5da96cd 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,58 +33,16 @@ use std::sync::Arc; use crate::rwlock::OwnedMappedRwLockReadGuard; use crate::rwlock::RwLock; +use crate::rwlock::access::WriteAccess; -/// 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, + access: WriteAccess>>, // Mutable access requires invariance over U. variance: PhantomData<*mut U>, } @@ -82,20 +58,14 @@ unsafe impl Sync for OwnedMappedRwLoc unsafe impl Send for OwnedMappedRwLockWriteGuard {} impl OwnedMappedRwLockWriteGuard { - pub(crate) fn new(d: NonNull, lock: Arc>, permits_acquired: usize) -> Self { + pub(crate) fn new(d: NonNull, access: WriteAccess>>) -> Self { Self { d, - lock, - permits_acquired, + access, 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 { @@ -125,54 +95,11 @@ impl DerefMut for OwnedMappedRwLockWriteGuard { } 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. + /// Selects a component while retaining the same lock access. /// - /// # 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(), - /// }, - /// }; - /// - /// 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"); - /// # } - /// ``` + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `OwnedMappedRwLockWriteGuard::map(guard, f)` to avoid shadowing methods + /// of the value. pub fn map(mut orig: Self, f: F) -> OwnedMappedRwLockWriteGuard where F: FnOnce(&mut U) -> &mut V, @@ -182,82 +109,14 @@ impl OwnedMappedRwLockWriteGuard { // 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) + OwnedMappedRwLockWriteGuard::new(d, 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 + /// 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::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, - /// } - /// - /// 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"); - /// } - /// } - /// # } - /// ``` + /// A panic in the closure releases the guard. Call this as + /// `OwnedMappedRwLockWriteGuard::filter_map(guard, f)`. pub fn filter_map(mut orig: Self, f: F) -> Result, Self> where F: FnOnce(&mut U) -> Option<&mut V>, @@ -269,76 +128,17 @@ impl OwnedMappedRwLockWriteGuard { 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)) + Ok(OwnedMappedRwLockWriteGuard::new(d, orig.access)) } None => Err(orig), } } - /// 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. + /// Retains shared access to the same projection while releasing exclusive access. /// - /// The returned `OwnedMappedRwLockReadGuard` preserves the original mapping and - /// has a `'static` lifetime. - /// - /// # Examples - /// - /// ``` - /// # #[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); - - // Release max_readers - 1 permits to convert the write lock to a read lock. - guard.lock.s.release(guard.permits_acquired - 1); - - // 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) + OwnedMappedRwLockReadGuard::new(self.d, self.access.downgrade()) } } diff --git a/asyncband/src/rwlock/owned_read_guard.rs b/asyncband/src/rwlock/owned_read_guard.rs index b9a2ae5..07fab31 100644 --- a/asyncband/src/rwlock/owned_read_guard.rs +++ b/asyncband/src/rwlock/owned_read_guard.rs @@ -1,7 +1,26 @@ -// 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 @@ -11,92 +30,40 @@ use std::sync::Arc; use crate::rwlock::OwnedMappedRwLockReadGuard; use crate::rwlock::RwLock; +use crate::rwlock::access::ReadAccess; 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); + /// Waits for a reader slot and returns a guard retaining this `Arc`. /// - /// 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(); - /// # } - /// ``` + /// 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; - OwnedRwLockReadGuard { lock: self } + OwnedRwLockReadGuard { + access: ReadAccess::new(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; + /// Returns an owned read guard immediately, or `None` when no reader slot is available. /// - /// 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()); - /// ``` + /// This consumes the passed `Arc` even when acquisition fails. pub fn try_read_owned(self: Arc) -> Option> { if self.s.try_acquire(1) { - Some(OwnedRwLockReadGuard { lock: self }) + Some(OwnedRwLockReadGuard { + access: ReadAccess::new(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); - } + pub(super) access: ReadAccess>>, } impl fmt::Debug for OwnedRwLockReadGuard { @@ -114,114 +81,43 @@ impl fmt::Display for OwnedRwLockReadGuard { impl Deref for OwnedRwLockReadGuard { type Target = T; fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.c.get() } + 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; - /// - /// use asyncband::rwlock::OwnedRwLockReadGuard; - /// use asyncband::rwlock::RwLock; + /// Selects a component while retaining the same lock access. /// - /// #[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); - /// # } - /// ``` + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `OwnedRwLockReadGuard::map(guard, f)` to avoid shadowing methods of the + /// value. pub fn map(orig: Self, f: 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); - - // SAFETY: The guard is wrapped in `ManuallyDrop` and will not be dropped, - // so the `Arc` can be moved out to transfer lock ownership to the new guard. - let lock = unsafe { std::ptr::read(&orig.lock) }; - - OwnedMappedRwLockReadGuard::new(d, lock) + // SAFETY: The guard keeps the lock alive and holds shared access, so the pointer to the + // value is valid and dereferencing it is safe. + let d = std::ptr::NonNull::from(f(unsafe { &*orig.access.owner().c.get() })); + OwnedMappedRwLockReadGuard::new(d, orig.access) } - /// Attempts to project this guard to a shared component of the protected value. + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// 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"); - /// # } - /// ``` + /// A panic in the closure releases the guard. Call this as + /// `OwnedRwLockReadGuard::filter_map(guard, f)`. pub fn filter_map(orig: Self, f: 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() }) { + // SAFETY: The guard keeps the lock alive and holds shared access, so the pointer to the + // value is valid and dereferencing it is safe. + match f(unsafe { &*orig.access.owner().c.get() }) { Some(d) => { let d = std::ptr::NonNull::from(d); - let orig = std::mem::ManuallyDrop::new(orig); - - // SAFETY: The guard is wrapped in `ManuallyDrop` and will not be dropped, - // so the `Arc` can be moved out to transfer lock ownership to the new guard. - let lock = unsafe { std::ptr::read(&orig.lock) }; - - Ok(OwnedMappedRwLockReadGuard::new(d, lock)) + Ok(OwnedMappedRwLockReadGuard::new(d, orig.access)) } None => Err(orig), } diff --git a/asyncband/src/rwlock/owned_write_guard.rs b/asyncband/src/rwlock/owned_write_guard.rs index 99f1342..f17accd 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,63 +33,28 @@ use std::sync::Arc; use crate::rwlock::OwnedMappedRwLockWriteGuard; use crate::rwlock::OwnedRwLockReadGuard; use crate::rwlock::RwLock; +use crate::rwlock::access::WriteAccess; impl RwLock { - /// Waits for exclusive write access and returns a guard that owns this [`Arc`]. + /// Waits for exclusive access and returns a guard retaining 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; - /// # } - /// ``` + /// Ordering and cancellation follow [`Self::write`]. pub async fn write_owned(self: Arc) -> OwnedRwLockWriteGuard { self.s.acquire(self.max_readers).await; + let permits_acquired = self.max_readers; OwnedRwLockWriteGuard { - permits_acquired: self.max_readers, - lock: self, + access: WriteAccess::new(self, permits_acquired), } } - /// 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); + /// Returns an owned write guard immediately, or `None` when exclusive access is unavailable. /// - /// let mut v = lock.try_write_owned().unwrap(); - /// *v = 2; - /// ``` + /// This consumes the passed `Arc` even when acquisition fails. pub fn try_write_owned(self: Arc) -> Option> { if self.s.try_acquire(self.max_readers) { + let permits_acquired = self.max_readers; Some(OwnedRwLockWriteGuard { - permits_acquired: self.max_readers, - lock: self, + access: WriteAccess::new(self, permits_acquired), }) } else { None @@ -79,25 +62,17 @@ impl RwLock { } } -/// 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 { - permits_acquired: usize, - lock: Arc>, + access: WriteAccess>>, } 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) @@ -113,50 +88,22 @@ impl fmt::Display for OwnedRwLockWriteGuard { impl Deref for OwnedRwLockWriteGuard { type Target = T; fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.c.get() } + unsafe { &*self.access.owner().c.get() } } } impl DerefMut for OwnedRwLockWriteGuard { fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { &mut *self.lock.c.get() } + 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 - /// - /// ``` - /// # #[tokio::main] - /// # async fn main() { - /// use std::sync::Arc; + /// Selects a component while retaining the same lock access. /// - /// 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"); - /// # } - /// ``` + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `OwnedRwLockWriteGuard::map(guard, f)` to avoid shadowing methods of the + /// value. pub fn map(orig: Self, f: F) -> OwnedMappedRwLockWriteGuard where F: FnOnce(&mut T) -> &mut U, @@ -164,59 +111,15 @@ impl OwnedRwLockWriteGuard { { // 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 d = NonNull::from(f(unsafe { &mut *orig.access.owner().c.get() })); + OwnedMappedRwLockWriteGuard::new(d, 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 - /// - /// ``` - /// # #[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, - /// } + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// 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"); - /// # } - /// ``` + /// A panic in the closure releases the guard. Call this as + /// `OwnedRwLockWriteGuard::filter_map(guard, f)`. pub fn filter_map(orig: Self, f: F) -> Result, Self> where F: FnOnce(&mut T) -> Option<&mut U>, @@ -224,67 +127,20 @@ impl OwnedRwLockWriteGuard { { // 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() }) { + let d = match f(unsafe { &mut *orig.access.owner().c.get() }) { Some(d) => NonNull::from(d), None => 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(OwnedMappedRwLockWriteGuard::new(d, 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); - - // 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); - - // 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 } + OwnedRwLockReadGuard { + access: self.access.downgrade(), + } } } diff --git a/asyncband/src/rwlock/read_guard.rs b/asyncband/src/rwlock/read_guard.rs index 48739cf..3e8290e 100644 --- a/asyncband/src/rwlock/read_guard.rs +++ b/asyncband/src/rwlock/read_guard.rs @@ -1,105 +1,75 @@ -// 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; 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; + /// Waits for a reader slot and returns a guard borrowing this lock. /// - /// 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(); - /// # } - /// ``` + /// An earlier queued writer must finish first. Cancelling this future releases its queue + /// position and any reserved permits. See [queue ordering](crate::rwlock) before acquiring + /// recursively. pub async fn read(&self) -> RwLockReadGuard<'_, T> { self.s.acquire(1).await; - RwLockReadGuard { lock: self } + RwLockReadGuard { + access: ReadAccess::new(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); + /// Returns a read guard immediately, or `None` when a reader slot cannot be acquired. /// - /// let v = lock.try_write().unwrap(); - /// assert!(lock.try_read().is_none()); - /// ``` + /// Readers cannot bypass an earlier queued writer. pub fn try_read(&self) -> Option> { if self.s.try_acquire(1) { - Some(RwLockReadGuard { lock: self }) + Some(RwLockReadGuard { + access: ReadAccess::new(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, + pub(super) access: ReadAccess<&'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); - } -} - impl fmt::Debug for RwLockReadGuard<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&**self, f) @@ -115,70 +85,29 @@ impl fmt::Display for RwLockReadGuard<'_, T> { impl Deref for RwLockReadGuard<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.c.get() } + 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. - /// - /// # 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())); + /// Selects a component while retaining the same lock access. /// - /// let guard = rwlock.read().await; - /// let mapped_guard = RwLockReadGuard::map(guard, |f| &f.0); - /// - /// assert_eq!(&*mapped_guard, "hello"); - /// # } - /// ``` + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `RwLockReadGuard::map(guard, f)` to avoid shadowing methods of the value. pub fn map(orig: Self, f: 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) + MappedRwLockReadGuard::new(d, 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"); + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// assert_eq!(&*mapped_guard, "hello"); - /// # } - /// ``` + /// A panic in the closure releases the guard. Call this as + /// `RwLockReadGuard::filter_map(guard, f)`. pub fn filter_map(orig: Self, f: F) -> Result, Self> where F: FnOnce(&T) -> Option<&U>, @@ -187,8 +116,7 @@ impl<'a, T: ?Sized> RwLockReadGuard<'a, T> { match f(&*orig) { Some(d) => { let d = NonNull::from(d); - let orig = ManuallyDrop::new(orig); - Ok(MappedRwLockReadGuard::new(d, &orig.lock.s)) + Ok(MappedRwLockReadGuard::new(d, orig.access.into_semaphore())) } None => Err(orig), } diff --git a/asyncband/src/rwlock/write_guard.rs b/asyncband/src/rwlock/write_guard.rs index a5dd89f..fbdcb08 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,57 +32,24 @@ use std::ptr::NonNull; use crate::rwlock::MappedRwLockWriteGuard; use crate::rwlock::RwLock; use crate::rwlock::RwLockReadGuard; +use crate::rwlock::access::WriteAccess; 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; + /// Waits for exclusive access and returns a guard borrowing this lock. /// - /// let lock = RwLock::new(1); - /// let mut n = lock.write().await; - /// *n = 2; - /// # } - /// ``` + /// 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; RwLockWriteGuard { - permits_acquired: self.max_readers, - lock: self, + access: WriteAccess::new(self, self.max_readers), } } - /// 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; - /// ``` + /// Returns a write guard immediately, or `None` when exclusive access is unavailable. pub fn try_write(&self) -> Option> { if self.s.try_acquire(self.max_readers) { Some(RwLockWriteGuard { - permits_acquired: self.max_readers, - lock: self, + access: WriteAccess::new(self, self.max_readers), }) } else { None @@ -72,24 +57,17 @@ impl RwLock { } } -/// 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> { - permits_acquired: usize, - 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); - } -} - impl fmt::Debug for RwLockWriteGuard<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&**self, f) @@ -105,156 +83,57 @@ impl fmt::Display for RwLockWriteGuard<'_, T> { impl Deref for RwLockWriteGuard<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { - unsafe { &*self.lock.c.get() } + 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() } + 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, - /// } - /// - /// let rwlock = RwLock::new(Foo { - /// a: 1, - /// b: "hello".to_owned(), - /// }); + /// Selects a component while retaining the same lock access. /// - /// 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); - /// # } - /// ``` + /// The closure runs while the original guard is held. If it panics, that guard is released. + /// Call this as `RwLockWriteGuard::map(guard, f)` to avoid shadowing methods of the + /// value. pub fn map(orig: Self, f: 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 d = NonNull::from(f(unsafe { &mut *orig.access.owner().c.get() })); + MappedRwLockWriteGuard::new(d, 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, - /// } + /// Selects a component, or returns the still-held original guard when the closure returns + /// `None`. /// - /// 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); - /// # } - /// ``` + /// A panic in the closure releases the guard. Call this as + /// `RwLockWriteGuard::filter_map(guard, f)`. pub fn filter_map(orig: Self, f: F) -> Result, Self> where F: FnOnce(&mut T) -> Option<&mut U>, U: ?Sized, { - match f(unsafe { &mut *orig.lock.c.get() }) { + match f(unsafe { &mut *orig.access.owner().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, - )) + Ok(MappedRwLockWriteGuard::new(d, orig.access.into_semaphore())) } None => Err(orig), } } - /// 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. + /// Retains shared access while releasing exclusive access. /// - /// # 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.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); - - // 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 } + RwLockReadGuard { + access: self.access.downgrade(), + } } } diff --git a/licenserc.toml b/licenserc.toml index 7b6c05d..6b883f0 100644 --- a/licenserc.toml +++ b/licenserc.toml @@ -28,15 +28,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", ] includes = [ "**/*.md", diff --git a/tests-integration/tests/rwlock_test.rs b/tests-integration/tests/rwlock_test.rs index dd4a5b4..3ee4dbb 100644 --- a/tests-integration/tests/rwlock_test.rs +++ b/tests-integration/tests/rwlock_test.rs @@ -286,3 +286,143 @@ 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 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()); + } +} + +#[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); + // The semaphore has granted the permit, but the future has not built a guard yet. + 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 get_mut_and_into_inner_use_exclusive_access() { + let mut rwlock = RwLock::new(100); + + *rwlock.get_mut() = 200; + + assert_eq!(*rwlock.get_mut(), 200); + assert_eq!(rwlock.into_inner(), 200); +} + +#[test] +fn rejected_rwlock_projection_returns_the_guard() { + let rwlock = RwLock::new(vec![1, 2]); + + let guard = rwlock.try_write().unwrap(); + // No third value exists, so the guard comes back and the push needs no second acquisition. + let mut guard = RwLockWriteGuard::filter_map(guard, |values| values.get_mut(2)).unwrap_err(); + assert!(rwlock.try_read().is_none()); + guard.push(3); + drop(guard); + + assert_eq!(*rwlock.try_read().unwrap(), [1, 2, 3]); +} + +#[test] +fn cancelled_mapped_reader_admits_a_queued_writer() { + let rwlock = RwLock::new(vec![1, 2]); + + let mut reader = Box::pin(async { + let _mapped = RwLockReadGuard::map(rwlock.read().await, |values| &values[0]); + std::future::pending::<()>().await; + }); + assert_pending!(poll_once(reader.as_mut())); + let mut writer = Box::pin(rwlock.write()); + assert_pending!(poll_once(writer.as_mut())); + + drop(reader); + let write_guard = assert_ready!(poll_once(writer.as_mut())); + assert_eq!(*write_guard, [1, 2]); +} + +#[test] +fn cancelled_mapped_writer_admits_a_queued_reader() { + let rwlock = RwLock::new(vec![1, 2]); + + let mut writer = Box::pin(async { + let mut mapped = RwLockWriteGuard::map(rwlock.write().await, |values| &mut values[0]); + *mapped = 10; + std::future::pending::<()>().await; + }); + assert_pending!(poll_once(writer.as_mut())); + let mut reader = Box::pin(rwlock.read()); + assert_pending!(poll_once(reader.as_mut())); + + drop(writer); + let read_guard = assert_ready!(poll_once(reader.as_mut())); + assert_eq!(*read_guard, [10, 2]); +} + +#[tokio::test] +async fn panicking_rwlock_projection_releases_access() { + let rwlock = Arc::new(RwLock::new(vec![1, 2])); + + let task = tokio::spawn({ + let rwlock = rwlock.clone(); + async move { + let mapped = OwnedRwLockReadGuard::map(rwlock.read_owned().await, Vec::as_slice); + // No third value exists, so this projection panics. + drop(OwnedMappedRwLockReadGuard::map(mapped, |values| &values[2])); + } + }); + assert!(task.await.unwrap_err().is_panic()); + + assert!(rwlock.try_write().is_some()); + assert_eq!(Arc::strong_count(&rwlock), 1); +} + +#[tokio::test] +async fn aborted_task_releases_its_owned_mapped_rwlock_guard() { + let rwlock = Arc::new(RwLock::new(vec![1, 2])); + + let write_guard = rwlock.clone().write_owned().await; + let mut mapped = OwnedRwLockWriteGuard::map(write_guard, |values| &mut values[0]); + *mapped = 10; + let task = tokio::spawn(async move { + let _mapped = mapped; + std::future::pending::<()>().await; + }); + assert!(rwlock.try_read().is_none()); + + task.abort(); + assert!(task.await.unwrap_err().is_cancelled()); + assert_eq!(*rwlock.try_read().unwrap(), [10, 2]); + assert_eq!(Arc::strong_count(&rwlock), 1); +} diff --git a/tests-integration/tests/traits_test.rs b/tests-integration/tests/traits_test.rs index 26205e3..47972d9 100644 --- a/tests-integration/tests/traits_test.rs +++ b/tests-integration/tests/traits_test.rs @@ -43,6 +43,9 @@ use asyncband::phaser::PhaserParticipants; 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; @@ -148,6 +151,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::>>();