use photon_ring::{channel_bounded, TryRecvError};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
#[test]
fn lossy_subscriber_never_gates_publisher() {
let (mut p, s) = channel_bounded::<u64>(16, 0);
let _idle = s.subscribe_lossy();
for i in 0..10_000u64 {
p.try_publish(i)
.expect("lossy subscriber must not exert backpressure");
}
assert_eq!(p.published(), 10_000);
}
#[test]
fn tracked_subscriber_does_gate_publisher() {
let (mut p, s) = channel_bounded::<u64>(16, 0);
let _idle = s.subscribe();
for i in 0..16u64 {
p.try_publish(i).expect("ring has room");
}
assert!(
p.try_publish(99).is_err(),
"a tracked subscriber that has read nothing must stop the publisher"
);
}
#[test]
fn mixed_criticality_on_one_ring() {
let (mut p, s) = channel_bounded::<u64>(8, 0);
let mut critical = s.subscribe();
let mut telemetry = s.subscribe_lossy();
for i in 0..64u64 {
p.publish(i);
assert_eq!(
critical.try_recv(),
Ok(i),
"tracked consumer must lose nothing"
);
}
assert_eq!(critical.total_lagged(), 0);
let mut lagged = false;
loop {
match telemetry.try_recv() {
Ok(_) => {}
Err(TryRecvError::Lagged { skipped }) => {
assert!(skipped > 0);
lagged = true;
}
Err(TryRecvError::Empty) => break,
}
}
assert!(lagged, "lossy consumer should have observed lag");
assert!(telemetry.total_lagged() > 0);
assert!(telemetry.receive_ratio() < 1.0);
}
#[test]
fn subscribers_share_a_sequence_space() {
let (mut p, s) = channel_bounded::<u64>(16, 0);
let mut critical = s.subscribe();
let mut tap = s.subscribe_lossy();
p.publish(100);
p.publish(200);
assert_eq!(critical.cursor(), tap.cursor());
assert_eq!(critical.try_recv(), Ok(100));
assert_eq!(tap.try_recv(), Ok(100));
assert_eq!(
critical.cursor(),
tap.cursor(),
"same message, same sequence"
);
}
#[test]
fn dead_consumer_releases_the_publisher() {
let (mut p, s) = channel_bounded::<u64>(16, 0);
let mut sub = s.subscribe();
let consumer = std::thread::spawn(move || {
loop {
if let Ok(v) = sub.try_recv() {
if v >= 4 {
panic!("consumer died"); }
}
}
});
let done = Arc::new(AtomicBool::new(false));
let done_w = done.clone();
let count = Arc::new(AtomicU64::new(0));
let count_w = count.clone();
let writer = std::thread::spawn(move || {
for i in 0..5_000u64 {
p.publish(i);
count_w.store(i + 1, Ordering::Relaxed);
}
done_w.store(true, Ordering::Relaxed);
});
let start = Instant::now();
while !done.load(Ordering::Relaxed) && start.elapsed() < Duration::from_secs(30) {
std::thread::yield_now();
}
assert!(
done.load(Ordering::Relaxed),
"publisher wedged after the consumer died: only {} of 5000 published",
count.load(Ordering::Relaxed)
);
let _ = consumer.join(); writer.join().expect("writer should finish");
}
#[test]
fn lossy_subscriber_hot_attaches_and_detaches() {
let (mut p, s) = channel_bounded::<u64>(16, 0);
let mut critical = s.subscribe();
for i in 0..8u64 {
p.publish(i);
assert_eq!(critical.try_recv(), Ok(i));
}
{
let mut tap = s.subscribe_lossy();
p.publish(99);
assert_eq!(critical.try_recv(), Ok(99));
assert_eq!(tap.try_recv(), Ok(99));
}
for i in 100..200u64 {
p.publish(i);
assert_eq!(critical.try_recv(), Ok(i));
}
assert_eq!(critical.total_lagged(), 0);
}
#[test]
fn losing_the_last_tracked_subscriber_unbounds_the_ring() {
let (mut p, s) = channel_bounded::<u64>(8, 0);
let mut lossy = s.subscribe_lossy();
{
let _critical = s.subscribe();
for i in 0..8u64 {
p.try_publish(i).expect("ring has room");
}
assert!(p.try_publish(99).is_err(), "tracked subscriber gates here");
}
for i in 0..1_000u64 {
p.try_publish(i)
.expect("nothing gates the publisher any more");
}
let mut lagged = false;
while let Err(TryRecvError::Lagged { .. }) | Ok(_) = lossy.try_recv() {
if lossy.total_lagged() > 0 {
lagged = true;
break;
}
}
assert!(lagged, "lossy subscriber should observe the lap as lag");
}