Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move garbage collection to seize #102

Merged
merged 29 commits into from
Feb 26, 2022
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3252380
move garbage collection to seize
ibraheemdev Feb 8, 2022
10667b6
fix serde/rayon build
ibraheemdev Feb 9, 2022
e8c71a4
update docs
ibraheemdev Feb 9, 2022
8e03dd5
appease clippy
ibraheemdev Feb 9, 2022
a60861f
disable evil collector test (for now)
ibraheemdev Feb 12, 2022
5170f14
disable miri isolation
ibraheemdev Feb 13, 2022
4c0b1d7
fix stacked borrows violation in `HashMap::put`
ibraheemdev Feb 15, 2022
2f03673
fetch seize patch
ibraheemdev Feb 16, 2022
8fd541b
bump msrv to 1.56
ibraheemdev Feb 16, 2022
6861f9a
pass -Zmiri-tag-raw-pointers, don't pass -Zmiri-ignore-leaks in CI
ibraheemdev Feb 16, 2022
eb6290d
disallow evil guards
ibraheemdev Feb 16, 2022
f704581
fix guard equality checks
ibraheemdev Feb 16, 2022
d50737c
reuse guards in `different_size_maps_not_equal` test
ibraheemdev Feb 19, 2022
9f8766d
update seize
ibraheemdev Feb 19, 2022
011a9df
relax failure ordering in `Table::get_moved`
ibraheemdev Feb 19, 2022
336477c
remove `Shared: From<*const Linked<T>>`
ibraheemdev Feb 19, 2022
a94060a
add safety guarantees for `defer_drop_without_values`
ibraheemdev Feb 19, 2022
41966b4
remove `Guard::unprotected` helper
ibraheemdev Feb 20, 2022
386eaa7
pass boxed bin to `TableBin::new`
ibraheemdev Feb 20, 2022
ad9d5f2
add flush operation
ibraheemdev Feb 20, 2022
d6307cf
seize 0.2.0
ibraheemdev Feb 20, 2022
55093a1
remove 'static bounds
ibraheemdev Feb 20, 2022
519a3fe
remove `crossbeam::epoch` from public documentation
ibraheemdev Feb 20, 2022
c6078d6
update safety comments
ibraheemdev Feb 25, 2022
5db3eb8
run ui doc tests
ibraheemdev Feb 25, 2022
16cb96a
tweak bin deref safety comments
ibraheemdev Feb 26, 2022
9a67108
fix punctuation
ibraheemdev Feb 26, 2022
cc93a0c
update table deref safety comments
ibraheemdev Feb 26, 2022
fe8a171
update node walk safety comments
ibraheemdev Feb 26, 2022
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
2 changes: 1 addition & 1 deletion .github/workflows/miri.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@ jobs:
command: miri
args: test
env:
MIRIFLAGS: "-Zmiri-ignore-leaks"
MIRIFLAGS: "-Zmiri-tag-raw-pointers -Zmiri-disable-isolation"
4 changes: 2 additions & 2 deletions .github/workflows/msrv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ jobs:
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.52.0 # rustdoc:: lint prefix
toolchain: 1.56.0 # UnsafeCell::raw_get
override: true
- uses: actions/checkout@v2
- name: cargo +1.52.0 check
- name: cargo +1.56.0 check
uses: actions-rs/cargo@v1
with:
command: check
5 changes: 1 addition & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,12 @@ azure-devops = { project = "jonhoo/jonhoo", pipeline = "flurry", build = "15" }
codecov = { repository = "jonhoo/flurry", branch = "master", service = "github" }
maintenance = { status = "experimental" }

[features]
sanitize = ['crossbeam-epoch/sanitize']

[dependencies]
crossbeam-epoch = "0.8.2"
parking_lot = "0.10"
num_cpus = "1.12.0"
rayon = {version = "1.3", optional = true}
serde = {version = "1.0.105", optional = true}
seize = "0.2.0"

[dependencies.ahash]
version = "0.3.2"
Expand Down
33 changes: 19 additions & 14 deletions src/iter/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod traverser;
pub(crate) use traverser::NodeIter;

use crossbeam_epoch::Guard;
use crate::reclaim::{Guard, Shared};
use std::sync::atomic::Ordering;

/// An iterator over a map's entries.
Expand All @@ -10,17 +10,23 @@ use std::sync::atomic::Ordering;
#[derive(Debug)]
pub struct Iter<'g, K, V> {
pub(crate) node_iter: NodeIter<'g, K, V>,
pub(crate) guard: &'g Guard,
pub(crate) guard: &'g Guard<'g>,
}

impl<'g, K, V> Iter<'g, K, V> {
pub(crate) fn next_internal(&mut self) -> Option<(&'g K, Shared<'g, V>)> {
let node = self.node_iter.next()?;
let value = node.value.load(Ordering::SeqCst, self.guard);
Some((&node.key, value))
}
}

impl<'g, K, V> Iterator for Iter<'g, K, V> {
type Item = (&'g K, &'g V);
fn next(&mut self) -> Option<Self::Item> {
let node = self.node_iter.next()?;
let value = node.value.load(Ordering::SeqCst, self.guard);
// safety: flurry does not drop or move until after guard drop
let value = unsafe { value.deref() };
Some((&node.key, value))
self.next_internal()
.map(|(k, v)| unsafe { (k, &**v.deref()) })
}
}

Expand All @@ -46,7 +52,7 @@ impl<'g, K, V> Iterator for Keys<'g, K, V> {
#[derive(Debug)]
pub struct Values<'g, K, V> {
pub(crate) node_iter: NodeIter<'g, K, V>,
pub(crate) guard: &'g Guard,
pub(crate) guard: &'g Guard<'g>,
}

impl<'g, K, V> Iterator for Values<'g, K, V> {
Expand All @@ -63,19 +69,18 @@ impl<'g, K, V> Iterator for Values<'g, K, V> {
#[cfg(test)]
mod tests {
use crate::HashMap;
use crossbeam_epoch as epoch;
use std::collections::HashSet;
use std::iter::FromIterator;

#[test]
fn iter() {
let map = HashMap::<usize, usize>::new();

let guard = epoch::pin();
let guard = map.guard();
map.insert(1, 42, &guard);
map.insert(2, 84, &guard);

let guard = epoch::pin();
let guard = map.guard();
assert_eq!(
map.iter(&guard).collect::<HashSet<(&usize, &usize)>>(),
HashSet::from_iter(vec![(&1, &42), (&2, &84)])
Expand All @@ -86,11 +91,11 @@ mod tests {
fn keys() {
let map = HashMap::<usize, usize>::new();

let guard = epoch::pin();
let guard = map.guard();
map.insert(1, 42, &guard);
map.insert(2, 84, &guard);

let guard = epoch::pin();
let guard = map.guard();
assert_eq!(
map.keys(&guard).collect::<HashSet<&usize>>(),
HashSet::from_iter(vec![&1, &2])
Expand All @@ -101,10 +106,10 @@ mod tests {
fn values() {
let map = HashMap::<usize, usize>::new();

let mut guard = epoch::pin();
let mut guard = map.guard();
map.insert(1, 42, &guard);
map.insert(2, 84, &guard);
guard.repin();
ibraheemdev marked this conversation as resolved.
Show resolved Hide resolved
guard.flush();

assert_eq!(
map.values(&guard).collect::<HashSet<&usize>>(),
Expand Down
99 changes: 54 additions & 45 deletions src/iter/traverser.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::node::{BinEntry, Node, TreeNode};
use crate::raw::Table;
use crossbeam_epoch::{Guard, Shared};
use crate::reclaim::{Guard, Linked, Shared};
use std::sync::atomic::Ordering;

#[derive(Debug)]
pub(crate) struct NodeIter<'g, K, V> {
/// Current table; update if resized
table: Option<&'g Table<K, V>>,
table: Option<&'g Linked<Table<K, V>>>,

stack: Option<Box<TableStack<'g, K, V>>>,
spare: Option<Box<TableStack<'g, K, V>>>,
Expand All @@ -26,11 +26,11 @@ pub(crate) struct NodeIter<'g, K, V> {
/// Initial table size
base_size: usize,

guard: &'g Guard,
guard: &'g Guard<'g>,
}

impl<'g, K, V> NodeIter<'g, K, V> {
pub(crate) fn new(table: Shared<'g, Table<K, V>>, guard: &'g Guard) -> Self {
pub(crate) fn new(table: Shared<'g, Table<K, V>>, guard: &'g Guard<'_>) -> Self {
jonhoo marked this conversation as resolved.
Show resolved Hide resolved
let (table, len) = if table.is_null() {
(None, 0)
} else {
Expand All @@ -53,7 +53,7 @@ impl<'g, K, V> NodeIter<'g, K, V> {
}
}

fn push_state(&mut self, t: &'g Table<K, V>, i: usize, n: usize) {
fn push_state(&mut self, t: &'g Linked<Table<K, V>>, i: usize, n: usize) {
let mut s = self.spare.take();
if let Some(ref mut s) = s {
self.spare = s.next.take();
Expand Down Expand Up @@ -121,11 +121,11 @@ impl<'g, K, V> Iterator for NodeIter<'g, K, V> {
// inheritance (everything is a node), but we have to explicitly
// check
// safety: flurry does not drop or move until after guard drop
match unsafe { next.deref() } {
BinEntry::Node(node) => {
match **unsafe { next.deref() } {
BinEntry::Node(ref node) => {
e = Some(node);
}
BinEntry::TreeNode(tree_node) => {
BinEntry::TreeNode(ref tree_node) => {
e = Some(&tree_node.node);
}
BinEntry::Moved => unreachable!("Nodes can only point to Nodes or TreeNodes"),
Expand Down Expand Up @@ -156,7 +156,7 @@ impl<'g, K, V> Iterator for NodeIter<'g, K, V> {
if !bin.is_null() {
// safety: flurry does not drop or move until after guard drop
let bin = unsafe { bin.deref() };
match bin {
match **bin {
BinEntry::Moved => {
// recurse down into the target table
// safety: same argument as for following Moved in Table::find
Expand All @@ -166,17 +166,17 @@ impl<'g, K, V> Iterator for NodeIter<'g, K, V> {
self.push_state(t, i, n);
continue;
}
BinEntry::Node(node) => {
BinEntry::Node(ref node) => {
e = Some(node);
}
BinEntry::Tree(tree_bin) => {
BinEntry::Tree(ref tree_bin) => {
// since we want to iterate over all entries, TreeBins
// are also traversed via the `next` pointers of their
// contained node
e = Some(
// safety: `bin` was read under our guard, at which
// point the tree was valid. Since our guard pins
// the current epoch, the TreeNodes remain valid for
// point the tree was valid. Since our guard marks
// the current thread as active, the TreeNodes remain valid for
// at least as long as we hold onto the guard.
// Structurally, TreeNodes always point to TreeNodes, so this is sound.
&unsafe {
Expand Down Expand Up @@ -210,51 +210,55 @@ impl<'g, K, V> Iterator for NodeIter<'g, K, V> {
struct TableStack<'g, K, V> {
length: usize,
index: usize,
table: &'g Table<K, V>,
table: &'g Linked<Table<K, V>>,
next: Option<Box<TableStack<'g, K, V>>>,
}

#[cfg(test)]
mod tests {
use super::*;
use crate::raw::Table;
use crossbeam_epoch::{self as epoch, Atomic, Owned};
use crate::reclaim::Atomic;
use parking_lot::Mutex;

#[test]
fn iter_new() {
let guard = epoch::pin();
let guard = unsafe { seize::Guard::unprotected() };
let iter = NodeIter::<usize, usize>::new(Shared::null(), &guard);
assert_eq!(iter.count(), 0);
}

#[test]
fn iter_empty() {
let table = Owned::new(Table::<usize, usize>::new(16));
let guard = epoch::pin();
let table = table.into_shared(&guard);
let collector = seize::Collector::new();

let table = Shared::boxed(Table::<usize, usize>::new(16, &collector), &collector);
let guard = collector.enter();
let iter = NodeIter::new(table, &guard);
assert_eq!(iter.count(), 0);

// safety: nothing holds on to references into the table any more
let mut t = unsafe { table.into_owned() };
let mut t = unsafe { table.into_box() };
t.drop_bins();
}

#[test]
fn iter_simple() {
let collector = seize::Collector::new();
let mut bins = vec![Atomic::null(); 16];
bins[8] = Atomic::new(BinEntry::Node(Node {
hash: 0,
key: 0usize,
value: Atomic::new(0usize),
next: Atomic::null(),
lock: Mutex::new(()),
}));

let table = Owned::new(Table::from(bins));
let guard = epoch::pin();
let table = table.into_shared(&guard);
bins[8] = Atomic::from(Shared::boxed(
BinEntry::Node(Node {
hash: 0,
key: 0usize,
value: Atomic::from(Shared::boxed(0usize, &collector)),
next: Atomic::null(),
lock: Mutex::new(()),
}),
&collector,
));

let table = Shared::boxed(Table::from(bins, &collector), &collector);
let guard = collector.enter();
{
let mut iter = NodeIter::new(table, &guard);
let e = iter.next().unwrap();
Expand All @@ -263,27 +267,32 @@ mod tests {
}

// safety: nothing holds on to references into the table any more
let mut t = unsafe { table.into_owned() };
let mut t = unsafe { table.into_box() };
t.drop_bins();
}

#[test]
fn iter_fw() {
// construct the forwarded-to table
let collector = seize::Collector::new();
let mut deep_bins = vec![Atomic::null(); 16];
deep_bins[8] = Atomic::new(BinEntry::Node(Node {
hash: 0,
key: 0usize,
value: Atomic::new(0usize),
next: Atomic::null(),
lock: Mutex::new(()),
}));
let guard = epoch::pin();
let deep_table = Owned::new(Table::from(deep_bins)).into_shared(&guard);
deep_bins[8] = Atomic::from(Shared::boxed(
BinEntry::Node(Node {
hash: 0,
key: 0usize,
value: Atomic::from(Shared::boxed(0usize, &collector)),
next: Atomic::null(),
lock: Mutex::new(()),
}),
&collector,
));

let guard = collector.enter();
let deep_table = Shared::boxed(Table::from(deep_bins, &collector), &collector);

// construct the forwarded-from table
let mut bins = vec![Shared::null(); 16];
let table = Table::<usize, usize>::new(bins.len());
let table = Table::<usize, usize>::new(bins.len(), &collector);
for bin in &mut bins[8..] {
// this also sets table.next_table to deep_table
*bin = table.get_moved(deep_table, &guard);
Expand All @@ -293,7 +302,7 @@ mod tests {
for i in 0..bins.len() {
table.store_bin(i, bins[i]);
}
let table = Owned::new(table).into_shared(&guard);
let table = Shared::boxed(table, &collector);
{
let mut iter = NodeIter::new(table, &guard);
let e = iter.next().unwrap();
Expand All @@ -302,9 +311,9 @@ mod tests {
}

// safety: nothing holds on to references into the table any more
let mut t = unsafe { table.into_owned() };
let mut t = unsafe { table.into_box() };
t.drop_bins();
// no one besides this test case uses deep_table
unsafe { deep_table.into_owned() }.drop_bins();
unsafe { deep_table.into_box() }.drop_bins();
}
}
Loading