Skip to content
Open
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
1 change: 1 addition & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
with:
reporter: 'github-pr-check'
github_token: ${{ secrets.GITHUB_TOKEN }}
clippy_flags: --all-features
semver:
runs-on: ubuntu-latest
name: semver
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/safety.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ jobs:
with:
toolchain: ${{ env.NIGHTLY }}
components: miri
- name: cargo miri test
- name: cargo miri test --all-features
run: cargo miri test
loom:
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license = "MIT"
rust-version = "1.72.0"

readme = "README.md"
description = "A concurrent, append-only vector"
description = "A concurrent, append-only vector and slot map"
repository = "https://github.com/ibraheemdev/boxcar"

categories = ["concurrency", "data-structures"]
Expand All @@ -22,6 +22,7 @@ name = "bench"
harness = false

[dependencies]
serde = { version = "1.0.219", optional = true }

[target.'cfg(loom)'.dependencies]
loom = { version = "0.7", optional = true }
Expand All @@ -31,3 +32,4 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] }

[dev-dependencies]
criterion = "0.5"
serde_json = "1.0.140"
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
[<img alt="github" src="https://img.shields.io/badge/github-boxcar-blue?style=for-the-badge" height="25">](https://github.com/ibraheemdev/boxcar)
[<img alt="docs.rs" src="https://img.shields.io/docsrs/boxcar?style=for-the-badge" height="25">](https://docs.rs/boxcar)

A concurrent, append-only vector.
A concurrent, append-only vector and slot map.

The vector provided by this crate supports lock-free `get` and `push` operations.
The vector grows internally but never reallocates, so element addresses are stable
for the lifetime of the vector. Additionally, both `get` and `push` run in constant-time.
The vector provided by this crate supports lock-free `get` and `push` operations,
and the slot map supports lock-free `get` and `insert`s. They grow internally but
never reallocate, so element addresses are stable for their lifetime.
Additionally, both `get`, `push` and `insert` run in constant-time.

We also expose a low-level `Buckets` primitive for building your own similar collections.

## Examples

Expand Down Expand Up @@ -58,3 +61,8 @@ std::thread::scope(|s| {
let x = vec[0].lock().unwrap();
assert_eq!(*x, 1);
```


## Feature flags

The `serde` feature flag implements `Serialize` and `Deserialize` for the slot map and key types.
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ pub use vec::Vec;
// Reexports for backward compatibility.
pub use vec::IntoIter;
pub use vec::Iter;

pub mod slotmap;
pub use slotmap::SlotMap;
52 changes: 39 additions & 13 deletions src/loom.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
pub use self::inner::*;

pub trait AtomicMut<T> {
fn read_mut(&mut self) -> T;
#[inline(always)]
fn read_mut(&mut self) -> T {
unsafe { self.unsync_load() }
}
unsafe fn unsync_load(&self) -> T;
fn write_mut(&mut self, value: T);
}

Expand All @@ -10,18 +14,28 @@ mod inner {
pub use loom::{cell, sync::atomic};

impl super::AtomicMut<bool> for atomic::AtomicBool {
fn read_mut(&mut self) -> bool {
self.load(atomic::Ordering::Relaxed)
unsafe fn unsync_load(&self) -> bool {
unsafe { self.unsync_load() }
}

fn write_mut(&mut self, value: bool) {
self.store(value, atomic::Ordering::Relaxed)
}
}

impl super::AtomicMut<u32> for atomic::AtomicU32 {
unsafe fn unsync_load(&self) -> u32 {
unsafe { self.unsync_load() }
}

fn write_mut(&mut self, value: u32) {
self.store(value, atomic::Ordering::Relaxed)
}
}

impl super::AtomicMut<usize> for atomic::AtomicUsize {
fn read_mut(&mut self) -> usize {
self.load(atomic::Ordering::Relaxed)
unsafe fn unsync_load(&self) -> usize {
unsafe { self.unsync_load() }
}

fn write_mut(&mut self, value: usize) {
Expand All @@ -30,8 +44,8 @@ mod inner {
}

impl<T> super::AtomicMut<*mut T> for atomic::AtomicPtr<T> {
fn read_mut(&mut self) -> *mut T {
self.load(atomic::Ordering::Relaxed)
unsafe fn unsync_load(&self) -> *mut T {
unsafe { self.unsync_load() }
}

fn write_mut(&mut self, value: *mut T) {
Expand All @@ -46,8 +60,8 @@ mod inner {

impl super::AtomicMut<bool> for atomic::AtomicBool {
#[inline(always)]
fn read_mut(&mut self) -> bool {
*self.get_mut()
unsafe fn unsync_load(&self) -> bool {
unsafe { *self.as_ptr() }
}

#[inline(always)]
Expand All @@ -56,10 +70,22 @@ mod inner {
}
}

impl super::AtomicMut<u32> for atomic::AtomicU32 {
#[inline(always)]
unsafe fn unsync_load(&self) -> u32 {
unsafe { *self.as_ptr() }
}

#[inline(always)]
fn write_mut(&mut self, value: u32) {
*self.get_mut() = value;
}
}

impl super::AtomicMut<usize> for atomic::AtomicUsize {
#[inline(always)]
fn read_mut(&mut self) -> usize {
*self.get_mut()
unsafe fn unsync_load(&self) -> usize {
unsafe { *self.as_ptr() }
}

#[inline(always)]
Expand All @@ -70,8 +96,8 @@ mod inner {

impl<T> super::AtomicMut<*mut T> for atomic::AtomicPtr<T> {
#[inline(always)]
fn read_mut(&mut self) -> *mut T {
*self.get_mut()
unsafe fn unsync_load(&self) -> *mut T {
unsafe { *self.as_ptr() }
}

#[inline(always)]
Expand Down
Loading
Loading