diff --git a/Cargo.toml b/Cargo.toml index 0e2fbdb..669be5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "flashmap" -version = "0.1.0" +version = "0.2.0" authors = ["Cassy343"] -edition = "2021" +edition = "2024" description = "A lock-free eventually consistent concurrent hash map." repository = "https://github.com/Cassy343/flashmap" @@ -12,21 +12,26 @@ readme = "README.md" keywords = ["map", "concurrent", "hashmap"] categories = ["concurrency", "data-structures"] +exclude = [".github/*", "bench-graphs/*", "hooks/*", "dev_setup.sh", "test.sh"] + [dependencies] num_cpus = "1" -slab = "0.4.7" +slab = "0.4.12" [dependencies.hashbrown] -version = "0.12.3" +version = "0.16.1" default-features = false -features = ["inline-more"] +features = ["inline-more", "raw-entry"] -[target.'cfg(loom)'.dependencies] -loom = { version = "0.5.6", features = ["checkpoint"] } +[dependencies.loom] +version = "0.7.2" +features = ["checkpoint"] +optional = true [features] default = [] nightly = [] +loom = ["dep:loom"] [profile.loomtest] inherits = "release" diff --git a/LICENSE b/LICENSE index cd3d915..fe2ad77 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Cassy343 +Copyright (c) 2026 Cassy343 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/src/core/mod.rs b/src/core/mod.rs index 0f65078..46eb5df 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -4,21 +4,21 @@ mod store; pub use refcount::*; pub use store::*; -use hashbrown::hash_map::DefaultHashBuilder; +use hashbrown::DefaultHashBuilder; use slab::Slab; +use crate::{BuilderArgs, Map, ReadHandle, WriteHandle, util::CachePadded}; use crate::{ loom::{ cell::{Cell, UnsafeCell}, sync::{ - atomic::{fence, AtomicIsize, Ordering}, Arc, Mutex, + atomic::{AtomicIsize, Ordering, fence}, }, thread::{self, Thread}, }, - util::{likely, lock, Alias}, + util::{Alias, likely, lock}, }; -use crate::{util::CachePadded, BuilderArgs, Map, ReadHandle, WriteHandle}; use std::hash::{BuildHasher, Hash}; use std::marker::PhantomData; use std::process::abort; diff --git a/src/core/store.rs b/src/core/store.rs index d22dd22..4232571 100644 --- a/src/core/store.rs +++ b/src/core/store.rs @@ -1,6 +1,6 @@ +use crate::Map; use crate::loom::cell::UnsafeCell; use crate::util::CachePadded; -use crate::Map; use std::{marker::PhantomData, mem, ptr::NonNull}; #[repr(usize)] diff --git a/src/lib.rs b/src/lib.rs index 1b810ec..ce43892 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(feature = "nightly", allow(internal_features))] #![cfg_attr(feature = "nightly", feature(core_intrinsics))] #![deny(rust_2018_idioms, unsafe_op_in_unsafe_fn)] #![warn(missing_docs)] @@ -12,7 +13,7 @@ mod write; pub use read::*; pub(crate) use util::loom; -pub use util::{deterministic::*, Alias}; +pub use util::{Alias, deterministic::*}; pub use view::View; pub use write::*; @@ -135,13 +136,13 @@ impl Builder { /// # Safety /// /// See [`crate::with_hasher`](crate::with_hasher). - pub unsafe fn with_hasher_generator(self, gen: fn() -> H) -> Builder + pub unsafe fn with_hasher_generator(self, gen_hasher: fn() -> H) -> Builder where H: BuildHasher, { Builder { capacity: self.capacity, - hasher: HasherGen::Generate(gen), + hasher: HasherGen::Generate(gen_hasher), } } @@ -201,7 +202,7 @@ enum HasherGen { impl HasherGen { fn generate(self) -> (S, S) { match self { - Self::Generate(gen) => (gen(), gen()), + Self::Generate(gen_hasher) => (gen_hasher(), gen_hasher()), Self::MakeBoth(make_both) => make_both(), Self::Clone(hasher, clone) => (clone(&hasher), hasher), } diff --git a/src/read.rs b/src/read.rs index 9031e9c..56f501f 100644 --- a/src/read.rs +++ b/src/read.rs @@ -1,12 +1,12 @@ use std::{collections::hash_map::RandomState, ptr::NonNull}; use crate::{ + Map, View, core::{Core, MapIndex, RefCount, SharedMapAccess}, loom::cell::UnsafeCell, loom::sync::Arc, util::unlikely, view::sealed::ReadAccess, - Map, View, }; /// A read handle for the map. diff --git a/src/util/deterministic.rs b/src/util/deterministic.rs index 004eb44..eebdf0e 100644 --- a/src/util/deterministic.rs +++ b/src/util/deterministic.rs @@ -61,14 +61,14 @@ use std::{ mem::{Discriminant, ManuallyDrop}, net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, num::{ - NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, - NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, Wrapping, + NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize, Wrapping, }, ops::{Bound, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}, path::{Component, Path, PathBuf, Prefix, PrefixComponent}, ptr::NonNull, rc::Rc, - sync::{atomic, Arc}, + sync::{Arc, atomic}, task::Poll, thread::ThreadId, time::{Duration, Instant, SystemTime}, @@ -122,18 +122,18 @@ trusted_hash_eq! { {'a} Component<'a>, {'a} Prefix<'a>, {T} VecDeque, - {A: ?Sized} (A,), - {A, B: ?Sized} (A, B), - {A, B, C: ?Sized} (A, B, C), - {A, B, C, D: ?Sized} (A, B, C, D), - {A, B, C, D, E: ?Sized} (A, B, C, D, E), - {A, B, C, D, E, F: ?Sized} (A, B, C, D, E, F), - {A, B, C, D, E, F, G: ?Sized} (A, B, C, D, E, F, G), - {A, B, C, D, E, F, G, H: ?Sized} (A, B, C, D, E, F, G, H), - {A, B, C, D, E, F, G, H, I: ?Sized} (A, B, C, D, E, F, G, H, I), - {A, B, C, D, E, F, G, H, I, J: ?Sized} (A, B, C, D, E, F, G, H, I, J), - {A, B, C, D, E, F, G, H, I, J, K: ?Sized} (A, B, C, D, E, F, G, H, I, J, K), - {A, B, C, D, E, F, G, H, I, J, K, L: ?Sized} (A, B, C, D, E, F, G, H, I, J, K, L), + {A} (A,), + {A, B} (A, B), + {A, B, C} (A, B, C), + {A, B, C, D} (A, B, C, D), + {A, B, C, D, E} (A, B, C, D, E), + {A, B, C, D, E, F} (A, B, C, D, E, F), + {A, B, C, D, E, F, G} (A, B, C, D, E, F, G), + {A, B, C, D, E, F, G, H} (A, B, C, D, E, F, G, H), + {A, B, C, D, E, F, G, H, I} (A, B, C, D, E, F, G, H, I), + {A, B, C, D, E, F, G, H, I, J} (A, B, C, D, E, F, G, H, I, J), + {A, B, C, D, E, F, G, H, I, J, K} (A, B, C, D, E, F, G, H, I, J, K), + {A, B, C, D, E, F, G, H, I, J, K, L} (A, B, C, D, E, F, G, H, I, J, K, L), {Idx} Range, {Idx} RangeFrom, {Idx} RangeInclusive, diff --git a/src/util/loom.rs b/src/util/loom.rs index 99cac23..8b9a14a 100644 --- a/src/util/loom.rs +++ b/src/util/loom.rs @@ -1,16 +1,16 @@ -#[cfg(loom)] -pub use loom::{hint, thread}; +#[cfg(feature = "loom")] +pub use loom::thread; -#[cfg(not(loom))] -pub use std::{hint, sync, thread}; +#[cfg(not(feature = "loom"))] +pub use std::{sync, thread}; -#[cfg(loom)] +#[cfg(feature = "loom")] pub mod sync { pub use loom::sync::*; pub use std::sync::PoisonError; } -#[cfg(loom)] +#[cfg(feature = "loom")] pub mod cell { pub use loom::cell::Cell; use std::{ @@ -82,7 +82,7 @@ pub mod cell { } } -#[cfg(not(loom))] +#[cfg(not(feature = "loom"))] pub mod cell { pub use std::cell::Cell; use std::{ diff --git a/src/view.rs b/src/view.rs index 854f0f0..eee58d1 100644 --- a/src/view.rs +++ b/src/view.rs @@ -99,10 +99,10 @@ where /// assert!(!guard.contains_key(&1)); /// ``` #[inline] - pub fn contains_key(&self, key: &Q) -> bool + pub fn contains_key(&self, key: &Q) -> bool where K: Borrow + Eq + Hash, - Q: Hash + Eq, + Q: Hash + Eq + ?Sized, { self.guard .with_map(|map| map.contains_key(BorrowHelper::new_ref(key))) @@ -123,10 +123,10 @@ where /// assert!(guard.get("bananas").is_none()); /// ``` #[inline] - pub fn get(&self, key: &Q) -> Option<&V> + pub fn get(&self, key: &Q) -> Option<&V> where K: Borrow + Eq + Hash, - Q: Hash + Eq, + Q: Hash + Eq + ?Sized, { self.guard .with_map(|map| map.get(BorrowHelper::new_ref(key)).map(Deref::deref)) @@ -155,7 +155,7 @@ where /// assert_eq!(result, i8::MAX); /// ``` #[inline] - pub fn iter<'read>(&'read self) -> impl Iterator + '_ + pub fn iter<'read>(&'read self) -> impl Iterator + 'read where (K, V): 'read, { @@ -186,7 +186,7 @@ where /// assert_eq!(result, 111); /// ``` #[inline] - pub fn keys<'read>(&'read self) -> impl Iterator + '_ + pub fn keys<'read>(&'read self) -> impl Iterator + 'read where (K, V): 'read, { @@ -216,7 +216,7 @@ where /// assert_eq!(result, 111); /// ``` #[inline] - pub fn values<'read>(&'read self) -> impl Iterator + '_ + pub fn values<'read>(&'read self) -> impl Iterator + 'read where (K, V): 'read, { diff --git a/src/write.rs b/src/write.rs index fcd50a2..673d4b0 100644 --- a/src/write.rs +++ b/src/write.rs @@ -10,12 +10,12 @@ use std::{ use hashbrown::hash_map::RawEntryMut; use crate::{ + Map, View, core::Core, loom::cell::UnsafeCell, loom::sync::Arc, util::{Alias, BorrowHelper}, view::sealed::ReadAccess, - Map, View, }; static NEXT_WRITER_UID: AtomicUsize = AtomicUsize::new(1); @@ -173,7 +173,7 @@ where } /// Returns a function which can safely reclaim leaked values. This is useful for reclaiming - /// multiple leaked values while only performign the necessary synchronization once. + /// multiple leaked values while only performing the necessary synchronization once. /// /// # Panics /// @@ -229,7 +229,7 @@ where { match operation { RawOperation::InsertUnique(key, value) => { - map.insert_unique_unchecked(key, value); + unsafe { map.insert_unique_unchecked(key, value) }; } RawOperation::Replace(ref key, value) => { let slot = @@ -536,9 +536,9 @@ impl<'a, K, V> Evicted<'a, K, V> { /// write.guard().drop_lazily(b); /// ``` pub fn leak(evicted: Self) -> Leaked { - evicted - .operations - .with_mut(|ptr| unsafe { (*ptr).get_unchecked_mut(evicted.operation) }.make_leaky()); + evicted.operations.with_mut(|ptr| { + unsafe { (&mut (*ptr)).get_unchecked_mut(evicted.operation) }.make_leaky() + }); evicted.leaked } diff --git a/test.sh b/test.sh index 4a62f89..1e91c1a 100755 --- a/test.sh +++ b/test.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash -cargo test -cargo miri test -- --nocapture -RUST_BACKTRACE=full RUSTFLAGS="--cfg loom" cargo test --test loom --profile loomtest -- --nocapture \ No newline at end of file +cargo +stable test +cargo +nightly miri test -- --nocapture +RUST_BACKTRACE=full cargo +stable test --features loom --test loom --profile loomtest -- --nocapture \ No newline at end of file diff --git a/tests/loom.rs b/tests/loom.rs index 3da1ea8..30aaee6 100644 --- a/tests/loom.rs +++ b/tests/loom.rs @@ -1,10 +1,12 @@ -//! These are tests that are specifically designed to be run with cfg(loom) to catch concurrency -//! bugs. Tests which take a long time to run should be gated behind cfg(long_test). +#![allow(unexpected_cfgs)] + +//! These are tests that are specifically designed to be run with cfg(feature = "loom") to catch +//! concurrency bugs. Tests which take a long time to run should be gated behind cfg(long_test). mod util; use flashmap::{Evicted, ReadHandle}; -use util::{thread, TrackAccess}; +use util::{TrackAccess, thread}; trait BoolExt { fn implies(self, other: Self) -> Self; @@ -111,10 +113,12 @@ pub fn many_different_writes() { .guard() .insert(TrackAccess::new(20), TrackAccess::new(40)); - assert!(write - .guard() - .replace(TrackAccess::new(20), |x| TrackAccess::new(*x.get() + 5)) - .is_some()); + assert!( + write + .guard() + .replace(TrackAccess::new(20), |x| TrackAccess::new(*x.get() + 5)) + .is_some() + ); assert!(write.guard().remove(TrackAccess::new(10)).is_some()); }); @@ -250,9 +254,11 @@ pub fn evicted_and_leaked_values() { let forty = read.guard().get(&20).map(|x| *x.get()); let eighty = read.guard().get(&40).map(|x| *x.get()); - assert!(twenty - .is_none() - .implies(forty == Some(45) && eighty == Some(90))); + assert!( + twenty + .is_none() + .implies(forty == Some(45) && eighty == Some(90)) + ); assert!((forty == Some(45)).implies(eighty == Some(90))); }); diff --git a/tests/util.rs b/tests/util.rs index a77b540..075f098 100644 --- a/tests/util.rs +++ b/tests/util.rs @@ -1,8 +1,8 @@ -#![allow(dead_code)] +#![allow(dead_code, unused_imports)] -#[cfg(loom)] +#[cfg(feature = "loom")] pub use loom::*; -#[cfg(not(loom))] +#[cfg(not(feature = "loom"))] pub use std::{sync, thread}; pub use track_access::*; @@ -11,13 +11,13 @@ pub fn maybe_loom_model(test: F) where F: Fn() + Send + Sync + 'static, { - #[cfg(loom)] + #[cfg(feature = "loom")] loom::model(test); - #[cfg(not(loom))] + #[cfg(not(feature = "loom"))] test(); } -#[cfg(loom)] +#[cfg(feature = "loom")] mod track_access { use loom::{alloc::Track, cell::UnsafeCell}; use std::{ @@ -68,7 +68,7 @@ mod track_access { } } -#[cfg(not(loom))] +#[cfg(not(feature = "loom"))] mod track_access { use std::borrow::Borrow; use std::hash::Hash;