diff --git a/Cargo.toml b/Cargo.toml index bdc5e8c..60e3bb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,43 +23,49 @@ name = "concread" path = "src/lib.rs" [features] -default = ["asynch", "foldhash", "ebr", "maps", "arcache-is-hashtrie"] +default = ["std", "asynch", "foldhash", "ebr", "maps", "arcache-is-hashtrie"] # Features to add/remove contents. ahash = ["dep:ahash"] foldhash = ["dep:foldhash"] - arcache = ["maps", "lru", "crossbeam-queue"] -asynch = ["tokio"] -ebr = ["crossbeam-epoch"] -maps = ["crossbeam-utils", "smallvec"] +asynch = ["dep:tokio", "std"] +ebr = ["std"] +maps = ["dep:crossbeam-utils", "smallvec"] tcache = [] +std = ["ahash/std", "ahash/runtime-rng", "crossbeam-epoch/std", "crossbeam-queue/std", "crossbeam-utils/std", "tracing/std", "dep:parking_lot", "smallvec/write"] +no_std = ["crossbeam-epoch/alloc", "crossbeam-queue/alloc", "serde/alloc", "ahash"] + # Internal features for tweaking some align/perf behaviours. dhat-heap = ["dep:dhat"] skinny = [] hashtrie_skinny = [] - arcache-is-hashmap = ["arcache"] arcache-is-hashtrie = ["arcache"] simd_support = [] + [dependencies] -ahash = { version = "0.8", optional = true } -foldhash = { version = "0.1.5", optional = true } -crossbeam-utils = { version = "0.8.21", optional = true } -crossbeam-epoch = { version = "0.9.11", optional = true } -crossbeam-queue = { version = "0.3.12", optional = true } +ahash = { version = "0.8", default-features = false, optional = true} +foldhash = { version = "0.1.5",default-features = false, optional = true } +crossbeam-utils = { version = "0.8.21", optional = true, default-features = false, features = []} +crossbeam-epoch = { version = "0.9.11", optional = true, default-features = false, features = [] } +crossbeam-queue = { version = "0.3.12", optional = true, default-features = false, features = [] } dhat = { version = "0.3.3", optional = true } -lru = { version = "0.13", optional = true } +lru = { version = "0.16", optional = true } serde = { version = "1.0", optional = true } smallvec = { version = "1.14", optional = true } sptr = "0.3" tokio = { version = "1", features = ["sync"], optional = true } -tracing = "0.1" +tracing = {version = "0.1", default-features = false} +lock_api = "0.4" +parking_lot = {version = "0.12.3", optional = true } +hashbrown = {version = "0.15.2", default-features = false} +cfg-if = "1.0.0" [dev-dependencies] -criterion = { version = "0.5", features = ["html_reports"] } +criterion = { version = "0.6.0", features = ["html_reports"] } rand = "0.9" tracing-subscriber = { version = "0.3", features = [ "env-filter", @@ -71,6 +77,7 @@ function_name = "0.3" serde_json = "1" tokio = { version = "1", features = ["rt", "macros"] } proptest = "1.0.0" +spin = {version = "0.10.0", default-features = false, features = ["lock_api", "spin_mutex", "rwlock"]} [[bench]] name = "hashmap_benchmark" diff --git a/benches/arccache.rs b/benches/arccache.rs index 642daa9..8199fee 100644 --- a/benches/arccache.rs +++ b/benches/arccache.rs @@ -1,7 +1,7 @@ use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; use function_name::named; use rand::distributions::uniform::SampleUniform; -use rand::{thread_rng, Rng}; +use rand::{rng, Rng}; use std::collections::HashMap; use std::fmt::Debug; use std::hash::Hash; @@ -11,7 +11,7 @@ use std::thread; use std::time::{Duration, Instant}; // use uuid::Uuid; -use concread::arcache::{ARCache, ARCacheBuilder}; +use concread::arcache::{ARCacheRaw, ARCacheBuilder}; use concread::threadcache::ThreadLocal; use criterion::measurement::{Measurement, ValueFormatter}; @@ -54,8 +54,8 @@ where fn next(&self) -> T { match self { AccessPattern::Random(min, max) => { - let mut rng = thread_rng(); - rng.gen_range(min.clone()..max.clone()) + let mut rng = rng(); + rng.random_range(min.clone()..max.clone()) } } } @@ -261,7 +261,7 @@ where } fn multi_thread_worker( - arc: Arc>, + arc: Arc>, backing_set: Arc>, backing_set_delay: Option, access_pattern: AccessPattern, @@ -311,7 +311,7 @@ where csize = 1; } - let arc: Arc> = Arc::new( + let arc: Arc> = Arc::new( ARCacheBuilder::new() .set_size(csize, 0) .set_watermark(0) @@ -420,7 +420,7 @@ where csize = 1; } - let arc: ARCache = ARCacheBuilder::new() + let arc: ARCacheRaw = ARCacheBuilder::new() .set_size(csize, 0) .set_watermark(0) .set_reader_quiesce(false) diff --git a/benches/hashmap_benchmark.rs b/benches/hashmap_benchmark.rs index 53de162..ae259ed 100644 --- a/benches/hashmap_benchmark.rs +++ b/benches/hashmap_benchmark.rs @@ -19,8 +19,10 @@ extern crate criterion; extern crate rand; use concread::hashmap::*; -use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; -use rand::{thread_rng, Rng}; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use rand::{rng, Rng}; + +use std::hint::black_box; // ranges of counts for different benchmarks (MINs are inclusive, MAXes exclusive): const INSERT_COUNT_MIN: usize = 120; @@ -168,7 +170,7 @@ criterion_main!(insert, remove, search); fn insert_vec( map: &mut HashMap, list: Vec<(u32, V)>, -) -> HashMapWriteTxn { +) -> HashMapWriteTxn { let mut write_txn = map.write(); for (key, val) in list.into_iter() { write_txn.insert(key, val); @@ -179,7 +181,7 @@ fn insert_vec( fn remove_vec<'a, V: Clone + Sync + Send + 'static>( map: &'a mut HashMap, list: &Vec, -) -> HashMapWriteTxn<'a, u32, V> { +) -> HashMapWriteTxn<'a, u32, V, parking_lot::RawMutex> { let mut write_txn = map.write(); for i in list.iter() { write_txn.remove(i); @@ -242,12 +244,12 @@ struct Struct { } fn prepare_insert(value: V) -> (HashMap, Vec<(u32, V)>) { - let mut rng = thread_rng(); - let count = rng.gen_range(INSERT_COUNT_MIN..INSERT_COUNT_MAX); + let mut rng = rng(); + let count = rng.random_range(INSERT_COUNT_MIN..INSERT_COUNT_MAX); let mut list = Vec::with_capacity(count); for _ in 0..count { list.push(( - rng.gen_range(0..INSERT_COUNT_MAX << 8) as u32, + rng.random_range(0..INSERT_COUNT_MAX << 8) as u32, value.clone(), )); } @@ -256,9 +258,9 @@ fn prepare_insert(value: V) -> (HashMap(value: V) -> (HashMap, Vec) { - let mut rng = thread_rng(); - let insert_count = rng.gen_range(INSERT_COUNT_FOR_REMOVE_MIN..INSERT_COUNT_FOR_REMOVE_MAX); - let remove_count = rng.gen_range(REMOVE_COUNT_MIN..REMOVE_COUNT_MAX); + let mut rng = rng(); + let insert_count = rng.random_range(INSERT_COUNT_FOR_REMOVE_MIN..INSERT_COUNT_FOR_REMOVE_MAX); + let remove_count = rng.random_range(REMOVE_COUNT_MIN..REMOVE_COUNT_MAX); let map = HashMap::new(); let mut write_txn = map.write(); for i in random_order(insert_count, insert_count).iter() { @@ -271,10 +273,10 @@ fn prepare_remove(value: V) -> (HashMap(value: V) -> (HashMap, Vec) { - let mut rng = thread_rng(); - let insert_count = rng.gen_range(INSERT_COUNT_FOR_SEARCH_MIN..INSERT_COUNT_FOR_SEARCH_MAX); + let mut rng = rng(); + let insert_count = rng.random_range(INSERT_COUNT_FOR_SEARCH_MIN..INSERT_COUNT_FOR_SEARCH_MAX); let search_limit = insert_count * SEARCH_SIZE_NUMERATOR / SEARCH_SIZE_DENOMINATOR; - let search_count = rng.gen_range(SEARCH_COUNT_MIN..SEARCH_COUNT_MAX); + let search_count = rng.random_range(SEARCH_COUNT_MIN..SEARCH_COUNT_MAX); // Create a HashMap with elements 0 through insert_count(-1) let map = HashMap::new(); @@ -287,20 +289,20 @@ fn prepare_search(value: V) -> (HashMap Vec { - let mut rng = thread_rng(); + let mut rng = rng(); let mut order = Vec::with_capacity(n); let mut generated = vec![false; up_to]; let mut remaining = n; let mut remaining_elems = up_to; while remaining > 0 { - let mut r = rng.gen_range(0..remaining_elems); + let mut r = rng.random_range(0..remaining_elems); // find the r-th yet nongenerated number: for i in 0..up_to { if generated[i] { diff --git a/src/arcache/ll.rs b/src/arcache/ll.rs index 32ec25a..06dd69a 100644 --- a/src/arcache/ll.rs +++ b/src/arcache/ll.rs @@ -1,3 +1,8 @@ +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; +#[cfg(feature = "std")] +use std::boxed::Box; + use std::fmt::Debug; use std::marker::PhantomData; use std::mem::MaybeUninit; @@ -309,10 +314,12 @@ where (*next).prev = prev; (*prev).next = next; // Null things for paranoia. - if cfg!(test) || cfg!(debug_assertions) { + + cfg_if::cfg_if! { if #[cfg(any(test, debug_assertions))] + { (*n.inner).prev = ptr::null_mut(); (*n.inner).next = ptr::null_mut(); - } + }} // (*n).tag = 0; } diff --git a/src/arcache/mod.rs b/src/arcache/mod.rs index bb43168..a3a27e5 100644 --- a/src/arcache/mod.rs +++ b/src/arcache/mod.rs @@ -10,6 +10,11 @@ //! writers that are serialised. This formally means that this is an ACID //! compliant Cache. +#[cfg(not(feature = "std"))] +use alloc::{borrow::ToOwned, sync::Arc, vec::Vec}; +#[cfg(feature = "std")] +use std::{borrow::ToOwned, sync::Arc, vec::Vec}; + mod ll; /// Stats collection for [ARCache] pub mod stats; @@ -19,19 +24,19 @@ use self::stats::{ARCacheReadStat, ARCacheWriteStat}; #[cfg(feature = "arcache-is-hashmap")] use crate::hashmap::{ - HashMap as DataMap, HashMapReadTxn as DataMapReadTxn, HashMapWriteTxn as DataMapWriteTxn, + HashMapRaw as DataMap, HashMapReadTxn as DataMapReadTxn, HashMapWriteTxn as DataMapWriteTxn, }; #[cfg(feature = "arcache-is-hashtrie")] use crate::hashtrie::{ - HashTrie as DataMap, HashTrieReadTxn as DataMapReadTxn, HashTrieWriteTxn as DataMapWriteTxn, + HashTrieRaw as DataMap, HashTrieReadTxn as DataMapReadTxn, HashTrieWriteTxn as DataMapWriteTxn, }; +use crate::utils::{self, Monotonic}; use crossbeam_queue::ArrayQueue; -use std::collections::HashMap as Map; +use hashbrown::HashMap as Map; +use lock_api::{Mutex, RawMutex, RawRwLock, RwLock}; use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; -use std::sync::{Mutex, RwLock}; use std::borrow::Borrow; use std::cell::UnsafeCell; @@ -41,7 +46,6 @@ use std::mem; use std::num::NonZeroUsize; use std::ops::Deref; use std::ops::DerefMut; -use std::time::Instant; use tracing::trace; @@ -59,18 +63,76 @@ const WATERMARK_DISABLE_MIN: usize = 128; const WATERMARK_DISABLE_DIVISOR: usize = 20; const WATERMARK_DISABLE_RATIO: usize = 18; +#[cfg(feature = "std")] +mod monotonic_timer { + pub struct MonotonicTimer; + + unsafe impl crate::utils::Monotonic for MonotonicTimer { + type Output = std::time::Instant; + + fn new() -> Self { + MonotonicTimer + } + + fn current(&self) -> Self::Output { + self.next() + } + + fn next(&self) -> Self::Output { + std::time::Instant::now() + } + } +} + +#[cfg(not(feature = "std"))] +mod monotonic_timer { + use std::sync::atomic::{AtomicUsize, Ordering}; + /// This provides a mnonotonic generation counter, with the bit width equal to the pointer width of the platform. + /// + /// # SAFETY + /// + /// This wraps around on overflow, so the result becomes invalid if you call it more than `usize::MAX`. + /// Overflow will panic on debug, and continue on release mode. + pub struct MonotonicTimer(AtomicUsize); + + unsafe impl crate::utils::Monotonic for MonotonicTimer { + type Output = usize; + + fn new() -> Self { + Self(AtomicUsize::new(0)) + } + + fn current(&self) -> Self::Output { + // If you are calling this function, you probably want more guarantees about the relative ordering. + // Therefore, we use Acquire semantics to get the best ordering for loads withour requiring SeqSct for everything + self.0.load(Ordering::Acquire) + } + + fn next(&self) -> Self::Output { + // we can use relaxed ordering here as it still guarantees that each value will only be observed once. + // the downside is that close calls may have re-ordered insertion do the relaxed ordering on the read. + let counter = self.0.fetch_add(1, Ordering::Relaxed); + debug_assert!( + counter != usize::MAX, + "The default monotonic counter reached the maximum number of valid calls" + ); + counter + } + } +} + enum ThreadCacheItem { Present(V, bool, usize), Removed(bool), } -struct CacheHitEvent { - t: Instant, +struct CacheHitEvent { + t: M::Output, k_hash: u64, } -struct CacheIncludeEvent { - t: Instant, +struct CacheIncludeEvent { + t: M::Output, k: K, v: V, txid: u64, @@ -147,10 +209,11 @@ pub(crate) struct CStat { p: usize, } -struct ArcInner +struct ArcInner where K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, + M: Monotonic, { /// Weight of items between the two caches. p: usize, @@ -159,22 +222,23 @@ where ghost_freq: LL>, ghost_rec: LL>, haunted: LL>, - hit_queue: Arc>, - inc_queue: Arc>>, + hit_queue: Arc>>, + inc_queue: Arc>>, min_txid: u64, } -struct ArcShared +struct ArcShared where K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, + M: Monotonic, { // Max number of elements to cache. max: usize, // Max number of elements for a reader per thread. read_max: usize, - hit_queue: Arc>, - inc_queue: Arc>>, + hit_queue: Arc>>, + inc_queue: Arc>>, /// The number of items that are present in the cache before we start to process /// the arc sets/lists. watermark: usize, @@ -182,36 +246,55 @@ where reader_quiesce: bool, } +/// ARCache structure with default sychronisation primitives for write transaction locking. +#[cfg(feature = "std")] +pub type ARCache = ARCacheRaw; + /// A concurrently readable adaptive replacement cache. Operations are performed on the /// cache via read and write operations. -pub struct ARCache -where +pub struct ARCacheRaw< + K, + V, + MonotonicCounter, + RawMutexImpl, + RawRwLockImpl, +> where K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, + MonotonicCounter: Monotonic + 'static, + RawMutexImpl: RawMutex + 'static, + RawRwLockImpl: RawRwLock + 'static, { // Use a unified tree, allows simpler movement of items between the // cache types. - cache: DataMap>, + cache: DataMap, RawMutexImpl>, // This is normally only ever taken in "read" mode, so it's effectively // an uncontended barrier. - shared: RwLock>, + shared: RwLock>, // These are only taken during a quiesce - inner: Mutex>, + inner: Mutex>, // stats: CowCell, above_watermark: AtomicBool, look_back_limit: u64, + monotonic: MonotonicCounter, } unsafe impl< K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, - > Send for ARCache + M: Monotonic + Send + Send + 'static, + Mutex: RawMutex + Sync + Send + 'static, + RwLock: RawRwLock + Sync + Send + 'static, + > Send for ARCacheRaw { } unsafe impl< K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, - > Sync for ARCache + M: Monotonic + Send + 'static, + Mutex: RawMutex + Sync + 'static, + RwLock: RawRwLock + Sync + 'static, + > Sync for ARCacheRaw { } @@ -252,18 +335,21 @@ where /// An active read transaction over the cache. The data is this cache is guaranteed to be /// valid at the point in time the read is created. You may include items during a cache /// miss via the "insert" function. -pub struct ARCacheReadTxn<'a, K, V, S> +pub struct ARCacheReadTxn<'a, K, V, S, M, Mutex, RwLock> where K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, S: ARCacheReadStat + Clone, + M: Monotonic + 'static, + Mutex: RawMutex + 'static, + RwLock: RawRwLock + 'static, { - caller: &'a ARCache, + caller: &'a ARCacheRaw, // ro_txn to cache - cache: DataMapReadTxn<'a, K, CacheItem>, + cache: DataMapReadTxn<'a, K, CacheItem, Mutex>, tlocal: Option>, - hit_queue: Arc>, - inc_queue: Arc>>, + hit_queue: Arc>>, + inc_queue: Arc>>, above_watermark: bool, reader_quiesce: bool, stats: S, @@ -273,14 +359,20 @@ unsafe impl< K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, S: ARCacheReadStat + Clone + Sync + Send + 'static, - > Send for ARCacheReadTxn<'_, K, V, S> + M: Monotonic + Sync + Send + 'static, + Mutex: RawMutex + Sync + Send + 'static, + RwLock: RawRwLock + Sync + Send + 'static, + > Send for ARCacheReadTxn<'_, K, V, S, M, Mutex, RwLock> { } unsafe impl< K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, S: ARCacheReadStat + Clone + Sync + Send + 'static, - > Sync for ARCacheReadTxn<'_, K, V, S> + M: Monotonic + Sync + 'static, + Mutex: RawMutex + Sync + 'static, + RwLock: RawRwLock + Sync + 'static, + > Sync for ARCacheReadTxn<'_, K, V, S, M, Mutex, RwLock> { } @@ -288,15 +380,18 @@ unsafe impl< /// from readers, and may be rolled-back if an error occurs. Changes only become /// globally visible once you call "commit". Items may be added to the cache on /// a miss via "insert", and you can explicitly remove items by calling "remove". -pub struct ARCacheWriteTxn<'a, K, V, S> +pub struct ARCacheWriteTxn<'a, K, V, S, M, Mutex, RwLock> where K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, S: ARCacheWriteStat, + M: Monotonic + 'static, + Mutex: RawMutex + 'static, + RwLock: RawRwLock + 'static, { - caller: &'a ARCache, + caller: &'a ARCacheRaw, // wr_txn to cache - cache: DataMapWriteTxn<'a, K, CacheItem>, + cache: DataMapWriteTxn<'a, K, CacheItem, Mutex>, // Cache of missed items (w_ dirty/clean) // On COMMIT we drain this to the main cache tlocal: Map>, @@ -342,15 +437,16 @@ impl< } /// A configurable builder to create new concurrent Adaptive Replacement Caches. -pub struct ARCacheBuilder { +pub struct ARCacheBuilder { max: Option, read_max: Option, watermark: Option, reader_quiesce: bool, look_back_limit: Option, + monotonic: Option, } -impl Default for ARCacheBuilder { +impl Default for ARCacheBuilder { fn default() -> Self { ARCacheBuilder { max: None, @@ -358,11 +454,15 @@ impl Default for ARCacheBuilder { watermark: None, reader_quiesce: true, look_back_limit: None, + monotonic: None, } } } -impl ARCacheBuilder { +impl ARCacheBuilder +where + M: Monotonic, +{ /// Create a new ARCache builder that you can configure before creation. pub fn new() -> Self { Self::default() @@ -429,6 +529,7 @@ impl ARCacheBuilder { watermark: self.watermark, reader_quiesce: self.reader_quiesce, look_back_limit: self.look_back_limit, + ..self } } @@ -477,7 +578,9 @@ impl ARCacheBuilder { /// Consume this builder, returning a cache if successful. If configured parameters are /// missing or incorrect, a None will be returned. - pub fn build(self) -> Option> + pub fn build( + self, + ) -> Option> where K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, @@ -489,6 +592,7 @@ impl ARCacheBuilder { watermark, reader_quiesce, look_back_limit, + monotonic, } = self; let (max, read_max) = max.zip(read_max)?; @@ -520,7 +624,7 @@ impl ARCacheBuilder { let chan_size = READ_THREAD_CHANNEL_SIZE; let inc_queue = Arc::new(ArrayQueue::new(chan_size)); - let shared = RwLock::new(ArcShared { + let shared = RwLock::>::new(ArcShared { max, read_max, // stat_tx, @@ -529,7 +633,7 @@ impl ARCacheBuilder { watermark, reader_quiesce, }); - let inner = Mutex::new(ArcInner { + let inner = Mutex::>::new(ArcInner { // We use p from the former stats. p: 0, freq: LL::new(), @@ -543,13 +647,14 @@ impl ARCacheBuilder { min_txid: 0, }); - Some(ARCache { + Some(ARCacheRaw { cache: DataMap::new(), shared, inner, // stats: CowCell::new(stats), above_watermark: AtomicBool::new(init_watermark), look_back_limit, + monotonic: monotonic.unwrap_or(M::new()), }) } } @@ -557,7 +662,10 @@ impl ARCacheBuilder { impl< K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, - > ARCache + M: Monotonic + 'static, + Mutex: RawMutex + 'static, + RwLock: RawRwLock + 'static, + > ARCacheRaw { /// Use ARCacheBuilder instead #[deprecated(since = "0.2.20", note = "please use`ARCacheBuilder` instead")] @@ -596,11 +704,11 @@ impl< /// Begin a read operation on the cache. This reader has a thread-local cache for items /// that are localled included via `insert`, and can communicate back to the main cache /// to safely include items. - pub fn read_stats(&self, stats: S) -> ARCacheReadTxn<'_, K, V, S> + pub fn read_stats(&self, stats: S) -> ARCacheReadTxn<'_, K, V, S, M, Mutex, RwLock> where S: ARCacheReadStat + Clone, { - let rshared = self.shared.read().unwrap(); + let rshared = self.shared.read(); let tlocal = if rshared.read_max > 0 { Some(ReadCache { set: Map::new(), @@ -627,19 +735,19 @@ impl< /// Begin a read operation on the cache. This reader has a thread-local cache for items /// that are localled included via `insert`, and can communicate back to the main cache /// to safely include items. - pub fn read(&self) -> ARCacheReadTxn<'_, K, V, ()> { + pub fn read(&self) -> ARCacheReadTxn<'_, K, V, (), M, Mutex, RwLock> { self.read_stats(()) } /// Begin a write operation on the cache. This writer has a thread-local store /// for all items that have been included or dirtied in the transactions, items /// may be removed from this cache (ie deleted, invalidated). - pub fn write(&self) -> ARCacheWriteTxn<'_, K, V, ()> { + pub fn write(&self) -> ARCacheWriteTxn<'_, K, V, (), M, Mutex, RwLock> { self.write_stats(()) } /// _ - pub fn write_stats(&self, stats: S) -> ARCacheWriteTxn<'_, K, V, S> + pub fn write_stats(&self, stats: S) -> ARCacheWriteTxn<'_, K, V, S, M, Mutex, RwLock> where S: ARCacheWriteStat, { @@ -656,7 +764,10 @@ impl< } } - fn try_write_stats(&self, stats: S) -> Result, S> + fn try_write_stats( + &self, + stats: S, + ) -> Result, S> where S: ARCacheWriteStat, { @@ -737,9 +848,9 @@ impl< fn drain_tlocal_inc( &self, - cache: &mut DataMapWriteTxn>, - inner: &mut ArcInner, - shared: &ArcShared, + cache: &mut DataMapWriteTxn, Mutex>, + inner: &mut ArcInner, + shared: &ArcShared, tlocal: Map>, commit_txid: u64, stats: &mut S, @@ -883,9 +994,9 @@ impl< fn drain_hit_rx( &self, - cache: &mut DataMapWriteTxn>, - inner: &mut ArcInner, - commit_ts: Instant, + cache: &mut DataMapWriteTxn, Mutex>, + inner: &mut ArcInner, + commit_ts: M::Output, ) { // * for each item // while let Ok(ce) = inner.rx.try_recv() { @@ -935,10 +1046,10 @@ impl< fn drain_inc_rx( &self, - cache: &mut DataMapWriteTxn>, - inner: &mut ArcInner, - shared: &ArcShared, - commit_ts: Instant, + cache: &mut DataMapWriteTxn, Mutex>, + inner: &mut ArcInner, + shared: &ArcShared, + commit_ts: M::Output, stats: &mut S, ) where S: ARCacheWriteStat, @@ -1083,8 +1194,8 @@ impl< fn drain_tlocal_hits( &self, - cache: &mut DataMapWriteTxn>, - inner: &mut ArcInner, + cache: &mut DataMapWriteTxn, Mutex>, + inner: &mut ArcInner, // shared: &ArcShared, commit_txid: u64, hit: Vec, @@ -1150,7 +1261,7 @@ impl< } fn evict_to_haunted_len( - cache: &mut DataMapWriteTxn>, + cache: &mut DataMapWriteTxn, Mutex>, ll: &mut LL>, to_ll: &mut LL>, size: usize, @@ -1181,7 +1292,7 @@ impl< } fn evict_to_len( - cache: &mut DataMapWriteTxn>, + cache: &mut DataMapWriteTxn, Mutex>, ll: &mut LL>, to_ll: &mut LL>, size: usize, @@ -1236,9 +1347,9 @@ impl< #[allow(clippy::cognitive_complexity)] fn evict( &self, - cache: &mut DataMapWriteTxn>, - inner: &mut ArcInner, - shared: &ArcShared, + cache: &mut DataMapWriteTxn, Mutex>, + inner: &mut ArcInner, + shared: &ArcShared, commit_txid: u64, stats: &mut S, ) where @@ -1369,7 +1480,7 @@ impl< } fn drain_ll_to_ghost( - cache: &mut DataMapWriteTxn>, + cache: &mut DataMapWriteTxn, Mutex>, ll: &mut LL>, gf: &mut LL>, gr: &mut LL>, @@ -1418,7 +1529,7 @@ impl< } fn drain_ll_min_txid( - cache: &mut DataMapWriteTxn>, + cache: &mut DataMapWriteTxn, Mutex>, ll: &mut LL>, min_txid: u64, ) { @@ -1440,7 +1551,7 @@ impl< #[allow(clippy::unnecessary_mut_passed)] fn commit( &self, - mut cache: DataMapWriteTxn>, + mut cache: DataMapWriteTxn, Mutex>, tlocal: Map>, hit: Vec, clear: bool, @@ -1452,11 +1563,11 @@ impl< S: ARCacheWriteStat, { // What is the time? - let commit_ts = Instant::now(); + let commit_generation = self.monotonic.next(); let commit_txid = cache.get_txid(); // Copy p + init cache sizes for adjustment. - let mut inner = self.inner.lock().unwrap(); - let shared = self.shared.read().unwrap(); + let mut inner = self.inner.lock(); + let shared = self.shared.read(); // Did we request to be cleared? If so, we move everything to a ghost set // that was live. @@ -1514,11 +1625,11 @@ impl< &mut cache, inner.deref_mut(), shared.deref(), - commit_ts, + commit_generation, &mut stats, ); - self.drain_hit_rx(&mut cache, inner.deref_mut(), commit_ts); + self.drain_hit_rx(&mut cache, inner.deref_mut(), commit_generation); // drain the tlocal hits into the main cache. @@ -1582,7 +1693,10 @@ impl< K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, S: ARCacheWriteStat, - > ARCacheWriteTxn<'_, K, V, S> + M: Monotonic + 'static, + Mutex: RawMutex + 'static, + RwLock: RawRwLock + 'static, + > ARCacheWriteTxn<'_, K, V, S, M, Mutex, RwLock> { /// Commit the changes of this writer, making them globally visible. This causes /// all items written to this thread's local store to become visible in the main @@ -1952,8 +2066,8 @@ impl< #[cfg(test)] pub(crate) fn peek_stat(&self) -> CStat { - let inner = self.caller.inner.lock().unwrap(); - let shared = self.caller.shared.read().unwrap(); + let inner = self.caller.inner.lock(); + let shared = self.caller.shared.read(); CStat { max: shared.max, cache: self.cache.len(), @@ -1974,7 +2088,10 @@ impl< K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, S: ARCacheReadStat + Clone, - > ARCacheReadTxn<'_, K, V, S> + M: Monotonic + 'static, + Mutex: RawMutex, + RwLock: RawRwLock, + > ARCacheReadTxn<'_, K, V, S, M, Mutex, RwLock> { /// Attempt to retrieve a k-v pair from the cache. If it is present in the main cache OR /// the thread local cache, a `Some` is returned, else you will receive a `None`. On a @@ -2003,7 +2120,7 @@ impl< if self.above_watermark { let _ = self.hit_queue.push(CacheHitEvent { - t: Instant::now(), + t: self.caller.monotonic.next(), k_hash, }); } @@ -2022,7 +2139,7 @@ impl< if self.above_watermark { let _ = self.hit_queue.push(CacheHitEvent { - t: Instant::now(), + t: self.caller.monotonic.next(), k_hash, }); } @@ -2062,7 +2179,7 @@ impl< if self .inc_queue .push(CacheIncludeEvent { - t: Instant::now(), + t: self.caller.monotonic.next(), k: k.clone(), v: v.clone(), txid: self.cache.get_txid(), @@ -2129,7 +2246,10 @@ impl< K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Debug + Sync + Send + 'static, S: ARCacheReadStat + Clone, - > Drop for ARCacheReadTxn<'_, K, V, S> + M: Monotonic + 'static, + Mutex: RawMutex, + RwLock: RawRwLock, + > Drop for ARCacheReadTxn<'_, K, V, S, M, Mutex, RwLock> { fn drop(&mut self) { // We could make this check the queue sizes rather than blindly quiescing diff --git a/src/bptree/asynch.rs b/src/bptree/asynch.rs index 6405423..e3c9adc 100644 --- a/src/bptree/asynch.rs +++ b/src/bptree/asynch.rs @@ -9,30 +9,36 @@ use serde::{ #[cfg(feature = "serde")] use crate::utils::MapCollector; -use crate::internals::lincowcell_async::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; +use crate::internals::lincowcell_async::{LinCowCellRaw, LinCowCellReadTxn, LinCowCellWriteTxn}; include!("impl.rs"); -impl - BptreeMap +impl< + K: Clone + Ord + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + R: RawMutex + 'static, + > BptreeMapRaw { /// Initiate a read transaction for the tree, concurrent to any /// other readers or writers. - pub fn read<'x>(&'x self) -> BptreeMapReadTxn<'x, K, V> { + pub fn read<'x>(&'x self) -> BptreeMapReadTxn<'x, K, V, R> { let inner = self.inner.read(); BptreeMapReadTxn { inner } } /// Initiate a write transaction for the tree, exclusive to this /// writer, and concurrently to all existing reads. - pub async fn write<'x>(&'x self) -> BptreeMapWriteTxn<'x, K, V> { + pub async fn write<'x>(&'x self) -> BptreeMapWriteTxn<'x, K, V, R> { let inner = self.inner.write().await; BptreeMapWriteTxn { inner } } } -impl - BptreeMapWriteTxn<'_, K, V> +impl< + K: Clone + Ord + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + R: RawMutex + 'static, + > BptreeMapWriteTxn<'_, K, V, R> { /// Commit the changes from this write transaction. Readers after this point /// will be able to perceive these changes. @@ -64,7 +70,7 @@ where } #[cfg(feature = "serde")] -impl Serialize for BptreeMap +impl Serialize for BptreeMapRaw where K: Serialize + Clone + Ord + Debug + Sync + Send + 'static, V: Serialize + Clone + Sync + Send + 'static, @@ -78,7 +84,7 @@ where } #[cfg(feature = "serde")] -impl<'de, K, V> Deserialize<'de> for BptreeMap +impl<'de, K, V> Deserialize<'de> for BptreeMapRaw where K: Deserialize<'de> + Clone + Ord + Debug + Sync + Send + 'static, V: Deserialize<'de> + Clone + Sync + Send + 'static, @@ -169,7 +175,7 @@ mod tests { async fn test_bptree2_map_from_iter_1() { let ins: Vec = (0..(L_CAPACITY << 4)).collect(); - let map = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); + let map: BptreeMap = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); { let w = map.write().await; @@ -187,7 +193,7 @@ mod tests { let mut ins: Vec = (0..(L_CAPACITY << 4)).collect(); ins.shuffle(&mut rng); - let map = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); + let map: BptreeMap = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); { let w = map.write().await; @@ -203,7 +209,7 @@ mod tests { async fn bptree_map_basic_concurrency(lower: usize, upper: usize) { // Create a map - let map = BptreeMap::new(); + let map: BptreeMap = BptreeMap::new(); // add values { @@ -268,7 +274,7 @@ mod tests { // Need to ensure that txns are dropped in order. // Add data, enough to cause a split. All data should be *2 - let map = BptreeMap::new(); + let map: BptreeMap = BptreeMap::new(); // add values { let mut w = map.write().await; diff --git a/src/bptree/impl.rs b/src/bptree/impl.rs index df08b16..f2ea1c0 100644 --- a/src/bptree/impl.rs +++ b/src/bptree/impl.rs @@ -1,3 +1,5 @@ +use lock_api::RawMutex; + use crate::internals::bptree::cursor::CursorReadOps; use crate::internals::bptree::cursor::{CursorRead, CursorWrite, SuperBlock}; use crate::internals::bptree::iter::{Iter, KeyIter, RangeIter, ValueIter}; @@ -8,6 +10,11 @@ use std::fmt::Debug; use std::iter::FromIterator; use std::ops::RangeBounds; + +/// B+Tree structure with a default mutex type for write transaction locking. +#[cfg(feature = "std")] +pub type BptreeMap = BptreeMapRaw; + /// A concurrently readable map based on a modified B+Tree structure. /// /// This structure can be used in locations where you would otherwise us @@ -29,40 +36,42 @@ use std::ops::RangeBounds; /// /// Transactions can be rolled-back (aborted) without penalty by dropping /// the `BptreeMapWriteTxn` without calling `commit()`. -pub struct BptreeMap +pub struct BptreeMapRaw where K: Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex + 'static { - inner: LinCowCell, CursorRead, CursorWrite>, + inner: LinCowCellRaw, CursorRead, CursorWrite, M>, } -unsafe impl Send - for BptreeMap +unsafe impl Send + for BptreeMapRaw { } -unsafe impl Sync - for BptreeMap +unsafe impl Sync + for BptreeMapRaw { } /// An active read transaction over a [BptreeMap]. The data in this tree /// is guaranteed to not change and will remain consistent for the life /// of this transaction. -pub struct BptreeMapReadTxn<'a, K, V> +pub struct BptreeMapReadTxn<'a, K, V, M> where K: Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex { - inner: LinCowCellReadTxn<'a, SuperBlock, CursorRead, CursorWrite>, + inner: LinCowCellReadTxn<'a, SuperBlock, CursorRead, CursorWrite, M>, } -unsafe impl Send - for BptreeMapReadTxn<'_, K, V> +unsafe impl Send + for BptreeMapReadTxn<'_, K, V, R> { } -unsafe impl Sync - for BptreeMapReadTxn<'_, K, V> +unsafe impl Sync + for BptreeMapReadTxn<'_, K, V, R> { } @@ -71,20 +80,22 @@ unsafe impl +pub struct BptreeMapWriteTxn<'a, K, V, M> where K: Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex + 'static { - inner: LinCowCellWriteTxn<'a, SuperBlock, CursorRead, CursorWrite>, + inner: LinCowCellWriteTxn<'a, SuperBlock, CursorRead, CursorWrite, M>, } -enum SnapshotType<'a, K, V> +enum SnapshotType<'a, K, V, M> where K: Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex { - R(&'a CursorRead), + R(&'a CursorRead), W(&'a CursorWrite), } @@ -96,70 +107,74 @@ where /// This snapshot IS safe within the read thread due to the nature of the /// implementation borrowing the inner tree to prevent mutations within the /// same thread while the read snapshot is open. -pub struct BptreeMapReadSnapshot<'a, K, V> +pub struct BptreeMapReadSnapshot<'a, K, V, M> where K: Ord + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex { - inner: SnapshotType<'a, K, V>, + inner: SnapshotType<'a, K, V, M>, } -impl Default - for BptreeMap +impl Default + for BptreeMapRaw { fn default() -> Self { Self::new() } } -impl - BptreeMap +impl + BptreeMapRaw { /// Construct a new concurrent tree pub fn new() -> Self { // I acknowledge I understand what is required to make this safe. - BptreeMap { - inner: LinCowCell::new(unsafe { SuperBlock::new() }), + BptreeMapRaw { + inner: LinCowCellRaw::new(unsafe { SuperBlock::new() }), } } /// Attempt to create a new write, returns None if another writer /// already exists. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.inner .try_write() .map(|inner| BptreeMapWriteTxn { inner }) } } -impl - FromIterator<(K, V)> for BptreeMap +impl + FromIterator<(K, V)> for BptreeMapRaw { fn from_iter>(iter: I) -> Self { let mut new_sblock = unsafe { SuperBlock::new() }; - let prev = new_sblock.create_reader(); - let mut cursor = new_sblock.create_writer(); + let prev: CursorRead = new_sblock.create_reader(); + + // TODO - fix this? + + let mut cursor = as LinCowCellCapable, CursorWrite>>::create_writer(&new_sblock); //new_sblock.create_writer(); cursor.extend(iter); let _ = new_sblock.pre_commit(cursor, &prev); - BptreeMap { - inner: LinCowCell::new(new_sblock), + BptreeMapRaw { + inner: LinCowCellRaw::new(new_sblock), } } } -impl - Extend<(K, V)> for BptreeMapWriteTxn<'_, K, V> +impl + Extend<(K, V)> for BptreeMapWriteTxn<'_, K, V, R> { fn extend>(&mut self, iter: I) { self.inner.as_mut().extend(iter); } } -impl - BptreeMapWriteTxn<'_, K, V> +impl + BptreeMapWriteTxn<'_, K, V, M> { // == RO methods @@ -305,15 +320,15 @@ impl BptreeMapReadSnapshot<'_, K, V> { + pub fn to_snapshot(&self) -> BptreeMapReadSnapshot<'_, K, V, M> { BptreeMapReadSnapshot { inner: SnapshotType::W(self.inner.as_ref()), } } } -impl - BptreeMapReadTxn<'_, K, V> +impl + BptreeMapReadTxn<'_, K, V, M> { /// Retrieve a value from the tree. If the value exists, a reference is returned /// as `Some(&V)`, otherwise if not present `None` is returned. @@ -386,7 +401,7 @@ impl BptreeMapReadSnapshot<'_, K, V> { + pub fn to_snapshot(&self) -> BptreeMapReadSnapshot<'_, K, V, M> { BptreeMapReadSnapshot { inner: SnapshotType::R(self.inner.as_ref()), } @@ -399,8 +414,8 @@ impl - BptreeMapReadSnapshot<'_, K, V> +impl + BptreeMapReadSnapshot<'_, K, V, M> { /// Retrieve a value from the tree. If the value exists, a reference is returned /// as `Some(&V)`, otherwise if not present `None` is returned. diff --git a/src/bptree/mod.rs b/src/bptree/mod.rs index 19ae741..d388ae9 100644 --- a/src/bptree/mod.rs +++ b/src/bptree/mod.rs @@ -12,30 +12,36 @@ use serde::{ #[cfg(feature = "serde")] use crate::utils::MapCollector; -use crate::internals::lincowcell::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; +use crate::internals::lincowcell::{LinCowCellRaw, LinCowCellReadTxn, LinCowCellWriteTxn}; include!("impl.rs"); -impl - BptreeMap +impl< + K: Clone + Ord + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex, + > BptreeMapRaw { /// Initiate a read transaction for the tree, concurrent to any /// other readers or writers. - pub fn read(&self) -> BptreeMapReadTxn<'_, K, V> { + pub fn read(&self) -> BptreeMapReadTxn<'_, K, V, M> { let inner = self.inner.read(); BptreeMapReadTxn { inner } } /// Initiate a write transaction for the tree, exclusive to this /// writer, and concurrently to all existing reads. - pub fn write(&self) -> BptreeMapWriteTxn<'_, K, V> { + pub fn write(&self) -> BptreeMapWriteTxn<'_, K, V, M> { let inner = self.inner.write(); BptreeMapWriteTxn { inner } } } -impl - BptreeMapWriteTxn<'_, K, V> +impl< + K: Clone + Ord + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex, + > BptreeMapWriteTxn<'_, K, V, M> { /// Commit the changes from this write transaction. Readers after this point /// will be able to perceive these changes. @@ -47,10 +53,11 @@ impl Serialize for BptreeMapReadTxn<'_, K, V> +impl Serialize for BptreeMapReadTxn<'_, K, V, M> where K: Serialize + Clone + Ord + Debug + Sync + Send + 'static, V: Serialize + Clone + Sync + Send + 'static, + M: RawMutex + 'static, { fn serialize(&self, serializer: S) -> Result where @@ -67,10 +74,11 @@ where } #[cfg(feature = "serde")] -impl Serialize for BptreeMap +impl Serialize for BptreeMap where K: Serialize + Clone + Ord + Debug + Sync + Send + 'static, V: Serialize + Clone + Sync + Send + 'static, + M: RawMutex + 'static, { fn serialize(&self, serializer: S) -> Result where @@ -81,10 +89,11 @@ where } #[cfg(feature = "serde")] -impl<'de, K, V> Deserialize<'de> for BptreeMap +impl<'de, K, V, M> Deserialize<'de> for BptreeMap where K: Deserialize<'de> + Clone + Ord + Debug + Sync + Send + 'static, V: Deserialize<'de> + Clone + Sync + Send + 'static, + M: RawMutex + 'static, { fn deserialize(deserializer: D) -> Result where @@ -174,7 +183,7 @@ mod tests { fn test_bptree2_map_from_iter_1() { let ins: Vec = (0..(L_CAPACITY << 4)).collect(); - let map = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); + let map: BptreeMap = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); { let w = map.write(); @@ -192,7 +201,7 @@ mod tests { let mut ins: Vec = (0..(L_CAPACITY << 4)).collect(); ins.shuffle(&mut rng); - let map = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); + let map: BptreeMap = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); { let w = map.write(); @@ -208,7 +217,7 @@ mod tests { fn bptree_map_basic_concurrency(lower: usize, upper: usize) { // Create a map - let map = BptreeMap::new(); + let map: BptreeMap = BptreeMap::new(); // add values { @@ -273,7 +282,7 @@ mod tests { // Need to ensure that txns are dropped in order. // Add data, enough to cause a split. All data should be *2 - let map = BptreeMap::new(); + let map: BptreeMap = BptreeMap::new(); // add values { let mut w = map.write(); @@ -343,7 +352,8 @@ mod tests { fn test_bptree2_map_rangeiter_1() { let ins: Vec = (0..100).collect(); - let map = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); + let map: BptreeMap = + BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); { let w = map.write(); @@ -359,7 +369,8 @@ mod tests { #[test] fn test_bptree2_map_rangeiter_2() { - let map = BptreeMap::from_iter([(3, ()), (4, ()), (0, ())]); + let map: BptreeMap = + BptreeMap::from_iter([(3, ()), (4, ()), (0, ())]); let r = map.read(); assert!(r.range(1..=2).count() == 0); @@ -367,7 +378,8 @@ mod tests { #[test] fn test_bptree2_map_rangeiter_3() { - let map = BptreeMap::from_iter([0, 1, 2, 3, 4, 5, 6, 8].map(|v| (v, ()))); + let map: BptreeMap = + BptreeMap::from_iter([0, 1, 2, 3, 4, 5, 6, 8].map(|v| (v, ()))); let r = map.read(); assert!(r.range((Bound::Excluded(6), Bound::Included(7))).count() == 0); @@ -377,7 +389,7 @@ mod tests { /* #[test] fn test_bptree2_map_write_compact() { - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let insa: Vec = (0..(L_CAPACITY << 4)).collect(); let map = BptreeMap::from_iter(insa.into_iter().map(|v| (v, v))); @@ -433,7 +445,7 @@ mod tests { .map(|_| { scope.spawn(move || { println!("Started reader ..."); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let mut proceed = true; while proceed { let m_read = mref.read(); @@ -441,7 +453,7 @@ mod tests { // Get a random number. // Add 10_000 * random // Remove 10_000 * random - let v1 = rng.gen_range(1, 18) * 10_000; + let v1 = rng.random_range(1, 18) * 10_000; let r1 = v1 + 10_000; for i in v1..r1 { m_read.get(&i); @@ -458,7 +470,7 @@ mod tests { .map(|_| { scope.spawn(move || { println!("Started writer ..."); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let mut proceed = true; while proceed { let mut m_write = mref.write(); @@ -466,9 +478,9 @@ mod tests { // Get a random number. // Add 10_000 * random // Remove 10_000 * random - let v1 = rng.gen_range(1, 18) * 10_000; + let v1 = rng.random_range(1, 18) * 10_000; let r1 = v1 + 10_000; - let v2 = rng.gen_range(1, 19) * 10_000; + let v2 = rng.random_range(1, 19) * 10_000; let r2 = v2 + 10_000; for i in v1..r1 { @@ -524,7 +536,7 @@ mod tests { .map(|_| { scope.spawn(move || { println!("Started reader ..."); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let mut proceed = true; while proceed { let m_read = mref.lock().unwrap(); @@ -532,7 +544,7 @@ mod tests { // Get a random number. // Add 10_000 * random // Remove 10_000 * random - let v1 = rng.gen_range(1, 18) * 10_000; + let v1 = rng.random_range(1, 18) * 10_000; let r1 = v1 + 10_000; for i in v1..r1 { m_read.get(&i); @@ -548,7 +560,7 @@ mod tests { .map(|_| { scope.spawn(move || { println!("Started writer ..."); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let mut proceed = true; while proceed { let mut m_write = mref.lock().unwrap(); @@ -556,9 +568,9 @@ mod tests { // Get a random number. // Add 10_000 * random // Remove 10_000 * random - let v1 = rng.gen_range(1, 18) * 10_000; + let v1 = rng.random_range(1, 18) * 10_000; let r1 = v1 + 10_000; - let v2 = rng.gen_range(1, 19) * 10_000; + let v2 = rng.random_range(1, 19) * 10_000; let r2 = v2 + 10_000; for i in v1..r1 { @@ -611,7 +623,7 @@ mod tests { .map(|_| { scope.spawn(move || { println!("Started reader ..."); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let mut proceed = true; while proceed { let m_read = mref.read().unwrap(); @@ -619,7 +631,7 @@ mod tests { // Get a random number. // Add 10_000 * random // Remove 10_000 * random - let v1 = rng.gen_range(1, 18) * 10_000; + let v1 = rng.random_range(1, 18) * 10_000; let r1 = v1 + 10_000; for i in v1..r1 { m_read.get(&i); @@ -635,7 +647,7 @@ mod tests { .map(|_| { scope.spawn(move || { println!("Started writer ..."); - let mut rng = rand::thread_rng(); + let mut rng = rand::rng(); let mut proceed = true; while proceed { let mut m_write = mref.write().unwrap(); @@ -643,9 +655,9 @@ mod tests { // Get a random number. // Add 10_000 * random // Remove 10_000 * random - let v1 = rng.gen_range(1, 18) * 10_000; + let v1 = rng.random_range(1, 18) * 10_000; let r1 = v1 + 10_000; - let v2 = rng.gen_range(1, 19) * 10_000; + let v2 = rng.random_range(1, 19) * 10_000; let r2 = v2 + 10_000; for i in v1..r1 { diff --git a/src/cowcell/asynch.rs b/src/cowcell/asynch.rs index 3a571a8..b9f8810 100644 --- a/src/cowcell/asynch.rs +++ b/src/cowcell/asynch.rs @@ -2,6 +2,7 @@ //! //! See `CowCell` for more details. +// We can use std here as the `asynch` feature requires the `std` feature use std::ops::{Deref, DerefMut}; use std::sync::Arc; use tokio::sync::{Mutex, MutexGuard}; diff --git a/src/cowcell/mod.rs b/src/cowcell/mod.rs index b5b977d..3ae18ca 100644 --- a/src/cowcell/mod.rs +++ b/src/cowcell/mod.rs @@ -12,11 +12,20 @@ #[cfg(feature = "asynch")] pub mod asynch; -use std::ops::{Deref, DerefMut}; +use core::ops::{Deref, DerefMut}; +use lock_api::{Mutex, MutexGuard, RawMutex}; + +#[cfg(not(feature = "std"))] +use alloc::sync::Arc; + +#[cfg(feature = "std")] use std::sync::Arc; -use std::sync::{Mutex, MutexGuard}; -/// A conncurrently readable cell. +/// CowCell with a default lock type provided. +#[cfg(feature = "std")] +pub type CowCell = CowCellRaw; + +/// A concurrently readable cell. /// /// This structure behaves in a similar manner to a `RwLock`. However unlike /// a `RwLock`, writes and parallel reads can be performed at the same time. This @@ -39,7 +48,7 @@ use std::sync::{Mutex, MutexGuard}; /// use concread::cowcell::CowCell; /// /// let data: i64 = 0; -/// let cowcell = CowCell::new(data); +/// let cowcell = CowCell::::new(data); /// /// // Begin a read transaction /// let read_txn = cowcell.read(); @@ -57,10 +66,19 @@ use std::sync::{Mutex, MutexGuard}; /// // And a new read transaction has '1' /// assert_eq!(*new_read_txn, 1); /// ``` -#[derive(Debug, Default)] -pub struct CowCell { - write: Mutex<()>, - active: Mutex>, +#[derive(Debug)] +pub struct CowCellRaw { + write: Mutex, + active: Mutex>, +} + +impl Default for CowCellRaw { + fn default() -> Self { + Self { + write: Mutex::new(()), + active: Mutex::new(Arc::new(Default::default())), + } + } } /// A `CowCell` Write Transaction handle. @@ -72,13 +90,13 @@ pub struct CowCell { /// rollback a change, don't call commit and allow the write transaction to /// be dropped. This causes the `CowCell` to unlock allowing the next writer /// to proceed. -pub struct CowCellWriteTxn<'a, T> { +pub struct CowCellWriteTxn<'a, T, R: RawMutex> { // Hold open the guard, and initiate the copy to here. work: Option, read: Arc, // This way we know who to contact for updating our data .... - caller: &'a CowCell, - _guard: MutexGuard<'a, ()>, + caller: &'a CowCellRaw, + _guard: MutexGuard<'a, R, ()>, } /// A `CowCell` Read Transaction handle. @@ -94,14 +112,15 @@ impl Clone for CowCellReadTxn { } } -impl CowCell +impl CowCellRaw where T: Clone, + R: RawMutex, { /// Create a new `CowCell` for storing type `T`. `T` must implement `Clone` /// to enable clone-on-write. pub fn new(data: T) -> Self { - CowCell { + CowCellRaw { write: Mutex::new(()), active: Mutex::new(Arc::new(data)), } @@ -111,7 +130,7 @@ where /// the read guard is guaranteed to be consistent for the life time of the /// read - even if writers commit during. pub fn read(&self) -> CowCellReadTxn { - let rwguard = self.active.lock().unwrap(); + let rwguard = self.active.lock(); CowCellReadTxn(rwguard.clone()) // rwguard ends here } @@ -119,12 +138,12 @@ where /// Begin a write transaction, returning a write guard. The content of the /// write is only visible to this thread, and is not visible to any reader /// until `commit()` is called. - pub fn write(&self) -> CowCellWriteTxn<'_, T> { + pub fn write(&self) -> CowCellWriteTxn<'_, T, R> { /* Take the exclusive write lock first */ - let mguard = self.write.lock().unwrap(); + let mguard = self.write.lock(); // We delay copying until the first get_mut. let read = { - let rwguard = self.active.lock().unwrap(); + let rwguard = self.active.lock(); rwguard.clone() }; /* Now build the write struct */ @@ -139,12 +158,12 @@ where /// Attempt to create a write transaction. If it fails, and err /// is returned. On success the `Ok(guard)` is returned. See also /// `write(&self)` - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { /* Take the exclusive write lock first */ - self.write.try_lock().ok().map(|mguard| { + self.write.try_lock().map(|mguard| { // We delay copying until the first get_mut. let read = { - let rwguard = self.active.lock().unwrap(); + let rwguard = self.active.lock(); rwguard.clone() }; /* Now build the write struct */ @@ -159,7 +178,7 @@ where fn commit(&self, newdata: Option) { if let Some(new_data) = newdata { - let mut rwguard = self.active.lock().unwrap(); + let mut rwguard = self.active.lock(); let new_inner = Arc::new(new_data); // now over-write the last value in the mutex. *rwguard = new_inner; @@ -178,9 +197,10 @@ impl Deref for CowCellReadTxn { } } -impl CowCellWriteTxn<'_, T> +impl CowCellWriteTxn<'_, T, R> where T: Clone, + R: RawMutex, { /// Access a mutable pointer of the data in the `CowCell`. This data is only /// visible to the write transaction object in this thread, until you call @@ -188,7 +208,7 @@ where pub fn get_mut(&mut self) -> &mut T { if self.work.is_none() { let mut data: Option = Some((*self.read).clone()); - std::mem::swap(&mut data, &mut self.work); + core::mem::swap(&mut data, &mut self.work); // Should be the none we previously had. debug_assert!(data.is_none()) } @@ -211,9 +231,10 @@ where } } -impl Deref for CowCellWriteTxn<'_, T> +impl Deref for CowCellWriteTxn<'_, T, R> where T: Clone, + R: RawMutex, { type Target = T; @@ -226,9 +247,10 @@ where } } -impl DerefMut for CowCellWriteTxn<'_, T> +impl DerefMut for CowCellWriteTxn<'_, T, R> where T: Clone, + R: RawMutex, { #[inline(always)] fn deref_mut(&mut self) -> &mut T { @@ -238,16 +260,14 @@ where #[cfg(test)] mod tests { - use super::CowCell; - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::time::Instant; + use super::CowCellRaw; - use std::thread::scope; + type CowCell = CowCellRaw>; #[test] fn test_deref_mut() { let data: i64 = 0; - let cc = CowCell::new(data); + let cc: CowCell = CowCell::new(data); { /* Take a write txn */ let mut cc_wrtxn = cc.write(); @@ -261,26 +281,26 @@ mod tests { #[test] fn test_try_write() { let data: i64 = 0; - let cc = CowCell::new(data); + let cc: CowCell = CowCell::new(data); /* Take a write txn */ - let cc_wrtxn_a = cc.try_write(); + let cc_wrtxn_a: Option>> = cc.try_write(); assert!(cc_wrtxn_a.is_some()); /* Because we already hold the writ, the second is guaranteed to fail */ - let cc_wrtxn_a = cc.try_write(); + let cc_wrtxn_a: Option>> = cc.try_write(); assert!(cc_wrtxn_a.is_none()); } #[test] fn test_simple_create() { let data: i64 = 0; - let cc = CowCell::new(data); + let cc: CowCell = CowCell::new(data); - let cc_rotxn_a = cc.read(); + let cc_rotxn_a: crate::cowcell::CowCellReadTxn = cc.read(); assert_eq!(*cc_rotxn_a, 0); { /* Take a write txn */ - let mut cc_wrtxn = cc.write(); + let mut cc_wrtxn: crate::cowcell::CowCellWriteTxn<'_, i64, spin::mutex::Mutex<()>> = cc.write(); /* Get the data ... */ { let mut_ptr = cc_wrtxn.get_mut(); @@ -302,6 +322,14 @@ mod tests { assert_eq!(*cc_rotxn_c, 1); assert_eq!(*cc_rotxn_a, 0); } +} + +#[cfg(all(test, feature = "std"))] +mod tests_std { + use super::CowCell; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::thread::scope; + use std::time::Instant; const MAX_TARGET: i64 = 2000; @@ -311,7 +339,7 @@ mod tests { let start = Instant::now(); // Create the new cowcell. let data: i64 = 0; - let cc = CowCell::new(data); + let cc: CowCell = CowCell::new(data); assert!(scope(|scope| { let cc_ref = &cc; diff --git a/src/ebrcell/mod.rs b/src/ebrcell/mod.rs index 74032a0..ae97a5d 100644 --- a/src/ebrcell/mod.rs +++ b/src/ebrcell/mod.rs @@ -21,6 +21,7 @@ use std::sync::atomic::Ordering::{Acquire, Relaxed, Release}; use std::mem; use std::ops::{Deref, DerefMut}; + use std::sync::{Mutex, MutexGuard}; /// An `EbrCell` Write Transaction handle. @@ -129,7 +130,10 @@ where /// assert_eq!(*new_read_txn, 1); /// ``` #[derive(Debug)] -pub struct EbrCell { +pub struct EbrCell +where + T: Clone + Sync + Send + 'static, +{ write: Mutex<()>, active: Atomic, } @@ -150,7 +154,7 @@ where /// Create a new `EbrCell` storing type `T`. `T` must implement `Clone`. pub fn new(data: T) -> Self { EbrCell { - write: Mutex::new(()), + write: Mutex::<()>::new(()), active: Atomic::new(data), } } diff --git a/src/hashmap/asynch.rs b/src/hashmap/asynch.rs index 26f32d7..85afe73 100644 --- a/src/hashmap/asynch.rs +++ b/src/hashmap/asynch.rs @@ -13,46 +13,52 @@ use serde::{ #[cfg(feature = "serde")] use crate::utils::MapCollector; -use crate::internals::lincowcell_async::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; +use crate::internals::lincowcell_async::{LinCowCellRaw, LinCowCellReadTxn, LinCowCellWriteTxn}; include!("impl.rs"); -impl - HashMap +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashMapRaw { /// Construct a new concurrent hashmap pub fn new() -> Self { // I acknowledge I understand what is required to make this safe. - HashMap { - inner: LinCowCell::new(unsafe { SuperBlock::new() }), + HashMapRaw { + inner: LinCowCellRaw::new(unsafe { SuperBlock::new() }), } } /// Initiate a read transaction for the Hashmap, concurrent to any /// other readers or writers. - pub fn read<'x>(&'x self) -> HashMapReadTxn<'x, K, V> { + pub fn read<'x>(&'x self) -> HashMapReadTxn<'x, K, V, M> { let inner = self.inner.read(); HashMapReadTxn { inner } } /// Initiate a write transaction for the map, exclusive to this /// writer, and concurrently to all existing reads. - pub async fn write<'x>(&'x self) -> HashMapWriteTxn<'x, K, V> { + pub async fn write<'x>(&'x self) -> HashMapWriteTxn<'x, K, V, M> { let inner = self.inner.write().await; HashMapWriteTxn { inner } } /// Attempt to create a new write, returns None if another writer /// already exists. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.inner .try_write() .map(|inner| HashMapWriteTxn { inner }) } } -impl - HashMapWriteTxn<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashMapWriteTxn<'_, K, V, M> { /// Commit the changes from this write transaction. Readers after this point /// will be able to perceive these changes. @@ -84,7 +90,7 @@ where } #[cfg(feature = "serde")] -impl Serialize for HashMap +impl Serialize for HashMapRaw where K: Serialize + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Serialize + Clone + Sync + Send + 'static, @@ -98,7 +104,7 @@ where } #[cfg(feature = "serde")] -impl<'de, K, V> Deserialize<'de> for HashMap +impl<'de, K, V> Deserialize<'de> for HashMapRaw where K: Deserialize<'de> + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Deserialize<'de> + Clone + Sync + Send + 'static, diff --git a/src/hashmap/impl.rs b/src/hashmap/impl.rs index cc927f7..114b71e 100644 --- a/src/hashmap/impl.rs +++ b/src/hashmap/impl.rs @@ -1,3 +1,5 @@ +use lock_api::RawMutex; + use crate::internals::hashmap::cursor::CursorReadOps; use crate::internals::hashmap::cursor::{CursorRead, CursorWrite, SuperBlock}; use crate::internals::hashmap::iter::*; @@ -9,6 +11,10 @@ use std::fmt::Debug; use std::hash::Hash; use std::iter::FromIterator; +/// B+Tree-based map with a default mutex type provided. +#[cfg(feature = "std")] +pub type HashMap = HashMapRaw; + /// A concurrently readable map based on a modified B+Tree structured with fast /// parallel hashed key lookup. /// @@ -27,32 +33,40 @@ use std::iter::FromIterator; /// /// Transactions can be rolled-back (aborted) without penalty by dropping /// the `HashMapWriteTxn` without calling `commit()`. -pub struct HashMap +pub struct HashMapRaw where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, { - inner: LinCowCell, CursorRead, CursorWrite>, + inner: LinCowCellRaw, CursorRead, CursorWrite, M>, } -unsafe impl - Send for HashMap +unsafe impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + Send + 'static, + > Send for HashMapRaw { } -unsafe impl - Sync for HashMap +unsafe impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + Send + Sync + 'static, + > Sync for HashMapRaw { } /// An active read transaction over a `HashMap`. The data in this tree /// is guaranteed to not change and will remain consistent for the life /// of this transaction. -pub struct HashMapReadTxn<'a, K, V> +pub struct HashMapReadTxn<'a, K, V, M> where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex, { - inner: LinCowCellReadTxn<'a, SuperBlock, CursorRead, CursorWrite>, + inner: LinCowCellReadTxn<'a, SuperBlock, CursorRead, CursorWrite, M>, } /// An active write transaction for a `HashMap`. The data in this tree @@ -60,20 +74,22 @@ where /// readers. The write may be rolledback/aborted by dropping this guard /// without calling `commit()`. Once `commit()` is called, readers will be /// able to access and perceive changes in new transactions. -pub struct HashMapWriteTxn<'a, K, V> +pub struct HashMapWriteTxn<'a, K, V, M> where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex, { - inner: LinCowCellWriteTxn<'a, SuperBlock, CursorRead, CursorWrite>, + inner: LinCowCellWriteTxn<'a, SuperBlock, CursorRead, CursorWrite, M>, } -enum SnapshotType<'a, K, V> +enum SnapshotType<'a, K, V, M> where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex, { - R(&'a CursorRead), + R(&'a CursorRead), W(&'a CursorWrite), } @@ -85,49 +101,65 @@ where /// This snapshot IS safe within the read thread due to the nature of the /// implementation borrowing the inner tree to prevent mutations within the /// same thread while the read snapshot is open. -pub struct HashMapReadSnapshot<'a, K, V> +pub struct HashMapReadSnapshot<'a, K, V, M> where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex, { - inner: SnapshotType<'a, K, V>, + inner: SnapshotType<'a, K, V, M>, } -impl Default - for HashMap +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > Default for HashMapRaw { fn default() -> Self { Self::new() } } -impl - FromIterator<(K, V)> for HashMap +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > FromIterator<(K, V)> for HashMapRaw { fn from_iter>(iter: I) -> Self { let mut new_sblock = unsafe { SuperBlock::new() }; - let prev = new_sblock.create_reader(); - let mut cursor = new_sblock.create_writer(); + let prev: CursorRead = new_sblock.create_reader(); + let mut cursor = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&new_sblock); //new_sblock.create_writer(); cursor.extend(iter); let _ = new_sblock.pre_commit(cursor, &prev); - HashMap { - inner: LinCowCell::new(new_sblock), + HashMapRaw { + inner: LinCowCellRaw::new(new_sblock), } } } -impl - Extend<(K, V)> for HashMapWriteTxn<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > Extend<(K, V)> for HashMapWriteTxn<'_, K, V, M> { fn extend>(&mut self, iter: I) { self.inner.as_mut().extend(iter); } } -impl - HashMapWriteTxn<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashMapWriteTxn<'_, K, V, M> { /* pub(crate) fn prehash(&self, k: &Q) -> u64 @@ -225,15 +257,18 @@ impl HashMapReadSnapshot<'_, K, V> { + pub fn to_snapshot(&self) -> HashMapReadSnapshot<'_, K, V, M> { HashMapReadSnapshot { inner: SnapshotType::W(self.inner.as_ref()), } } } -impl - HashMapReadTxn<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex, + > HashMapReadTxn<'_, K, V, M> { pub(crate) fn get_prehashed(&self, k: &Q, k_hash: u64) -> Option<&V> where @@ -260,7 +295,7 @@ impl, Q: Hash + Eq + ?Sized, { - self.get(k).is_some() + self.get::(k).is_some() } /// Returns the current number of k:v pairs in the tree @@ -270,7 +305,7 @@ impl bool { - self.inner.as_ref().len() == 0 + 0usize == self.inner.as_ref().len() } /// Iterator over `(&K, &V)` of the set @@ -290,15 +325,18 @@ impl HashMapReadSnapshot<'_, K, V> { + pub fn to_snapshot(&self) -> HashMapReadSnapshot<'_, K, V, M> { HashMapReadSnapshot { inner: SnapshotType::R(self.inner.as_ref()), } } } -impl - HashMapReadSnapshot<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashMapReadSnapshot<'_, K, V, M> { /// Retrieve a value from the tree. If the value exists, a reference is returned /// as `Some(&V)`, otherwise if not present `None` is returned. diff --git a/src/hashmap/mod.rs b/src/hashmap/mod.rs index ce1bf54..a3dec7f 100644 --- a/src/hashmap/mod.rs +++ b/src/hashmap/mod.rs @@ -34,46 +34,52 @@ use crate::utils::MapCollector; #[cfg(all(feature = "arcache", feature = "arcache-is-hashmap"))] use crate::internals::hashmap::cursor::Datum; -use crate::internals::lincowcell::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; +use crate::internals::lincowcell::{LinCowCellRaw, LinCowCellReadTxn, LinCowCellWriteTxn}; include!("impl.rs"); -impl - HashMap +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashMapRaw { /// Construct a new concurrent hashmap pub fn new() -> Self { // I acknowledge I understand what is required to make this safe. - HashMap { - inner: LinCowCell::new(unsafe { SuperBlock::new() }), + HashMapRaw { + inner: LinCowCellRaw::new(unsafe { SuperBlock::new() }), } } /// Initiate a read transaction for the Hashmap, concurrent to any /// other readers or writers. - pub fn read(&self) -> HashMapReadTxn<'_, K, V> { + pub fn read(&self) -> HashMapReadTxn<'_, K, V, M> { let inner = self.inner.read(); HashMapReadTxn { inner } } /// Initiate a write transaction for the map, exclusive to this /// writer, and concurrently to all existing reads. - pub fn write(&self) -> HashMapWriteTxn<'_, K, V> { + pub fn write(&self) -> HashMapWriteTxn<'_, K, V, M> { let inner = self.inner.write(); HashMapWriteTxn { inner } } /// Attempt to create a new write, returns None if another writer /// already exists. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.inner .try_write() .map(|inner| HashMapWriteTxn { inner }) } } -impl - HashMapWriteTxn<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashMapWriteTxn<'_, K, V, M> { #[cfg(all(feature = "arcache", feature = "arcache-is-hashmap"))] pub(crate) fn get_txid(&self) -> u64 { @@ -107,8 +113,11 @@ impl - HashMapReadTxn<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex, + > HashMapReadTxn<'_, K, V, M> { #[cfg(all(feature = "arcache", feature = "arcache-is-hashmap"))] pub(crate) fn get_txid(&self) -> u64 { @@ -126,10 +135,11 @@ impl Serialize for HashMapReadTxn<'_, K, V> +impl Serialize for HashMapReadTxn<'_, K, V, M> where K: Serialize + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Serialize + Clone + Sync + Send + 'static, + M: RawMutex, { fn serialize(&self, serializer: S) -> Result where @@ -146,10 +156,11 @@ where } #[cfg(feature = "serde")] -impl Serialize for HashMap +impl Serialize for HashMap where K: Serialize + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Serialize + Clone + Sync + Send + 'static, + M: RawMutex + 'static, { fn serialize(&self, serializer: S) -> Result where @@ -160,10 +171,11 @@ where } #[cfg(feature = "serde")] -impl<'de, K, V> Deserialize<'de> for HashMap +impl<'de, K, V, M> Deserialize<'de> for HashMap where K: Deserialize<'de> + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Deserialize<'de> + Clone + Sync + Send + 'static, + M: RawMutex + 'static, { fn deserialize(deserializer: D) -> Result where diff --git a/src/hashtrie/asynch.rs b/src/hashtrie/asynch.rs index b15cf68..fab36f6 100644 --- a/src/hashtrie/asynch.rs +++ b/src/hashtrie/asynch.rs @@ -13,46 +13,52 @@ use serde::{ #[cfg(feature = "serde")] use crate::utils::MapCollector; -use crate::internals::lincowcell_async::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; +use crate::internals::lincowcell_async::{LinCowCellRaw, LinCowCellReadTxn, LinCowCellWriteTxn}; include!("impl.rs"); -impl - HashTrie +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashTrieRaw { /// Construct a new concurrent hashtrie pub fn new() -> Self { // I acknowledge I understand what is required to make this safe. - HashTrie { - inner: LinCowCell::new(unsafe { SuperBlock::new() }), + HashTrieRaw { + inner: LinCowCellRaw::new(unsafe { SuperBlock::new() }), } } /// Initiate a read transaction for the Hashmap, concurrent to any /// other readers or writers. - pub fn read<'x>(&'x self) -> HashTrieReadTxn<'x, K, V> { + pub fn read<'x>(&'x self) -> HashTrieReadTxn<'x, K, V, M> { let inner = self.inner.read(); HashTrieReadTxn { inner } } /// Initiate a write transaction for the map, exclusive to this /// writer, and concurrently to all existing reads. - pub async fn write<'x>(&'x self) -> HashTrieWriteTxn<'x, K, V> { + pub async fn write<'x>(&'x self) -> HashTrieWriteTxn<'x, K, V, M> { let inner = self.inner.write().await; HashTrieWriteTxn { inner } } /// Attempt to create a new write, returns None if another writer /// already exists. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.inner .try_write() .map(|inner| HashTrieWriteTxn { inner }) } } -impl - HashTrieWriteTxn<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashTrieWriteTxn<'_, K, V, M> { /// Commit the changes from this write transaction. Readers after this point /// will be able to perceieve these changes. @@ -84,7 +90,7 @@ where } #[cfg(feature = "serde")] -impl Serialize for HashTrie +impl Serialize for HashTrieRaw where K: Serialize + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Serialize + Clone + Sync + Send + 'static, @@ -98,7 +104,7 @@ where } #[cfg(feature = "serde")] -impl<'de, K, V> Deserialize<'de> for HashTrie +impl<'de, K, V> Deserialize<'de> for HashTrieRaw where K: Deserialize<'de> + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Deserialize<'de> + Clone + Sync + Send + 'static, diff --git a/src/hashtrie/impl.rs b/src/hashtrie/impl.rs index 5699d56..c7d9690 100644 --- a/src/hashtrie/impl.rs +++ b/src/hashtrie/impl.rs @@ -1,3 +1,5 @@ +use lock_api::RawMutex; + use crate::internals::hashtrie::cursor::CursorReadOps; use crate::internals::hashtrie::cursor::{CursorRead, CursorWrite, SuperBlock}; use crate::internals::hashtrie::iter::*; @@ -9,6 +11,10 @@ use std::fmt::Debug; use std::hash::Hash; use std::iter::FromIterator; +/// HashTrie with a default lock type provided. +#[cfg(feature = "std")] +pub type HashTrie = HashTrieRaw; + /// A concurrently readable map based on a modified Trie. /// /// @@ -25,32 +31,34 @@ use std::iter::FromIterator; /// /// Transactions can be rolled-back (aborted) without penalty by dropping /// the `HashTrieWriteTxn` without calling `commit()`. -pub struct HashTrie +pub struct HashTrieRaw where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex + 'static { - inner: LinCowCell, CursorRead, CursorWrite>, + inner: LinCowCellRaw, CursorRead, CursorWrite, M>, } -unsafe impl - Send for HashTrie +unsafe impl + Send for HashTrieRaw { } -unsafe impl - Sync for HashTrie +unsafe impl + Sync for HashTrieRaw { } /// An active read transaction over a `HashTrie`. The data in this tree /// is guaranteed to not change and will remain consistent for the life /// of this transaction. -pub struct HashTrieReadTxn<'a, K, V> +pub struct HashTrieReadTxn<'a, K, V, M> where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex { - inner: LinCowCellReadTxn<'a, SuperBlock, CursorRead, CursorWrite>, + inner: LinCowCellReadTxn<'a, SuperBlock, CursorRead, CursorWrite, M>, } /// An active write transaction for a `HashTrie`. The data in this tree @@ -58,20 +66,22 @@ where /// readers. The write may be rolledback/aborted by dropping this guard /// without calling `commit()`. Once `commit()` is called, readers will be /// able to access and perceive changes in new transactions. -pub struct HashTrieWriteTxn<'a, K, V> +pub struct HashTrieWriteTxn<'a, K, V, M> where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex { - inner: LinCowCellWriteTxn<'a, SuperBlock, CursorRead, CursorWrite>, + inner: LinCowCellWriteTxn<'a, SuperBlock, CursorRead, CursorWrite, M>, } -enum SnapshotType<'a, K, V> +enum SnapshotType<'a, K, V, M> where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex + 'static { - R(&'a CursorRead), + R(&'a CursorRead), W(&'a CursorWrite), } @@ -83,49 +93,53 @@ where /// This snapshot IS safe within the read thread due to the nature of the /// implementation borrowing the inner tree to prevent mutations within the /// same thread while the read snapshot is open. -pub struct HashTrieReadSnapshot<'a, K, V> +pub struct HashTrieReadSnapshot<'a, K, V, M> where K: Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static, + M: RawMutex + 'static { - inner: SnapshotType<'a, K, V>, + inner: SnapshotType<'a, K, V, M>, } -impl Default - for HashTrie +impl Default + for HashTrieRaw { fn default() -> Self { Self::new() } } -impl - FromIterator<(K, V)> for HashTrie +impl + FromIterator<(K, V)> for HashTrieRaw { fn from_iter>(iter: I) -> Self { let mut new_sblock = unsafe { SuperBlock::new() }; - let prev = new_sblock.create_reader(); - let mut cursor = new_sblock.create_writer(); + let prev: CursorRead = new_sblock.create_reader(); + + // TODO - can we specify the bound some other way that doesn't make this type vomit? + use crate::internals::hashtrie::cursor; + let mut cursor = as LinCowCellCapable, cursor::CursorWrite>>::create_writer(&new_sblock); cursor.extend(iter); let _ = new_sblock.pre_commit(cursor, &prev); - HashTrie { - inner: LinCowCell::new(new_sblock), + HashTrieRaw { + inner: LinCowCellRaw::new(new_sblock), } } } -impl - Extend<(K, V)> for HashTrieWriteTxn<'_, K, V> +impl + Extend<(K, V)> for HashTrieWriteTxn<'_, K, V, M> { fn extend>(&mut self, iter: I) { self.inner.as_mut().extend(iter); } } -impl - HashTrieWriteTxn<'_, K, V> +impl + HashTrieWriteTxn<'_, K, V, M> { /* pub(crate) fn prehash(&self, k: &Q) -> u64 @@ -223,15 +237,15 @@ impl HashTrieReadSnapshot<'_, K, V> { + pub fn to_snapshot(&self) -> HashTrieReadSnapshot<'_, K, V, M> { HashTrieReadSnapshot { inner: SnapshotType::W(self.inner.as_ref()), } } } -impl - HashTrieReadTxn<'_, K, V> +impl + HashTrieReadTxn<'_, K, V, M> { pub(crate) fn get_prehashed(&self, k: &Q, k_hash: u64) -> Option<&V> where @@ -288,15 +302,15 @@ impl HashTrieReadSnapshot<'_, K, V> { + pub fn to_snapshot(&self) -> HashTrieReadSnapshot<'_, K, V, M> { HashTrieReadSnapshot { inner: SnapshotType::R(self.inner.as_ref()), } } } -impl - HashTrieReadSnapshot<'_, K, V> +impl + HashTrieReadSnapshot<'_, K, V, M> { /// Retrieve a value from the tree. If the value exists, a reference is returned /// as `Some(&V)`, otherwise if not present `None` is returned. diff --git a/src/hashtrie/mod.rs b/src/hashtrie/mod.rs index aba2775..9ff1969 100644 --- a/src/hashtrie/mod.rs +++ b/src/hashtrie/mod.rs @@ -39,46 +39,54 @@ use crate::internals::hashtrie::cursor::Datum; #[cfg(feature = "serde")] use crate::utils::MapCollector; -use crate::internals::lincowcell::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; +use crate::internals::lincowcell::{LinCowCellRaw, LinCowCellReadTxn, LinCowCellWriteTxn}; include!("impl.rs"); -impl - HashTrie +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashTrieRaw { /// Construct a new concurrent hashtrie pub fn new() -> Self { // I acknowledge I understand what is required to make this safe. - HashTrie { - inner: LinCowCell::new(unsafe { SuperBlock::new() }), + HashTrieRaw { + inner: LinCowCellRaw::, CursorRead, CursorWrite, M>::new( + unsafe { SuperBlock::new() }, + ), } } /// Initiate a read transaction for the Hashmap, concurrent to any /// other readers or writers. - pub fn read(&self) -> HashTrieReadTxn<'_, K, V> { + pub fn read(&self) -> HashTrieReadTxn<'_, K, V, M> { let inner = self.inner.read(); HashTrieReadTxn { inner } } /// Initiate a write transaction for the map, exclusive to this /// writer, and concurrently to all existing reads. - pub fn write(&self) -> HashTrieWriteTxn<'_, K, V> { + pub fn write(&self) -> HashTrieWriteTxn<'_, K, V, M> { let inner = self.inner.write(); HashTrieWriteTxn { inner } } /// Attempt to create a new write, returns None if another writer /// already exists. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.inner .try_write() .map(|inner| HashTrieWriteTxn { inner }) } } -impl - HashTrieWriteTxn<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashTrieWriteTxn<'_, K, V, M> { /// View the current transaction ID for this cache. This is a monotonically increasing /// value. If two transactions have the same txid, they are the same data generation. @@ -113,8 +121,11 @@ impl - HashTrieReadTxn<'_, K, V> +impl< + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + 'static, + > HashTrieReadTxn<'_, K, V, M> { /// View the current transaction ID for this cache. This is a monotonically increasing /// value. If two transactions have the same txid, they are the same data generation. @@ -133,10 +144,11 @@ impl Serialize for HashTrieReadTxn<'_, K, V> +impl Serialize for HashTrieReadTxn<'_, K, V, M> where K: Serialize + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Serialize + Clone + Sync + Send + 'static, + M: RawMutex + 'static, { fn serialize(&self, serializer: S) -> Result where @@ -153,10 +165,11 @@ where } #[cfg(feature = "serde")] -impl Serialize for HashTrie +impl Serialize for HashTrie where K: Serialize + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Serialize + Clone + Sync + Send + 'static, + M: RawMutex + 'static, { fn serialize(&self, serializer: S) -> Result where @@ -167,10 +180,11 @@ where } #[cfg(feature = "serde")] -impl<'de, K, V> Deserialize<'de> for HashTrie +impl<'de, K, V, M> Deserialize<'de> for HashTrie where K: Deserialize<'de> + Hash + Eq + Clone + Debug + Sync + Send + 'static, V: Deserialize<'de> + Clone + Sync + Send + 'static, + M: RawMutex + 'static, { fn deserialize(deserializer: D) -> Result where diff --git a/src/internals/bptree/cursor.rs b/src/internals/bptree/cursor.rs index 67b9a68..eb61e9f 100644 --- a/src/internals/bptree/cursor.rs +++ b/src/internals/bptree/cursor.rs @@ -4,6 +4,13 @@ // Additionally, the cursor also is responsible for general movement // throughout the structure and how to handle that effectively +#[cfg(not(feature = "std"))] +use alloc::vec; +#[cfg(feature = "std")] +use std::vec; + +use vec::Vec; + use super::node::*; use crate::internals::lincowcell::LinCowCellCapable; use std::borrow::Borrow; @@ -15,7 +22,7 @@ use super::mutiter::RangeMutIter; use super::states::*; use std::ops::RangeBounds; -use std::sync::Mutex; +use lock_api::{Mutex, RawMutex}; /// The internal root of the tree, with associated garbage lists etc. #[derive(Debug)] @@ -38,10 +45,10 @@ unsafe impl LinCowCellCapable, CursorWrite> - for SuperBlock +impl + LinCowCellCapable, CursorWrite> for SuperBlock { - fn create_reader(&self) -> CursorRead { + fn create_reader(&self) -> CursorRead { // This sets up the first reader. CursorRead::new(self) } @@ -54,9 +61,9 @@ impl LinCowCellCapable, Curso fn pre_commit( &mut self, mut new: CursorWrite, - prev: &CursorRead, - ) -> CursorRead { - let mut prev_last_seen = prev.last_seen.lock().unwrap(); + prev: &CursorRead, + ) -> CursorRead { + let mut prev_last_seen = prev.last_seen.lock(); debug_assert!((*prev_last_seen).is_empty()); let new_last_seen = &mut new.last_seen; @@ -129,23 +136,30 @@ impl SuperBlock { } #[derive(Debug)] -pub(crate) struct CursorRead +pub(crate) struct CursorRead where K: Ord + Clone + Debug, V: Clone, + R: RawMutex, { txid: u64, length: usize, root: *mut Node, - last_seen: Mutex>>, + last_seen: Mutex>>, } -unsafe impl Send - for CursorRead +unsafe impl< + K: Clone + Ord + Debug + Send + 'static, + V: Clone + Send + 'static, + R: RawMutex + Send + 'static, + > Send for CursorRead { } -unsafe impl Sync - for CursorRead +unsafe impl< + K: Clone + Ord + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + R: RawMutex + Send + Sync + 'static, + > Sync for CursorRead { } @@ -584,7 +598,7 @@ impl Drop for CursorWrite { } } -impl Drop for CursorRead { +impl Drop for CursorRead { fn drop(&mut self) { // If there is content in last_seen, a future generation wants us to remove it! let last_seen_guard = self @@ -609,7 +623,7 @@ impl Drop for SuperBlock { } } -impl CursorRead { +impl CursorRead { pub(crate) fn new(sblock: &SuperBlock) -> Self { // println!("starting rd txid -> {:?}", sblock.txid); CursorRead { @@ -621,7 +635,7 @@ impl CursorRead { } } -impl CursorReadOps for CursorRead { +impl CursorReadOps for CursorRead { fn get_root_ref(&self) -> &Node { unsafe { &*(self.root) } } @@ -1285,7 +1299,7 @@ mod tests { use super::super::node::*; use super::super::states::*; use super::SuperBlock; - use super::{CursorRead, CursorReadOps}; + use super::{CursorRead, CursorReadOps, CursorWrite}; use crate::internals::lincowcell::LinCowCellCapable; use rand::seq::SliceRandom; use std::mem; @@ -1335,7 +1349,10 @@ mod tests { // First create the node + cursor let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); eprintln!("{:?}", wcurs); @@ -1376,7 +1393,10 @@ mod tests { let node = create_leaf_node_full(10); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let prev_txid = wcurs.root_txid(); let r = wcurs.insert(1, 1); @@ -1398,7 +1418,10 @@ mod tests { // to trigger a clone of leaf AND THEN to cause the split. let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(L_CAPACITY + 1) { // println!("ITER v {}", v); @@ -1426,7 +1449,10 @@ mod tests { let rnode = create_leaf_node_full(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // println!("{:?}", wcurs); @@ -1456,7 +1482,10 @@ mod tests { let rnode = create_leaf_node_full(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); let r = wcurs.insert(29, 29); @@ -1484,7 +1513,10 @@ mod tests { let rnode = create_leaf_node(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Now insert to trigger the needed actions. @@ -1518,7 +1550,10 @@ mod tests { let rnode = create_leaf_node(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Now insert to trigger the needed actions. @@ -1549,7 +1584,10 @@ mod tests { let rnode = create_leaf_node(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); let r = wcurs.insert(11, 11); @@ -1584,7 +1622,10 @@ mod tests { let rnode = create_leaf_node_full(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); let r = wcurs.insert(19, 19); @@ -1609,7 +1650,10 @@ mod tests { // so we do this to a reasonable number. let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(L_CAPACITY << 4) { // println!("ITER v {}", v); @@ -1630,7 +1674,10 @@ mod tests { // Insert descending let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in (1..(L_CAPACITY << 4)).rev() { // println!("ITER v {}", v); @@ -1655,7 +1702,10 @@ mod tests { let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in ins.into_iter() { let r = wcurs.insert(v, v); @@ -1676,10 +1726,13 @@ mod tests { // Insert ascending - we want to ensure the tree is a few levels deep // so we do this to a reasonable number. let mut sb = unsafe { SuperBlock::new() }; - let mut rdr = sb.create_reader(); + let mut rdr: CursorRead = sb.create_reader(); for v in 1..(L_CAPACITY << 4) { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("ITER v {}", v); let r = wcurs.insert(v, v); assert!(r.is_none()); @@ -1698,10 +1751,13 @@ mod tests { fn test_bptree2_cursor_insert_stress_5() { // Insert descending let mut sb = unsafe { SuperBlock::new() }; - let mut rdr = sb.create_reader(); + let mut rdr: CursorRead = sb.create_reader(); for v in (1..(L_CAPACITY << 4)).rev() { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("ITER v {}", v); let r = wcurs.insert(v, v); assert!(r.is_none()); @@ -1723,10 +1779,13 @@ mod tests { ins.shuffle(&mut rng); let mut sb = unsafe { SuperBlock::new() }; - let mut rdr = sb.create_reader(); + let mut rdr: CursorRead = sb.create_reader(); for v in ins.into_iter() { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let r = wcurs.insert(v, v); assert!(r.is_none()); assert!(wcurs.verify()); @@ -1743,7 +1802,10 @@ mod tests { fn test_bptree2_cursor_search_1() { let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(L_CAPACITY << 4) { let r = wcurs.insert(v, v); @@ -1767,7 +1829,10 @@ mod tests { // Check the length is consistent on operations. let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(L_CAPACITY << 4) { let r = wcurs.insert(v, v); @@ -1787,7 +1852,10 @@ mod tests { // let lnode = create_leaf_node_full(0); let sb = SuperBlock::new_test(1, lnode); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("{:?}", wcurs); for v in 0..L_CAPACITY { @@ -1810,7 +1878,10 @@ mod tests { fn test_bptree2_cursor_remove_01_p1() { let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let _ = wcurs.remove(&0); // println!("{:?}", wcurs); @@ -1836,7 +1907,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(rnode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("{:?}", wcurs); assert!(wcurs.verify()); @@ -1863,7 +1937,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(znode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(&10); @@ -1889,7 +1966,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(rnode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Setup sibling leaf to already be cloned. @@ -1919,7 +1999,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(rnode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Setup leaf to already be cloned. @@ -1949,7 +2032,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(znode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Setup leaf to already be cloned. @@ -1988,7 +2074,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); @@ -2026,7 +2115,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(&10); @@ -2063,7 +2155,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(&80); @@ -2100,7 +2195,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(&10); @@ -2137,7 +2235,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); @@ -2178,7 +2279,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.path_clone(&20); @@ -2218,7 +2322,10 @@ mod tests { let root = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); // let count = BV_CAPACITY + 2; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.path_clone(&0); @@ -2258,7 +2365,10 @@ mod tests { let root = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); for i in 0..BV_CAPACITY { @@ -2282,7 +2392,10 @@ mod tests { let rnode = create_leaf_node(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(&20); @@ -2300,7 +2413,10 @@ mod tests { let rnode = create_leaf_node_full(20) as *mut Node; let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(&10); @@ -2311,14 +2427,17 @@ mod tests { assert_released(); } - fn tree_create_rand() -> (SuperBlock, CursorRead) { + fn tree_create_rand() -> (SuperBlock, CursorRead) { let mut rng = rand::rng(); let mut ins: Vec = (1..(L_CAPACITY << 4)).collect(); ins.shuffle(&mut rng); let mut sb = unsafe { SuperBlock::new() }; let rdr = sb.create_reader(); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in ins.into_iter() { let r = wcurs.insert(v, v); @@ -2334,7 +2453,10 @@ mod tests { // Insert ascending - we want to ensure the tree is a few levels deep // so we do this to a reasonable number. let (mut sb, rdr) = tree_create_rand(); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(L_CAPACITY << 4) { // println!("-- ITER v {}", v); @@ -2355,7 +2477,10 @@ mod tests { fn test_bptree2_cursor_remove_stress_2() { // Insert descending let (mut sb, rdr) = tree_create_rand(); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in (1..(L_CAPACITY << 4)).rev() { // println!("ITER v {}", v); @@ -2378,7 +2503,10 @@ mod tests { ins.shuffle(&mut rng); let (mut sb, rdr) = tree_create_rand(); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in ins.into_iter() { let r = wcurs.remove(&v); @@ -2400,7 +2528,10 @@ mod tests { let (mut sb, mut rdr) = tree_create_rand(); for v in 1..(L_CAPACITY << 4) { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("ITER v {}", v); let r = wcurs.remove(&v); assert!(r == Some(v)); @@ -2418,7 +2549,10 @@ mod tests { let (mut sb, mut rdr) = tree_create_rand(); for v in (1..(L_CAPACITY << 4)).rev() { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("ITER v {}", v); let r = wcurs.remove(&v); assert!(r == Some(v)); @@ -2440,7 +2574,10 @@ mod tests { let (mut sb, mut rdr) = tree_create_rand(); for v in ins.into_iter() { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let r = wcurs.remove(&v); assert!(r == Some(v)); assert!(wcurs.verify()); @@ -2542,7 +2679,10 @@ mod tests { // Do a split_off_lt. let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); wcurs.split_off_lt(&5); @@ -2560,7 +2700,10 @@ mod tests { // Do a split_off_lt. let node = create_leaf_node_full(10); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); wcurs.split_off_lt(&11); @@ -2578,7 +2721,10 @@ mod tests { // Do a split_off_lt. let node = create_leaf_node_full(10); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); wcurs.path_clone(&11); wcurs.split_off_lt(&11); @@ -2597,7 +2743,10 @@ mod tests { let tree = create_split_off_tree(); let sb = SuperBlock::new_test(1, tree); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // 0 is min, and not present, will cause no change. // clone everything let outer: [usize; 4] = [0, 100, 200, 300]; @@ -2628,7 +2777,10 @@ mod tests { // println!("START -> {:?}", tree); let sb = SuperBlock::new_test(1, tree); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // 0 is min, and not present, will cause no change. wcurs.split_off_lt(&v); assert!(wcurs.verify()); @@ -2684,7 +2836,10 @@ mod tests { for v in data.iter() { let node: *mut Leaf = Node::new_leaf(0) as *mut _; let sb = SuperBlock::new_test(1, node as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); wcurs.extend(data.iter().map(|v| (*v, *v))); if v > &0 { @@ -2707,7 +2862,10 @@ mod tests { fn test_bptree_cursor_double_extend() { let node: *mut Leaf = Node::new_leaf(0) as *mut _; let sb = SuperBlock::new_test(1, node as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); wcurs.extend([(0, 0), (1, 1), (2, 2), (3, 3)]); assert!(wcurs.len() == 4); diff --git a/src/internals/bptree/iter.rs b/src/internals/bptree/iter.rs index b251c22..cc284cb 100644 --- a/src/internals/bptree/iter.rs +++ b/src/internals/bptree/iter.rs @@ -1,12 +1,16 @@ //! Iterators for the map. +#[cfg(not(feature = "std"))] +use alloc::collections::VecDeque; +#[cfg(feature = "std")] +use std::collections::VecDeque; + // Iterators for the bptree use super::node::{Branch, Leaf, Meta, Node}; -use std::borrow::Borrow; -use std::collections::VecDeque; -use std::fmt::Debug; -use std::marker::PhantomData; -use std::ops::{Bound, RangeBounds}; +use core::borrow::Borrow; +use core::fmt::Debug; +use core::marker::PhantomData; +use core::ops::{Bound, RangeBounds}; pub(crate) struct LeafIter<'a, K, V> where diff --git a/src/internals/bptree/mutiter.rs b/src/internals/bptree/mutiter.rs index 155c593..10486b7 100644 --- a/src/internals/bptree/mutiter.rs +++ b/src/internals/bptree/mutiter.rs @@ -81,9 +81,9 @@ mod tests { use super::super::cursor::SuperBlock; use super::super::node::{Leaf, Node, L_CAPACITY}; use super::RangeMutIter; - use std::ops::Bound; - use std::ops::Bound::*; + use std::ops::Bound::{self, *}; + use crate::internals::bptree::cursor::{CursorRead, CursorWrite}; use crate::internals::lincowcell::LinCowCellCapable; fn create_leaf_node_full(vbase: usize) -> *mut Node { @@ -104,7 +104,10 @@ mod tests { let node = create_leaf_node_full(10); let sb = SuperBlock::new_test(1, node as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let bounds: (Bound, Bound) = (Unbounded, Unbounded); let range_mut_iter = RangeMutIter::new(&mut wcurs, bounds); diff --git a/src/internals/bptree/node.rs b/src/internals/bptree/node.rs index a891c2e..7c86d1c 100644 --- a/src/internals/bptree/node.rs +++ b/src/internals/bptree/node.rs @@ -9,6 +9,14 @@ use std::mem::MaybeUninit; use std::ptr; use std::slice; +#[cfg(not(feature = "std"))] +use alloc::{boxed, vec}; +#[cfg(feature = "std")] +use std::{boxed, vec}; + +use boxed::Box; +use vec::Vec; + #[cfg(test)] use std::collections::BTreeSet; #[cfg(all(test, not(miri)))] @@ -649,7 +657,7 @@ impl Leaf { nid: alloc_nid(), })); - debug_assert!((x.meta.0 & FLAG_INVALID) != 0); + debug_assert!(0u64 != (x.meta.0 & FLAG_INVALID)); // Copy in the values to the correct location. for idx in 0..self.count() { @@ -663,7 +671,7 @@ impl Leaf { // Finally undo the invalid flag to allow drop to proceed. x.meta.0 &= !FLAG_INVALID; - debug_assert!((x.meta.0 & FLAG_INVALID) == 0); + debug_assert!(0u64 == (x.meta.0 & FLAG_INVALID)); Some(Box::into_raw(x) as *mut Node) } @@ -844,11 +852,11 @@ impl Leaf { let rk: &K = unsafe { &*self.key[work_idx].as_ptr() }; if lk >= rk { // println!("{:?}", self); - if cfg!(test) { + cfg_if::cfg_if! {if #[cfg(test)] { return false; } else { debug_assert!(false); - } + }} } lk = rk; } @@ -868,11 +876,11 @@ impl Leaf { let rk: &K = unsafe { &*(*pointer).key[work_idx].as_ptr() }; if lk >= rk { // println!("{:?}", self); - if cfg!(test) { + cfg_if::cfg_if! { if #[cfg(test)] { return false; } else { debug_assert!(false); - } + }} } lk = rk; } @@ -1008,7 +1016,7 @@ impl Branch { nid: alloc_nid(), })); - debug_assert!((x.meta.0 & FLAG_INVALID) != 0); + debug_assert!(0u64 != (x.meta.0 & FLAG_INVALID)); // Copy in the keys to the correct location. for idx in 0..self.count() { @@ -1020,7 +1028,7 @@ impl Branch { // Finally undo the invalid flag to allow drop to proceed. x.meta.0 &= !FLAG_INVALID; - debug_assert!((x.meta.0 & FLAG_INVALID) == 0); + debug_assert!(0u64 == (x.meta.0 & FLAG_INVALID)); Some(Box::into_raw(x) as *mut Node) } diff --git a/src/internals/hashmap/cursor.rs b/src/internals/hashmap/cursor.rs index bb66cb3..3f29c5c 100644 --- a/src/internals/hashmap/cursor.rs +++ b/src/internals/hashmap/cursor.rs @@ -9,11 +9,18 @@ use std::borrow::Borrow; use std::fmt::Debug; use std::mem; -#[cfg(feature = "ahash")] +#[cfg(not(feature = "std"))] +use alloc::vec; +#[cfg(feature = "std")] +use std::vec; + +use vec::Vec; + +#[cfg(any(feature = "ahash", not(feature = "std")))] use ahash::RandomState; -#[cfg(feature = "foldhash")] -use foldhash::fast::RandomState; +//#[cfg(feature = "foldhash")] +//use foldhash::fast::RandomState; #[cfg(all(not(feature = "ahash"), not(feature = "foldhash")))] use std::collections::hash_map::RandomState; @@ -22,7 +29,7 @@ use std::hash::{BuildHasher, Hash, Hasher}; use super::iter::{Iter, KeyIter, ValueIter}; use super::states::*; -use std::sync::Mutex; +use lock_api::{Mutex, RawMutex}; use crate::internals::lincowcell::LinCowCellCapable; @@ -61,10 +68,10 @@ unsafe impl LinCowCellCapable, CursorWrite> - for SuperBlock +impl + LinCowCellCapable, CursorWrite> for SuperBlock { - fn create_reader(&self) -> CursorRead { + fn create_reader(&self) -> CursorRead { CursorRead::new(self) } @@ -75,9 +82,9 @@ impl LinCowCellCapable, fn pre_commit( &mut self, mut new: CursorWrite, - prev: &CursorRead, - ) -> CursorRead { - let mut prev_last_seen = prev.last_seen.lock().unwrap(); + prev: &CursorRead, + ) -> CursorRead { + let mut prev_last_seen = prev.last_seen.lock(); debug_assert!((*prev_last_seen).is_empty()); let new_last_seen = &mut new.last_seen; @@ -138,25 +145,32 @@ impl SuperBlock { } #[derive(Debug)] -pub(crate) struct CursorRead +pub(crate) struct CursorRead where K: Hash + Eq + Clone + Debug, V: Clone, + M: RawMutex, { #[allow(dead_code)] txid: u64, length: usize, root: *mut Node, - last_seen: Mutex>>, + last_seen: Mutex>>, build_hasher: RandomState, } -unsafe impl Send - for CursorRead +unsafe impl< + K: Clone + Hash + Eq + Debug + Send + 'static, + V: Clone + Send + 'static, + M: RawMutex + Send + 'static, + > Send for CursorRead { } -unsafe impl - Sync for CursorRead +unsafe impl< + K: Clone + Hash + Eq + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + M: RawMutex + Send + Sync + 'static, + > Sync for CursorRead { } @@ -506,7 +520,7 @@ impl Drop for CursorWrite { } } -impl Drop for CursorRead { +impl Drop for CursorRead { fn drop(&mut self) { // If there is content in last_seen, a future generation wants us to remove it! let last_seen_guard = self @@ -531,7 +545,7 @@ impl Drop for SuperBlock { } } -impl CursorRead { +impl CursorRead { pub(crate) fn new(sblock: &SuperBlock) -> Self { // println!("starting rd txid -> {:?}", sblock.txid); let build_hasher = sblock.build_hasher.clone(); @@ -553,7 +567,9 @@ impl Drop for CursorRead { } */ -impl CursorReadOps for CursorRead { +impl CursorReadOps + for CursorRead +{ fn get_root_ref(&self) -> &Node { unsafe { &*(self.root) } } @@ -1096,6 +1112,7 @@ mod tests { use super::super::states::*; use super::SuperBlock; use super::{CursorRead, CursorReadOps}; + use crate::internals::hashmap::cursor::CursorWrite; use crate::internals::lincowcell::LinCowCellCapable; use rand::seq::SliceRandom; use std::mem; @@ -1145,7 +1162,10 @@ mod tests { // First create the node + cursor let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let prev_txid = wcurs.root_txid(); // Now insert - the txid should be different. @@ -1181,7 +1201,10 @@ mod tests { let node = create_leaf_node_full(10); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let prev_txid = wcurs.root_txid(); let r = wcurs.insert(1, 1, 1); @@ -1203,7 +1226,10 @@ mod tests { // to trigger a clone of leaf AND THEN to cause the split. let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(H_CAPACITY + 1) { // println!("ITER v {}", v); @@ -1231,7 +1257,10 @@ mod tests { let rnode = create_leaf_node_full(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // println!("{:?}", wcurs); @@ -1261,7 +1290,10 @@ mod tests { let rnode = create_leaf_node_full(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); let r = wcurs.insert(29, 29, 29); @@ -1289,7 +1321,10 @@ mod tests { let rnode = create_leaf_node(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Now insert to trigger the needed actions. @@ -1323,7 +1358,10 @@ mod tests { let rnode = create_leaf_node(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Now insert to trigger the needed actions. @@ -1354,7 +1392,10 @@ mod tests { let rnode = create_leaf_node(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); let r = wcurs.insert(11, 11, 11); @@ -1389,7 +1430,10 @@ mod tests { let rnode = create_leaf_node_full(20); let root = Node::new_branch(0, lnode, rnode); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); let r = wcurs.insert(19, 19, 19); @@ -1414,7 +1458,10 @@ mod tests { // so we do this to a reasonable number. let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(H_CAPACITY << 4) { // println!("ITER v {}", v); @@ -1435,7 +1482,10 @@ mod tests { // Insert descending let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in (1..(H_CAPACITY << 4)).rev() { // println!("ITER v {}", v); @@ -1460,7 +1510,10 @@ mod tests { let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in ins.into_iter() { let r = wcurs.insert(v as u64, v, v); @@ -1481,10 +1534,13 @@ mod tests { // Insert ascending - we want to ensure the tree is a few levels deep // so we do this to a reasonable number. let mut sb = unsafe { SuperBlock::new() }; - let mut rdr = sb.create_reader(); + let mut rdr: CursorRead = sb.create_reader(); for v in 1..(H_CAPACITY << 4) { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("ITER v {}", v); let r = wcurs.insert(v as u64, v, v); assert!(r.is_none()); @@ -1502,10 +1558,13 @@ mod tests { fn test_hashmap2_cursor_insert_stress_5() { // Insert descending let mut sb = unsafe { SuperBlock::new() }; - let mut rdr = sb.create_reader(); + let mut rdr: CursorRead = sb.create_reader(); for v in (1..(H_CAPACITY << 4)).rev() { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("ITER v {}", v); let r = wcurs.insert(v as u64, v, v); assert!(r.is_none()); @@ -1527,10 +1586,13 @@ mod tests { ins.shuffle(&mut rng); let mut sb = unsafe { SuperBlock::new() }; - let mut rdr = sb.create_reader(); + let mut rdr: CursorRead = sb.create_reader(); for v in ins.into_iter() { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let r = wcurs.insert(v as u64, v, v); assert!(r.is_none()); assert!(wcurs.verify()); @@ -1547,7 +1609,10 @@ mod tests { fn test_hashmap2_cursor_search_1() { let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(H_CAPACITY << 4) { let r = wcurs.insert(v as u64, v, v); @@ -1571,7 +1636,10 @@ mod tests { // Check the length is consistent on operations. let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(H_CAPACITY << 4) { let r = wcurs.insert(v as u64, v, v); @@ -1594,7 +1662,10 @@ mod tests { // let lnode = create_leaf_node_full(0); let sb = SuperBlock::new_test(1, lnode); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("{:?}", wcurs); for v in 0..H_CAPACITY { @@ -1617,7 +1688,10 @@ mod tests { fn test_hashmap2_cursor_remove_01_p1() { let node = create_leaf_node(0); let sb = SuperBlock::new_test(1, node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let _ = wcurs.remove(0, &0); // println!("{:?}", wcurs); @@ -1643,7 +1717,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(rnode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); println!("{:?}", wcurs); assert!(wcurs.verify()); wcurs.remove(20, &20); @@ -1669,7 +1746,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(znode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(10, &10); @@ -1695,7 +1775,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(rnode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Setup sibling leaf to already be cloned. @@ -1725,7 +1808,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(rnode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Setup leaf to already be cloned. @@ -1755,7 +1841,10 @@ mod tests { // Prevent the tree shrinking. unsafe { (*root).add_node(znode) }; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); // Setup leaf to already be cloned. @@ -1795,7 +1884,10 @@ mod tests { Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); @@ -1833,7 +1925,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(10, &10); @@ -1870,7 +1965,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(80, &80); @@ -1907,7 +2005,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(10, &10); @@ -1944,7 +2045,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); @@ -1985,7 +2089,10 @@ mod tests { let root: *mut Branch = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.path_clone(20); @@ -2026,7 +2133,10 @@ mod tests { Node::new_branch(0, lbranch as *mut _, rbranch as *mut _) as *mut Node; // let count = HBV_CAPACITY + 2; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.path_clone(0); @@ -2067,7 +2177,10 @@ mod tests { let root = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _) as *mut Node; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); for i in 0..HBV_CAPACITY { @@ -2091,7 +2204,10 @@ mod tests { let rnode = create_leaf_node(20); let root = Node::new_branch(0, lnode, rnode) as *mut Node; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(20, &20); @@ -2109,7 +2225,10 @@ mod tests { let rnode = create_leaf_node_full(20) as *mut Node; let root = Node::new_branch(0, lnode, rnode) as *mut Node; let sb = SuperBlock::new_test(1, root as *mut Node); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wcurs.verify()); wcurs.remove(10, &10); @@ -2120,14 +2239,17 @@ mod tests { assert_released(); } - fn tree_create_rand() -> (SuperBlock, CursorRead) { + fn tree_create_rand() -> (SuperBlock, CursorRead) { let mut rng = rand::rng(); let mut ins: Vec = (1..(H_CAPACITY << 4)).collect(); ins.shuffle(&mut rng); let mut sb = unsafe { SuperBlock::new() }; let rdr = sb.create_reader(); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in ins.into_iter() { let r = wcurs.insert(v as u64, v, v); @@ -2144,7 +2266,10 @@ mod tests { // Insert ascending - we want to ensure the tree is a few levels deep // so we do this to a reasonable number. let (mut sb, rdr) = tree_create_rand(); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in 1..(H_CAPACITY << 4) { // println!("-- ITER v {}", v); @@ -2165,7 +2290,10 @@ mod tests { fn test_hashmap2_cursor_remove_stress_2() { // Insert descending let (mut sb, rdr) = tree_create_rand(); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in (1..(H_CAPACITY << 4)).rev() { // println!("ITER v {}", v); @@ -2191,7 +2319,10 @@ mod tests { ins.shuffle(&mut rng); let (mut sb, rdr) = tree_create_rand(); - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); for v in ins.into_iter() { let r = wcurs.remove(v as u64, &v); @@ -2216,7 +2347,10 @@ mod tests { let (mut sb, mut rdr) = tree_create_rand(); for v in 1..(H_CAPACITY << 4) { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("ITER v {}", v); let r = wcurs.remove(v as u64, &v); assert!(r == Some(v)); @@ -2236,7 +2370,10 @@ mod tests { let (mut sb, mut rdr) = tree_create_rand(); for v in (1..(H_CAPACITY << 4)).rev() { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); // println!("ITER v {}", v); let r = wcurs.remove(v as u64, &v); assert!(r == Some(v)); @@ -2261,7 +2398,10 @@ mod tests { let (mut sb, mut rdr) = tree_create_rand(); for v in ins.into_iter() { - let mut wcurs = sb.create_writer(); + let mut wcurs = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); let r = wcurs.remove(v as u64, &v); assert!(r == Some(v)); assert!(wcurs.verify()); diff --git a/src/internals/hashmap/iter.rs b/src/internals/hashmap/iter.rs index d7e371c..d506432 100644 --- a/src/internals/hashmap/iter.rs +++ b/src/internals/hashmap/iter.rs @@ -1,11 +1,15 @@ //! Iterators for the map. +#[cfg(not(feature = "std"))] +use alloc::collections::VecDeque; +#[cfg(feature = "std")] +use std::collections::VecDeque; + // Iterators for the bptree use super::node::{Branch, Leaf, Meta, Node}; -use std::collections::VecDeque; -use std::fmt::Debug; -use std::hash::Hash; -use std::marker::PhantomData; +use core::fmt::Debug; +use core::hash::Hash; +use core::marker::PhantomData; pub(crate) struct LeafIter<'a, K, V> where diff --git a/src/internals/hashmap/node.rs b/src/internals/hashmap/node.rs index 8f351fa..67e5574 100644 --- a/src/internals/hashmap/node.rs +++ b/src/internals/hashmap/node.rs @@ -14,7 +14,15 @@ use std::ptr; use smallvec::SmallVec; #[cfg(feature = "simd_support")] -use core_simd::u64x8; +use std::simd::u64x8; + +#[cfg(not(feature = "std"))] +use alloc::{boxed, vec}; +#[cfg(feature = "std")] +use std::{boxed, vec}; + +use boxed::Box; +use vec::Vec; #[cfg(test)] use std::collections::BTreeSet; @@ -96,6 +104,7 @@ pub(crate) fn assert_released() { } } +#[derive(Clone, Copy, Debug)] #[repr(C)] pub(crate) struct Meta(u64); @@ -652,7 +661,7 @@ impl Leaf { for idx in 0..self.slots() { unsafe { let lvalue: Bucket = (*self.values[idx].as_ptr()).clone(); - (&mut (*x)).values[idx].as_mut_ptr().write(lvalue); + (&mut (*x)).values[idx].write(lvalue); } } @@ -903,11 +912,11 @@ impl Leaf { // Eq not ok as we have buckets. if lk >= rk { // println!("{:?}", self); - if cfg!(test) { + cfg_if::cfg_if! { if #[cfg(test)] { return false; } else { debug_assert!(false); - } + }} } lk = rk; } @@ -1938,6 +1947,7 @@ impl Branch { // Check everything above slots is u64::max for work_idx in unsafe { self.ctrl.a.0.slots() }..H_CAPACITY { if unsafe { self.ctrl.a.1[work_idx] } != u64::MAX { + #[cfg(feature = "std")] eprintln!("FAILED ARRAY -> {:?}", unsafe { self.ctrl.a.1 }); debug_assert!(false); } diff --git a/src/internals/hashmap/simd.rs b/src/internals/hashmap/simd.rs index 6d3b27b..916f97a 100644 --- a/src/internals/hashmap/simd.rs +++ b/src/internals/hashmap/simd.rs @@ -1,8 +1,8 @@ -#[cfg(feature = "simd_support")] -use core_simd::u64x8; use std::borrow::Borrow; use std::fmt::Debug; use std::hash::Hash; +#[cfg(feature = "simd_support")] +use std::simd::u64x8; use super::node::{Branch, Leaf}; diff --git a/src/internals/hashtrie/cursor.rs b/src/internals/hashtrie/cursor.rs index 797ad99..00f8e38 100644 --- a/src/internals/hashtrie/cursor.rs +++ b/src/internals/hashtrie/cursor.rs @@ -4,30 +4,39 @@ //! Additionally, the cursor also is responsible for general movement //! throughout the structure and how to handle that effectively +#[cfg(not(feature = "std"))] +use alloc::{boxed, collections, vec}; +#[cfg(feature = "std")] +use std::{boxed, collections, vec}; + +use boxed::Box; +use vec::Vec; + use crate::internals::lincowcell::LinCowCellCapable; +use collections::{BTreeSet, VecDeque}; +use lock_api::{Mutex, RawMutex}; use std::borrow::Borrow; use std::cmp::Ordering; -use std::collections::{BTreeSet, VecDeque}; -use std::fmt::{self, Debug}; +use std::fmt; +use std::fmt::Debug; use std::marker::PhantomData; use std::ptr; -use std::sync::Mutex; use smallvec::SmallVec; use super::iter::*; -#[cfg(feature = "ahash")] +#[cfg(any(feature = "ahash", not(feature = "std")))] use ahash::RandomState; -#[cfg(feature = "foldhash")] -use foldhash::fast::RandomState; +//#[cfg(feature = "foldhash")] +//use foldhash::fast::RandomState; #[cfg(all(not(feature = "ahash"), not(feature = "foldhash")))] use std::collections::hash_map::RandomState; -use std::hash::{BuildHasher, Hash, Hasher}; +use core::hash::{BuildHasher, Hash, Hasher}; // This defines the max height of our tree. Gives 16777216.0 entries // This only consumes 16KB if fully populated @@ -77,17 +86,17 @@ macro_rules! hash_key { } #[cfg(all(test, not(miri)))] -thread_local!(static ALLOC_LIST: Mutex> = const { Mutex::new(BTreeSet::new()) }); +thread_local!(static ALLOC_LIST: Mutex> = const { Mutex::new(BTreeSet::new()) }); #[cfg(all(test, not(miri)))] -thread_local!(static WRITE_LIST: Mutex> = const { Mutex::new(BTreeSet::new()) }); +thread_local!(static WRITE_LIST: Mutex> = const { Mutex::new(BTreeSet::new()) }); #[cfg(test)] fn assert_released() { #[cfg(not(miri))] { let is_empty = ALLOC_LIST.with(|llist| { - let x = llist.lock().unwrap(); + let x = llist.lock(); println!("Remaining -> {:?}", x); x.is_empty() }); @@ -172,14 +181,14 @@ impl Ptr { #[inline(always)] fn mark_dirty(&mut self) { #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] - WRITE_LIST.with(|llist| assert!(llist.lock().unwrap().insert(self.untagged()))); + WRITE_LIST.with(|llist| assert!(llist.lock().insert(self.untagged()))); self.p = self.p.map_addr(|a| a | FLAG_DIRTY) } #[inline(always)] fn mark_clean(&mut self) { #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] - WRITE_LIST.with(|llist| assert!(llist.lock().unwrap().remove(&(self.untagged())))); + WRITE_LIST.with(|llist| assert!(llist.lock().remove(&(self.untagged())))); self.p = self.p.map_addr(|a| a & MARK_CLEAN) } @@ -187,7 +196,7 @@ impl Ptr { pub(crate) fn as_bucket(&self) -> &Bucket { debug_assert!(self.is_bucket()); #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] - ALLOC_LIST.with(|llist| assert!(llist.lock().unwrap().contains(&self.untagged()))); + ALLOC_LIST.with(|llist| assert!(llist.lock().contains(&self.untagged()))); unsafe { &*(self.p.map_addr(|a| a & UNTAG) as *const Bucket) } } @@ -195,7 +204,7 @@ impl Ptr { fn as_bucket_raw(&self) -> *mut Bucket { debug_assert!(self.is_bucket()); #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] - ALLOC_LIST.with(|llist| assert!(llist.lock().unwrap().contains(&self.untagged()))); + ALLOC_LIST.with(|llist| assert!(llist.lock().contains(&self.untagged()))); self.p.map_addr(|a| a & UNTAG) as *mut Bucket } @@ -208,7 +217,7 @@ impl Ptr { debug_assert!(self.is_dirty()); #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] WRITE_LIST.with(|llist| { - let wlist_guard = llist.lock().unwrap(); + let wlist_guard = llist.lock(); assert!(wlist_guard.contains(&self.untagged())) }); unsafe { &mut *(self.p.map_addr(|a| a & UNTAG) as *mut Bucket) } @@ -218,7 +227,7 @@ impl Ptr { pub(crate) fn as_branch(&self) -> &Branch { debug_assert!(self.is_branch()); #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] - ALLOC_LIST.with(|llist| assert!(llist.lock().unwrap().contains(&self.untagged()))); + ALLOC_LIST.with(|llist| assert!(llist.lock().contains(&self.untagged()))); unsafe { &*(self.p.map_addr(|a| a & UNTAG) as *const Branch) } } @@ -226,7 +235,7 @@ impl Ptr { fn as_branch_raw(&self) -> *mut Branch { debug_assert!(self.is_branch()); #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] - ALLOC_LIST.with(|llist| assert!(llist.lock().unwrap().contains(&self.untagged()))); + ALLOC_LIST.with(|llist| assert!(llist.lock().contains(&self.untagged()))); self.p.map_addr(|a| a & UNTAG) as *mut Branch } @@ -239,7 +248,7 @@ impl Ptr { debug_assert!(self.is_dirty()); #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] WRITE_LIST.with(|llist| { - let wlist_guard = llist.lock().unwrap(); + let wlist_guard = llist.lock(); assert!(wlist_guard.contains(&self.untagged())) }); unsafe { &mut *(self.p.map_addr(|a| a & UNTAG) as *mut Branch) } @@ -259,7 +268,7 @@ impl Ptr { fn free(&self) { // We MUST have allocated this, else it's a double free #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] - ALLOC_LIST.with(|llist| assert!(llist.lock().unwrap().contains(&self.untagged()))); + ALLOC_LIST.with(|llist| assert!(llist.lock().contains(&self.untagged()))); // It's getting freeeeeedddd unsafe { @@ -272,11 +281,11 @@ impl Ptr { #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] if self.is_dirty() { - WRITE_LIST.with(|llist| assert!(llist.lock().unwrap().remove(&(self.untagged())))) + WRITE_LIST.with(|llist| assert!(llist.lock().remove(&(self.untagged())))) }; #[cfg(all(test, not(miri), not(feature = "dhat-heap")))] - ALLOC_LIST.with(|llist| assert!(llist.lock().unwrap().remove(&self.untagged()))); + ALLOC_LIST.with(|llist| assert!(llist.lock().remove(&self.untagged()))); } } @@ -288,7 +297,7 @@ impl From>> for Ptr { p: rptr.map_addr(|a| a | FLAG_BRANCH) as *mut i32, }; #[cfg(all(test, not(miri)))] - ALLOC_LIST.with(|llist| llist.lock().unwrap().insert(r.untagged())); + ALLOC_LIST.with(|llist| llist.lock().insert(r.untagged())); r } } @@ -301,7 +310,7 @@ impl From>> for Ptr { p: rptr.map_addr(|a| a | FLAG_BUCKET) as *mut i32, }; #[cfg(all(test, not(miri)))] - ALLOC_LIST.with(|llist| llist.lock().unwrap().insert(r.untagged())); + ALLOC_LIST.with(|llist| llist.lock().insert(r.untagged())); r } } @@ -436,10 +445,10 @@ impl SuperBlock { } } -impl LinCowCellCapable, CursorWrite> - for SuperBlock +impl + LinCowCellCapable, CursorWrite> for SuperBlock { - fn create_reader(&self) -> CursorRead { + fn create_reader(&self) -> CursorRead { CursorRead::new(self) } @@ -450,9 +459,9 @@ impl LinCowCellCapable, fn pre_commit( &mut self, mut new: CursorWrite, - prev: &CursorRead, - ) -> CursorRead { - let mut prev_last_seen = prev.last_seen.lock().unwrap(); + prev: &CursorRead, + ) -> CursorRead { + let mut prev_last_seen = prev.last_seen.lock(); debug_assert!((*prev_last_seen).is_empty()); let new_last_seen = &mut new.last_seen; @@ -674,11 +683,11 @@ impl CursorWrite { } } - if cfg!(debug_assertions) { + cfg_if::cfg_if! {if #[cfg(debug_assertions)] { for n in tgt_ptr.as_branch::().nodes.iter() { assert!(n.is_null() || !n.is_dirty()); } - } + }} } } @@ -854,7 +863,7 @@ impl CursorWrite { let tgt_bkt_mut = tgt_ptr.as_bucket_mut::(); let Datum { v, .. } = tgt_bkt_mut.remove(0); // Keep any pointer that ISN'T the one we are oob freeing. - self.first_seen.retain(|e| *e != tgt_ptr); + self.first_seen.retain(|e: &Ptr| *e != tgt_ptr); tgt_ptr.free::(); v } else { @@ -1046,21 +1055,22 @@ impl CursorReadOps for CursorWrite } #[derive(Debug)] -pub(crate) struct CursorRead +pub(crate) struct CursorRead where K: Hash + Eq + Clone + Debug, V: Clone, + R: RawMutex, { txid: u64, length: usize, root: Ptr, - last_seen: Mutex>, + last_seen: Mutex>, build_hasher: RandomState, k: PhantomData, v: PhantomData, } -impl CursorRead { +impl CursorRead { pub(crate) fn new(sblock: &SuperBlock) -> Self { let build_hasher = sblock.build_hasher.clone(); CursorRead { @@ -1075,7 +1085,7 @@ impl CursorRead { } } -impl Drop for CursorRead { +impl Drop for CursorRead { fn drop(&mut self) { let last_seen_guard = self .last_seen @@ -1086,7 +1096,9 @@ impl Drop for CursorRead { } } -impl CursorReadOps for CursorRead { +impl CursorReadOps + for CursorRead +{ fn get_root_ptr(&self) -> Ptr { self.root } @@ -1120,7 +1132,10 @@ mod tests { fn test_hashtrie_cursor_basic() { let sb: SuperBlock = unsafe { SuperBlock::new() }; - let mut wr = sb.create_writer(); + let mut wr = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wr.len() == 0); assert!(wr.search(0, &0).is_none()); @@ -1141,8 +1156,11 @@ mod tests { #[test] fn test_hashtrie_cursor_insert_max_depth() { let mut sb: SuperBlock = unsafe { SuperBlock::new() }; - let rdr = sb.create_reader(); - let mut wr = sb.create_writer(); + let rdr: CursorRead = sb.create_reader(); + let mut wr = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wr.len() == 0); for i in 0..(ABS_MAX_HEIGHT * 2) { @@ -1177,8 +1195,11 @@ mod tests { #[test] fn test_hashtrie_cursor_insert_broad() { let mut sb: SuperBlock = unsafe { SuperBlock::new() }; - let rdr = sb.create_reader(); - let mut wr = sb.create_writer(); + let rdr: CursorRead = sb.create_reader(); + let mut wr = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wr.len() == 0); for i in 0..(ABS_MAX_HEIGHT * ABS_MAX_HEIGHT) { @@ -1212,20 +1233,23 @@ mod tests { #[test] fn test_hashtrie_cursor_insert_multiple_txns() { let mut sb: SuperBlock = unsafe { SuperBlock::new() }; - let mut rdr = sb.create_reader(); + let mut rdr: CursorRead = sb.create_reader(); // Do thing assert!(rdr.len() == 0); for i in 0..(ABS_MAX_HEIGHT * ABS_MAX_HEIGHT) { - let mut wr = sb.create_writer(); + let mut wr = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wr.insert(i, i, i).is_none()); wr.verify(); rdr = sb.pre_commit(wr, &rdr); } { - let rdr2 = sb.create_reader(); + let rdr2: CursorRead = sb.create_reader(); assert!(rdr2.len() == (ABS_MAX_HEIGHT * ABS_MAX_HEIGHT) as usize); for i in 0..(ABS_MAX_HEIGHT * ABS_MAX_HEIGHT) { assert!(rdr2.search(i, &i).is_some()); @@ -1233,7 +1257,10 @@ mod tests { } for i in 0..(ABS_MAX_HEIGHT * ABS_MAX_HEIGHT) { - let mut wr = sb.create_writer(); + let mut wr = as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&sb); assert!(wr.remove(i, &i).is_some()); wr.verify(); rdr = sb.pre_commit(wr, &rdr); diff --git a/src/internals/hashtrie/iter.rs b/src/internals/hashtrie/iter.rs index a7e33c6..13b1347 100644 --- a/src/internals/hashtrie/iter.rs +++ b/src/internals/hashtrie/iter.rs @@ -1,10 +1,14 @@ //! Iterators for the hashtrie -use super::cursor::{Ptr, HT_CAPACITY, MAX_HEIGHT}; +#[cfg(not(feature = "std"))] +use alloc::collections::VecDeque; +#[cfg(feature = "std")] use std::collections::VecDeque; -use std::fmt::Debug; -use std::hash::Hash; -use std::marker::PhantomData; + +use super::cursor::{Ptr, HT_CAPACITY, MAX_HEIGHT}; +use core::fmt::Debug; +use core::hash::Hash; +use core::marker::PhantomData; /// Iterator over references to Key Value pairs stored in the map. pub struct Iter<'a, K, V> diff --git a/src/internals/lincowcell/mod.rs b/src/internals/lincowcell/mod.rs index c6a3b65..5a3459c 100644 --- a/src/internals/lincowcell/mod.rs +++ b/src/internals/lincowcell/mod.rs @@ -55,11 +55,22 @@ * */ -use std::marker::PhantomData; -use std::ops::Deref; -use std::ops::DerefMut; +#[cfg(not(feature = "std"))] +use alloc::sync::Arc; +#[cfg(feature = "std")] use std::sync::Arc; -use std::sync::{Mutex, MutexGuard}; + +use core::fmt::Debug; +use core::marker::PhantomData; +use core::ops::Deref; +use core::ops::DerefMut; +use lock_api::RawMutex; +use lock_api::{Mutex, MutexGuard}; + + +/// Linear Copy-on-write cell with default Mutex type provided +#[cfg(feature = "std")] +pub type LinCowCell = LinCowCellRaw; /// Do not implement this. You don't need this negativity in your life. pub trait LinCowCellCapable { @@ -75,40 +86,80 @@ pub trait LinCowCellCapable { fn pre_commit(&mut self, new: U, prev: &R) -> R; } -#[derive(Debug)] /// A concurrently readable cell with linearised drop behaviour. -pub struct LinCowCell { +pub struct LinCowCellRaw { updater: PhantomData, - write: Mutex, - active: Mutex>>, + write: Mutex, + active: Mutex>>, } -#[derive(Debug)] -/// A write txn over a linear cell. -pub struct LinCowCellWriteTxn<'a, T, R, U> { - // This way we know who to contact for updating our data .... - caller: &'a LinCowCell, - guard: MutexGuard<'a, T>, - work: U, +impl Debug for LinCowCellRaw { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut f = f.debug_struct("LinCowCell"); + match self.write.try_lock() { + Some(guard) => { + f.field("write", &&*guard); + } + None => { + struct LockedPlaceholder; + impl core::fmt::Debug for LockedPlaceholder { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("") + } + } + + f.field("write", &LockedPlaceholder); + } + } + match self.active.try_lock() { + Some(guard) => { + f.field("active", &&*guard); + } + None => { + struct LockedPlaceholder; + impl core::fmt::Debug for LockedPlaceholder { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("") + } + } + + f.field("active", &LockedPlaceholder); + } + } + + f.finish() + } } -#[derive(Debug)] -struct LinCowCellInner { +struct LinCowCellInner { // This gives the chain effect. - pin: Mutex>>>, + pin: Mutex>>>, data: R, } -#[derive(Debug)] -/// A read txn over a linear cell. -pub struct LinCowCellReadTxn<'a, T, R, U> { - // We must outlive the root - _caller: &'a LinCowCell, - // We pin the current version. - work: Arc>, +impl Debug for LinCowCellInner { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut f = f.debug_struct("LinCowCellInner"); + match self.pin.try_lock() { + Some(guard) => { + f.field("pin", &&*guard); + } + None => { + struct LockedPlaceholder; + impl core::fmt::Debug for LockedPlaceholder { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("") + } + } + + f.field("pin", &LockedPlaceholder); + } + } + f.field("data", &self.data).finish() + } } -impl LinCowCellInner { +impl LinCowCellInner { pub fn new(data: R) -> Self { LinCowCellInner { pin: Mutex::new(None), @@ -117,10 +168,10 @@ impl LinCowCellInner { } } -impl Drop for LinCowCellInner { +impl Drop for LinCowCellInner { fn drop(&mut self) { // Ensure the default drop won't recursively drop the chain - let mut current = self.pin.lock().unwrap().take(); + let mut current: Option>> = self.pin.lock().deref_mut().take(); // Drop the chain iteratively to avoid stack overflow while let Some(arc) = current { @@ -128,7 +179,7 @@ impl Drop for LinCowCellInner { match Arc::try_unwrap(arc) { Ok(inner) => { // Continue with the next link. - current = inner.pin.lock().unwrap().take(); + current = inner.pin.lock().deref_mut().take(); } Err(_) => { // Another reference exists, so we can safely let it drop normally without recursion @@ -139,14 +190,49 @@ impl Drop for LinCowCellInner { } } -impl LinCowCell +/// A read txn over a linear cell. +pub struct LinCowCellReadTxn<'a, T, R, U, M: RawMutex> { + // We must outlive the root + _caller: &'a LinCowCellRaw, + // We pin the current version. + work: Arc>, +} + +impl Debug for LinCowCellReadTxn<'_, T, R, U, M> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("LinCowCellReadTxn") + .field("work", &self.work) + .finish_non_exhaustive() + } +} + +/// A write txn over a linear cell. +pub struct LinCowCellWriteTxn<'a, T, R, U, M: RawMutex> { + // This way we know who to contact for updating our data .... + caller: &'a LinCowCellRaw, + guard: MutexGuard<'a, M, T>, + work: U, +} + +impl Debug for LinCowCellWriteTxn<'_, T, R, U, M> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("LinCowCellWriteTxn") + .field("caller", &self.caller) + .field("guard", &self.guard) + .field("work", &self.work) + .finish() + } +} + +impl LinCowCellRaw where T: LinCowCellCapable, + M: RawMutex, { /// Create a new linear 🐄 cell. pub fn new(data: T) -> Self { let r = data.create_reader(); - LinCowCell { + LinCowCellRaw { updater: PhantomData, write: Mutex::new(data), active: Mutex::new(Arc::new(LinCowCellInner::new(r))), @@ -154,8 +240,8 @@ where } /// Begin a read txn - pub fn read(&self) -> LinCowCellReadTxn<'_, T, R, U> { - let rwguard = self.active.lock().unwrap(); + pub fn read(&self) -> LinCowCellReadTxn<'_, T, R, U, M> { + let rwguard = self.active.lock(); LinCowCellReadTxn { _caller: self, // inc the arc. @@ -164,9 +250,9 @@ where } /// Begin a write txn - pub fn write(&self) -> LinCowCellWriteTxn<'_, T, R, U> { + pub fn write(&self) -> LinCowCellWriteTxn<'_, T, R, U, M> { /* Take the exclusive write lock first */ - let write_guard = self.write.lock().unwrap(); + let write_guard = self.write.lock(); /* Now take a ro-txn to get the data copied */ // let active_guard = self.active.lock(); /* This copies the data */ @@ -180,8 +266,8 @@ where } /// Attempt a write txn - pub fn try_write(&self) -> Option> { - self.write.try_lock().ok().map(|write_guard| { + pub fn try_write(&self) -> Option> { + self.write.try_lock().map(|write_guard| { /* This copies the data */ let work: U = (*write_guard).create_writer(); /* Now build the write struct */ @@ -193,7 +279,7 @@ where }) } - fn commit(&self, write: LinCowCellWriteTxn) { + fn commit(&self, write: LinCowCellWriteTxn) { // Destructure our writer. let LinCowCellWriteTxn { // This is self. @@ -203,7 +289,7 @@ where } = write; // Get the previous generation. - let mut rwguard = self.active.lock().unwrap(); + let mut rwguard = self.active.lock(); // Start to setup for the commit. let newdata = guard.pre_commit(work, &rwguard.data); @@ -211,7 +297,7 @@ where let new_inner = Arc::new(LinCowCellInner::new(newdata)); { // This modifies the next pointer of the existing read txns - let mut rwguard_inner = rwguard.pin.lock().unwrap(); + let mut rwguard_inner = rwguard.pin.lock(); // Create the arc pointer to our new data // add it to the last value *rwguard_inner = Some(new_inner.clone()); @@ -221,7 +307,7 @@ where } } -impl Deref for LinCowCellReadTxn<'_, T, R, U> { +impl Deref for LinCowCellReadTxn<'_, T, R, U, M> { type Target = R; #[inline] @@ -230,16 +316,17 @@ impl Deref for LinCowCellReadTxn<'_, T, R, U> { } } -impl AsRef for LinCowCellReadTxn<'_, T, R, U> { +impl AsRef for LinCowCellReadTxn<'_, T, R, U, M> { #[inline] fn as_ref(&self) -> &R { &self.work.data } } -impl LinCowCellWriteTxn<'_, T, R, U> +impl LinCowCellWriteTxn<'_, T, R, U, M> where T: LinCowCellCapable, + M: RawMutex, { #[inline] /// Get the mutable inner of this type @@ -254,7 +341,7 @@ where } } -impl Deref for LinCowCellWriteTxn<'_, T, R, U> { +impl Deref for LinCowCellWriteTxn<'_, T, R, U, M> { type Target = U; #[inline] @@ -263,21 +350,21 @@ impl Deref for LinCowCellWriteTxn<'_, T, R, U> { } } -impl DerefMut for LinCowCellWriteTxn<'_, T, R, U> { +impl DerefMut for LinCowCellWriteTxn<'_, T, R, U, M> { #[inline] fn deref_mut(&mut self) -> &mut U { &mut self.work } } -impl AsRef for LinCowCellWriteTxn<'_, T, R, U> { +impl AsRef for LinCowCellWriteTxn<'_, T, R, U, M> { #[inline] fn as_ref(&self) -> &U { &self.work } } -impl AsMut for LinCowCellWriteTxn<'_, T, R, U> { +impl AsMut for LinCowCellWriteTxn<'_, T, R, U, M> { #[inline] fn as_mut(&mut self) -> &mut U { &mut self.work @@ -331,7 +418,7 @@ mod tests { #[test] fn test_simple_create() { let data = TestData { x: 0 }; - let cc = LinCowCell::new(data); + let cc: LinCowCell = LinCowCell::new(data); let cc_rotxn_a = cc.read(); println!("cc_rotxn_a -> {:?}", cc_rotxn_a); @@ -357,7 +444,13 @@ mod tests { assert_eq!(cc_rotxn_a.work.data.x, 0); { /* Take a new write txn */ - let mut cc_wrtxn = cc.write(); + let mut cc_wrtxn: crate::internals::lincowcell::LinCowCellWriteTxn< + '_, + TestData, + TestDataReadTxn, + TestDataWriteTxn, + parking_lot::RawMutex, + > = cc.write(); println!("cc_wrtxn -> {:?}", cc_wrtxn); assert_eq!(cc_wrtxn.work.x, 0); assert_eq!(cc_wrtxn.as_ref().x, 0); @@ -547,7 +640,7 @@ mod tests { #[cfg_attr(miri, ignore)] fn test_long_chain_drop_no_stack_overflow() { let data = TestData { x: 0 }; - let cc = LinCowCell::new(data); + let cc: LinCowCell = LinCowCell::new(data); // Simulate a read txn that is not dropped. let initial_read = cc.read(); @@ -635,7 +728,11 @@ mod tests_linear { GC_COUNT.store(0, Ordering::Release); assert!(GC_COUNT.load(Ordering::Acquire) == 0); let data = TestGcWrapper { data: 0 }; - let cc = LinCowCell::new(data); + let cc: LinCowCell< + TestGcWrapper, + TestGcWrapperReadTxn, + TestGcWrapperWriteTxn, + > = LinCowCell::new(data); // Open a read A. let cc_rotxn_a = cc.read(); diff --git a/src/internals/lincowcell_async/mod.rs b/src/internals/lincowcell_async/mod.rs index 267ee04..7d79a45 100644 --- a/src/internals/lincowcell_async/mod.rs +++ b/src/internals/lincowcell_async/mod.rs @@ -65,19 +65,21 @@ use crate::internals::lincowcell::LinCowCellCapable; #[derive(Debug)] /// A concurrently readable cell with linearised drop behaviour. -pub struct LinCowCell { +pub struct LinCowCellRaw { updater: PhantomData, write: Mutex, active: SyncMutex>>, + _phantom: PhantomData, } #[derive(Debug)] /// A write txn over a linear cell. -pub struct LinCowCellWriteTxn<'a, T, R, U> { +pub struct LinCowCellWriteTxn<'a, T, R, U, M> { // This way we know who to contact for updating our data .... - caller: &'a LinCowCell, + caller: &'a LinCowCellRaw, guard: MutexGuard<'a, T>, work: U, + _phantom: PhantomData, } #[derive(Debug)] @@ -89,9 +91,9 @@ struct LinCowCellInner { #[derive(Debug)] /// A read txn over a linear cell. -pub struct LinCowCellReadTxn<'a, T, R, U> { +pub struct LinCowCellReadTxn<'a, T, R, U, M> { // We must outlive the root - _caller: &'a LinCowCell, + _caller: &'a LinCowCellRaw, // We pin the current version. work: Arc>, } @@ -127,22 +129,23 @@ impl Drop for LinCowCellInner { } } -impl LinCowCell +impl LinCowCellRaw where T: LinCowCellCapable, { /// Create a new linear 🐄 cell. pub fn new(data: T) -> Self { let r = data.create_reader(); - LinCowCell { + LinCowCellRaw { updater: PhantomData, write: Mutex::new(data), active: SyncMutex::new(Arc::new(LinCowCellInner::new(r))), + _phantom: PhantomData, } } /// Begin a read txn - pub fn read(&self) -> LinCowCellReadTxn<'_, T, R, U> { + pub fn read(&self) -> LinCowCellReadTxn<'_, T, R, U, M> { let rwguard = self.active.lock().unwrap(); LinCowCellReadTxn { _caller: self, @@ -152,7 +155,7 @@ where } /// Begin a write txn - pub async fn write<'x>(&'x self) -> LinCowCellWriteTxn<'x, T, R, U> { + pub async fn write<'x>(&'x self) -> LinCowCellWriteTxn<'x, T, R, U, M> { /* Take the exclusive write lock first */ let write_guard = self.write.lock().await; /* Now take a ro-txn to get the data copied */ @@ -164,11 +167,12 @@ where caller: self, guard: write_guard, work, + _phantom: PhantomData, } } /// Attempt a write txn - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.write .try_lock() .map(|write_guard| { @@ -179,18 +183,20 @@ where caller: self, guard: write_guard, work, + _phantom: PhantomData, } }) .ok() } - fn commit(&self, write: LinCowCellWriteTxn<'_, T, R, U>) { + fn commit(&self, write: LinCowCellWriteTxn<'_, T, R, U, M>) { // Destructure our writer. let LinCowCellWriteTxn { // This is self. caller: _caller, mut guard, work, + _phantom: PhantomData, } = write; // Get the previous generation. @@ -212,7 +218,7 @@ where } } -impl Deref for LinCowCellReadTxn<'_, T, R, U> { +impl Deref for LinCowCellReadTxn<'_, T, R, U, M> { type Target = R; #[inline] @@ -221,14 +227,14 @@ impl Deref for LinCowCellReadTxn<'_, T, R, U> { } } -impl AsRef for LinCowCellReadTxn<'_, T, R, U> { +impl AsRef for LinCowCellReadTxn<'_, T, R, U, M> { #[inline] fn as_ref(&self) -> &R { &self.work.data } } -impl LinCowCellWriteTxn<'_, T, R, U> +impl LinCowCellWriteTxn<'_, T, R, U, M> where T: LinCowCellCapable, { @@ -245,7 +251,7 @@ where } } -impl Deref for LinCowCellWriteTxn<'_, T, R, U> { +impl Deref for LinCowCellWriteTxn<'_, T, R, U, M> { type Target = U; #[inline] @@ -254,21 +260,21 @@ impl Deref for LinCowCellWriteTxn<'_, T, R, U> { } } -impl DerefMut for LinCowCellWriteTxn<'_, T, R, U> { +impl DerefMut for LinCowCellWriteTxn<'_, T, R, U, M> { #[inline] fn deref_mut(&mut self) -> &mut U { &mut self.work } } -impl AsRef for LinCowCellWriteTxn<'_, T, R, U> { +impl AsRef for LinCowCellWriteTxn<'_, T, R, U, M> { #[inline] fn as_ref(&self) -> &U { &self.work } } -impl AsMut for LinCowCellWriteTxn<'_, T, R, U> { +impl AsMut for LinCowCellWriteTxn<'_, T, R, U, M> { #[inline] fn as_mut(&mut self) -> &mut U { &mut self.work @@ -277,7 +283,7 @@ impl AsMut for LinCowCellWriteTxn<'_, T, R, U> { #[cfg(test)] mod tests { - use super::LinCowCell; + use super::LinCowCellRaw; use super::LinCowCellCapable; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -321,7 +327,7 @@ mod tests { #[tokio::test] async fn test_simple_create() { let data = TestData { x: 0 }; - let cc = LinCowCell::new(data); + let cc: LinCowCellRaw = LinCowCellRaw::new(data); let cc_rotxn_a = cc.read(); println!("cc_rotxn_a -> {:?}", cc_rotxn_a); @@ -372,7 +378,7 @@ mod tests { // == mt tests == - async fn mt_writer(cc: Arc>) { + async fn mt_writer(cc: Arc>) { let mut last_value: i64 = 0; while last_value < 500 { let mut cc_wrtxn = cc.write().await; @@ -386,7 +392,7 @@ mod tests { } } - fn rt_writer(cc: Arc>) { + fn rt_writer(cc: Arc>) { let mut last_value: i64 = 0; while last_value < 500 { let cc_rotxn = cc.read(); @@ -405,7 +411,8 @@ mod tests { let start = Instant::now(); // Create the new cowcell. let data = TestData { x: 0 }; - let cc = Arc::new(LinCowCell::new(data)); + let cc: Arc> = + Arc::new(LinCowCellRaw::new(data)); let _ = tokio::join!( tokio::task::spawn_blocking({ @@ -487,7 +494,7 @@ mod tests { async fn test_gc_operation_thread( cc: Arc< - LinCowCell, TestGcWrapperReadTxn, TestGcWrapperWriteTxn>, + LinCowCellRaw, TestGcWrapperReadTxn, TestGcWrapperWriteTxn>, >, ) { while GC_COUNT.load(Ordering::Acquire) < 50 { @@ -508,7 +515,9 @@ mod tests { async fn test_gc_operation() { GC_COUNT.store(0, Ordering::Release); let data = TestGcWrapper { data: 0 }; - let cc = Arc::new(LinCowCell::new(data)); + let cc: Arc< + LinCowCellRaw, TestGcWrapperReadTxn, TestGcWrapperWriteTxn>, + > = Arc::new(LinCowCellRaw::new(data)); let _ = tokio::join!( tokio::task::spawn(test_gc_operation_thread(cc.clone())), @@ -524,7 +533,7 @@ mod tests { #[cfg_attr(miri, ignore)] async fn test_long_chain_drop_no_stack_overflow() { let data = TestData { x: 0 }; - let cc = LinCowCell::new(data); + let cc: LinCowCellRaw = LinCowCellRaw::new(data); // Simulate a read txn that is not dropped. let initial_read = cc.read(); @@ -546,7 +555,7 @@ mod tests { #[cfg(test)] mod tests_linear { - use super::LinCowCell; + use super::LinCowCellRaw; use super::LinCowCellCapable; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -612,7 +621,11 @@ mod tests_linear { GC_COUNT.store(0, Ordering::Release); assert!(GC_COUNT.load(Ordering::Acquire) == 0); let data = TestGcWrapper { data: 0 }; - let cc = LinCowCell::new(data); + let cc: LinCowCellRaw< + TestGcWrapper, + TestGcWrapperReadTxn, + TestGcWrapperWriteTxn, + > = LinCowCellRaw::new(data); // Open a read A. let cc_rotxn_a = cc.read(); diff --git a/src/lc_tests.rs b/src/lc_tests.rs index 5b09e66..ccbaf51 100644 --- a/src/lc_tests.rs +++ b/src/lc_tests.rs @@ -1,5 +1,5 @@ use crate::internals::bptree::cursor::{CursorRead, CursorWrite, SuperBlock}; -use crate::internals::lincowcell::{LinCowCell, LinCowCellCapable}; +use crate::internals::lincowcell::{LinCowCellRaw, LinCowCellCapable}; struct TestStruct { bptree_map_a: SuperBlock, @@ -7,8 +7,8 @@ struct TestStruct { } struct TestStructRead { - bptree_map_a: CursorRead, - bptree_map_b: CursorRead, + bptree_map_a: CursorRead, + bptree_map_b: CursorRead, } struct TestStructWrite { @@ -28,8 +28,14 @@ impl LinCowCellCapable for TestStruct { fn create_writer(&self) -> TestStructWrite { // This sets up the first writer. TestStructWrite { - bptree_map_a: self.bptree_map_a.create_writer(), - bptree_map_b: self.bptree_map_b.create_writer(), + bptree_map_a: as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&self.bptree_map_a), + bptree_map_b: as LinCowCellCapable< + CursorRead, + CursorWrite, + >>::create_writer(&self.bptree_map_b), } } @@ -56,10 +62,11 @@ impl LinCowCellCapable for TestStruct { #[test] fn test_lc_basic() { - let lcc = LinCowCell::new(TestStruct { - bptree_map_a: unsafe { SuperBlock::new() }, - bptree_map_b: unsafe { SuperBlock::new() }, - }); + let lcc: LinCowCellRaw = + LinCowCellRaw::new(TestStruct { + bptree_map_a: unsafe { SuperBlock::new() }, + bptree_map_b: unsafe { SuperBlock::new() }, + }); let x = lcc.write(); diff --git a/src/lib.rs b/src/lib.rs index b944e2f..696f31b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,12 +33,20 @@ //! By default all of these features are enabled. If you are planning to use this crate in a wasm //! context we recommend you use only `maps` as a feature. +//#![no_std] +#![cfg_attr(not(feature = "std"), no_std)] #![deny(warnings)] #![warn(unused_extern_crates)] #![warn(missing_docs)] #![allow(clippy::needless_lifetimes)] #![cfg_attr(feature = "simd_support", feature(portable_simd))] +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(any(test, feature = "std"))] +extern crate std; + #[cfg(all(test, feature = "dhat-heap"))] #[global_allocator] static ALLOC: dhat::Alloc = dhat::Alloc; @@ -48,7 +56,7 @@ static ALLOC: dhat::Alloc = dhat::Alloc; extern crate smallvec; pub mod cowcell; -pub use cowcell::CowCell; +pub use cowcell::CowCellRaw; #[cfg(feature = "ebr")] pub mod ebrcell; @@ -63,8 +71,11 @@ pub mod threadcache; // This is where the scary rust lives. #[cfg(feature = "maps")] pub mod internals; + // This is where the good rust lives. -#[cfg(feature = "maps")] +// We're allowing unused here since we may or may not use all items based on enabled features +// All potentially incompatible features must be feature gated internally. +#[allow(unused)] mod utils; #[cfg(feature = "maps")] @@ -74,5 +85,5 @@ pub mod hashmap; #[cfg(feature = "maps")] pub mod hashtrie; -#[cfg(test)] +#[cfg(all(test, feature = "maps"))] mod lc_tests; diff --git a/src/unsound3.rs b/src/unsound3.rs index 89b27bf..c0f380a 100644 --- a/src/unsound3.rs +++ b/src/unsound3.rs @@ -13,7 +13,7 @@ enum RefOrInt<'a> { Int(u64), } -#[cfg(feature = "unsoundness")] +#[cfg(all(feature = "unsoundness", feature = "std"))] fn main() { use concread::arcache::ARCache; use std::cell::Cell; diff --git a/src/utils.rs b/src/utils.rs index 927008a..8281466 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,13 +1,13 @@ -use std::borrow::Borrow; -use std::cmp::Ordering; -// use std::mem::MaybeUninit; +use core::borrow::Borrow; +use core::cmp::{Ordering, PartialOrd}; +// use core::mem::MaybeUninit; #[cfg(feature = "serde")] -use std::fmt; +use core::fmt; #[cfg(feature = "serde")] -use std::iter; +use core::iter; #[cfg(feature = "serde")] -use std::marker::PhantomData; -use std::ptr; +use core::marker::PhantomData; +use core::ptr; #[cfg(feature = "serde")] use serde::de::{Deserialize, MapAccess, Visitor}; @@ -86,17 +86,17 @@ where Err(slice.len()) } -#[cfg(feature = "serde")] +#[cfg(all(feature = "serde", feature = "maps"))] pub struct MapCollector(PhantomData<(T, K, V)>); -#[cfg(feature = "serde")] +#[cfg(all(feature = "serde", feature = "maps"))] impl MapCollector { pub fn new() -> Self { Self(PhantomData) } } -#[cfg(feature = "serde")] +#[cfg(all(feature = "serde", feature = "maps"))] impl<'de, T, K, V> Visitor<'de> for MapCollector where T: FromIterator<(K, V)>, @@ -116,3 +116,20 @@ where iter::from_fn(|| access.next_entry().transpose()).collect() } } + +/// This is intended for comparing the insertion times of items into the ArCache type. +/// This would Default to an implementation over the Instant type on std, but could be an atomic counter with a caller-defined bit width in no_std environments. +/// +/// # Safety +/// +/// This has been marked unsafe as there is a behaviour contract on the `next` function that will not be checked by the caller. Subsequent calls to `next` should +/// ALWAYS return an equal or greater value (based on the type's impl of PartialOrd) +pub unsafe trait Monotonic { + type Output: PartialOrd + Copy; + /// Create a new instance, taking no arguments - this type shoud be instantiated without runtime generated inputs. + fn new() -> Self; + /// Gets the current value - provides an option for introspection where the value can change without calls to `next`, + /// but they don't _have_ to changed without `next`. + fn current(&self) -> Self::Output; + fn next(&self) -> Self::Output; +} \ No newline at end of file diff --git a/tests/bptree_map.rs b/tests/bptree_map.rs index 4ed4fbc..1bb50bb 100644 --- a/tests/bptree_map.rs +++ b/tests/bptree_map.rs @@ -1,80 +1,83 @@ -use std::collections::{BTreeMap, BTreeSet}; -use std::ops::Bound; +#[cfg(feature = "maps")] +mod bptree_map_tests { -use concread::bptree::BptreeMap; + use concread::bptree::BptreeMap; + use std::collections::{BTreeMap, BTreeSet}; + use std::ops::Bound; -proptest::proptest! { - #[test] - fn bptree_range_iter_consistent(values: BTreeSet, left in 0..u8::MAX - 1, len in 1..u8::MAX, bounds: (Bound<()>, Bound<()>)) { - let range = (bounds.0.map(|()| left), bounds.1.map(|()| left.saturating_add(len))); - let btree_map = BTreeMap::from_iter(values.iter().cloned().map(|v| (v, ()))); - let bptree_map = BptreeMap::from_iter(values.iter().cloned().map(|v| (v, ()))); - let bptree_map_read_tx = bptree_map.read(); - - let btree_iter = btree_map.range(range); - let bptree_iter = bptree_map_read_tx.range(range); - - assert!( - btree_iter.eq(bptree_iter) - ) - } - - #[test] - fn bptree_get_consistent(values: BTreeSet, key: u8) { - let btree_map = BTreeMap::from_iter(values.iter().cloned().map(|v| (v, v))); - let bptree_map = BptreeMap::from_iter(values.iter().cloned().map(|v| (v, v))); - let bptree_map_read_tx = bptree_map.read(); - - let btree_value = btree_map.get(&key); - let bptree_value = bptree_map_read_tx.get(&key); + proptest::proptest! { + #[test] + fn bptree_range_iter_consistent(values: BTreeSet, left in 0..u8::MAX - 1, len in 1..u8::MAX, bounds: (Bound<()>, Bound<()>)) { + let range = (bounds.0.map(|()| left), bounds.1.map(|()| left.saturating_add(len))); + let btree_map = BTreeMap::from_iter(values.iter().cloned().map(|v| (v, ()))); + let bptree_map: BptreeMap = BptreeMap::from_iter(values.iter().cloned().map(|v| (v, ()))); + let bptree_map_read_tx = bptree_map.read(); - assert_eq!(btree_value, bptree_value); - } + let btree_iter = btree_map.range(range); + let bptree_iter = bptree_map_read_tx.range(range); - #[test] - fn bptree_remove_consistent(values in proptest::collection::btree_set(proptest::arbitrary::any::(), 1..256), indices: Vec ) { - let mut btree_map = BTreeMap::from_iter(values.iter().cloned().map(|v| (v.to_string(), v.to_string()))); - let bptree_map = BptreeMap::from_iter(values.iter().cloned().map(|v| (v.to_string(), v.to_string()))); - let mut bptree_map_write_tx = bptree_map.write(); - - for index in indices { - let index = index.index(values.len()); - let key = values.iter().nth(index).unwrap().to_string(); + assert!( + btree_iter.eq(bptree_iter) + ) + } - assert_eq!( - btree_map.remove(&key), - bptree_map_write_tx.remove(&key) - ); + #[test] + fn bptree_get_consistent(values: BTreeSet, key: u8) { + let btree_map = BTreeMap::from_iter(values.iter().cloned().map(|v| (v, v))); + let bptree_map: BptreeMap = BptreeMap::from_iter(values.iter().cloned().map(|v| (v, v))); + let bptree_map_read_tx = bptree_map.read(); let btree_value = btree_map.get(&key); - assert_eq!(btree_value, None); - let bptree_value = bptree_map_write_tx.get(&key); - assert_eq!(bptree_value, None); + let bptree_value = bptree_map_read_tx.get(&key); - assert!( - btree_map.iter().eq(bptree_map_write_tx.iter()) - ); + assert_eq!(btree_value, bptree_value); } - } -} - -#[test] -fn bptree_remove_1() { - let values = [ - 4u8, 9, 12, 27, 34, 40, 59, 81, 89, 100, 142, 183, 189, 196, 218, 241, - ]; - let to_remove = [9u8, 27, 40, 4].map(|v| v.to_string()); + #[test] + fn bptree_remove_consistent(values in proptest::collection::btree_set(proptest::arbitrary::any::(), 1..256), indices: Vec ) { + let mut btree_map = BTreeMap::from_iter(values.iter().cloned().map(|v| (v.to_string(), v.to_string()))); + let bptree_map: BptreeMap = BptreeMap::from_iter(values.iter().cloned().map(|v| (v.to_string(), v.to_string()))); + let mut bptree_map_write_tx = bptree_map.write(); + + for index in indices { + let index = index.index(values.len()); + let key = values.iter().nth(index).unwrap().to_string(); + + assert_eq!( + btree_map.remove(&key), + bptree_map_write_tx.remove(&key) + ); + + let btree_value = btree_map.get(&key); + assert_eq!(btree_value, None); + let bptree_value = bptree_map_write_tx.get(&key); + assert_eq!(bptree_value, None); + + assert!( + btree_map.iter().eq(bptree_map_write_tx.iter()) + ); + } + } + } - let bptree_map = BptreeMap::from_iter( - values - .iter() - .cloned() - .map(|v| (v.to_string(), v.to_string())), - ); - let mut bptree_map_write_tx = bptree_map.write(); + #[test] + fn bptree_remove_1() { + let values = [ + 4u8, 9, 12, 27, 34, 40, 59, 81, 89, 100, 142, 183, 189, 196, 218, 241, + ]; + + let to_remove = [9u8, 27, 40, 4].map(|v| v.to_string()); + + let bptree_map: BptreeMap = BptreeMap::from_iter( + values + .iter() + .cloned() + .map(|v| (v.to_string(), v.to_string())), + ); + let mut bptree_map_write_tx = bptree_map.write(); - for key in to_remove { - assert!(bptree_map_write_tx.remove(&key).is_some()); + for key in to_remove { + assert!(bptree_map_write_tx.remove(&key).is_some()); + } } } diff --git a/tests/lib.rs b/tests/lib.rs index c393578..8b13789 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1 +1 @@ -mod bptree_map; +