use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::thread;
use std::time::{Duration, Instant};
use subetha_cxc::capacity_adaptive_ring::{
CapacityAdaptiveRing, CapacityAdaptiveRingSidecar, DefaultCapacityPolicy,
};
const INITIAL_CAPACITY: usize = 256;
const N_CYCLES: usize = 6;
const BURST_ITEMS: u64 = 8_000;
const DRAIN_PAUSE_MS: u64 = 250;
fn main() {
println!("=== CapacityAdaptiveRingSidecar E2E ===");
println!("Initial capacity: {INITIAL_CAPACITY}");
println!("Cycles: {N_CYCLES} (each: producer bursts {BURST_ITEMS} items,");
println!("then pauses {DRAIN_PAUSE_MS}ms while consumer drains)");
println!();
let ring = Arc::new(
CapacityAdaptiveRing::create_anon(1, 1, INITIAL_CAPACITY)
.expect("create_anon"),
);
ring.register_producer().expect("register producer");
ring.register_consumer().expect("register consumer");
let policy = DefaultCapacityPolicy {
grow_at: 0.75,
shrink_at: 0.10,
min_capacity: 64,
max_capacity: 8192,
hysteresis: Duration::from_millis(50),
};
let sidecar = CapacityAdaptiveRingSidecar::spawn(
Arc::clone(&ring),
policy,
Duration::from_millis(10),
);
let stop_obs = Arc::new(AtomicBool::new(false));
let stop_obs_c = Arc::clone(&stop_obs);
let ring_obs = Arc::clone(&ring);
let t0 = Instant::now();
let obs_h = thread::spawn(move || {
let mut last_cap = ring_obs.current_capacity();
println!("[{:6.3}s] start cap = {last_cap}", t0.elapsed().as_secs_f64());
while !stop_obs_c.load(Ordering::Acquire) {
let now_cap = ring_obs.current_capacity();
if now_cap != last_cap {
let direction = if now_cap > last_cap { "GROW" } else { "SHRINK" };
println!(
"[{:6.3}s] {direction} {} -> {}",
t0.elapsed().as_secs_f64(),
last_cap,
now_cap
);
last_cap = now_cap;
}
thread::sleep(Duration::from_millis(5));
}
});
let r_prod = Arc::clone(&ring);
let producer = thread::spawn(move || {
for cycle in 0..N_CYCLES {
for i in 0..BURST_ITEMS {
let mut payload = [0u8; 56];
payload[..8].copy_from_slice(&((cycle as u64) << 32 | i).to_le_bytes());
while r_prod.try_send(0, &payload).is_err() {
std::hint::spin_loop();
}
}
thread::sleep(Duration::from_millis(DRAIN_PAUSE_MS));
}
});
let r_cons = Arc::clone(&ring);
let total_items = N_CYCLES as u64 * BURST_ITEMS;
let drained = Arc::new(AtomicU64::new(0));
let drained_c = Arc::clone(&drained);
let consumer = thread::spawn(move || {
let mut buf = [0u8; 64];
let mut count: u64 = 0;
while count < total_items {
for _ in 0..32 {
if count >= total_items {
break;
}
while r_cons.try_recv(0, &mut buf).is_err() {
std::hint::spin_loop();
}
count += 1;
}
drained_c.store(count, Ordering::Release);
thread::sleep(Duration::from_micros(50));
}
});
producer.join().expect("producer thread");
consumer.join().expect("consumer thread");
thread::sleep(Duration::from_millis(150));
stop_obs.store(true, Ordering::Release);
obs_h.join().expect("observer thread");
let morphs = sidecar.morphs_triggered();
sidecar.shutdown();
println!();
println!("=== Result ===");
println!(" total items produced/consumed: {total_items}");
println!(" drained: {}", drained.load(Ordering::Acquire));
println!(" final capacity: {}", ring.current_capacity());
println!(" sidecar morphs triggered: {morphs}");
println!();
assert_eq!(
drained.load(Ordering::Acquire),
total_items,
"all items must be drained"
);
assert!(
morphs >= 2,
"sidecar must have triggered at least one grow and one shrink (observed {morphs})"
);
println!("PASS - sidecar auto-morphed the capacity {morphs} times");
}