#![allow(dead_code)]
use cdpt::Handle;
use fastrand::shuffle;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread::{available_parallelism, scope};
pub trait ValueRef<V> {
fn borrow(&self) -> &V;
}
pub trait ConcurrentMap<K, V>: Send + Sync
where
K: Send + Sync,
V: Send + Sync,
{
type ValueRef<'h>: ValueRef<V>
where
Self: 'h;
fn new() -> Self;
fn get<'h>(&self, key: &K, handle: &'h Handle) -> Option<Self::ValueRef<'h>>;
fn insert(&self, key: K, value: V, handle: &Handle) -> bool;
fn remove<'h>(&self, key: &K, handle: &'h Handle) -> Option<Self::ValueRef<'h>>;
}
pub const SMOKE_THREADS: i32 = 30;
pub const SMOKE_ELEMENTS_PER_THREAD: i32 = 1000;
pub fn smoke<M>()
where
M: ConcurrentMap<i32, String>,
for<'h> M::ValueRef<'h>: ValueRef<String>,
{
use cdpt::handle;
let map = &M::new();
scope(|s| {
for t in 0..SMOKE_THREADS {
s.spawn(move || {
let handle = handle();
let mut keys: Vec<i32> = (0..SMOKE_ELEMENTS_PER_THREAD)
.map(|k| k * SMOKE_THREADS + t)
.collect();
shuffle(&mut keys);
for i in keys {
assert!(map.insert(i, i.to_string(), &handle));
}
});
}
});
scope(|s| {
for t in 0..(SMOKE_THREADS / 2) {
s.spawn(move || {
let handle = handle();
let mut keys: Vec<i32> = (0..SMOKE_ELEMENTS_PER_THREAD)
.map(|k| k * SMOKE_THREADS + t)
.collect();
shuffle(&mut keys);
for i in keys {
assert_eq!(
Some(&i.to_string()),
map.remove(&i, &handle).as_ref().map(|v| v.borrow())
);
}
});
}
});
scope(|s| {
for t in (SMOKE_THREADS / 2)..SMOKE_THREADS {
s.spawn(move || {
let handle = handle();
let mut keys: Vec<i32> = (0..SMOKE_ELEMENTS_PER_THREAD)
.map(|k| k * SMOKE_THREADS + t)
.collect();
shuffle(&mut keys);
for i in keys {
assert_eq!(
Some(&i.to_string()),
map.get(&i, &handle).as_ref().map(|v| v.borrow())
);
}
});
}
});
}
pub fn test_basic_operations<M>()
where
M: ConcurrentMap<i32, String>,
for<'h> M::ValueRef<'h>: ValueRef<String>,
{
use cdpt::handle;
let handle = handle();
let map = M::new();
assert!(map.get(&1, &handle).is_none());
assert!(map.remove(&1, &handle).is_none());
assert!(map.insert(1, "one".to_string(), &handle));
assert_eq!(
Some(&"one".to_string()),
map.get(&1, &handle).as_ref().map(|v| v.borrow())
);
assert!(!map.insert(1, "one again".to_string(), &handle));
assert_eq!(
Some(&"one".to_string()),
map.get(&1, &handle).as_ref().map(|v| v.borrow())
);
assert_eq!(
Some(&"one".to_string()),
map.remove(&1, &handle).as_ref().map(|v| v.borrow())
);
assert!(map.get(&1, &handle).is_none());
assert!(map.remove(&1, &handle).is_none());
}
pub fn test_multiple_elements<M>()
where
M: ConcurrentMap<i32, String>,
for<'h> M::ValueRef<'h>: ValueRef<String>,
{
use cdpt::handle;
let handle = handle();
let map = M::new();
for i in 0..100 {
assert!(map.insert(i, i.to_string(), &handle));
}
for i in 0..100 {
assert_eq!(
Some(&i.to_string()),
map.get(&i, &handle).as_ref().map(|v| v.borrow())
);
}
for i in (1..100).step_by(2) {
assert_eq!(
Some(&i.to_string()),
map.remove(&i, &handle).as_ref().map(|v| v.borrow())
);
}
for i in 0..100 {
if i % 2 == 0 {
assert_eq!(
Some(&i.to_string()),
map.get(&i, &handle).as_ref().map(|v| v.borrow())
);
} else {
assert!(map.get(&i, &handle).is_none());
}
}
}
pub fn test_reverse_order_insert<M>()
where
M: ConcurrentMap<i32, String>,
for<'h> M::ValueRef<'h>: ValueRef<String>,
{
use cdpt::handle;
let handle = handle();
let map = M::new();
for i in (0..100).rev() {
assert!(map.insert(i, i.to_string(), &handle));
}
for i in 0..100 {
assert_eq!(
Some(&i.to_string()),
map.get(&i, &handle).as_ref().map(|v| v.borrow())
);
}
}
pub fn test_concurrent_insert_remove<M>()
where
M: ConcurrentMap<i32, String>,
for<'h> M::ValueRef<'h>: ValueRef<String>,
{
use cdpt::handle;
const THREADS: usize = 8;
const OPS_PER_THREAD: usize = 1000;
let map = &M::new();
let successful_inserts = &AtomicUsize::new(0);
let successful_removes = &AtomicUsize::new(0);
scope(|s| {
for _ in 0..(THREADS / 2) {
s.spawn(|| {
let handle = handle();
for i in 0..OPS_PER_THREAD as i32 {
if map.insert(i, i.to_string(), &handle) {
successful_inserts.fetch_add(1, Ordering::Relaxed);
}
}
});
}
for _ in 0..(THREADS / 2) {
s.spawn(|| {
let handle = handle();
for i in 0..OPS_PER_THREAD as i32 {
if map.remove(&i, &handle).is_some() {
successful_removes.fetch_add(1, Ordering::Relaxed);
}
}
});
}
});
let remaining_inserts = successful_inserts.load(Ordering::Relaxed);
let total_removes = successful_removes.load(Ordering::Relaxed);
let handle = handle();
let mut remaining = 0;
for i in 0..OPS_PER_THREAD as i32 {
if map.get(&i, &handle).is_some() {
remaining += 1;
}
}
assert_eq!(remaining_inserts - total_removes, remaining);
}
pub struct StressConfig {
pub threads: usize,
pub ops_per_thread: usize,
pub key_range: i32,
}
impl Default for StressConfig {
fn default() -> Self {
Self {
threads: 16,
ops_per_thread: 10000,
key_range: 1000,
}
}
}
impl StressConfig {
pub fn for_list() -> Self {
Self {
threads: available_parallelism().map(|t| t.get()).unwrap_or(16),
ops_per_thread: 10 * 1000 * 1000, key_range: 50, }
}
pub fn for_tree() -> Self {
Self {
threads: available_parallelism().map(|t| t.get()).unwrap_or(16),
ops_per_thread: 10 * 1000 * 1000, key_range: 1000,
}
}
}
pub fn stress_test_with_config<M>(config: StressConfig)
where
M: ConcurrentMap<i32, String>,
for<'h> M::ValueRef<'h>: ValueRef<String>,
{
use cdpt::handle;
let map = &M::new();
scope(|s| {
for thread_id in 0..config.threads {
s.spawn(move || {
let handle = handle();
let mut rng = fastrand::Rng::with_seed(thread_id as u64);
for _ in 0..config.ops_per_thread {
let key = rng.i32(0..config.key_range);
let op = rng.u8(0..3);
match op {
0 => {
let _ = map.insert(key, key.to_string(), &handle);
}
1 => {
if let Some(v) = map.get(&key, &handle) {
assert_eq!(v.borrow(), &key.to_string());
}
}
2 => {
if let Some(v) = map.remove(&key, &handle) {
assert_eq!(v.borrow(), &key.to_string());
}
}
_ => unreachable!(),
}
}
});
}
});
}
pub fn stress_test<M>()
where
M: ConcurrentMap<i32, String>,
for<'h> M::ValueRef<'h>: ValueRef<String>,
{
stress_test_with_config::<M>(StressConfig::for_tree());
}
pub fn stress_test_list<M>()
where
M: ConcurrentMap<i32, String>,
for<'h> M::ValueRef<'h>: ValueRef<String>,
{
stress_test_with_config::<M>(StressConfig::for_list());
}