You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asyncband.apache.org by ti...@apache.org on 2026/10/10 02:41:22 UTC
(asyncband) branch main updated: refactor(rwlock): move guard permits into access tokens (#351)
This is an automated email from the ASF dual-hosted git repository.
tisonkun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/asyncband.git
The following commit(s) were added to refs/heads/main by this push:
new 44fc2d8 refactor(rwlock): move guard permits into access tokens (#351)
44fc2d8 is described below
commit 44fc2d82010b66cb4fe1dcfb0ae7fb679b6054b2
Author: Jeff Chung <78...@users.noreply.github.com>
AuthorDate: Sat Oct 10 10:41:18 2026 +0800
refactor(rwlock): move guard permits into access tokens (#351)
---
LICENSE | 23 +-
asyncband/src/rwlock/access.rs | 131 ++++++++++
asyncband/src/rwlock/mapped_read_guard.rs | 196 +++------------
asyncband/src/rwlock/mapped_write_guard.rs | 269 ++++-----------------
asyncband/src/rwlock/mod.rs | 172 +++++++------
asyncband/src/rwlock/owned_mapped_read_guard.rs | 226 +++--------------
asyncband/src/rwlock/owned_mapped_write_guard.rs | 294 ++++-------------------
asyncband/src/rwlock/owned_read_guard.rs | 207 ++++------------
asyncband/src/rwlock/owned_write_guard.rs | 261 +++++---------------
asyncband/src/rwlock/read_guard.rs | 166 ++++---------
asyncband/src/rwlock/write_guard.rs | 228 +++++-------------
licenserc.toml | 9 -
tests-integration/tests/rwlock_test.rs | 141 +++++++++++
tests-integration/tests/traits_test.rs | 6 +
14 files changed, 770 insertions(+), 1559 deletions(-)
diff --git a/LICENSE b/LICENSE
index 546e801..22762c1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -262,13 +262,22 @@ The exact Tokio 1.42.0 revision is:
https://github.com/tokio-rs/tokio/tree/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync
-Asyncband adapted RwLock to its own semaphore and guard model. Acquisition has
-no semaphore-close error, nonblocking methods return Option, and max_readers is
-any NonZeroUsize instead of Tokio's restricted range. Mapped read guards use
-separate borrowed and owned types rather than changing a type parameter on the
-original read guard. All mapped guards use filter_map naming, and mutable mapped
-guards carry explicit invariance. The local guard types release and downgrade
-permits through Asyncband's usize-based semaphore implementation.
+Asyncband retains semaphore-based fair scheduling and substantially rewrote
+RwLock's guard lifecycle. The private asyncband/src/rwlock/access.rs
+implementation owns acquired permits in RAII tokens that release them through
+Asyncband's usize-based semaphore on drop. Guards carry no destructor of their
+own and project data by moving their token. Downgrading establishes a read
+token before releasing the other permits. 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.
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..6bd9e8c
--- /dev/null
+++ b/asyncband/src/rwlock/access.rs
@@ -0,0 +1,131 @@
+// 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: Clone {
+ /// Returns the semaphore that the token's permits belong to.
+ fn semaphore(&self) -> &Semaphore;
+}
+
+impl Owner for &Semaphore {
+ fn semaphore(&self) -> &Semaphore {
+ self
+ }
+}
+
+impl<T: ?Sized> Owner for &RwLock<T> {
+ fn semaphore(&self) -> &Semaphore {
+ &self.s
+ }
+}
+
+impl<T: ?Sized> Owner for Arc<RwLock<T>> {
+ fn semaphore(&self) -> &Semaphore {
+ &self.s
+ }
+}
+
+/// Owns one read permit.
+pub struct ReadAccess<O: Owner> {
+ owner: O,
+}
+
+impl<O: Owner> ReadAccess<O> {
+ /// Takes over one permit already acquired from `owner`.
+ pub fn new(owner: O) -> Self {
+ Self { owner }
+ }
+
+ /// Returns the owner that the permit was acquired from.
+ pub fn owner(&self) -> &O {
+ &self.owner
+ }
+}
+
+impl<'a, T: ?Sized> ReadAccess<&'a RwLock<T>> {
+ /// Drops the value type so that mapped guards need not name it.
+ pub fn into_semaphore(self) -> ReadAccess<&'a Semaphore> {
+ let lock = self.owner;
+ // The token holds only a reference and no other resource, so
+ // skipping its drop leaks nothing.
+ std::mem::forget(self);
+ ReadAccess::new(&lock.s)
+ }
+}
+
+impl<O: Owner> Drop for ReadAccess<O> {
+ fn drop(&mut self) {
+ self.owner.semaphore().release(1);
+ }
+}
+
+/// Owns every permit of a lock, which together grant write access.
+pub struct WriteAccess<O: Owner> {
+ owner: O,
+ permits_acquired: usize,
+}
+
+impl<O: Owner> WriteAccess<O> {
+ /// Takes over `permits_acquired` permits already acquired from `owner`.
+ pub fn new(owner: O, permits_acquired: usize) -> Self {
+ Self {
+ owner,
+ permits_acquired,
+ }
+ }
+
+ /// Returns the owner that the permits were acquired from.
+ pub fn owner(&self) -> &O {
+ &self.owner
+ }
+
+ /// Releases all permits but one, which the returned token keeps.
+ pub fn downgrade(mut self) -> ReadAccess<O> {
+ let read = ReadAccess::new(self.owner.clone());
+ self.permits_acquired -= 1;
+ drop(self);
+ read
+ }
+}
+
+impl<'a, T: ?Sized> WriteAccess<&'a RwLock<T>> {
+ /// Drops the value type so that mapped guards need not name it.
+ pub fn into_semaphore(self) -> WriteAccess<&'a Semaphore> {
+ let lock = self.owner;
+ let permits_acquired = self.permits_acquired;
+ // The token holds only a reference and no other resource, so
+ // skipping its drop leaks nothing.
+ std::mem::forget(self);
+ WriteAccess::new(&lock.s, permits_acquired)
+ }
+}
+
+impl<O: Owner> Drop for WriteAccess<O> {
+ fn drop(&mut self) {
+ self.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..197d646 100644
--- a/asyncband/src/rwlock/mapped_read_guard.rs
+++ b/asyncband/src/rwlock/mapped_read_guard.rs
@@ -1,7 +1,24 @@
-// 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 rewrote the guard lifecycle around private access tokens; see LICENSE.
// 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 +28,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<T>,
- s: &'a semaphore::Semaphore,
+ access: ReadAccess<&'a Semaphore>,
variance: PhantomData<fn() -> T>,
}
@@ -74,21 +54,15 @@ unsafe impl<T: ?Sized + Sync> Send for MappedRwLockReadGuard<'_, T> {}
unsafe impl<T: ?Sized + Sync> Sync for MappedRwLockReadGuard<'_, T> {}
impl<'a, T: ?Sized> MappedRwLockReadGuard<'a, T> {
- pub(crate) fn new(d: NonNull<T>, s: &'a semaphore::Semaphore) -> Self {
+ pub(crate) fn new(d: NonNull<T>, access: ReadAccess<&'a Semaphore>) -> Self {
Self {
d,
- s,
+ access,
variance: PhantomData,
}
}
}
-impl<T: ?Sized> Drop for MappedRwLockReadGuard<'_, T> {
- fn drop(&mut self) {
- self.s.release(1);
- }
-}
-
impl<T: ?Sized + fmt::Debug> fmt::Debug for MappedRwLockReadGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
@@ -110,124 +84,34 @@ impl<T: ?Sized> 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<U, F>(orig: Self, f: F) -> MappedRwLockReadGuard<'a, U>
where
F: FnOnce(&T) -> &U,
U: ?Sized,
{
- // SAFETY: orig.d is a valid NonNull<T> pointer that was created from a valid reference
- // when the original MappedRwLockReadGuard was constructed. The guard guarantees shared
- // access to the data through the rwlock, so dereferencing is safe.
- let d = NonNull::from(f(unsafe { orig.d.as_ref() }));
- let orig = std::mem::ManuallyDrop::new(orig);
- MappedRwLockReadGuard::new(d, orig.s)
+ let d = NonNull::from(f(&*orig));
+ 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<String>,
- /// }
- ///
- /// 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<U, F>(orig: Self, f: F) -> Result<MappedRwLockReadGuard<'a, U>, Self>
where
F: FnOnce(&T) -> Option<&U>,
U: ?Sized,
{
- // SAFETY: orig.d is a valid NonNull<T> pointer that was created from a valid reference
- // when the original MappedRwLockReadGuard was constructed. The guard guarantees shared
- // access to the data through the rwlock, so dereferencing is safe.
- match f(unsafe { orig.d.as_ref() }) {
+ match f(&*orig) {
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..8c2f9b4 100644
--- a/asyncband/src/rwlock/mapped_write_guard.rs
+++ b/asyncband/src/rwlock/mapped_write_guard.rs
@@ -1,68 +1,45 @@
-// 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 rewrote the guard lifecycle around private access tokens; see LICENSE.
// 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<T>,
- s: &'a semaphore::Semaphore,
- permits_acquired: usize,
+ access: WriteAccess<&'a Semaphore>,
// Mutable access requires invariance over T.
variance: PhantomData<&'a mut T>,
}
@@ -77,22 +54,15 @@ unsafe impl<T: ?Sized + Send + Sync> Sync for MappedRwLockWriteGuard<'_, T> {}
unsafe impl<T: ?Sized + Send> Send for MappedRwLockWriteGuard<'_, T> {}
impl<'a, T: ?Sized> MappedRwLockWriteGuard<'a, T> {
- pub(crate) fn new(d: NonNull<T>, s: &'a semaphore::Semaphore, permits_acquired: usize) -> Self {
+ pub(crate) fn new(d: NonNull<T>, access: WriteAccess<&'a Semaphore>) -> Self {
Self {
d,
- s,
- permits_acquired,
+ access,
variance: PhantomData,
}
}
}
-impl<T: ?Sized> Drop for MappedRwLockWriteGuard<'_, T> {
- fn drop(&mut self) {
- self.s.release(self.permits_acquired);
- }
-}
-
impl<T: ?Sized + fmt::Debug> fmt::Debug for MappedRwLockWriteGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
@@ -121,195 +91,44 @@ impl<T: ?Sized> 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<U, F>(mut orig: Self, f: F) -> MappedRwLockWriteGuard<'a, U>
where
F: FnOnce(&mut T) -> &mut U,
U: ?Sized,
{
- // SAFETY: orig.d is a valid NonNull<T> pointer that was created from a valid reference
- // when the original MappedRwLockWriteGuard was constructed. The guard guarantees exclusive
- // access to the data through the rwlock, so dereferencing is safe.
- let d = NonNull::from(f(unsafe { orig.d.as_mut() }));
- let permits_acquired = orig.permits_acquired;
- let orig = ManuallyDrop::new(orig);
- MappedRwLockWriteGuard::new(d, orig.s, permits_acquired)
+ let d = NonNull::from(f(&mut *orig));
+ 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<Metadata>,
- /// }
- ///
- /// #[derive(Debug)]
- /// struct Metadata {
- /// author: String,
- /// version: Option<u32>,
- /// }
- ///
- /// 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<U, F>(mut orig: Self, f: F) -> Result<MappedRwLockWriteGuard<'a, U>, Self>
where
F: FnOnce(&mut T) -> Option<&mut U>,
U: ?Sized,
{
- // SAFETY: orig.d is a valid NonNull<T> pointer that was created from a valid reference
- // when the original MappedRwLockWriteGuard was constructed. The guard guarantees exclusive
- // access to the data through the rwlock, so dereferencing is safe.
- match f(unsafe { orig.d.as_mut() }) {
+ match f(&mut *orig) {
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..a806d03 100644
--- a/asyncband/src/rwlock/mod.rs
+++ b/asyncband/src/rwlock/mod.rs
@@ -1,46 +1,102 @@
-// 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 rewrote the guard lifecycle around
+// private access tokens; see LICENSE.
// 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 +106,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 +125,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<T: ?Sized> {
- /// Maximum number of concurrent readers.
- ///
- /// This is ensured to be non-zero.
max_readers: usize,
s: Semaphore,
c: UnsafeCell<T>,
@@ -107,35 +161,16 @@ impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLock<T> {
}
impl<T> RwLock<T> {
- /// 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<T> {
// 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<T> {
let max_readers = max_readers.get();
let s = Semaphore::new(max_readers);
@@ -143,37 +178,16 @@ impl<T> RwLock<T> {
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<T: ?Sized> RwLock<T> {
- /// 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..512d652 100644
--- a/asyncband/src/rwlock/owned_mapped_read_guard.rs
+++ b/asyncband/src/rwlock/owned_mapped_read_guard.rs
@@ -1,68 +1,44 @@
-// 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 rewrote the guard lifecycle around private access tokens; see LICENSE.
// 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<T: ?Sized, U: ?Sized> {
- lock: Arc<RwLock<T>>,
+ access: ReadAccess<Arc<RwLock<T>>>,
d: NonNull<U>,
variance: PhantomData<fn() -> U>,
}
@@ -77,19 +53,14 @@ unsafe impl<T: ?Sized + Send + Sync, U: ?Sized + Sync> Send for OwnedMappedRwLoc
unsafe impl<T: ?Sized + Send + Sync, U: ?Sized + Sync> Sync for OwnedMappedRwLockReadGuard<T, U> {}
impl<T: ?Sized, U: ?Sized> OwnedMappedRwLockReadGuard<T, U> {
- pub(crate) fn new(d: NonNull<U>, lock: Arc<RwLock<T>>) -> Self {
+ pub(crate) fn new(d: NonNull<U>, access: ReadAccess<Arc<RwLock<T>>>) -> Self {
Self {
d,
- lock,
+ access,
variance: PhantomData,
}
}
}
-impl<T: ?Sized, U: ?Sized> Drop for OwnedMappedRwLockReadGuard<T, U> {
- fn drop(&mut self) {
- self.lock.s.release(1);
- }
-}
impl<T: ?Sized, U: ?Sized + fmt::Debug> fmt::Debug for OwnedMappedRwLockReadGuard<T, U> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -112,153 +83,34 @@ impl<T: ?Sized, U: ?Sized> Deref for OwnedMappedRwLockReadGuard<T, U> {
}
impl<T: ?Sized, U: ?Sized> OwnedMappedRwLockReadGuard<T, U> {
- /// 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<V, F>(orig: Self, f: F) -> OwnedMappedRwLockReadGuard<T, V>
where
F: FnOnce(&U) -> &V,
V: ?Sized,
{
- // SAFETY: orig.d is a valid NonNull<U> pointer that was created from a valid reference
- // when the original OwnedMappedRwLockReadGuard was constructed. The guard guarantees shared
- // access to the data through the rwlock, so dereferencing is safe.
- let d = NonNull::from(f(unsafe { orig.d.as_ref() }));
- let orig = ManuallyDrop::new(orig);
-
- // SAFETY: The original guard is wrapped in `ManuallyDrop` and will not be dropped.
- // This allows us to safely move the `Arc` out of it and transfer ownership to the new
- // guard.
- let lock = unsafe { std::ptr::read(&orig.lock) };
-
- OwnedMappedRwLockReadGuard::new(d, lock)
+ let d = NonNull::from(f(&*orig));
+ 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<String, CacheEntry>,
- /// stats: CacheStats,
- /// }
- ///
- /// #[derive(Debug)]
- /// struct CacheEntry {
- /// data: String,
- /// metadata: Option<String>,
- /// }
+ /// 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<V, F>(orig: Self, f: F) -> Result<OwnedMappedRwLockReadGuard<T, V>, Self>
where
F: FnOnce(&U) -> Option<&V>,
V: ?Sized,
{
- // SAFETY: orig.d is a valid NonNull<U> pointer that was created from a valid reference
- // when the original OwnedMappedRwLockReadGuard was constructed. The guard guarantees shared
- // access to the data through the rwlock, so dereferencing is safe.
- match f(unsafe { orig.d.as_ref() }) {
+ match f(&*orig) {
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..d1b349c 100644
--- a/asyncband/src/rwlock/owned_mapped_write_guard.rs
+++ b/asyncband/src/rwlock/owned_mapped_write_guard.rs
@@ -1,13 +1,29 @@
-// 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 rewrote the guard lifecycle around private access tokens; see LICENSE.
// 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 +31,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<T: ?Sized, U: ?Sized> {
d: NonNull<U>,
- lock: Arc<RwLock<T>>,
- permits_acquired: usize,
+ access: WriteAccess<Arc<RwLock<T>>>,
// Mutable access requires invariance over U.
variance: PhantomData<*mut U>,
}
@@ -82,20 +56,14 @@ unsafe impl<T: ?Sized + Send + Sync, U: ?Sized + Sync> Sync for OwnedMappedRwLoc
unsafe impl<T: ?Sized + Send + Sync, U: ?Sized + Send> Send for OwnedMappedRwLockWriteGuard<T, U> {}
impl<T: ?Sized, U: ?Sized> OwnedMappedRwLockWriteGuard<T, U> {
- pub(crate) fn new(d: NonNull<U>, lock: Arc<RwLock<T>>, permits_acquired: usize) -> Self {
+ pub(crate) fn new(d: NonNull<U>, access: WriteAccess<Arc<RwLock<T>>>) -> Self {
Self {
d,
- lock,
- permits_acquired,
+ access,
variance: PhantomData,
}
}
}
-impl<T: ?Sized, U: ?Sized> Drop for OwnedMappedRwLockWriteGuard<T, U> {
- fn drop(&mut self) {
- self.lock.s.release(self.permits_acquired);
- }
-}
impl<T: ?Sized, U: ?Sized + fmt::Debug> fmt::Debug for OwnedMappedRwLockWriteGuard<T, U> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -125,220 +93,44 @@ impl<T: ?Sized, U: ?Sized> DerefMut for OwnedMappedRwLockWriteGuard<T, U> {
}
impl<T: ?Sized, U: ?Sized> OwnedMappedRwLockWriteGuard<T, U> {
- /// 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<V, F>(mut orig: Self, f: F) -> OwnedMappedRwLockWriteGuard<T, V>
where
F: FnOnce(&mut U) -> &mut V,
V: ?Sized,
{
- // SAFETY: orig.d is a valid NonNull<U> pointer that was created from a valid reference
- // when the original OwnedMappedRwLockWriteGuard was constructed. The guard guarantees
- // exclusive access to the data through the rwlock, so dereferencing is safe.
- let d = NonNull::from(f(unsafe { orig.d.as_mut() }));
- let orig = ManuallyDrop::new(orig);
-
- let permits_acquired = orig.permits_acquired;
- // SAFETY: The original guard is wrapped in `ManuallyDrop` and will not be dropped.
- // This allows us to safely move the `Arc` out of it and transfer ownership to the new
- // guard.
- let lock = unsafe { std::ptr::read(&orig.lock) };
-
- OwnedMappedRwLockWriteGuard::new(d, lock, permits_acquired)
+ let d = NonNull::from(f(&mut *orig));
+ 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<Metrics>,
- /// }
- ///
- /// #[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<AppState, Option<Metrics>>
- /// 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<V, F>(mut orig: Self, f: F) -> Result<OwnedMappedRwLockWriteGuard<T, V>, Self>
where
F: FnOnce(&mut U) -> Option<&mut V>,
V: ?Sized,
{
- // SAFETY: orig.d is a valid NonNull<U> pointer that was created from a valid reference
- // when the original OwnedMappedRwLockWriteGuard was constructed. The guard guarantees
- // exclusive access to the data through the rwlock, so dereferencing is safe.
- match f(unsafe { orig.d.as_mut() }) {
+ match f(&mut *orig) {
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<T, U> {
- // 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..7424c70 100644
--- a/asyncband/src/rwlock/owned_read_guard.rs
+++ b/asyncband/src/rwlock/owned_read_guard.rs
@@ -1,7 +1,24 @@
-// 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 rewrote the guard lifecycle around private access tokens; see LICENSE.
// Upstream source:
// https://github.com/tokio-rs/tokio/blob/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync/rwlock/owned_read_guard.rs
@@ -11,92 +28,45 @@ use std::sync::Arc;
use crate::rwlock::OwnedMappedRwLockReadGuard;
use crate::rwlock::RwLock;
+use crate::rwlock::access::ReadAccess;
impl<T: ?Sized> RwLock<T> {
- /// 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.
+ /// Waits for a reader slot and returns a guard retaining this `Arc`.
///
- /// 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.
+ /// Ordering follows [`Self::read`]. The guard keeps the lock alive without borrowing the
+ /// caller's `Arc`.
///
/// # Cancel safety
///
- /// Pending lock requests complete in order. Cancelling this call loses its place among them.
- ///
- /// # Examples
- ///
- /// ```
- /// # #[tokio::main]
- /// # async fn main() {
- /// use std::sync::Arc;
- ///
- /// use asyncband::rwlock::RwLock;
- ///
- /// let lock = Arc::new(RwLock::new(1));
- /// let lock_clone = lock.clone();
- ///
- /// let n = lock.read_owned().await;
- /// assert_eq!(*n, 1);
- ///
- /// tokio::spawn(async move {
- /// // while the outer read lock is held, we acquire a read lock, too
- /// let r = lock_clone.read_owned().await;
- /// assert_eq!(*r, 1);
- /// })
- /// .await
- /// .unwrap();
- /// # }
- /// ```
+ /// Pending lock requests complete in order. Cancelling this future loses its place among them
+ /// and releases any reserved permits.
pub async fn read_owned(self: Arc<Self>) -> OwnedRwLockReadGuard<T> {
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 an owned read guard immediately, or `None` when no reader slot is available.
///
- /// Returns `None` if read access is unavailable.
- ///
- /// # Examples
- ///
- /// ```
- /// use std::sync::Arc;
- ///
- /// use asyncband::rwlock::RwLock;
- ///
- /// let lock = Arc::new(RwLock::new(1));
- ///
- /// let v = lock.clone().try_read_owned().unwrap();
- /// assert_eq!(*v, 1);
- /// drop(v);
- ///
- /// let v = lock.try_write().unwrap();
- /// assert!(lock.clone().try_read_owned().is_none());
- /// ```
+ /// This consumes the passed `Arc` even when acquisition fails.
pub fn try_read_owned(self: Arc<Self>) -> Option<OwnedRwLockReadGuard<T>> {
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<T: ?Sized> {
- pub(super) lock: Arc<RwLock<T>>,
-}
-
-impl<T: ?Sized> Drop for OwnedRwLockReadGuard<T> {
- fn drop(&mut self) {
- self.lock.s.release(1);
- }
+ pub(super) access: ReadAccess<Arc<RwLock<T>>>,
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for OwnedRwLockReadGuard<T> {
@@ -114,114 +84,41 @@ impl<T: ?Sized + fmt::Display> fmt::Display for OwnedRwLockReadGuard<T> {
impl<T: ?Sized> Deref for OwnedRwLockReadGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
- unsafe { &*self.lock.c.get() }
+ // SAFETY: `access` holds a read permit on the lock, so no writer can reach the cell
+ // while this guard lives.
+ unsafe { &*self.access.owner().c.get() }
}
}
impl<T: ?Sized> OwnedRwLockReadGuard<T> {
- /// Projects this guard to a shared component of the protected value.
+ /// Selects a component while retaining the same lock access.
///
- /// 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;
- ///
- /// #[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<U, F>(orig: Self, f: F) -> OwnedMappedRwLockReadGuard<T, U>
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)
+ let d = std::ptr::NonNull::from(f(&*orig));
+ 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<U, F>(orig: Self, f: F) -> Result<OwnedMappedRwLockReadGuard<T, U>, 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() }) {
+ match f(&*orig) {
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..c9bd2ae 100644
--- a/asyncband/src/rwlock/owned_write_guard.rs
+++ b/asyncband/src/rwlock/owned_write_guard.rs
@@ -1,12 +1,28 @@
-// 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 rewrote the guard lifecycle around private access tokens; see LICENSE.
// 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 +31,33 @@ use std::sync::Arc;
use crate::rwlock::OwnedMappedRwLockWriteGuard;
use crate::rwlock::OwnedRwLockReadGuard;
use crate::rwlock::RwLock;
+use crate::rwlock::access::WriteAccess;
impl<T: ?Sized> RwLock<T> {
- /// 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.
+ /// Ordering follows [`Self::write`].
///
/// # 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;
- /// # }
- /// ```
+ /// Pending lock requests complete in order. Cancelling this future loses its place among them
+ /// and releases any reserved permits.
pub async fn write_owned(self: Arc<Self>) -> OwnedRwLockWriteGuard<T> {
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
+ /// Returns an owned write guard immediately, or `None` when exclusive access is unavailable.
///
- /// ```
- /// use std::sync::Arc;
- ///
- /// use asyncband::rwlock::RwLock;
- ///
- /// let lock = Arc::new(RwLock::new(1));
- ///
- /// let v = lock.try_read().unwrap();
- /// assert!(lock.clone().try_write_owned().is_none());
- /// drop(v);
- ///
- /// let mut v = lock.try_write_owned().unwrap();
- /// *v = 2;
- /// ```
+ /// This consumes the passed `Arc` even when acquisition fails.
pub fn try_write_owned(self: Arc<Self>) -> Option<OwnedRwLockWriteGuard<T>> {
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 +65,17 @@ impl<T: ?Sized> RwLock<T> {
}
}
-/// 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<T: ?Sized> {
- permits_acquired: usize,
- lock: Arc<RwLock<T>>,
+ access: WriteAccess<Arc<RwLock<T>>>,
}
unsafe impl<T: ?Sized + Send + Sync> Send for OwnedRwLockWriteGuard<T> {}
unsafe impl<T: ?Sized + Send + Sync> Sync for OwnedRwLockWriteGuard<T> {}
-impl<T: ?Sized> Drop for OwnedRwLockWriteGuard<T> {
- fn drop(&mut self) {
- self.lock.s.release(self.permits_acquired);
- }
-}
-
impl<T: ?Sized + fmt::Debug> fmt::Debug for OwnedRwLockWriteGuard<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
@@ -113,178 +91,59 @@ impl<T: ?Sized + fmt::Display> fmt::Display for OwnedRwLockWriteGuard<T> {
impl<T: ?Sized> Deref for OwnedRwLockWriteGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
- unsafe { &*self.lock.c.get() }
+ // SAFETY: `access` holds the write permit on the lock, so this guard is the only path to
+ // the cell while it lives.
+ unsafe { &*self.access.owner().c.get() }
}
}
impl<T: ?Sized> DerefMut for OwnedRwLockWriteGuard<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
- unsafe { &mut *self.lock.c.get() }
+ // SAFETY: `access` holds the write permit on the lock, so this guard is the only path to
+ // the cell while it lives.
+ unsafe { &mut *self.access.owner().c.get() }
}
}
impl<T: ?Sized> OwnedRwLockWriteGuard<T> {
- /// 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;
- ///
- /// use asyncband::rwlock::OwnedRwLockWriteGuard;
- /// 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 mut guard = rwlock.write_owned().await;
- /// let mut mapped_guard = OwnedRwLockWriteGuard::map(guard, |foo| &mut foo.b);
- ///
- /// mapped_guard.push_str(" world");
- /// assert_eq!(&*mapped_guard, "hello world");
- /// # }
- /// ```
- pub fn map<U, F>(orig: Self, f: F) -> OwnedMappedRwLockWriteGuard<T, U>
+ /// 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<U, F>(mut orig: Self, f: F) -> OwnedMappedRwLockWriteGuard<T, U>
where
F: FnOnce(&mut T) -> &mut U,
U: ?Sized,
{
- // SAFETY: We have exclusive write access to the data through the rwlock.
- // The data pointer is valid for the lifetime of the guard.
- let d = NonNull::from(f(unsafe { &mut *orig.lock.c.get() }));
- let orig = ManuallyDrop::new(orig);
-
- let permits_acquired = orig.permits_acquired;
- // SAFETY: The original guard is wrapped in `ManuallyDrop` and will not be dropped.
- // This allows us to safely move the `Arc` out of it and transfer ownership to the new
- // guard.
- let lock = unsafe { std::ptr::read(&orig.lock) };
-
- OwnedMappedRwLockWriteGuard::new(d, lock, permits_acquired)
+ let d = NonNull::from(f(&mut *orig));
+ OwnedMappedRwLockWriteGuard::new(d, orig.access)
}
- /// Attempts to project this guard to a mutable 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
- /// `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,
- /// }
- ///
- /// let rwlock = Arc::new(RwLock::new(Foo {
- /// a: 1,
- /// b: "hello".to_owned(),
- /// }));
- ///
- /// let mut guard = rwlock.write_owned().await;
- /// let mut mapped_guard = OwnedRwLockWriteGuard::filter_map(guard, |foo| {
- /// if foo.b.len() > 3 {
- /// Some(&mut foo.b)
- /// } else {
- /// None
- /// }
- /// })
- /// .expect("should have mapped");
- ///
- /// mapped_guard.push_str(" world");
- /// assert_eq!(&*mapped_guard, "hello world");
- /// # }
- /// ```
- pub fn filter_map<U, F>(orig: Self, f: F) -> Result<OwnedMappedRwLockWriteGuard<T, U>, Self>
+ /// A panic in the closure releases the guard. Call this as
+ /// `OwnedRwLockWriteGuard::filter_map(guard, f)`.
+ pub fn filter_map<U, F>(mut orig: Self, f: F) -> Result<OwnedMappedRwLockWriteGuard<T, U>, Self>
where
F: FnOnce(&mut T) -> Option<&mut U>,
U: ?Sized,
{
- // SAFETY: We have exclusive write access to the data through the rwlock.
- // The data pointer is valid for the lifetime of the guard.
- let d = match f(unsafe { &mut *orig.lock.c.get() }) {
+ let d = match f(&mut *orig) {
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<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);
-
- // 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..e3f35d2 100644
--- a/asyncband/src/rwlock/read_guard.rs
+++ b/asyncband/src/rwlock/read_guard.rs
@@ -1,105 +1,77 @@
-// 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 rewrote the guard lifecycle around private access tokens; see LICENSE.
// 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<T: ?Sized> RwLock<T> {
- /// 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.
+ /// Waits for a reader slot and returns a guard borrowing this lock.
///
- /// Holding a read guard, queuing a write request, and then waiting for another read guard in
- /// the same task can therefore deadlock.
+ /// An earlier queued writer must finish first. See [queue ordering](crate::rwlock) before
+ /// acquiring recursively.
///
/// # Cancel safety
///
- /// Pending lock requests complete in order. Cancelling this call loses its place among them.
- ///
- /// # Examples
- ///
- /// ```
- /// # #[tokio::main]
- /// # async fn main() {
- /// use std::sync::Arc;
- ///
- /// use asyncband::rwlock::RwLock;
- ///
- /// let lock = Arc::new(RwLock::new(1));
- /// let lock_clone = lock.clone();
- ///
- /// let n = lock.read().await;
- /// assert_eq!(*n, 1);
- ///
- /// tokio::spawn(async move {
- /// // while the outer read lock is held, we acquire a read lock, too
- /// let r = lock_clone.read().await;
- /// assert_eq!(*r, 1);
- /// })
- /// .await
- /// .unwrap();
- /// # }
- /// ```
+ /// Pending lock requests complete in order. Cancelling this future loses its place among them
+ /// and releases any reserved permits.
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;
+ /// Returns a read guard immediately, or `None` when a reader slot cannot be acquired.
///
- /// let lock = Arc::new(RwLock::new(1));
- ///
- /// let v = lock.try_read().unwrap();
- /// assert_eq!(*v, 1);
- /// drop(v);
- ///
- /// let v = lock.try_write().unwrap();
- /// assert!(lock.try_read().is_none());
- /// ```
+ /// Readers cannot bypass an earlier queued writer.
pub fn try_read(&self) -> Option<RwLockReadGuard<'_, T>> {
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<T>,
+ pub(super) access: ReadAccess<&'a RwLock<T>>,
}
unsafe impl<T: ?Sized + Sync> Send for RwLockReadGuard<'_, T> {}
unsafe impl<T: ?Sized + Send + Sync> Sync for RwLockReadGuard<'_, T> {}
-impl<T: ?Sized> Drop for RwLockReadGuard<'_, T> {
- fn drop(&mut self) {
- self.lock.s.release(1);
- }
-}
-
impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLockReadGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
@@ -115,70 +87,31 @@ impl<T: ?Sized + fmt::Display> fmt::Display for RwLockReadGuard<'_, T> {
impl<T: ?Sized> Deref for RwLockReadGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
- unsafe { &*self.lock.c.get() }
+ // SAFETY: `access` holds a read permit on the lock, so no writer can reach the cell
+ // while this guard lives.
+ 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<U, F>(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<U, F>(orig: Self, f: F) -> Result<MappedRwLockReadGuard<'a, U>, Self>
where
F: FnOnce(&T) -> Option<&U>,
@@ -187,8 +120,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..7cce4b9 100644
--- a/asyncband/src/rwlock/write_guard.rs
+++ b/asyncband/src/rwlock/write_guard.rs
@@ -1,12 +1,28 @@
-// 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 rewrote the guard lifecycle around private access tokens; see LICENSE.
// 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 +30,27 @@ use std::ptr::NonNull;
use crate::rwlock::MappedRwLockWriteGuard;
use crate::rwlock::RwLock;
use crate::rwlock::RwLockReadGuard;
+use crate::rwlock::access::WriteAccess;
impl<T: ?Sized> RwLock<T> {
- /// Waits for exclusive write access and returns a borrowed guard.
+ /// Waits for exclusive access and returns a guard borrowing this lock.
///
/// # Cancel safety
///
- /// Pending lock requests complete in order. Cancelling this call loses its place among them.
- ///
- /// # Examples
- ///
- /// ```
- /// # #[tokio::main]
- /// # async fn main() {
- /// use asyncband::rwlock::RwLock;
- ///
- /// let lock = RwLock::new(1);
- /// let mut n = lock.write().await;
- /// *n = 2;
- /// # }
- /// ```
+ /// Pending lock requests complete in order. Cancelling this future loses its place among them
+ /// and releases 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<RwLockWriteGuard<'_, T>> {
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 +58,17 @@ impl<T: ?Sized> RwLock<T> {
}
}
-/// 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<T>,
+ access: WriteAccess<&'a RwLock<T>>,
}
unsafe impl<T: ?Sized + Send + Sync> Send for RwLockWriteGuard<'_, T> {}
unsafe impl<T: ?Sized + Send + Sync> Sync for RwLockWriteGuard<'_, T> {}
-impl<T: ?Sized> Drop for RwLockWriteGuard<'_, T> {
- fn drop(&mut self) {
- self.lock.s.release(self.permits_acquired);
- }
-}
-
impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLockWriteGuard<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
@@ -105,156 +84,61 @@ impl<T: ?Sized + fmt::Display> fmt::Display for RwLockWriteGuard<'_, T> {
impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
- unsafe { &*self.lock.c.get() }
+ // SAFETY: `access` holds the write permit on the lock, so this guard is the only path to
+ // the cell while it lives.
+ unsafe { &*self.access.owner().c.get() }
}
}
impl<T: ?Sized> DerefMut for RwLockWriteGuard<'_, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
- unsafe { &mut *self.lock.c.get() }
+ // SAFETY: `access` holds the write permit on the lock, so this guard is the only path to
+ // the cell while it lives.
+ 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);
- /// # }
- /// ```
- pub fn map<U, F>(orig: Self, f: F) -> MappedRwLockWriteGuard<'a, U>
+ /// The closure runs while the original guard is held. If it panics, that guard is released.
+ /// Call this as `RwLockWriteGuard::map(guard, f)` to avoid shadowing methods of the
+ /// value.
+ pub fn map<U, F>(mut 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(&mut *orig));
+ 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);
- /// # }
- /// ```
- pub fn filter_map<U, F>(orig: Self, f: F) -> Result<MappedRwLockWriteGuard<'a, U>, Self>
+ /// A panic in the closure releases the guard. Call this as
+ /// `RwLockWriteGuard::filter_map(guard, f)`.
+ pub fn filter_map<U, F>(mut orig: Self, f: F) -> Result<MappedRwLockWriteGuard<'a, U>, Self>
where
F: FnOnce(&mut T) -> Option<&mut U>,
U: ?Sized,
{
- match f(unsafe { &mut *orig.lock.c.get() }) {
+ match f(&mut *orig) {
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..226e2f4 100644
--- a/tests-integration/tests/rwlock_test.rs
+++ b/tests-integration/tests/rwlock_test.rs
@@ -286,3 +286,144 @@ 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());
+
+ tokio::task::yield_now().await;
+ 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: Send>(_: T) {}
assert_send::<RwLockReadGuard<'_, std::sync::MutexGuard<'static, ()>>>();
+ assert_send::<MappedRwLockReadGuard<'_, std::sync::MutexGuard<'static, ()>>>();
+ assert_send::<MappedRwLockWriteGuard<'_, Cell<u8>>>();
+ assert_send::<OwnedMappedRwLockWriteGuard<(), Cell<u8>>>();
assert_send::<oneshot::Receiver<i64>>();
assert_send::<oneshot::Recv<i64>>();
assert_send::<pool::unbounded::Object<Cell<u8>>>();
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@asyncband.apache.org
For additional commands, e-mail: commits-help@asyncband.apache.org