#![cfg(feature = "scheduler")]
use hyphae::{
CellMap, MapQuery, batch,
traits::{LeftJoinExt, LeftSemiJoinExt, MultiLeftJoinExt},
};
const PAIRS: usize = 8;
const ITERATIONS: i64 = 3_000;
fn scheduler_test_serial() -> std::sync::MutexGuard<'static, ()> {
hyphae::scheduler::set_wave_threshold_for_test(4);
static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
LOCK.lock().unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[test]
fn concurrent_left_joins_never_settle_on_a_torn_value() {
let _serial = scheduler_test_serial();
let mut lefts = Vec::new();
let mut rights = Vec::new();
let mut outputs = Vec::new();
for _ in 0..PAIRS {
let l = CellMap::<String, i64>::new();
let r = CellMap::<String, i64>::new();
l.insert("k".into(), 0);
r.insert("k".into(), 0);
let out = l.clone().left_join(r.clone()).materialize();
lefts.push(l);
rights.push(r);
outputs.push(out);
}
for it in 1..=ITERATIONS {
batch(|| {
#[allow(clippy::needless_range_loop)]
for i in 0..PAIRS {
let base = it * 1000 + i as i64;
lefts[i].insert("k".into(), base);
rights[i].insert("k".into(), base + 500);
}
});
#[allow(clippy::needless_range_loop)]
for i in 0..PAIRS {
let base = it * 1000 + i as i64;
assert_eq!(
outputs[i].get_value(&"k".to_string()),
Some((base, vec![base + 500])),
"left_join settled on a torn/stale value at iteration {it}, pair {i}"
);
}
}
}
#[test]
fn concurrent_left_semi_joins_never_settle_on_a_stale_left_value() {
let _serial = scheduler_test_serial();
let mut lefts = Vec::new();
let mut rights = Vec::new();
let mut outputs = Vec::new();
for _ in 0..PAIRS {
let l = CellMap::<String, i64>::new();
let r = CellMap::<String, i64>::new();
l.insert("k".into(), 0);
r.insert("k".into(), 0);
let out = l.clone().left_semi_join(r.clone()).materialize();
lefts.push(l);
rights.push(r);
outputs.push(out);
}
for it in 1..=ITERATIONS {
batch(|| {
#[allow(clippy::needless_range_loop)]
for i in 0..PAIRS {
let base = it * 1000 + i as i64;
lefts[i].insert("k".into(), base);
rights[i].insert("k".into(), base + 500);
}
});
#[allow(clippy::needless_range_loop)]
for i in 0..PAIRS {
let base = it * 1000 + i as i64;
assert_eq!(
outputs[i].get_value(&"k".to_string()),
Some(base),
"left_semi_join settled on a stale left value at iteration {it}, pair {i}"
);
}
}
}
#[test]
fn concurrent_multi_left_joins_never_settle_on_a_torn_value() {
let _serial = scheduler_test_serial();
let mut lefts = Vec::new();
let mut rights = Vec::new();
let mut outputs = Vec::new();
for _ in 0..PAIRS {
let l = CellMap::<String, i64>::new();
let r = CellMap::<String, i64>::new();
l.insert("k".into(), 0);
r.insert("k".into(), 0);
let out = l
.clone()
.multi_left_join_by(
r.clone(),
|k: &String, _v: &i64| vec![k.clone()],
|k: &String, _v: &i64| k.clone(),
)
.materialize();
lefts.push(l);
rights.push(r);
outputs.push(out);
}
for it in 1..=ITERATIONS {
batch(|| {
#[allow(clippy::needless_range_loop)]
for i in 0..PAIRS {
let base = it * 1000 + i as i64;
lefts[i].insert("k".into(), base);
rights[i].insert("k".into(), base + 500);
}
});
#[allow(clippy::needless_range_loop)]
for i in 0..PAIRS {
let base = it * 1000 + i as i64;
assert_eq!(
outputs[i].get_value(&"k".to_string()),
Some((base, vec![base + 500])),
"multi_left_join_by settled on a torn/stale value at iteration {it}, pair {i}"
);
}
}
}