Skip to content

Commit

Permalink
builder API, upgrade to seize 0.4.1
Browse files Browse the repository at this point in the history
  • Loading branch information
ibraheemdev committed May 11, 2024
1 parent c6c1805 commit 9fae704
Show file tree
Hide file tree
Showing 8 changed files with 317 additions and 113 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2021"

[dependencies]
atomic-wait = "1.1.0"
seize = "0.4.0"
seize = "0.4.1"

[dev-dependencies]
rand = "0.8.5"
Expand Down
172 changes: 135 additions & 37 deletions src/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,124 @@ use std::borrow::Borrow;
use std::collections::hash_map::RandomState;
use std::fmt;
use std::hash::{BuildHasher, Hash};
use std::marker::PhantomData;

/// A concurrent hash table.
///
/// Most hash table operations require a [`Guard`](crate::Guard), which can be acquired through
/// [`HashMap::guard`] or using the [`HashMap::pin`] API. See the [crate-level documentation](crate)
/// for details.
pub struct HashMap<K, V, S = RandomState> {
pub raw: raw::HashMap<K, V, S>,
pub initial: *mut u8,
raw: raw::HashMap<K, V, S>,
}

unsafe impl<K, V, S: Send> Send for HashMap<K, V, S> {}
unsafe impl<K, V, S: Sync> Sync for HashMap<K, V, S> {}

/// A builder for a [`HashMap`].
///
/// # Examples
///
/// ```rust
/// use papaya::{HashMap, ResizeMode};
/// use seize::Collector;
/// use std::collections::hash_map::RandomState;
///
/// let map: HashMap<i32, i32> = HashMap::builder()
/// // set the inital capacity
/// .capacity(2048)
/// // set the hasher
/// .hasher(RandomState::new())
/// // set the resize mode
/// .resize_mode(ResizeMode::Blocking)
/// // set a custom collector
/// .collector(Collector::new().batch_size(128))
/// // construct the hash map
/// .build();
/// ```
pub struct HashMapBuilder<K, V, S = RandomState> {
hasher: S,
capacity: usize,
collector: Collector,
resize_mode: ResizeMode,
_kv: PhantomData<(K, V)>,
}

impl<K, V> HashMapBuilder<K, V> {
/// Set the hash builder used to hash keys.
///
/// Warning: `hash_builder` is normally randomly generated, and is designed
/// to allow HashMaps to be resistant to attacks that cause many collisions
/// and very poor performance. Setting it manually using this function can
/// expose a DoS attack vector.
///
/// The `hash_builder` passed should implement the [`BuildHasher`] trait for
/// the HashMap to be useful, see its documentation for details.
pub fn hasher<S>(self, hasher: S) -> HashMapBuilder<K, V, S> {
HashMapBuilder {
hasher,
capacity: self.capacity,
collector: self.collector,
resize_mode: self.resize_mode,
_kv: PhantomData,
}
}
}

impl<K, V, S> HashMapBuilder<K, V, S> {
/// Set the initial capacity of the map.
///
/// Note the table should be able to hold at least `capacity` elements before
/// resizing, but may prematurely resize due to poor hash distribution. If `capacity`
/// is 0, the hash map will not allocate.
pub fn capacity(self, capacity: usize) -> HashMapBuilder<K, V, S> {
HashMapBuilder {
capacity,
hasher: self.hasher,
collector: self.collector,
resize_mode: self.resize_mode,
_kv: PhantomData,
}
}

/// Set the resizing mode of the map.
///
/// See [`ResizeMode`] for details.
pub fn resize_mode(self, resize_mode: ResizeMode) -> Self {
HashMapBuilder {
resize_mode,
hasher: self.hasher,
capacity: self.capacity,
collector: self.collector,
_kv: PhantomData,
}
}

/// Set the [`seize::Collector`] used for memory reclamation.
///
/// This method may be useful when you want more control over memory reclamation.
/// See [`seize::Collector`] for details.
///
/// Note that all `Guard` references used to access the map must be produced by
/// the provided `collector`.
pub fn collector(self, collector: Collector) -> Self {
HashMapBuilder {
collector,
hasher: self.hasher,
capacity: self.capacity,
resize_mode: self.resize_mode,
_kv: PhantomData,
}
}

/// Construct a [`HashMap`] from the builder, using the configured options.
pub fn build(self) -> HashMap<K, V, S> {
HashMap {
raw: raw::HashMap::new(self.capacity, self.hasher, self.collector, self.resize_mode),
}
}
}

/// Resize behavior for a [`HashMap`].
///
/// Hash maps must resize when the underlying table becomes full, migrating all key and value pairs
Expand Down Expand Up @@ -86,6 +190,20 @@ impl<K, V> HashMap<K, V> {
pub fn with_capacity(capacity: usize) -> HashMap<K, V> {
HashMap::with_capacity_and_hasher(capacity, RandomState::new())
}

/// Returns a builder for a `HashMap`.
///
/// The builder can be used for more complex configuration, such as using
/// a custom [`Collector`], or [`ResizeMode`].
pub fn builder() -> HashMapBuilder<K, V> {
HashMapBuilder {
capacity: 0,
hasher: RandomState::default(),
collector: Collector::new(),
resize_mode: ResizeMode::default(),
_kv: PhantomData,
}
}
}

impl<K, V, S> Default for HashMap<K, V, S>
Expand Down Expand Up @@ -149,46 +267,23 @@ impl<K, V, S> HashMap<K, V, S> {
/// map.pin().insert(1, 2);
/// ```
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> HashMap<K, V, S> {
let raw = raw::HashMap::with_capacity_and_hasher(capacity, hash_builder);
HashMap {
initial: raw.root_ptr(),
raw,
raw: raw::HashMap::new(
capacity,
hash_builder,
Collector::default(),
ResizeMode::default(),
),
}
}

/// Associate a custom [`seize::Collector`] with this map.
///
/// This method may be useful when you want more control over memory reclamation.
/// See [`seize::Collector`] for details.
///
/// Note that all `Guard` references used to access the map must be produced by
/// `collector`.
pub fn with_collector(mut self, collector: Collector) -> Self {
self.raw.collector = collector;
self
}

/// Configures the resizing mode for this map.
///
/// See [`ResizeMode`] for details.
pub fn resize_mode(mut self, resize: ResizeMode) -> Self {
assert_eq!(
self.raw.root_ptr(),
self.initial,
"cannot change resize mode after initialization"
);

self.raw.resize = resize;
self
}

/// Returns a guard for use with this map.
///
/// Note that holding on to a guard pins the current thread, preventing garbage
/// collection. See the [crate-level documentation](crate) for details.
#[inline]
pub fn guard(&self) -> LocalGuard<'_> {
self.raw.collector.enter()
self.raw.collector().enter()
}

/// Returns an owned guard for use with this map.
Expand All @@ -201,7 +296,7 @@ impl<K, V, S> HashMap<K, V, S> {
/// collection. See the [crate-level documentation](crate) for details.
#[inline]
pub fn owned_guard(&self) -> OwnedGuard<'_> {
self.raw.collector.enter_owned()
self.raw.collector().enter_owned()
}

/// Returns a pinned reference to the map.
Expand Down Expand Up @@ -392,7 +487,7 @@ where
#[inline]
pub fn insert<'g>(&'g self, key: K, value: V, guard: &'g impl Guard) -> Option<&'g V> {
match self.raw.root(guard).insert(key, value, true, guard) {
EntryStatus::Empty(_) | EntryStatus::Tombstone(_) => None,
EntryStatus::Empty(_) => None,
EntryStatus::Replaced(value) => Some(value),
EntryStatus::Error { .. } => unreachable!(),
}
Expand Down Expand Up @@ -427,7 +522,7 @@ where
guard: &'g impl Guard,
) -> Result<&'g V, OccupiedError<'g, V>> {
match self.raw.root(guard).insert(key, value, false, guard) {
EntryStatus::Empty(value) | EntryStatus::Tombstone(value) => Ok(value),
EntryStatus::Empty(value) => Ok(value),
EntryStatus::Error {
current,
not_inserted,
Expand Down Expand Up @@ -768,8 +863,11 @@ where
S: BuildHasher + Clone,
{
fn clone(&self) -> HashMap<K, V, S> {
let other = Self::with_capacity_and_hasher(self.len(), self.raw.hasher.clone())
.with_collector(self.raw.collector.clone());
let other = HashMap::builder()
.capacity(self.len())
.hasher(self.raw.hasher.clone())
.collector(self.raw.collector().clone())
.build();

{
let (guard1, guard2) = (&self.guard(), &other.guard());
Expand Down
36 changes: 12 additions & 24 deletions src/raw/alloc.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use std::alloc::Layout;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicPtr, AtomicU32, AtomicU8, AtomicUsize};
use std::sync::Mutex;
use std::sync::atomic::{AtomicPtr, AtomicU8};
use std::{alloc, mem, ptr};

use seize::Collector;

use super::State;

// A hash table layed out in a single allocation
#[repr(transparent)]
pub struct RawTable(u8);
Expand All @@ -26,23 +29,6 @@ struct TableLayout {
entries: [AtomicPtr<()>; 0],
}

#[derive(Default)]
pub struct State {
pub next: AtomicPtr<RawTable>,
pub allocating: Mutex<()>,
pub copied: AtomicUsize,
pub claim: AtomicUsize,
pub status: AtomicU32,
// todo: use seize linked lists here
pub deferred: Mutex<Vec<*mut ()>>,
}

impl State {
pub const PENDING: u32 = 0;
pub const ABORTED: u32 = 1;
pub const PROMOTED: u32 = 2;
}

// Manages a table allocation.
#[repr(C)]
pub struct Table<T> {
Expand All @@ -64,7 +50,7 @@ impl<T> Clone for Table<T> {
}

impl<T> Table<T> {
pub fn new(len: usize, link: seize::Link) -> Table<T> {
pub fn new(len: usize, collector: &Collector) -> Table<T> {
assert!(len.is_power_of_two());
assert!(mem::align_of::<seize::Link>() % mem::align_of::<*mut T>() == 0);

Expand All @@ -83,10 +69,13 @@ impl<T> Table<T> {

// write the table layout state
ptr.cast::<TableLayout>().write(TableLayout {
link,
link: collector.link(),
len,
capacity,
state: State::default(),
state: State {
collector,
..State::default()
},
meta: [],
entries: [],
});
Expand Down Expand Up @@ -172,8 +161,7 @@ impl<T> Table<T> {
fn layout() {
unsafe {
let collector = seize::Collector::new();
let link = collector.link();
let table: Table<u8> = Table::new(4, link);
let table: Table<u8> = Table::new(4, &collector);
let table: Table<u8> = Table::from_raw(table.raw);
assert_eq!(table.len, 4);
// padded for pointer alignment
Expand Down
Loading

0 comments on commit 9fae704

Please sign in to comment.