Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

## Unreleased

### Bug fixes

* Release the retained read access and owned lock reference when a waiter wake callback panics during `RwLock` guard downgrade.

## v0.7.2

### Improvements
Expand Down
23 changes: 16 additions & 7 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,22 @@ The exact Tokio 1.42.0 revision is:

https://github.com/tokio-rs/tokio/tree/bb9d57017e100985f86d8ca41ac105ee9140423e/tokio/src/sync

Asyncband adapted RwLock to its own semaphore and guard model. Acquisition has
no semaphore-close error, nonblocking methods return Option, and max_readers is
any NonZeroUsize instead of Tokio's restricted range. Mapped read guards use
separate borrowed and owned types rather than changing a type parameter on the
original read guard. All mapped guards use filter_map naming, and mutable mapped
guards carry explicit invariance. The local guard types release and downgrade
permits through Asyncband's usize-based semaphore implementation.
Asyncband retains semaphore-based fair scheduling and substantially rewrote
RwLock's guard lifecycle. The private rwlock/access.rs implementation owns
acquired permits in movable RAII tokens. Guards project data by transferring
these tokens, with no manual destruction suppression or raw Arc extraction.
Downgrading establishes a read token before releasing the other permits, so
unwinding from a wake callback also releases the retained permit and ownership.
The public documentation and examples were rewritten around access, projection,
and owned lifetimes. The standard ASF source headers cover these substantial
modifications contributed to Apache Asyncband; the incorporated Tokio-derived
portions remain under the MIT License below.

Acquisition has no semaphore-close error, nonblocking methods return Option,
and max_readers accepts every NonZeroUsize. Mapped read guards use separate
borrowed and owned types. All mapped guards use filter_map naming, and mutable
mapped guards preserve invariance. The public guard types and FIFO ordering
remain unchanged by the RAII rewrite.

Portions of the following files originated from Tokio 1.47.0's OnceCell. Each
local path is followed by its upstream source path:
Expand Down
120 changes: 120 additions & 0 deletions asyncband/src/rwlock/access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

//! Owns acquired permits independently of the guard's data projection.
//!
//! Tokens are created only after acquisition succeeds. Their optional owner is present until an
//! ownership transfer; taking it disarms the old token without suppressing Rust's drop machinery.
//! All supported owners are references or Arcs, so Option adds no storage to them.

use std::sync::Arc;

use crate::internal::semaphore::Semaphore;
use crate::rwlock::RwLock;

pub trait Owner {
fn semaphore(&self) -> &Semaphore;
}

impl Owner for &Semaphore {
fn semaphore(&self) -> &Semaphore {
self
}
}

impl<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
}
}

pub struct ReadAccess<O: Owner> {
owner: Option<O>,
}

impl<O: Owner> ReadAccess<O> {
/// Takes responsibility for one already-acquired permit.
pub fn new(owner: O) -> Self {
Self { owner: Some(owner) }
}

pub fn owner(&self) -> &O {
self.owner.as_ref().unwrap()
}
}

impl<'a, T: ?Sized> ReadAccess<&'a RwLock<T>> {
/// A borrowed projection no longer needs the original value's type.
pub fn into_semaphore(mut self) -> ReadAccess<&'a Semaphore> {
ReadAccess::new(&self.owner.take().unwrap().s)
}
}

impl<O: Owner> Drop for ReadAccess<O> {
fn drop(&mut self) {
if let Some(owner) = &self.owner {
owner.semaphore().release(1);
}
}
}

pub struct WriteAccess<O: Owner> {
owner: Option<O>,
permits: usize,
}

impl<O: Owner> WriteAccess<O> {
/// Takes responsibility for all permits of a lock, which must already be acquired.
pub fn new(owner: O, permits: usize) -> Self {
Self {
owner: Some(owner),
permits,
}
}

pub fn owner(&self) -> &O {
self.owner.as_ref().unwrap()
}

pub fn downgrade(mut self) -> ReadAccess<O> {
let read = ReadAccess::new(self.owner.take().unwrap());
// Keep the retained permit and owner in a live token before release can invoke wakers.
// If waking panics, unwinding drops this token instead of leaking a permit or an Arc.
read.owner().semaphore().release(self.permits - 1);
read
}
}

impl<'a, T: ?Sized> WriteAccess<&'a RwLock<T>> {
pub fn into_semaphore(mut self) -> WriteAccess<&'a Semaphore> {
WriteAccess::new(&self.owner.take().unwrap().s, self.permits)
}
}

impl<O: Owner> Drop for WriteAccess<O> {
fn drop(&mut self) {
if let Some(owner) = &self.owner {
owner.semaphore().release(self.permits);
}
}
}
Loading