Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/core/store.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
9 changes: 5 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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::*;

Expand Down Expand Up @@ -135,13 +136,13 @@ impl<S> Builder<S> {
/// # Safety
///
/// See [`crate::with_hasher`](crate::with_hasher).
pub unsafe fn with_hasher_generator<H>(self, gen: fn() -> H) -> Builder<H>
pub unsafe fn with_hasher_generator<H>(self, gen_hasher: fn() -> H) -> Builder<H>
where
H: BuildHasher,
{
Builder {
capacity: self.capacity,
hasher: HasherGen::Generate(gen),
hasher: HasherGen::Generate(gen_hasher),
}
}

Expand Down Expand Up @@ -201,7 +202,7 @@ enum HasherGen<S> {
impl<S> HasherGen<S> {
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),
}
Expand Down
2 changes: 1 addition & 1 deletion src/read.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
30 changes: 15 additions & 15 deletions src/util/deterministic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -122,18 +122,18 @@ trusted_hash_eq! {
{'a} Component<'a>,
{'a} Prefix<'a>,
{T} VecDeque<T>,
{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>,
{Idx} RangeFrom<Idx>,
{Idx} RangeInclusive<Idx>,
Expand Down
14 changes: 7 additions & 7 deletions src/util/loom.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -82,7 +82,7 @@ pub mod cell {
}
}

#[cfg(not(loom))]
#[cfg(not(feature = "loom"))]
pub mod cell {
pub use std::cell::Cell;
use std::{
Expand Down
14 changes: 7 additions & 7 deletions src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ where
/// assert!(!guard.contains_key(&1));
/// ```
#[inline]
pub fn contains_key<Q: ?Sized>(&self, key: &Q) -> bool
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q> + Eq + Hash,
Q: Hash + Eq,
Q: Hash + Eq + ?Sized,
{
self.guard
.with_map(|map| map.contains_key(BorrowHelper::new_ref(key)))
Expand All @@ -123,10 +123,10 @@ where
/// assert!(guard.get("bananas").is_none());
/// ```
#[inline]
pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q> + Eq + Hash,
Q: Hash + Eq,
Q: Hash + Eq + ?Sized,
{
self.guard
.with_map(|map| map.get(BorrowHelper::new_ref(key)).map(Deref::deref))
Expand Down Expand Up @@ -155,7 +155,7 @@ where
/// assert_eq!(result, i8::MAX);
/// ```
#[inline]
pub fn iter<'read>(&'read self) -> impl Iterator<Item = (&K, &V)> + '_
pub fn iter<'read>(&'read self) -> impl Iterator<Item = (&'read K, &'read V)> + 'read
where
(K, V): 'read,
{
Expand Down Expand Up @@ -186,7 +186,7 @@ where
/// assert_eq!(result, 111);
/// ```
#[inline]
pub fn keys<'read>(&'read self) -> impl Iterator<Item = &K> + '_
pub fn keys<'read>(&'read self) -> impl Iterator<Item = &'read K> + 'read
where
(K, V): 'read,
{
Expand Down Expand Up @@ -216,7 +216,7 @@ where
/// assert_eq!(result, 111);
/// ```
#[inline]
pub fn values<'read>(&'read self) -> impl Iterator<Item = &V> + '_
pub fn values<'read>(&'read self) -> impl Iterator<Item = &'read V> + 'read
where
(K, V): 'read,
{
Expand Down
12 changes: 6 additions & 6 deletions src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
///
Expand Down Expand Up @@ -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 =
Expand Down Expand Up @@ -536,9 +536,9 @@ impl<'a, K, V> Evicted<'a, K, V> {
/// write.guard().drop_lazily(b);
/// ```
pub fn leak(evicted: Self) -> Leaked<V> {
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
}
Expand Down
6 changes: 3 additions & 3 deletions test.sh
Original file line number Diff line number Diff line change
@@ -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
cargo +stable test
cargo +nightly miri test -- --nocapture
RUST_BACKTRACE=full cargo +stable test --features loom --test loom --profile loomtest -- --nocapture
26 changes: 16 additions & 10 deletions tests/loom.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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());
});
Expand Down Expand Up @@ -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)));
});

Expand Down
Loading
Loading