diff --git a/tokio/src/sync/mod.rs b/tokio/src/sync/mod.rs index 5d8c82ea0..e1d1a83a3 100644 --- a/tokio/src/sync/mod.rs +++ b/tokio/src/sync/mod.rs @@ -451,6 +451,9 @@ cfg_sync! { mod rwlock; pub use rwlock::RwLock; + pub use rwlock::owned_read_guard::OwnedRwLockReadGuard; + pub use rwlock::owned_write_guard::OwnedRwLockWriteGuard; + pub use rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard; pub use rwlock::read_guard::RwLockReadGuard; pub use rwlock::write_guard::RwLockWriteGuard; pub use rwlock::write_guard_mapped::RwLockMappedWriteGuard; diff --git a/tokio/src/sync/rwlock.rs b/tokio/src/sync/rwlock.rs index 30e69fde0..22182f22a 100644 --- a/tokio/src/sync/rwlock.rs +++ b/tokio/src/sync/rwlock.rs @@ -2,10 +2,19 @@ use crate::sync::batch_semaphore::{Semaphore, TryAcquireError}; use crate::sync::mutex::TryLockError; use std::cell::UnsafeCell; use std::marker; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; +use std::sync::Arc; +pub(crate) mod owned_read_guard; +pub(crate) mod owned_write_guard; +pub(crate) mod owned_write_guard_mapped; pub(crate) mod read_guard; pub(crate) mod write_guard; pub(crate) mod write_guard_mapped; +pub(crate) use owned_read_guard::OwnedRwLockReadGuard; +pub(crate) use owned_write_guard::OwnedRwLockWriteGuard; +pub(crate) use owned_write_guard_mapped::OwnedRwLockMappedWriteGuard; pub(crate) use read_guard::RwLockReadGuard; pub(crate) use write_guard::RwLockWriteGuard; pub(crate) use write_guard_mapped::RwLockMappedWriteGuard; @@ -101,13 +110,31 @@ fn bounds() { check_sync::>(); check_unpin::>(); + check_send::>(); + check_sync::>(); + check_unpin::>(); + check_send::>(); check_sync::>(); check_unpin::>(); - let rwlock = RwLock::new(0); + check_send::>(); + check_sync::>(); + check_unpin::>(); + + check_send::>(); + check_sync::>(); + check_unpin::>(); + + check_send::>(); + check_sync::>(); + check_unpin::>(); + + let rwlock = Arc::new(RwLock::new(0)); check_send_sync_val(rwlock.read()); + check_send_sync_val(Arc::clone(&rwlock).read_owned()); check_send_sync_val(rwlock.write()); + check_send_sync_val(Arc::clone(&rwlock).write_owned()); } // As long as T: Send + Sync, it's fine to send and share RwLock between threads. @@ -120,14 +147,42 @@ unsafe impl Sync for RwLock where T: ?Sized + Send + Sync {} // `T` is `Send`. unsafe impl Send for RwLockReadGuard<'_, T> where T: ?Sized + Sync {} unsafe impl Sync for RwLockReadGuard<'_, T> where T: ?Sized + Send + Sync {} +// T is required to be `Send` because an OwnedRwLockReadGuard can be used to drop the value held in +// the RwLock, unlike RwLockReadGuard. +unsafe impl Send for OwnedRwLockReadGuard +where + T: ?Sized + Send + Sync, + U: ?Sized + Sync, +{ +} +unsafe impl Sync for OwnedRwLockReadGuard +where + T: ?Sized + Send + Sync, + U: ?Sized + Send + Sync, +{ +} unsafe impl Sync for RwLockWriteGuard<'_, T> where T: ?Sized + Send + Sync {} +unsafe impl Sync for OwnedRwLockWriteGuard where T: ?Sized + Send + Sync {} unsafe impl Sync for RwLockMappedWriteGuard<'_, T> where T: ?Sized + Send + Sync {} +unsafe impl Sync for OwnedRwLockMappedWriteGuard +where + T: ?Sized + Send + Sync, + U: ?Sized + Send + Sync, +{ +} // Safety: Stores a raw pointer to `T`, so if `T` is `Sync`, the lock guard over // `T` is `Send` - but since this is also provides mutable access, we need to // make sure that `T` is `Send` since its value can be sent across thread // boundaries. unsafe impl Send for RwLockWriteGuard<'_, T> where T: ?Sized + Send + Sync {} +unsafe impl Send for OwnedRwLockWriteGuard where T: ?Sized + Send + Sync {} unsafe impl Send for RwLockMappedWriteGuard<'_, T> where T: ?Sized + Send + Sync {} +unsafe impl Send for OwnedRwLockMappedWriteGuard +where + T: ?Sized + Send + Sync, + U: ?Sized + Send + Sync, +{ +} impl RwLock { /// Creates a new instance of an `RwLock` which is unlocked. @@ -222,6 +277,64 @@ impl RwLock { } } + /// Locks this `RwLock` with shared read access, causing the current task + /// to yield until the lock has been acquired. + /// + /// The calling task will yield until there are no writers which hold the + /// lock. There may be other readers inside the lock when the task resumes. + /// + /// This method is identical to [`RwLock::read`], except that the returned + /// guard references the `RwLock` with an [`Arc`] rather than by borrowing + /// it. Therefore, the `RwLock` must be wrapped in an `Arc` to call this + /// method, and the guard will live for the `'static` lifetime, as it keeps + /// the `RwLock` alive by holding an `Arc`. + /// + /// Note that under the priority policy of [`RwLock`], read locks are not + /// granted until prior write locks, to prevent starvation. Therefore + /// deadlock may occur if a read lock is held by the current task, a write + /// lock attempt is made, and then a subsequent read lock attempt is made + /// by the current task. + /// + /// Returns an RAII guard which will drop this read access of the `RwLock` + /// when dropped. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use tokio::sync::RwLock; + /// + /// #[tokio::main] + /// async fn main() { + /// let lock = Arc::new(RwLock::new(1)); + /// let c_lock = lock.clone(); + /// + /// let n = lock.read_owned().await; + /// assert_eq!(*n, 1); + /// + /// tokio::spawn(async move { + /// // While main has an active read lock, we acquire one too. + /// let r = c_lock.read_owned().await; + /// assert_eq!(*r, 1); + /// }).await.expect("The spawned task has panicked"); + /// + /// // Drop the guard after the spawned task finishes. + /// drop(n); + ///} + /// ``` + pub async fn read_owned(self: Arc) -> OwnedRwLockReadGuard { + self.s.acquire(1).await.unwrap_or_else(|_| { + // The semaphore was closed. but, we never explicitly close it, and we have a + // handle to it through the Arc, which means that this can never happen. + unreachable!() + }); + OwnedRwLockReadGuard { + data: self.c.get(), + lock: ManuallyDrop::new(self), + _p: PhantomData, + } + } + /// Attempts to acquire this `RwLock` with shared read access. /// /// If the access couldn't be acquired immediately, returns [`TryLockError`]. @@ -268,6 +381,58 @@ impl RwLock { }) } + /// Attempts to acquire this `RwLock` with shared read access. + /// + /// If the access couldn't be acquired immediately, returns [`TryLockError`]. + /// Otherwise, an RAII guard is returned which will release read access + /// when dropped. + /// + /// This method is identical to [`RwLock::try_read`], except that the + /// returned guard references the `RwLock` with an [`Arc`] rather than by + /// borrowing it. Therefore, the `RwLock` must be wrapped in an `Arc` to + /// call this method, and the guard will live for the `'static` lifetime, + /// as it keeps the `RwLock` alive by holding an `Arc`. + /// + /// [`TryLockError`]: TryLockError + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use tokio::sync::RwLock; + /// + /// #[tokio::main] + /// async fn main() { + /// let lock = Arc::new(RwLock::new(1)); + /// let c_lock = lock.clone(); + /// + /// let v = lock.try_read_owned().unwrap(); + /// assert_eq!(*v, 1); + /// + /// tokio::spawn(async move { + /// // While main has an active read lock, we acquire one too. + /// let n = c_lock.read_owned().await; + /// assert_eq!(*n, 1); + /// }).await.expect("The spawned task has panicked"); + /// + /// // Drop the guard when spawned task finishes. + /// drop(v); + /// } + /// ``` + pub fn try_read_owned(self: Arc) -> Result, TryLockError> { + match self.s.try_acquire(1) { + Ok(permit) => permit, + Err(TryAcquireError::NoPermits) => return Err(TryLockError(())), + Err(TryAcquireError::Closed) => unreachable!(), + } + + Ok(OwnedRwLockReadGuard { + data: self.c.get(), + lock: ManuallyDrop::new(self), + _p: PhantomData, + }) + } + /// Locks this `RwLock` with exclusive write access, causing the current /// task to yield until the lock has been acquired. /// @@ -303,6 +468,48 @@ impl RwLock { } } + /// Locks this `RwLock` with exclusive write access, causing the current + /// task to yield until the lock has been acquired. + /// + /// The calling task will yield while other writers or readers currently + /// have access to the lock. + /// + /// This method is identical to [`RwLock::write`], except that the returned + /// guard references the `RwLock` with an [`Arc`] rather than by borrowing + /// it. Therefore, the `RwLock` must be wrapped in an `Arc` to call this + /// method, and the guard will live for the `'static` lifetime, as it keeps + /// the `RwLock` alive by holding an `Arc`. + /// + /// Returns an RAII guard which will drop the write access of this `RwLock` + /// when dropped. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use tokio::sync::RwLock; + /// + /// #[tokio::main] + /// async fn main() { + /// let lock = Arc::new(RwLock::new(1)); + /// + /// let mut n = lock.write_owned().await; + /// *n = 2; + ///} + /// ``` + pub async fn write_owned(self: Arc) -> OwnedRwLockWriteGuard { + self.s.acquire(MAX_READS as u32).await.unwrap_or_else(|_| { + // The semaphore was closed. but, we never explicitly close it, and we have a + // handle to it through the Arc, which means that this can never happen. + unreachable!() + }); + OwnedRwLockWriteGuard { + data: self.c.get(), + lock: ManuallyDrop::new(self), + _p: PhantomData, + } + } + /// Attempts to acquire this `RwLock` with exclusive write access. /// /// If the access couldn't be acquired immediately, returns [`TryLockError`]. @@ -340,6 +547,50 @@ impl RwLock { }) } + /// Attempts to acquire this `RwLock` with exclusive write access. + /// + /// If the access couldn't be acquired immediately, returns [`TryLockError`]. + /// Otherwise, an RAII guard is returned which will release write access + /// when dropped. + /// + /// This method is identical to [`RwLock::try_write`], except that the + /// returned guard references the `RwLock` with an [`Arc`] rather than by + /// borrowing it. Therefore, the `RwLock` must be wrapped in an `Arc` to + /// call this method, and the guard will live for the `'static` lifetime, + /// as it keeps the `RwLock` alive by holding an `Arc`. + /// + /// [`TryLockError`]: TryLockError + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use tokio::sync::RwLock; + /// + /// #[tokio::main] + /// async fn main() { + /// let rw = Arc::new(RwLock::new(1)); + /// + /// let v = Arc::clone(&rw).read_owned().await; + /// assert_eq!(*v, 1); + /// + /// assert!(rw.try_write_owned().is_err()); + /// } + /// ``` + pub fn try_write_owned(self: Arc) -> Result, TryLockError> { + match self.s.try_acquire(MAX_READS as u32) { + Ok(permit) => permit, + Err(TryAcquireError::NoPermits) => return Err(TryLockError(())), + Err(TryAcquireError::Closed) => unreachable!(), + } + + Ok(OwnedRwLockWriteGuard { + data: self.c.get(), + lock: ManuallyDrop::new(self), + _p: PhantomData, + }) + } + /// Returns a mutable reference to the underlying data. /// /// Since this call borrows the `RwLock` mutably, no actual locking needs to diff --git a/tokio/src/sync/rwlock/owned_read_guard.rs b/tokio/src/sync/rwlock/owned_read_guard.rs new file mode 100644 index 000000000..b7f3926a4 --- /dev/null +++ b/tokio/src/sync/rwlock/owned_read_guard.rs @@ -0,0 +1,149 @@ +use crate::sync::rwlock::RwLock; +use std::fmt; +use std::marker::PhantomData; +use std::mem; +use std::mem::ManuallyDrop; +use std::ops; +use std::sync::Arc; + +/// Owned RAII structure used to release the shared read access of a lock when +/// dropped. +/// +/// This structure is created by the [`read_owned`] method on +/// [`RwLock`]. +/// +/// [`read_owned`]: method@crate::sync::RwLock::read_owned +/// [`RwLock`]: struct@crate::sync::RwLock +pub struct OwnedRwLockReadGuard { + // ManuallyDrop allows us to destructure into this field without running the destructor. + pub(super) lock: ManuallyDrop>>, + pub(super) data: *const U, + pub(super) _p: PhantomData, +} + +impl OwnedRwLockReadGuard { + /// Make a new `OwnedRwLockReadGuard` for a component of the locked data. + /// This operation cannot fail as the `OwnedRwLockReadGuard` passed in + /// already locked the data. + /// + /// This is an associated function that needs to be + /// used as `OwnedRwLockReadGuard::map(...)`. A method would interfere with + /// methods of the same name on the contents of the locked data. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use tokio::sync::{RwLock, OwnedRwLockReadGuard}; + /// + /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] + /// struct Foo(u32); + /// + /// # #[tokio::main] + /// # async fn main() { + /// let lock = Arc::new(RwLock::new(Foo(1))); + /// + /// let guard = lock.read_owned().await; + /// let guard = OwnedRwLockReadGuard::map(guard, |f| &f.0); + /// + /// assert_eq!(1, *guard); + /// # } + /// ``` + #[inline] + pub fn map(mut this: Self, f: F) -> OwnedRwLockReadGuard + where + F: FnOnce(&U) -> &V, + { + let data = f(&*this) as *const V; + let lock = unsafe { ManuallyDrop::take(&mut this.lock) }; + // NB: Forget to avoid drop impl from being called. + mem::forget(this); + OwnedRwLockReadGuard { + lock: ManuallyDrop::new(lock), + data, + _p: PhantomData, + } + } + + /// 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 `OwnedRwLockReadGuard` passed in + /// already locked the data. + /// + /// This is an associated function that needs to be used as + /// `OwnedRwLockReadGuard::try_map(..)`. A method would interfere with + /// methods of the same name on the contents of the locked data. + /// + /// # Examples + /// + /// ``` + /// use std::sync::Arc; + /// use tokio::sync::{RwLock, OwnedRwLockReadGuard}; + /// + /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] + /// struct Foo(u32); + /// + /// # #[tokio::main] + /// # async fn main() { + /// let lock = Arc::new(RwLock::new(Foo(1))); + /// + /// let guard = lock.read_owned().await; + /// let guard = OwnedRwLockReadGuard::try_map(guard, |f| Some(&f.0)).expect("should not fail"); + /// + /// assert_eq!(1, *guard); + /// # } + /// ``` + #[inline] + pub fn try_map(mut this: Self, f: F) -> Result, Self> + where + F: FnOnce(&U) -> Option<&V>, + { + let data = match f(&*this) { + Some(data) => data as *const V, + None => return Err(this), + }; + let lock = unsafe { ManuallyDrop::take(&mut this.lock) }; + // NB: Forget to avoid drop impl from being called. + mem::forget(this); + Ok(OwnedRwLockReadGuard { + lock: ManuallyDrop::new(lock), + data, + _p: PhantomData, + }) + } +} + +impl ops::Deref for OwnedRwLockReadGuard { + type Target = U; + + fn deref(&self) -> &U { + unsafe { &*self.data } + } +} + +impl fmt::Debug for OwnedRwLockReadGuard +where + U: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Display for OwnedRwLockReadGuard +where + U: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +impl Drop for OwnedRwLockReadGuard { + fn drop(&mut self) { + self.lock.s.release(1); + unsafe { ManuallyDrop::drop(&mut self.lock) }; + } +} diff --git a/tokio/src/sync/rwlock/owned_write_guard.rs b/tokio/src/sync/rwlock/owned_write_guard.rs new file mode 100644 index 000000000..0d942bc4e --- /dev/null +++ b/tokio/src/sync/rwlock/owned_write_guard.rs @@ -0,0 +1,229 @@ +use crate::sync::rwlock::owned_read_guard::OwnedRwLockReadGuard; +use crate::sync::rwlock::owned_write_guard_mapped::OwnedRwLockMappedWriteGuard; +use crate::sync::rwlock::RwLock; +use std::fmt; +use std::marker::PhantomData; +use std::mem::{self, ManuallyDrop}; +use std::ops; +use std::sync::Arc; + +/// Owned RAII structure used to release the exclusive write access of a lock when +/// dropped. +/// +/// This structure is created by the [`write_owned`] method +/// on [`RwLock`]. +/// +/// [`write_owned`]: method@crate::sync::RwLock::write_owned +/// [`RwLock`]: struct@crate::sync::RwLock +pub struct OwnedRwLockWriteGuard { + // ManuallyDrop allows us to destructure into this field without running the destructor. + pub(super) lock: ManuallyDrop>>, + pub(super) data: *mut T, + pub(super) _p: PhantomData, +} + +impl OwnedRwLockWriteGuard { + /// Make a new [`OwnedRwLockMappedWriteGuard`] 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::map(..)`. A method would interfere with methods + /// of the same name on the contents of the locked data. + /// + /// # 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 lock = Arc::clone(&lock); + /// let mut mapped = OwnedRwLockWriteGuard::map(lock.write_owned().await, |f| &mut f.0); + /// *mapped = 2; + /// } + /// + /// assert_eq!(Foo(2), *lock.read().await); + /// # } + /// ``` + #[inline] + pub fn map(mut this: Self, f: F) -> OwnedRwLockMappedWriteGuard + where + F: FnOnce(&mut T) -> &mut U, + { + let data = f(&mut *this) as *mut U; + let lock = unsafe { ManuallyDrop::take(&mut this.lock) }; + // NB: Forget to avoid drop impl from being called. + mem::forget(this); + OwnedRwLockMappedWriteGuard { + lock: ManuallyDrop::new(lock), + data, + _p: PhantomData, + } + } + + /// Attempts to make a new [`OwnedRwLockMappedWriteGuard`] 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_map(...)`. A method would interfere + /// with methods of the same name on the contents of the locked data. + /// + /// [`RwLockMappedWriteGuard`]: struct@crate::sync::RwLockMappedWriteGuard + /// + /// # 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 mut guard = OwnedRwLockWriteGuard::try_map(guard, |f| Some(&mut f.0)).expect("should not fail"); + /// *guard = 2; + /// } + /// + /// assert_eq!(Foo(2), *lock.read().await); + /// # } + /// ``` + #[inline] + pub fn try_map( + mut this: Self, + f: F, + ) -> Result, Self> + where + F: FnOnce(&mut T) -> Option<&mut U>, + { + let data = match f(&mut *this) { + Some(data) => data as *mut U, + None => return Err(this), + }; + let lock = unsafe { ManuallyDrop::take(&mut this.lock) }; + // NB: Forget to avoid drop impl from being called. + mem::forget(this); + Ok(OwnedRwLockMappedWriteGuard { + lock: ManuallyDrop::new(lock), + data, + _p: PhantomData, + }) + } + + /// 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. + /// + /// This is equivalent to calling `OwnedRwLockWriteGuard::map(guard, |me| me)`. + #[inline] + pub fn into_mapped(this: Self) -> OwnedRwLockMappedWriteGuard { + Self::map(this, |me| me) + } + + /// Atomically downgrades a write lock into a read lock without allowing + /// any writers to take exclusive access of the lock in the meantime. + /// + /// **Note:** This won't *necessarily* allow any additional readers to acquire + /// locks, since [`RwLock`] is fair and it is possible that a writer is next + /// in line. + /// + /// Returns an RAII guard which will drop this read access of the `RwLock` + /// when dropped. + /// + /// # Examples + /// + /// ``` + /// # use tokio::sync::RwLock; + /// # use std::sync::Arc; + /// # + /// # #[tokio::main] + /// # async fn main() { + /// let lock = Arc::new(RwLock::new(1)); + /// + /// let n = lock.clone().write_owned().await; + /// + /// let cloned_lock = lock.clone(); + /// let handle = tokio::spawn(async move { + /// *cloned_lock.write_owned().await = 2; + /// }); + /// + /// let n = n.downgrade(); + /// assert_eq!(*n, 1, "downgrade is atomic"); + /// + /// drop(n); + /// handle.await.unwrap(); + /// assert_eq!(*lock.read().await, 2, "second writer obtained write lock"); + /// # } + /// ``` + pub fn downgrade(mut self) -> OwnedRwLockReadGuard { + let lock = unsafe { ManuallyDrop::take(&mut self.lock) }; + let data = self.data; + + // Release all but one of the permits held by the write guard + lock.s.release(super::MAX_READS - 1); + // NB: Forget to avoid drop impl from being called. + mem::forget(self); + OwnedRwLockReadGuard { + lock: ManuallyDrop::new(lock), + data, + _p: PhantomData, + } + } +} + +impl ops::Deref for OwnedRwLockWriteGuard { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.data } + } +} + +impl ops::DerefMut for OwnedRwLockWriteGuard { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.data } + } +} + +impl fmt::Debug for OwnedRwLockWriteGuard +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Display for OwnedRwLockWriteGuard +where + T: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +impl Drop for OwnedRwLockWriteGuard { + fn drop(&mut self) { + self.lock.s.release(super::MAX_READS); + unsafe { ManuallyDrop::drop(&mut self.lock) }; + } +} diff --git a/tokio/src/sync/rwlock/owned_write_guard_mapped.rs b/tokio/src/sync/rwlock/owned_write_guard_mapped.rs new file mode 100644 index 000000000..4ab644d71 --- /dev/null +++ b/tokio/src/sync/rwlock/owned_write_guard_mapped.rs @@ -0,0 +1,166 @@ +use crate::sync::rwlock::RwLock; +use std::fmt; +use std::marker::PhantomData; +use std::mem::{self, ManuallyDrop}; +use std::ops; +use std::sync::Arc; + +/// Owned RAII structure used to release the exclusive write access of a lock when +/// dropped. +/// +/// This structure is created by [mapping] an [`OwnedRwLockWriteGuard`]. It is a +/// separate type from `OwnedRwLockWriteGuard` to disallow downgrading a mapped +/// guard, since doing so can cause undefined behavior. +/// +/// [mapping]: method@crate::sync::OwnedRwLockWriteGuard::map +/// [`OwnedRwLockWriteGuard`]: struct@crate::sync::OwnedRwLockWriteGuard +pub struct OwnedRwLockMappedWriteGuard { + // ManuallyDrop allows us to destructure into this field without running the destructor. + pub(super) lock: ManuallyDrop>>, + pub(super) data: *mut U, + pub(super) _p: PhantomData, +} + +impl OwnedRwLockMappedWriteGuard { + /// Make a new `OwnedRwLockMappedWriteGuard` for a component of the locked + /// data. + /// + /// This operation cannot fail as the `OwnedRwLockMappedWriteGuard` passed + /// in already locked the data. + /// + /// This is an associated function that needs to be used as + /// `OwnedRwLockWriteGuard::map(..)`. A method would interfere with methods + /// of the same name on the contents of the locked data. + /// + /// # 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 lock = Arc::clone(&lock); + /// let mut mapped = OwnedRwLockWriteGuard::map(lock.write_owned().await, |f| &mut f.0); + /// *mapped = 2; + /// } + /// + /// assert_eq!(Foo(2), *lock.read().await); + /// # } + /// ``` + #[inline] + pub fn map(mut this: Self, f: F) -> OwnedRwLockMappedWriteGuard + where + F: FnOnce(&mut U) -> &mut V, + { + let data = f(&mut *this) as *mut V; + let lock = unsafe { ManuallyDrop::take(&mut this.lock) }; + // NB: Forget to avoid drop impl from being called. + mem::forget(this); + OwnedRwLockMappedWriteGuard { + lock: ManuallyDrop::new(lock), + data, + _p: PhantomData, + } + } + + /// Attempts to make a new `OwnedRwLockMappedWriteGuard` for a component + /// of the locked data. The original guard is returned if the closure + /// returns `None`. + /// + /// This operation cannot fail as the `OwnedRwLockMappedWriteGuard` passed + /// in already locked the data. + /// + /// This is an associated function that needs to be + /// used as `OwnedRwLockMappedWriteGuard::try_map(...)`. A method would interfere with + /// methods of the same name on the contents of the locked data. + /// + /// # 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 mut guard = OwnedRwLockWriteGuard::try_map(guard, |f| Some(&mut f.0)).expect("should not fail"); + /// *guard = 2; + /// } + /// + /// assert_eq!(Foo(2), *lock.read().await); + /// # } + /// ``` + #[inline] + pub fn try_map( + mut this: Self, + f: F, + ) -> Result, Self> + where + F: FnOnce(&mut U) -> Option<&mut V>, + { + let data = match f(&mut *this) { + Some(data) => data as *mut V, + None => return Err(this), + }; + let lock = unsafe { ManuallyDrop::take(&mut this.lock) }; + // NB: Forget to avoid drop impl from being called. + mem::forget(this); + Ok(OwnedRwLockMappedWriteGuard { + lock: ManuallyDrop::new(lock), + data, + _p: PhantomData, + }) + } +} + +impl ops::Deref for OwnedRwLockMappedWriteGuard { + type Target = U; + + fn deref(&self) -> &U { + unsafe { &*self.data } + } +} + +impl ops::DerefMut for OwnedRwLockMappedWriteGuard { + fn deref_mut(&mut self) -> &mut U { + unsafe { &mut *self.data } + } +} + +impl fmt::Debug for OwnedRwLockMappedWriteGuard +where + U: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&**self, f) + } +} + +impl fmt::Display for OwnedRwLockMappedWriteGuard +where + U: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&**self, f) + } +} + +impl Drop for OwnedRwLockMappedWriteGuard { + fn drop(&mut self) { + self.lock.s.release(super::MAX_READS); + unsafe { ManuallyDrop::drop(&mut self.lock) }; + } +} diff --git a/tokio/src/sync/rwlock/read_guard.rs b/tokio/src/sync/rwlock/read_guard.rs index d648944b2..38eec7727 100644 --- a/tokio/src/sync/rwlock/read_guard.rs +++ b/tokio/src/sync/rwlock/read_guard.rs @@ -18,7 +18,7 @@ pub struct RwLockReadGuard<'a, T: ?Sized> { pub(super) marker: marker::PhantomData<&'a T>, } -impl<'a, T> RwLockReadGuard<'a, T> { +impl<'a, T: ?Sized> RwLockReadGuard<'a, T> { /// Make a new `RwLockReadGuard` for a component of the locked data. /// /// This operation cannot fail as the `RwLockReadGuard` passed in already diff --git a/tokio/src/sync/rwlock/write_guard.rs b/tokio/src/sync/rwlock/write_guard.rs index 9f52d0461..1737d203c 100644 --- a/tokio/src/sync/rwlock/write_guard.rs +++ b/tokio/src/sync/rwlock/write_guard.rs @@ -9,7 +9,7 @@ use std::ops; /// RAII structure used to release the exclusive write access of a lock when /// dropped. /// -/// This structure is created by the [`write`] and method +/// This structure is created by the [`write`] method /// on [`RwLock`]. /// /// [`write`]: method@crate::sync::RwLock::write diff --git a/tokio/src/sync/rwlock/write_guard_mapped.rs b/tokio/src/sync/rwlock/write_guard_mapped.rs index ea2e9df7f..4e1b0110a 100644 --- a/tokio/src/sync/rwlock/write_guard_mapped.rs +++ b/tokio/src/sync/rwlock/write_guard_mapped.rs @@ -26,8 +26,8 @@ impl<'a, T: ?Sized> RwLockMappedWriteGuard<'a, T> { /// locked the data. /// /// This is an associated function that needs to be used as - /// `RwLockWriteGuard::map(..)`. A method would interfere with methods of - /// the same name on the contents of the locked data. + /// `RwLockMappedWriteGuard::map(..)`. A method would interfere with methods + /// of the same name on the contents of the locked data. /// /// This is an asynchronous version of [`RwLockWriteGuard::map`] from the /// [`parking_lot` crate]. @@ -71,7 +71,7 @@ impl<'a, T: ?Sized> RwLockMappedWriteGuard<'a, T> { } } - /// Attempts to make a new [`RwLockMappedWriteGuard`] for a component of + /// Attempts to make a new [`RwLockMappedWriteGuard`] for a component of /// the locked data. The original guard is returned if the closure returns /// `None`. /// @@ -79,8 +79,8 @@ impl<'a, T: ?Sized> RwLockMappedWriteGuard<'a, T> { /// locked the data. /// /// This is an associated function that needs to be - /// used as `RwLockWriteGuard::try_map(...)`. A method would interfere with - /// methods of the same name on the contents of the locked data. + /// used as `RwLockMappedWriteGuard::try_map(...)`. A method would interfere + /// with methods of the same name on the contents of the locked data. /// /// This is an asynchronous version of [`RwLockWriteGuard::try_map`] from /// the [`parking_lot` crate]. diff --git a/tokio/src/sync/tests/loom_rwlock.rs b/tokio/src/sync/tests/loom_rwlock.rs index 2834a2635..4b5cc7edc 100644 --- a/tokio/src/sync/tests/loom_rwlock.rs +++ b/tokio/src/sync/tests/loom_rwlock.rs @@ -22,7 +22,7 @@ fn concurrent_write() { let rwclone = rwlock.clone(); let t2 = thread::spawn(move || { block_on(async { - let mut guard = rwclone.write().await; + let mut guard = rwclone.write_owned().await; *guard += 5; }); }); @@ -53,7 +53,7 @@ fn concurrent_read_write() { let rwclone = rwlock.clone(); let t2 = thread::spawn(move || { block_on(async { - let mut guard = rwclone.write().await; + let mut guard = rwclone.write_owned().await; *guard += 5; }); }); @@ -67,6 +67,12 @@ fn concurrent_read_write() { }); }); + { + let guard = block_on(rwlock.clone().read_owned()); + //at this state the value on the lock may either be 0, 5, or 10 + assert!(*guard == 0 || *guard == 5 || *guard == 10); + } + t1.join().expect("thread 1 write should not panic"); t2.join().expect("thread 2 write should not panic"); t3.join().expect("thread 3 read should not panic");