mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-08-29 00:00:11 +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
|
/// of the locked data. The original guard is returned if the closure
|
||||||
/// returns `None`.
|
/// 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
|
/// Converts this `OwnedRwLockWriteGuard` into an
|
||||||
/// `OwnedRwLockMappedWriteGuard`. This method can be used to store a
|
/// `OwnedRwLockMappedWriteGuard`. This method can be used to store a
|
||||||
/// non-mapped guard in a struct field that expects a mapped guard.
|
/// 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
|
/// the locked data. The original guard is returned if the closure returns
|
||||||
/// `None`.
|
/// `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
|
/// Converts this `RwLockWriteGuard` into an `RwLockMappedWriteGuard`. This
|
||||||
/// method can be used to store a non-mapped guard in a struct field that
|
/// method can be used to store a non-mapped guard in a struct field that
|
||||||
/// expects a mapped guard.
|
/// expects a mapped guard.
|
||||||
|
|||||||
@@ -160,6 +160,9 @@ impl<'a, T: ?Sized> RwLockMappedWriteGuard<'a, T> {
|
|||||||
resource_span: this.resource_span,
|
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> {
|
impl<T: ?Sized> ops::Deref for RwLockMappedWriteGuard<'_, T> {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use std::task::Poll;
|
|||||||
|
|
||||||
use futures::future::FutureExt;
|
use futures::future::FutureExt;
|
||||||
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::{RwLock, RwLockWriteGuard};
|
||||||
use tokio_test::task::spawn;
|
use tokio_test::task::spawn;
|
||||||
use tokio_test::{assert_pending, assert_ready};
|
use tokio_test::{assert_pending, assert_ready};
|
||||||
|
|
||||||
@@ -279,3 +279,53 @@ fn try_read_try_write() {
|
|||||||
|
|
||||||
assert_eq!(*lock.try_read().unwrap(), 1515);
|
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