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