mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-16 00:00:12 +02:00
sync: add RwLockWriteGuard::{downgrade_map, try_downgrade_map} (#5527)
This commit is contained in:
@@ -100,7 +100,78 @@ 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 guard = Arc::clone(&lock).write_owned().await;
|
||||
/// let mapped = OwnedRwLockWriteGuard::downgrade_map(guard, |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 +230,87 @@ 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 = Arc::clone(&lock).write_owned().await;
|
||||
/// let guard = OwnedRwLockWriteGuard::try_downgrade_map(guard, |f| Some(&f.0)).expect("should not fail");
|
||||
/// 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.
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
/// [`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.
|
||||
|
||||
@@ -160,6 +160,9 @@ impl<'a, T: ?Sized> RwLockMappedWriteGuard<'a, T> {
|
||||
resource_span: this.resource_span,
|
||||
})
|
||||
}
|
||||
|
||||
// Note: No `downgrade`, `downgrade_map` nor `try_downgrade_map` because they would be unsound, as we're already
|
||||
// potentially been mapped with internal mutability.
|
||||
}
|
||||
|
||||
impl<T: ?Sized> ops::Deref for RwLockMappedWriteGuard<'_, T> {
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::task::Poll;
|
||||
|
||||
use futures::future::FutureExt;
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::sync::{RwLock, RwLockWriteGuard};
|
||||
use tokio_test::task::spawn;
|
||||
use tokio_test::{assert_pending, assert_ready};
|
||||
|
||||
@@ -279,3 +279,53 @@ fn try_read_try_write() {
|
||||
|
||||
assert_eq!(*lock.try_read().unwrap(), 1515);
|
||||
}
|
||||
|
||||
#[maybe_tokio_test]
|
||||
async fn downgrade_map() {
|
||||
let lock = RwLock::new(0);
|
||||
let write_guard = lock.write().await;
|
||||
let mut read_t = spawn(lock.read());
|
||||
|
||||
// We can't create a read when a write exists
|
||||
assert_pending!(read_t.poll());
|
||||
|
||||
// During the call to `f`, `read_t` doesn't have access yet.
|
||||
let read_guard1 = RwLockWriteGuard::downgrade_map(write_guard, |v| {
|
||||
assert_pending!(read_t.poll());
|
||||
v
|
||||
});
|
||||
|
||||
// After the downgrade, `read_t` got the lock
|
||||
let read_guard2 = assert_ready!(read_t.poll());
|
||||
|
||||
// Ensure they're equal, as we return the original value
|
||||
assert_eq!(&*read_guard1 as *const _, &*read_guard2 as *const _);
|
||||
}
|
||||
|
||||
#[maybe_tokio_test]
|
||||
async fn try_downgrade_map() {
|
||||
let lock = RwLock::new(0);
|
||||
let write_guard = lock.write().await;
|
||||
let mut read_t = spawn(lock.read());
|
||||
|
||||
// We can't create a read when a write exists
|
||||
assert_pending!(read_t.poll());
|
||||
|
||||
// During the call to `f`, `read_t` doesn't have access yet.
|
||||
let write_guard = RwLockWriteGuard::try_downgrade_map(write_guard, |_| {
|
||||
assert_pending!(read_t.poll());
|
||||
None::<&()>
|
||||
})
|
||||
.expect_err("downgrade didn't fail");
|
||||
|
||||
// After `f` returns `None`, `read_t` doesn't have access
|
||||
assert_pending!(read_t.poll());
|
||||
|
||||
// After `f` returns `Some`, `read_t` does have access
|
||||
let read_guard1 = RwLockWriteGuard::try_downgrade_map(write_guard, |v| Some(v))
|
||||
.expect("downgrade didn't succeed");
|
||||
let read_guard2 = assert_ready!(read_t.poll());
|
||||
|
||||
// Ensure they're equal, as we return the original value
|
||||
assert_eq!(&*read_guard1 as *const _, &*read_guard2 as *const _);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user