summaryrefslogtreecommitdiffstats
path: root/vendor/elsa/src/sync.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/elsa/src/sync.rs')
-rw-r--r--vendor/elsa/src/sync.rs624
1 files changed, 624 insertions, 0 deletions
diff --git a/vendor/elsa/src/sync.rs b/vendor/elsa/src/sync.rs
new file mode 100644
index 000000000..afa4bb7c7
--- /dev/null
+++ b/vendor/elsa/src/sync.rs
@@ -0,0 +1,624 @@
+//! **This module is experimental**
+//!
+//! This module provides threadsafe versions of FrozenMap and FrozenVec,
+//! ideal for use as a cache.
+//!
+//! These lock internally, however locks only last as long as the method calls
+//!
+
+use stable_deref_trait::StableDeref;
+use std::alloc::Layout;
+use std::borrow::Borrow;
+use std::collections::BTreeMap;
+use std::collections::HashMap;
+use std::hash::Hash;
+use std::iter::{FromIterator, IntoIterator};
+use std::mem::MaybeUninit;
+use std::ops::Index;
+
+use std::sync::atomic::AtomicPtr;
+use std::sync::atomic::AtomicUsize;
+use std::sync::atomic::Ordering;
+use std::sync::RwLock;
+
+/// Append-only threadsafe version of `std::collections::HashMap` where
+/// insertion does not require mutable access
+pub struct FrozenMap<K, V> {
+ map: RwLock<HashMap<K, V>>,
+}
+
+impl<K, V> Default for FrozenMap<K, V> {
+ fn default() -> Self {
+ Self {
+ map: Default::default(),
+ }
+ }
+}
+
+impl<K: Eq + Hash, V: StableDeref> FrozenMap<K, V> {
+ // these should never return &K or &V
+ // these should never delete any entries
+
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// If the key exists in the map, returns a reference
+ /// to the corresponding value, otherwise inserts a
+ /// new entry in the map for that key and returns a
+ /// reference to the given value.
+ ///
+ /// Existing values are never overwritten.
+ ///
+ /// The key may be any borrowed form of the map's key type, but
+ /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
+ /// the key type.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenMap;
+ ///
+ /// let map = FrozenMap::new();
+ /// assert_eq!(map.insert(1, Box::new("a")), &"a");
+ /// assert_eq!(map.insert(1, Box::new("b")), &"a");
+ /// ```
+ pub fn insert(&self, k: K, v: V) -> &V::Target {
+ let mut map = self.map.write().unwrap();
+ let ret = unsafe {
+ let inserted = &**map.entry(k).or_insert(v);
+ &*(inserted as *const _)
+ };
+ ret
+ }
+
+ /// If the key exists in the map, returns a reference to the corresponding
+ /// value, otherwise inserts a new entry in the map for that key and the
+ /// value returned by the creation function, and returns a reference to the
+ /// generated value.
+ ///
+ /// Existing values are never overwritten.
+ ///
+ /// The key may be any borrowed form of the map's key type, but [`Hash`] and
+ /// [`Eq`] on the borrowed form *must* match those for the key type.
+ ///
+ /// **Note** that the write lock is held for the duration of this function’s
+ /// execution, even while the value creation function is executing (if
+ /// needed). This will block any concurrent `get` or `insert` calls.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenMap;
+ ///
+ /// let map = FrozenMap::new();
+ /// assert_eq!(map.insert_with(1, || Box::new("a")), &"a");
+ /// assert_eq!(map.insert_with(1, || unreachable!()), &"a");
+ /// ```
+ pub fn insert_with(&self, k: K, f: impl FnOnce() -> V) -> &V::Target {
+ let mut map = self.map.write().unwrap();
+ let ret = unsafe {
+ let inserted = &**map.entry(k).or_insert_with(f);
+ &*(inserted as *const _)
+ };
+ ret
+ }
+
+ /// If the key exists in the map, returns a reference to the corresponding
+ /// value, otherwise inserts a new entry in the map for that key and the
+ /// value returned by the creation function, and returns a reference to the
+ /// generated value.
+ ///
+ /// Existing values are never overwritten.
+ ///
+ /// The key may be any borrowed form of the map's key type, but [`Hash`] and
+ /// [`Eq`] on the borrowed form *must* match those for the key type.
+ ///
+ /// **Note** that the write lock is held for the duration of this function’s
+ /// execution, even while the value creation function is executing (if
+ /// needed). This will block any concurrent `get` or `insert` calls.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenMap;
+ ///
+ /// let map = FrozenMap::new();
+ /// assert_eq!(map.insert_with_key(1, |_| Box::new("a")), &"a");
+ /// assert_eq!(map.insert_with_key(1, |_| unreachable!()), &"a");
+ /// ```
+ pub fn insert_with_key(&self, k: K, f: impl FnOnce(&K) -> V) -> &V::Target {
+ let mut map = self.map.write().unwrap();
+ let ret = unsafe {
+ let inserted = &**map.entry(k).or_insert_with_key(f);
+ &*(inserted as *const _)
+ };
+ ret
+ }
+
+ /// Returns a reference to the value corresponding to the key.
+ ///
+ /// The key may be any borrowed form of the map's key type, but
+ /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
+ /// the key type.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenMap;
+ ///
+ /// let map = FrozenMap::new();
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map.get(&1), Some(&"a"));
+ /// assert_eq!(map.get(&2), None);
+ /// ```
+ pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V::Target>
+ where
+ K: Borrow<Q>,
+ Q: Hash + Eq,
+ {
+ let map = self.map.read().unwrap();
+ let ret = unsafe { map.get(k).map(|x| &*(&**x as *const V::Target)) };
+ ret
+ }
+
+ /// Applies a function to the owner of the value corresponding to the key (if any).
+ ///
+ /// The key may be any borrowed form of the map's key type, but
+ /// [`Hash`] and [`Eq`] on the borrowed form *must* match those for
+ /// the key type.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenMap;
+ ///
+ /// let map = FrozenMap::new();
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map.map_get(&1, Clone::clone), Some(Box::new("a")));
+ /// assert_eq!(map.map_get(&2, Clone::clone), None);
+ /// ```
+ pub fn map_get<Q: ?Sized, T, F>(&self, k: &Q, f: F) -> Option<T>
+ where
+ K: Borrow<Q>,
+ Q: Hash + Eq,
+ F: FnOnce(&V) -> T,
+ {
+ let map = self.map.read().unwrap();
+ let ret = map.get(k).map(f);
+ ret
+ }
+
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenMap;
+ ///
+ /// let map = FrozenMap::new();
+ /// assert_eq!(map.len(), 0);
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map.len(), 1);
+ /// ```
+ pub fn len(&self) -> usize {
+ let map = self.map.read().unwrap();
+ map.len()
+ }
+
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenMap;
+ ///
+ /// let map = FrozenMap::new();
+ /// assert_eq!(map.is_empty(), true);
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map.is_empty(), false);
+ /// ```
+ pub fn is_empty(&self) -> bool {
+ let map = self.map.read().unwrap();
+ map.is_empty()
+ }
+
+ // TODO add more
+}
+
+/// Append-only threadsafe version of `std::vec::Vec` where
+/// insertion does not require mutable access
+pub struct FrozenVec<T> {
+ vec: RwLock<Vec<T>>,
+}
+
+impl<T> Default for FrozenVec<T> {
+ fn default() -> Self {
+ Self {
+ vec: Default::default(),
+ }
+ }
+}
+
+impl<T: StableDeref> FrozenVec<T> {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ // these should never return &T
+ // these should never delete any entries
+
+ pub fn push(&self, val: T) {
+ let mut vec = self.vec.write().unwrap();
+ vec.push(val);
+ }
+
+ /// Push, immediately getting a reference to the element
+ pub fn push_get(&self, val: T) -> &T::Target {
+ let mut vec = self.vec.write().unwrap();
+ vec.push(val);
+ unsafe { &*(&**vec.get_unchecked(vec.len() - 1) as *const T::Target) }
+ }
+
+ /// Push, immediately getting a an index of the element
+ ///
+ /// Index can then be used with the `get` method
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenVec;
+ ///
+ /// let map = FrozenVec::new();
+ /// let idx = map.push_get_index(String::from("a"));
+ /// assert_eq!(map.get(idx), Some("a"));
+ /// assert_eq!(idx, 0);
+ /// assert_eq!(map.push_get_index(String::from("b")), 1);
+ /// ```
+ pub fn push_get_index(&self, val: T) -> usize {
+ let mut vec = self.vec.write().unwrap();
+ let index = vec.len();
+ vec.push(val);
+ return index;
+ }
+
+ pub fn get(&self, index: usize) -> Option<&T::Target> {
+ let vec = self.vec.read().unwrap();
+ unsafe { vec.get(index).map(|x| &*(&**x as *const T::Target)) }
+ }
+
+ // TODO add more
+}
+
+/// Append-only threadsafe version of `std::vec::Vec` where
+/// insertion does not require mutable access.
+/// Does not have locks, only allows `Copy` types and will
+/// spinlock on contention. The spinlocks are really rare as
+/// they only happen on reallocation due to a push going over
+/// the capacity.
+pub struct LockFreeFrozenVec<T: Copy> {
+ data: AtomicPtr<T>,
+ len: AtomicUsize,
+ cap: AtomicUsize,
+}
+
+impl<T: Copy> Drop for LockFreeFrozenVec<T> {
+ fn drop(&mut self) {
+ let cap = *self.cap.get_mut();
+ let layout = self.layout(cap);
+ unsafe {
+ std::alloc::dealloc((*self.data.get_mut()).cast(), layout);
+ }
+ }
+}
+
+impl<T: Copy> Default for LockFreeFrozenVec<T> {
+ fn default() -> Self {
+ Self {
+ // FIXME: use `std::ptr::invalid_mut()` once that is stable.
+ data: AtomicPtr::new(std::mem::align_of::<T>() as *mut T),
+ len: AtomicUsize::new(0),
+ cap: AtomicUsize::new(0),
+ }
+ }
+}
+
+impl<T: Copy> LockFreeFrozenVec<T> {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ pub fn with_capacity(cap: usize) -> Self {
+ Self {
+ data: AtomicPtr::new(
+ Box::into_raw(vec![MaybeUninit::<T>::uninit(); cap].into_boxed_slice()).cast(),
+ ),
+ len: AtomicUsize::new(0),
+ cap: AtomicUsize::new(cap),
+ }
+ }
+
+ fn lock<U>(&self, f: impl FnOnce(&mut *mut T) -> U) -> U {
+ let mut ptr;
+ loop {
+ ptr = self.data.swap(std::ptr::null_mut(), Ordering::Acquire);
+ if !ptr.is_null() {
+ // Wheeeee spinlock
+ break;
+ }
+ }
+
+ let ret = f(&mut ptr);
+ self.data.store(ptr, Ordering::Release);
+ ret
+ }
+
+ fn layout(&self, cap: usize) -> Layout {
+ let num_bytes = std::mem::size_of::<T>() * cap;
+ let align = std::mem::align_of::<T>();
+ Layout::from_size_align(num_bytes, align).unwrap()
+ }
+
+ // these should never return &T
+ // these should never delete any entries
+
+ const NOT_ZST: () = if std::mem::size_of::<T>() == 0 {
+ panic!("`LockFreeFrozenVec` cannot be used with ZSTs");
+ };
+
+ pub fn push(&self, val: T) -> usize {
+ // This statement actually does something: it evaluates a constant.
+ #[allow(path_statements)]
+ {
+ Self::NOT_ZST
+ }
+ self.lock(|ptr| {
+ // These values must be consistent with the pointer we got.
+ let len = self.len.load(Ordering::Acquire);
+ let cap = self.cap.load(Ordering::Acquire);
+ if len >= cap {
+ if cap == 0 {
+ // No memory allocated yet
+ let layout = self.layout(128);
+ // SAFETY: `LockFreeFrozenVec` statically rejects zsts
+ unsafe {
+ *ptr = std::alloc::alloc(layout).cast::<T>();
+ }
+ // This is written before the end of the `lock` closure, so no one will observe this
+ // until the data pointer has been updated anyway.
+ self.cap.store(128, Ordering::Release);
+ } else {
+ // Out of memory, realloc with double the capacity
+ let layout = self.layout(cap);
+ let new_size = layout.size() * 2;
+ // SAFETY: `LockFreeFrozenVec` statically rejects zsts and the input `ptr` has always been
+ // allocated at the size stated in `cap`.
+ unsafe {
+ *ptr = std::alloc::realloc((*ptr).cast(), layout, new_size).cast::<T>();
+ }
+ // This is written before the end of the `lock` closure, so no one will observe this
+ // until the data pointer has been updated anyway.
+ self.cap.store(cap * 2, Ordering::Release);
+ }
+ assert!(!ptr.is_null());
+ }
+ unsafe {
+ ptr.add(len).write(val);
+ }
+ // This is written before updating the data pointer. Other `push` calls cannot observe this,
+ // because they are blocked on aquiring the data pointer before they ever read the `len`.
+ // `get` may read the length before actually aquiring the data pointer lock, but that is fine,
+ // as once it is able to aquire the lock, there will be actually the right number of elements
+ // stored.
+ self.len.store(len + 1, Ordering::Release);
+ len
+ })
+ }
+
+ pub fn get(&self, index: usize) -> Option<T> {
+ // The length can only grow, so just doing the length check
+ // independently of the `lock` and read is fine. Worst case we
+ // read an old length value and end up returning `None` even if
+ // another thread already inserted the value.
+ let len = self.len.load(Ordering::Relaxed);
+ if index >= len {
+ return None;
+ }
+ self.lock(|ptr| Some(unsafe { ptr.add(index).read() }))
+ }
+}
+
+#[test]
+fn test_non_lockfree() {
+ #[derive(Copy, Clone, Debug, PartialEq, Eq)]
+ struct Moo(i32);
+
+ for vec in [
+ LockFreeFrozenVec::new(),
+ LockFreeFrozenVec::with_capacity(1),
+ LockFreeFrozenVec::with_capacity(2),
+ LockFreeFrozenVec::with_capacity(1000),
+ ] {
+ assert_eq!(vec.get(1), None);
+
+ vec.push(Moo(1));
+ let i = vec.push(Moo(2));
+ vec.push(Moo(3));
+
+ assert_eq!(vec.get(i), Some(Moo(2)));
+
+ std::thread::scope(|s| {
+ s.spawn(|| {
+ for i in 0..1000 {
+ vec.push(Moo(i));
+ }
+ });
+ s.spawn(|| {
+ for i in 0..1000 {
+ vec.push(Moo(i));
+ }
+ });
+ for i in 0..2000 {
+ while vec.get(i).is_none() {}
+ }
+ });
+ }
+}
+
+/// Append-only threadsafe version of `std::collections::BTreeMap` where
+/// insertion does not require mutable access
+#[derive(Debug)]
+pub struct FrozenBTreeMap<K, V>(RwLock<BTreeMap<K, V>>);
+
+impl<K: Clone + Ord, V: StableDeref> FrozenBTreeMap<K, V> {
+ pub fn new() -> Self {
+ Self(RwLock::new(BTreeMap::new()))
+ }
+
+ // these should never return &K or &V
+ // these should never delete any entries
+
+ /// Returns a reference to the value corresponding to the key.
+ ///
+ /// The key may be any borrowed form of the map's key type, but
+ /// [`Ord`] on the borrowed form *must* match those for
+ /// the key type.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenBTreeMap;
+ ///
+ /// let map = FrozenBTreeMap::new();
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map.get(&1), Some(&"a"));
+ /// assert_eq!(map.get(&2), None);
+ /// ```
+ pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V::Target>
+ where
+ K: Borrow<Q>,
+ Q: Ord,
+ {
+ let map = self.0.read().unwrap();
+ let ret = unsafe { map.get(k).map(|x| &*(&**x as *const V::Target)) };
+ ret
+ }
+
+ /// Insert a new value into the map. Does nothing if the key is already occupied.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenBTreeMap;
+ ///
+ /// let map = FrozenBTreeMap::new();
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map.get(&1), Some(&"a"));
+ /// ```
+ pub fn insert(&self, k: K, v: V) -> &V::Target {
+ let mut map = self.0.write().unwrap();
+ let ret = unsafe {
+ let inserted = &**map.entry(k).or_insert(v);
+ &*(inserted as *const _)
+ };
+ ret
+ }
+
+ /// Applies a function to the owner of the value corresponding to the key (if any).
+ ///
+ /// The key may be any borrowed form of the map's key type, but
+ /// [`Ord`] on the borrowed form *must* match those for
+ /// the key type.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenBTreeMap;
+ ///
+ /// let map = FrozenBTreeMap::new();
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map.map_get(&1, Clone::clone), Some(Box::new("a")));
+ /// assert_eq!(map.map_get(&2, Clone::clone), None);
+ /// ```
+ pub fn map_get<Q: ?Sized, T, F>(&self, k: &Q, f: F) -> Option<T>
+ where
+ K: Borrow<Q>,
+ Q: Ord,
+ F: FnOnce(&V) -> T,
+ {
+ let map = self.0.read().unwrap();
+ let ret = map.get(k).map(f);
+ ret
+ }
+
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenBTreeMap;
+ ///
+ /// let map = FrozenBTreeMap::new();
+ /// assert_eq!(map.len(), 0);
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map.len(), 1);
+ /// ```
+ pub fn len(&self) -> usize {
+ let map = self.0.read().unwrap();
+ map.len()
+ }
+
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenBTreeMap;
+ ///
+ /// let map = FrozenBTreeMap::new();
+ /// assert_eq!(map.is_empty(), true);
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map.is_empty(), false);
+ /// ```
+ pub fn is_empty(&self) -> bool {
+ let map = self.0.read().unwrap();
+ map.is_empty()
+ }
+}
+
+impl<K: Clone + Ord, V: StableDeref> From<BTreeMap<K, V>> for FrozenBTreeMap<K, V> {
+ fn from(map: BTreeMap<K, V>) -> Self {
+ Self(RwLock::new(map))
+ }
+}
+
+impl<Q: ?Sized, K, V> Index<&Q> for FrozenBTreeMap<K, V>
+where
+ Q: Ord,
+ K: Clone + Ord + Borrow<Q>,
+ V: StableDeref,
+{
+ type Output = V::Target;
+
+ /// # Examples
+ ///
+ /// ```
+ /// use elsa::sync::FrozenBTreeMap;
+ ///
+ /// let map = FrozenBTreeMap::new();
+ /// map.insert(1, Box::new("a"));
+ /// assert_eq!(map[&1], "a");
+ /// ```
+ fn index(&self, idx: &Q) -> &V::Target {
+ self.get(idx)
+ .expect("attempted to index FrozenBTreeMap with unknown key")
+ }
+}
+
+impl<K: Clone + Ord, V: StableDeref> FromIterator<(K, V)> for FrozenBTreeMap<K, V> {
+ fn from_iter<T>(iter: T) -> Self
+ where
+ T: IntoIterator<Item = (K, V)>,
+ {
+ let map: BTreeMap<_, _> = iter.into_iter().collect();
+ map.into()
+ }
+}
+
+impl<K: Clone + Ord, V: StableDeref> Default for FrozenBTreeMap<K, V> {
+ fn default() -> Self {
+ Self::new()
+ }
+}