New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add RwLockWriteGuard::{downgrade_map, try_downgrade_map}
.
#5527
Changes from 3 commits
91fe74a
180e2d3
6f636d5
cd5e3cb
f9f2930
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,7 +100,77 @@ impl<T: ?Sized> OwnedRwLockWriteGuard<T> { | |
} | ||
} | ||
|
||
/// Attempts to make a new [`OwnedRwLockMappedWriteGuard`] for a component | ||
/// Makes a new [`OwnedRwLockReadGuard`] for a component of the locked data. | ||
/// | ||
/// This operation cannot fail as the `OwnedRwLockWriteGuard` passed in already | ||
/// locked the data. | ||
/// | ||
/// This is an associated function that needs to be used as | ||
/// `OwnedRwLockWriteGuard::downgrade_map(..)`. A method would interfere with methods of | ||
/// the same name on the contents of the locked data. | ||
/// | ||
/// Inside of `f`, you retain exclusive access to the data, despite only being given a `&T`. Handing out a | ||
/// `&mut T` would result in unsoundness, as you could use interior mutability. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use std::sync::Arc; | ||
/// use tokio::sync::{RwLock, OwnedRwLockWriteGuard}; | ||
/// | ||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
/// struct Foo(u32); | ||
/// | ||
/// # #[tokio::main] | ||
/// # async fn main() { | ||
/// let lock = Arc::new(RwLock::new(Foo(1))); | ||
/// | ||
/// let mapped = OwnedRwLockWriteGuard::downgrade_map(Arc::clone(&lock).write_owned().await, |f| &f.0); | ||
/// let foo = lock.read_owned().await; | ||
/// assert_eq!(foo.0, *mapped); | ||
/// # } | ||
/// ``` | ||
#[inline] | ||
pub fn downgrade_map<F, U: ?Sized>(this: Self, f: F) -> OwnedRwLockReadGuard<T, U> | ||
where | ||
F: FnOnce(&T) -> &U, | ||
{ | ||
let data = f(&*this) as *const U; | ||
let this = this.skip_drop(); | ||
let guard = OwnedRwLockReadGuard { | ||
lock: this.lock, | ||
data, | ||
_p: PhantomData, | ||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
resource_span: this.resource_span, | ||
}; | ||
|
||
// Release all but one of the permits held by the write guard | ||
let to_release = (this.permits_acquired - 1) as usize; | ||
guard.lock.s.release(to_release); | ||
|
||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
guard.resource_span.in_scope(|| { | ||
tracing::trace!( | ||
target: "runtime::resource::state_update", | ||
write_locked = false, | ||
write_locked.op = "override", | ||
) | ||
}); | ||
|
||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
guard.resource_span.in_scope(|| { | ||
tracing::trace!( | ||
target: "runtime::resource::state_update", | ||
current_readers = 1, | ||
current_readers.op = "add", | ||
) | ||
}); | ||
|
||
guard | ||
} | ||
|
||
/// Attempts to make a new [`OwnedRwLockMappedWriteGuard`] for a component | ||
/// of the locked data. The original guard is returned if the closure | ||
/// returns `None`. | ||
/// | ||
|
@@ -159,6 +229,86 @@ impl<T: ?Sized> OwnedRwLockWriteGuard<T> { | |
}) | ||
} | ||
|
||
/// Attempts to make a new [`OwnedRwLockReadGuard`] for a component of | ||
/// the locked data. The original guard is returned if the closure returns | ||
/// `None`. | ||
/// | ||
/// This operation cannot fail as the `OwnedRwLockWriteGuard` passed in already | ||
/// locked the data. | ||
/// | ||
/// This is an associated function that needs to be | ||
/// used as `OwnedRwLockWriteGuard::try_downgrade_map(...)`. A method would interfere with | ||
/// methods of the same name on the contents of the locked data. | ||
/// | ||
/// Inside of `f`, you retain exclusive access to the data, despite only being given a `&T`. Handing out a | ||
/// `&mut T` would result in unsoundness, as you could use interior mutability. | ||
/// | ||
/// If this function returns `Err(...)`, the lock is never unlocked nor downgraded. | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use std::sync::Arc; | ||
/// use tokio::sync::{RwLock, OwnedRwLockWriteGuard}; | ||
/// | ||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
/// struct Foo(u32); | ||
/// | ||
/// # #[tokio::main] | ||
/// # async fn main() { | ||
/// let lock = Arc::new(RwLock::new(Foo(1))); | ||
/// | ||
/// let guard = OwnedRwLockWriteGuard::try_downgrade_map(Arc::clone(&lock).write_owned().await, |f| Some(&f.0)).expect("should not fail"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same here. |
||
/// let foo = lock.read_owned().await; | ||
/// assert_eq!(foo.0, *guard); | ||
/// # } | ||
/// ``` | ||
#[inline] | ||
pub fn try_downgrade_map<F, U: ?Sized>( | ||
this: Self, | ||
f: F, | ||
) -> Result<OwnedRwLockReadGuard<T, U>, Self> | ||
where | ||
F: FnOnce(&T) -> Option<&U>, | ||
{ | ||
let data = match f(&*this) { | ||
Some(data) => data as *const U, | ||
None => return Err(this), | ||
}; | ||
let this = this.skip_drop(); | ||
let guard = OwnedRwLockReadGuard { | ||
lock: this.lock, | ||
data, | ||
_p: PhantomData, | ||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
resource_span: this.resource_span, | ||
}; | ||
|
||
// Release all but one of the permits held by the write guard | ||
let to_release = (this.permits_acquired - 1) as usize; | ||
guard.lock.s.release(to_release); | ||
|
||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
guard.resource_span.in_scope(|| { | ||
tracing::trace!( | ||
target: "runtime::resource::state_update", | ||
write_locked = false, | ||
write_locked.op = "override", | ||
) | ||
}); | ||
|
||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
guard.resource_span.in_scope(|| { | ||
tracing::trace!( | ||
target: "runtime::resource::state_update", | ||
current_readers = 1, | ||
current_readers.op = "add", | ||
) | ||
}); | ||
|
||
Ok(guard) | ||
} | ||
|
||
/// Converts this `OwnedRwLockWriteGuard` into an | ||
/// `OwnedRwLockMappedWriteGuard`. This method can be used to store a | ||
/// non-mapped guard in a struct field that expects a mapped guard. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,7 +102,84 @@ impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> { | |
} | ||
} | ||
|
||
/// Attempts to make a new [`RwLockMappedWriteGuard`] for a component of | ||
/// Makes a new [`RwLockReadGuard`] for a component of the locked data. | ||
/// | ||
/// This operation cannot fail as the `RwLockWriteGuard` passed in already | ||
/// locked the data. | ||
/// | ||
/// This is an associated function that needs to be used as | ||
/// `RwLockWriteGuard::downgrade_map(..)`. A method would interfere with methods of | ||
/// the same name on the contents of the locked data. | ||
/// | ||
/// This is equivalent to a combination of asynchronous [`RwLockWriteGuard::map`] and [`RwLockWriteGuard::downgrade`] | ||
/// from the [`parking_lot` crate]. | ||
/// | ||
/// Inside of `f`, you retain exclusive access to the data, despite only being given a `&T`. Handing out a | ||
/// `&mut T` would result in unsoundness, as you could use interior mutability. | ||
/// | ||
/// [`RwLockMappedWriteGuard`]: struct@crate::sync::RwLockMappedWriteGuard | ||
/// [`RwLockWriteGuard::map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.map | ||
/// [`RwLockWriteGuard::downgrade`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.downgrade | ||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use tokio::sync::{RwLock, RwLockWriteGuard}; | ||
/// | ||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
/// struct Foo(u32); | ||
/// | ||
/// # #[tokio::main] | ||
/// # async fn main() { | ||
/// let lock = RwLock::new(Foo(1)); | ||
/// | ||
/// let mapped = RwLockWriteGuard::downgrade_map(lock.write().await, |f| &f.0); | ||
/// let foo = lock.read().await; | ||
/// assert_eq!(foo.0, *mapped); | ||
/// # } | ||
/// ``` | ||
#[inline] | ||
pub fn downgrade_map<F, U: ?Sized>(this: Self, f: F) -> RwLockReadGuard<'a, U> | ||
where | ||
F: FnOnce(&T) -> &U, | ||
{ | ||
let data = f(&*this) as *const U; | ||
let this = this.skip_drop(); | ||
let guard = RwLockReadGuard { | ||
s: this.s, | ||
data, | ||
marker: PhantomData, | ||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
resource_span: this.resource_span, | ||
}; | ||
|
||
// Release all but one of the permits held by the write guard | ||
let to_release = (this.permits_acquired - 1) as usize; | ||
this.s.release(to_release); | ||
|
||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
guard.resource_span.in_scope(|| { | ||
tracing::trace!( | ||
target: "runtime::resource::state_update", | ||
write_locked = false, | ||
write_locked.op = "override", | ||
) | ||
}); | ||
|
||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
guard.resource_span.in_scope(|| { | ||
tracing::trace!( | ||
target: "runtime::resource::state_update", | ||
current_readers = 1, | ||
current_readers.op = "add", | ||
) | ||
}); | ||
|
||
guard | ||
} | ||
|
||
/// Attempts to make a new [`RwLockMappedWriteGuard`] for a component of | ||
/// the locked data. The original guard is returned if the closure returns | ||
/// `None`. | ||
/// | ||
|
@@ -165,6 +242,90 @@ impl<'a, T: ?Sized> RwLockWriteGuard<'a, T> { | |
}) | ||
} | ||
|
||
/// Attempts to make a new [`RwLockReadGuard`] for a component of | ||
/// the locked data. The original guard is returned if the closure returns | ||
/// `None`. | ||
/// | ||
/// This operation cannot fail as the `RwLockWriteGuard` passed in already | ||
/// locked the data. | ||
/// | ||
/// This is an associated function that needs to be | ||
/// used as `RwLockWriteGuard::try_downgrade_map(...)`. A method would interfere with | ||
/// methods of the same name on the contents of the locked data. | ||
/// | ||
/// This is equivalent to a combination of asynchronous [`RwLockWriteGuard::try_map`] and [`RwLockWriteGuard::downgrade`] | ||
/// from the [`parking_lot` crate]. | ||
/// | ||
/// Inside of `f`, you retain exclusive access to the data, despite only being given a `&T`. Handing out a | ||
/// `&mut T` would result in unsoundness, as you could use interior mutability. | ||
/// | ||
/// If this function returns `Err(...)`, the lock is never unlocked nor downgraded. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be great to have a test which checks this guarantee. For example, a test which checks if a reader which was already waiting for the access before calling this function is still waiting after this function returns |
||
/// | ||
/// [`RwLockMappedWriteGuard`]: struct@crate::sync::RwLockMappedWriteGuard | ||
/// [`RwLockWriteGuard::map`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.map | ||
/// [`RwLockWriteGuard::downgrade`]: https://docs.rs/lock_api/latest/lock_api/struct.RwLockWriteGuard.html#method.downgrade | ||
/// [`parking_lot` crate]: https://crates.io/crates/parking_lot | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// use tokio::sync::{RwLock, RwLockWriteGuard}; | ||
/// | ||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
/// struct Foo(u32); | ||
/// | ||
/// # #[tokio::main] | ||
/// # async fn main() { | ||
/// let lock = RwLock::new(Foo(1)); | ||
/// | ||
/// let guard = RwLockWriteGuard::try_downgrade_map(lock.write().await, |f| Some(&f.0)).expect("should not fail"); | ||
/// let foo = lock.read().await; | ||
/// assert_eq!(foo.0, *guard); | ||
/// # } | ||
/// ``` | ||
#[inline] | ||
pub fn try_downgrade_map<F, U: ?Sized>(this: Self, f: F) -> Result<RwLockReadGuard<'a, U>, Self> | ||
where | ||
F: FnOnce(&T) -> Option<&U>, | ||
{ | ||
let data = match f(&*this) { | ||
Some(data) => data as *const U, | ||
None => return Err(this), | ||
}; | ||
let this = this.skip_drop(); | ||
let guard = RwLockReadGuard { | ||
s: this.s, | ||
data, | ||
marker: PhantomData, | ||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
resource_span: this.resource_span, | ||
}; | ||
|
||
// Release all but one of the permits held by the write guard | ||
let to_release = (this.permits_acquired - 1) as usize; | ||
this.s.release(to_release); | ||
|
||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
guard.resource_span.in_scope(|| { | ||
tracing::trace!( | ||
target: "runtime::resource::state_update", | ||
write_locked = false, | ||
write_locked.op = "override", | ||
) | ||
}); | ||
|
||
#[cfg(all(tokio_unstable, feature = "tracing"))] | ||
guard.resource_span.in_scope(|| { | ||
tracing::trace!( | ||
target: "runtime::resource::state_update", | ||
current_readers = 1, | ||
current_readers.op = "add", | ||
) | ||
}); | ||
|
||
Ok(guard) | ||
} | ||
|
||
/// Converts this `RwLockWriteGuard` into an `RwLockMappedWriteGuard`. This | ||
/// method can be used to store a non-mapped guard in a struct field that | ||
/// expects a mapped guard. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you split this line? It's hard to see what's going on here from the docs page.