#![cfg(feature = "scheduler")]
use std::{
sync::{
Arc, Mutex,
atomic::{AtomicI64, Ordering},
},
thread,
time::{Duration, Instant},
};
use hyphae::{
AuditExt, BackpressureExt, BufferTimeExt, Cell, ColdExt, ConcatExt, DebounceExt, DelayExt,
FinalizeExt, Gettable, MaterializeDefinite, MaterializeEmpty, Mutable, ParallelExt, RetryExt,
Signal, ThrottleExt, TimeoutExt, Watchable, batch,
};
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())
}
const WIDE: usize = 16;
fn wait_until(deadline: Duration, mut settled: impl FnMut() -> bool) -> bool {
let start = Instant::now();
loop {
if settled() {
return true;
}
if start.elapsed() >= deadline {
return false;
}
thread::sleep(Duration::from_millis(5));
}
}
const SETTLE_DEADLINE: Duration = Duration::from_secs(5);
#[test]
fn debounce_wide_parallel_input_wave_all_fire() {
let _serial = scheduler_test_serial();
let dur = Duration::from_millis(30);
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources.iter().map(|s| s.debounce(dur)).collect();
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(1000 + i as i64);
}
});
wait_until(SETTLE_DEADLINE, || {
outs.iter()
.enumerate()
.all(|(i, o)| o.get() == 1000 + i as i64)
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
1000 + i as i64,
"debounce instance {i} settled on the wrong value under a wide input wave"
);
}
}
#[test]
fn throttle_wide_parallel_wave_leading_emit_correct() {
let _serial = scheduler_test_serial();
let dur = Duration::from_millis(20);
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources.iter().map(|s| s.throttle(dur)).collect();
for round in 0..3i64 {
thread::sleep(Duration::from_millis(60));
let base = (round + 1) * 1000;
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(base + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
base + i as i64,
"throttle instance {i} leading-edge emit wrong under a wide wave (round {round})"
);
}
}
}
#[test]
fn delay_wide_parallel_input_wave_all_fire() {
let _serial = scheduler_test_serial();
let dur = Duration::from_millis(30);
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources.iter().map(|s| s.delay(dur)).collect();
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(2000 + i as i64);
}
});
wait_until(SETTLE_DEADLINE, || {
outs.iter()
.enumerate()
.all(|(i, o)| o.get() == 2000 + i as i64)
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
2000 + i as i64,
"delay instance {i} delivered the wrong value under a wide input wave"
);
}
}
#[test]
fn timeout_wide_parallel_wave_value_passthrough_correct() {
let _serial = scheduler_test_serial();
const ITERS: i64 = 100;
let dur = Duration::from_millis(500);
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources.iter().map(|s| s.timeout(dur)).collect();
for it in 1..=ITERS {
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(it * 1000 + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
it * 1000 + i as i64,
"timeout instance {i} passed a torn/stale value under a wide wave (iter {it})"
);
}
}
}
#[test]
fn buffer_time_wide_parallel_input_wave_all_collect() {
let _serial = scheduler_test_serial();
let dur = Duration::from_millis(30);
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources.iter().map(|s| s.buffer_time(dur)).collect();
let seen: Vec<Arc<Mutex<Vec<i64>>>> = (0..WIDE)
.map(|_| Arc::new(Mutex::new(Vec::new())))
.collect();
let mut guards = Vec::new();
for (i, out) in outs.iter().enumerate() {
let sink = seen[i].clone();
guards.push(out.subscribe(move |sig| {
if let Signal::Value(v) = sig {
sink.lock().unwrap().extend(v.iter().copied());
}
}));
}
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(3000 + i as i64);
}
});
wait_until(SETTLE_DEADLINE, || {
seen.iter()
.enumerate()
.all(|(i, s)| *s.lock().unwrap() == vec![3000 + i as i64])
});
for (i, s) in seen.iter().enumerate() {
assert_eq!(
*s.lock().unwrap(),
vec![3000 + i as i64],
"buffer_time instance {i} lost/leaked its buffered value under a wide input wave"
);
}
drop(guards);
}
#[test]
fn audit_wide_parallel_input_wave_all_fire() {
let _serial = scheduler_test_serial();
let dur = Duration::from_millis(30);
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources.iter().map(|s| s.audit(dur)).collect();
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(4000 + i as i64);
}
});
wait_until(SETTLE_DEADLINE, || {
outs.iter()
.enumerate()
.all(|(i, o)| o.get() == 4000 + i as i64)
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
4000 + i as i64,
"audit instance {i} sampled the wrong last value under a wide input wave"
);
}
}
#[test]
fn backpressure_drop_oldest_wide_parallel_wave_correct() {
let _serial = scheduler_test_serial();
const ITERS: i64 = 200;
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources.iter().map(|s| s.drop_oldest(8)).collect();
for it in 1..=ITERS {
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(it * 1000 + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
it * 1000 + i as i64,
"drop_oldest instance {i} wrong under a wide wave (iter {it})"
);
}
}
}
#[test]
fn backpressure_drop_newest_wide_parallel_wave_correct() {
let _serial = scheduler_test_serial();
const ITERS: i64 = 200;
const CAP: i64 = 8;
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources
.iter()
.map(|s| s.drop_newest(CAP as usize))
.collect();
for it in 1..=ITERS {
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(it * 1000 + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
let expected = it.min(CAP) * 1000 + i as i64;
assert_eq!(
out.get(),
expected,
"drop_newest instance {i} wrong under a wide wave (iter {it})"
);
}
}
}
#[test]
fn backpressure_sample_latest_wide_parallel_wave_correct() {
let _serial = scheduler_test_serial();
const ITERS: i64 = 200;
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources.iter().map(|s| s.sample_latest()).collect();
for it in 1..=ITERS {
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(it * 1000 + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
it * 1000 + i as i64,
"sample_latest instance {i} wrong under a wide wave (iter {it})"
);
}
}
}
#[test]
fn concat_wide_parallel_wave_both_sides_correct() {
let _serial = scheduler_test_serial();
let firsts: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let seconds: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = firsts
.iter()
.zip(seconds.iter())
.map(|(f, s)| f.concat(s))
.collect();
batch(|| {
for (i, f) in firsts.iter().enumerate() {
f.set(5000 + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
5000 + i as i64,
"concat instance {i} wrong on the first-input side under a wide wave"
);
}
for f in &firsts {
f.complete();
}
batch(|| {
for (i, s) in seconds.iter().enumerate() {
s.set(6000 + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
6000 + i as i64,
"concat instance {i} wrong on the second-input side after the completion hand-off"
);
}
}
#[test]
fn cold_wide_parallel_wave_settles_some() {
let _serial = scheduler_test_serial();
const ITERS: i64 = 200;
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources
.iter()
.map(|s| s.clone().cold().materialize())
.collect();
for (i, out) in outs.iter().enumerate() {
assert_eq!(out.get(), None, "cold instance {i} should start None");
}
for it in 1..=ITERS {
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(it * 1000 + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
Some(Arc::new(it * 1000 + i as i64)),
"cold instance {i} settled wrong under a wide wave (iter {it})"
);
}
}
}
#[test]
fn finalize_wide_parallel_wave_passthrough_and_terminal() {
let _serial = scheduler_test_serial();
const ITERS: i64 = 200;
let flags: Vec<Arc<AtomicI64>> = (0..WIDE).map(|_| Arc::new(AtomicI64::new(0))).collect();
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources
.iter()
.zip(flags.iter())
.map(|(s, flag)| {
let flag = flag.clone();
s.clone()
.finalize(move || {
flag.fetch_add(1, Ordering::SeqCst);
})
.materialize()
})
.collect();
for it in 1..=ITERS {
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(it * 1000 + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
it * 1000 + i as i64,
"finalize instance {i} passed a wrong value under a wide wave (iter {it})"
);
}
}
for s in &sources {
s.complete();
}
for (i, flag) in flags.iter().enumerate() {
assert_eq!(
flag.load(Ordering::SeqCst),
1,
"finalize instance {i} terminal callback did not fire exactly once"
);
}
}
#[test]
fn retry_wide_parallel_wave_value_passthrough() {
let _serial = scheduler_test_serial();
const ITERS: i64 = 200;
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let outs: Vec<_> = sources.iter().map(|s| s.retry(1_000)).collect();
for it in 1..=ITERS {
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(it * 1000 + i as i64);
}
});
for (i, out) in outs.iter().enumerate() {
assert_eq!(
out.get(),
it * 1000 + i as i64,
"retry instance {i} passed a wrong value under a wide wave (iter {it})"
);
}
}
}
#[test]
fn parallel_wide_parallel_input_wave_correct() {
let _serial = scheduler_test_serial();
const ITERS: i64 = 200;
let sources: Vec<Cell<i64, _>> = (0..WIDE).map(|_| Cell::new(0i64)).collect();
let cells: Vec<_> = sources.iter().map(|s| s.parallel()).collect();
let slots: Vec<Arc<AtomicI64>> = (0..WIDE)
.map(|_| Arc::new(AtomicI64::new(i64::MIN)))
.collect();
let mut guards = Vec::new();
for (i, cell) in cells.iter().enumerate() {
let slot = slots[i].clone();
guards.push(cell.subscribe(move |sig| {
if let Signal::Value(v) = sig {
slot.store(**v, Ordering::SeqCst);
}
}));
}
for it in 1..=ITERS {
batch(|| {
for (i, s) in sources.iter().enumerate() {
s.set(it * 1000 + i as i64);
}
});
for (i, slot) in slots.iter().enumerate() {
assert_eq!(
slot.load(Ordering::SeqCst),
it * 1000 + i as i64,
"parallel instance {i} delivered a wrong value under a wide wave (iter {it})"
);
}
}
drop(guards);
}