diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/futures-util/src/lock/bilock.rs | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/futures-util/src/lock/bilock.rs')
-rw-r--r-- | third_party/rust/futures-util/src/lock/bilock.rs | 293 |
1 files changed, 293 insertions, 0 deletions
diff --git a/third_party/rust/futures-util/src/lock/bilock.rs b/third_party/rust/futures-util/src/lock/bilock.rs new file mode 100644 index 0000000000..7ddc66ad2c --- /dev/null +++ b/third_party/rust/futures-util/src/lock/bilock.rs @@ -0,0 +1,293 @@ +//! Futures-powered synchronization primitives. + +use alloc::boxed::Box; +use alloc::sync::Arc; +use core::cell::UnsafeCell; +use core::ops::{Deref, DerefMut}; +use core::pin::Pin; +use core::sync::atomic::AtomicPtr; +use core::sync::atomic::Ordering::SeqCst; +use core::{fmt, ptr}; +#[cfg(feature = "bilock")] +use futures_core::future::Future; +use futures_core::task::{Context, Poll, Waker}; + +/// A type of futures-powered synchronization primitive which is a mutex between +/// two possible owners. +/// +/// This primitive is not as generic as a full-blown mutex but is sufficient for +/// many use cases where there are only two possible owners of a resource. The +/// implementation of `BiLock` can be more optimized for just the two possible +/// owners. +/// +/// Note that it's possible to use this lock through a poll-style interface with +/// the `poll_lock` method but you can also use it as a future with the `lock` +/// method that consumes a `BiLock` and returns a future that will resolve when +/// it's locked. +/// +/// A `BiLock` is typically used for "split" operations where data which serves +/// two purposes wants to be split into two to be worked with separately. For +/// example a TCP stream could be both a reader and a writer or a framing layer +/// could be both a stream and a sink for messages. A `BiLock` enables splitting +/// these two and then using each independently in a futures-powered fashion. +/// +/// This type is only available when the `bilock` feature of this +/// library is activated. +#[derive(Debug)] +#[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] +pub struct BiLock<T> { + arc: Arc<Inner<T>>, +} + +#[derive(Debug)] +struct Inner<T> { + state: AtomicPtr<Waker>, + value: Option<UnsafeCell<T>>, +} + +unsafe impl<T: Send> Send for Inner<T> {} +unsafe impl<T: Send> Sync for Inner<T> {} + +impl<T> BiLock<T> { + /// Creates a new `BiLock` protecting the provided data. + /// + /// Two handles to the lock are returned, and these are the only two handles + /// that will ever be available to the lock. These can then be sent to separate + /// tasks to be managed there. + /// + /// The data behind the bilock is considered to be pinned, which allows `Pin` + /// references to locked data. However, this means that the locked value + /// will only be available through `Pin<&mut T>` (not `&mut T`) unless `T` is `Unpin`. + /// Similarly, reuniting the lock and extracting the inner value is only + /// possible when `T` is `Unpin`. + pub fn new(t: T) -> (Self, Self) { + let arc = Arc::new(Inner { + state: AtomicPtr::new(ptr::null_mut()), + value: Some(UnsafeCell::new(t)), + }); + + (Self { arc: arc.clone() }, Self { arc }) + } + + /// Attempt to acquire this lock, returning `Pending` if it can't be + /// acquired. + /// + /// This function will acquire the lock in a nonblocking fashion, returning + /// immediately if the lock is already held. If the lock is successfully + /// acquired then `Poll::Ready` is returned with a value that represents + /// the locked value (and can be used to access the protected data). The + /// lock is unlocked when the returned `BiLockGuard` is dropped. + /// + /// If the lock is already held then this function will return + /// `Poll::Pending`. In this case the current task will also be scheduled + /// to receive a notification when the lock would otherwise become + /// available. + /// + /// # Panics + /// + /// This function will panic if called outside the context of a future's + /// task. + pub fn poll_lock(&self, cx: &mut Context<'_>) -> Poll<BiLockGuard<'_, T>> { + let mut waker = None; + loop { + let n = self.arc.state.swap(invalid_ptr(1), SeqCst); + match n as usize { + // Woohoo, we grabbed the lock! + 0 => return Poll::Ready(BiLockGuard { bilock: self }), + + // Oops, someone else has locked the lock + 1 => {} + + // A task was previously blocked on this lock, likely our task, + // so we need to update that task. + _ => unsafe { + let mut prev = Box::from_raw(n); + *prev = cx.waker().clone(); + waker = Some(prev); + }, + } + + // type ascription for safety's sake! + let me: Box<Waker> = waker.take().unwrap_or_else(|| Box::new(cx.waker().clone())); + let me = Box::into_raw(me); + + match self.arc.state.compare_exchange(invalid_ptr(1), me, SeqCst, SeqCst) { + // The lock is still locked, but we've now parked ourselves, so + // just report that we're scheduled to receive a notification. + Ok(_) => return Poll::Pending, + + // Oops, looks like the lock was unlocked after our swap above + // and before the compare_exchange. Deallocate what we just + // allocated and go through the loop again. + Err(n) if n.is_null() => unsafe { + waker = Some(Box::from_raw(me)); + }, + + // The top of this loop set the previous state to 1, so if we + // failed the CAS above then it's because the previous value was + // *not* zero or one. This indicates that a task was blocked, + // but we're trying to acquire the lock and there's only one + // other reference of the lock, so it should be impossible for + // that task to ever block itself. + Err(n) => panic!("invalid state: {}", n as usize), + } + } + } + + /// Perform a "blocking lock" of this lock, consuming this lock handle and + /// returning a future to the acquired lock. + /// + /// This function consumes the `BiLock<T>` and returns a sentinel future, + /// `BiLockAcquire<T>`. The returned future will resolve to + /// `BiLockAcquired<T>` which represents a locked lock similarly to + /// `BiLockGuard<T>`. + /// + /// Note that the returned future will never resolve to an error. + #[cfg(feature = "bilock")] + #[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] + pub fn lock(&self) -> BiLockAcquire<'_, T> { + BiLockAcquire { bilock: self } + } + + /// Attempts to put the two "halves" of a `BiLock<T>` back together and + /// recover the original value. Succeeds only if the two `BiLock<T>`s + /// originated from the same call to `BiLock::new`. + pub fn reunite(self, other: Self) -> Result<T, ReuniteError<T>> + where + T: Unpin, + { + if Arc::ptr_eq(&self.arc, &other.arc) { + drop(other); + let inner = Arc::try_unwrap(self.arc) + .ok() + .expect("futures: try_unwrap failed in BiLock<T>::reunite"); + Ok(unsafe { inner.into_value() }) + } else { + Err(ReuniteError(self, other)) + } + } + + fn unlock(&self) { + let n = self.arc.state.swap(ptr::null_mut(), SeqCst); + match n as usize { + // we've locked the lock, shouldn't be possible for us to see an + // unlocked lock. + 0 => panic!("invalid unlocked state"), + + // Ok, no one else tried to get the lock, we're done. + 1 => {} + + // Another task has parked themselves on this lock, let's wake them + // up as its now their turn. + _ => unsafe { + Box::from_raw(n).wake(); + }, + } + } +} + +impl<T: Unpin> Inner<T> { + unsafe fn into_value(mut self) -> T { + self.value.take().unwrap().into_inner() + } +} + +impl<T> Drop for Inner<T> { + fn drop(&mut self) { + assert!(self.state.load(SeqCst).is_null()); + } +} + +/// Error indicating two `BiLock<T>`s were not two halves of a whole, and +/// thus could not be `reunite`d. +#[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] +pub struct ReuniteError<T>(pub BiLock<T>, pub BiLock<T>); + +impl<T> fmt::Debug for ReuniteError<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("ReuniteError").field(&"...").finish() + } +} + +impl<T> fmt::Display for ReuniteError<T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "tried to reunite two BiLocks that don't form a pair") + } +} + +#[cfg(feature = "std")] +impl<T: core::any::Any> std::error::Error for ReuniteError<T> {} + +/// Returned RAII guard from the `poll_lock` method. +/// +/// This structure acts as a sentinel to the data in the `BiLock<T>` itself, +/// implementing `Deref` and `DerefMut` to `T`. When dropped, the lock will be +/// unlocked. +#[derive(Debug)] +#[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] +pub struct BiLockGuard<'a, T> { + bilock: &'a BiLock<T>, +} + +// We allow parallel access to T via Deref, so Sync bound is also needed here. +unsafe impl<T: Send + Sync> Sync for BiLockGuard<'_, T> {} + +impl<T> Deref for BiLockGuard<'_, T> { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.bilock.arc.value.as_ref().unwrap().get() } + } +} + +impl<T: Unpin> DerefMut for BiLockGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.bilock.arc.value.as_ref().unwrap().get() } + } +} + +impl<T> BiLockGuard<'_, T> { + /// Get a mutable pinned reference to the locked value. + pub fn as_pin_mut(&mut self) -> Pin<&mut T> { + // Safety: we never allow moving a !Unpin value out of a bilock, nor + // allow mutable access to it + unsafe { Pin::new_unchecked(&mut *self.bilock.arc.value.as_ref().unwrap().get()) } + } +} + +impl<T> Drop for BiLockGuard<'_, T> { + fn drop(&mut self) { + self.bilock.unlock(); + } +} + +/// Future returned by `BiLock::lock` which will resolve when the lock is +/// acquired. +#[cfg(feature = "bilock")] +#[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] +#[must_use = "futures do nothing unless you `.await` or poll them"] +#[derive(Debug)] +pub struct BiLockAcquire<'a, T> { + bilock: &'a BiLock<T>, +} + +// Pinning is never projected to fields +#[cfg(feature = "bilock")] +impl<T> Unpin for BiLockAcquire<'_, T> {} + +#[cfg(feature = "bilock")] +impl<'a, T> Future for BiLockAcquire<'a, T> { + type Output = BiLockGuard<'a, T>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { + self.bilock.poll_lock(cx) + } +} + +// Based on core::ptr::invalid_mut. Equivalent to `addr as *mut T`, but is strict-provenance compatible. +#[allow(clippy::useless_transmute)] +#[inline] +fn invalid_ptr<T>(addr: usize) -> *mut T { + // SAFETY: every valid integer is also a valid pointer (as long as you don't dereference that + // pointer). + unsafe { core::mem::transmute(addr) } +} |