#![cfg(feature = "scheduler")]
use std::sync::{
Arc, Mutex,
atomic::{AtomicUsize, Ordering},
};
use hyphae::{
Cell, Gettable, MergeExt, MergeMapExt, Mutable, Signal, SwitchMapExt, Watchable, batch,
scheduler::no_coalesce,
};
const WIDTH: 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_merges_never_drop_or_duplicate_and_complete_once() {
let _serial = scheduler_test_serial();
let mut lefts = Vec::new();
let mut rights = Vec::new();
let mut received = Vec::new();
let mut completes = Vec::new();
let mut guards = Vec::new();
for _ in 0..WIDTH {
let (l, r, m) = no_coalesce(|| {
let l = Cell::new(0i64);
let r = Cell::new(0i64);
let m = l.merge(&r);
(l, r, m)
});
let got = Arc::new(Mutex::new(Vec::<i64>::new()));
let done = Arc::new(AtomicUsize::new(0));
let (g, d) = (got.clone(), done.clone());
let guard = m.subscribe(move |sig| match sig {
Signal::Value(v) => g.lock().unwrap().push(**v),
Signal::Complete => {
d.fetch_add(1, Ordering::SeqCst);
}
Signal::Error(_) => {}
});
lefts.push(l);
rights.push(r);
received.push(got);
completes.push(done);
guards.push(guard);
}
for it in 1..=ITERATIONS {
for buf in &received {
buf.lock().unwrap().clear();
}
batch(|| {
#[allow(clippy::needless_range_loop)]
for i in 0..WIDTH {
let base = it * 1000 + (i as i64) * 2;
lefts[i].set(base);
rights[i].set(base + 1);
}
});
#[allow(clippy::needless_range_loop)]
for i in 0..WIDTH {
let base = it * 1000 + (i as i64) * 2;
let mut got = received[i].lock().unwrap().clone();
got.sort_unstable();
assert_eq!(
got,
vec![base, base + 1],
"merge dropped/duplicated/mis-forwarded an emission at iteration {it}, merge {i}"
);
}
}
batch(|| {
#[allow(clippy::needless_range_loop)]
for i in 0..WIDTH {
lefts[i].complete();
rights[i].complete();
}
});
#[allow(clippy::needless_range_loop)]
for i in 0..WIDTH {
assert_eq!(
completes[i].load(Ordering::SeqCst),
1,
"merge {i} fired Complete {} times (expected exactly 1)",
completes[i].load(Ordering::SeqCst)
);
}
drop(guards);
}
#[test]
fn concurrent_merge_maps_complete_exactly_once() {
let _serial = scheduler_test_serial();
for it in 0..ITERATIONS {
let mut outers = Vec::new();
let mut inner_srcs = Vec::new();
let mut completes = Vec::new();
let mut guards = Vec::new();
no_coalesce(|| {
for _ in 0..WIDTH {
let outer = Cell::new(0i64);
let inner_src = Cell::new(0i64);
let ic = inner_src.clone();
let mm = outer.merge_map(move |_: &i64| ic.clone().lock());
let done = Arc::new(AtomicUsize::new(0));
let d = done.clone();
let guard = mm.subscribe(move |sig| {
if matches!(sig, Signal::Complete) {
d.fetch_add(1, Ordering::SeqCst);
}
});
outers.push(outer);
inner_srcs.push(inner_src);
completes.push(done);
guards.push(guard);
}
});
batch(|| {
#[allow(clippy::needless_range_loop)]
for i in 0..WIDTH {
outers[i].complete();
inner_srcs[i].complete();
}
});
#[allow(clippy::needless_range_loop)]
for i in 0..WIDTH {
assert_eq!(
completes[i].load(Ordering::SeqCst),
1,
"merge_map fired Complete {} times at iteration {it}, unit {i} (expected exactly 1)",
completes[i].load(Ordering::SeqCst)
);
}
drop(guards);
}
}
#[test]
fn concurrent_switch_map_latest_inner_wins_same_height() {
let _serial = scheduler_test_serial();
for it in 1..=ITERATIONS {
let mut sels = Vec::new();
let mut old_srcs = Vec::new();
let mut results = Vec::new();
for i in 0..WIDTH {
let sel = Cell::new(0i64); let old_src = Cell::new(0i64);
let bval = it * 1000 + i as i64 + 500;
let new_src = Cell::new(bval);
let old = old_src.clone().lock();
let new = new_src.clone().lock();
let result = sel.switch_map(move |&k| if k == 0 { old.clone() } else { new.clone() });
sels.push(sel);
old_srcs.push(old_src);
results.push(result);
}
batch(|| {
#[allow(clippy::needless_range_loop)]
for i in 0..WIDTH {
let aval = it * 1000 + i as i64; old_srcs[i].set(aval);
sels[i].set(1); }
});
#[allow(clippy::needless_range_loop)]
for i in 0..WIDTH {
let bval = it * 1000 + i as i64 + 500;
assert_eq!(
results[i].get(),
bval,
"switch_map settled on a STALE old-inner value at iteration {it}, unit {i} \
(expected the switched-in inner's {bval})"
);
}
}
}