use subms_timer_wheel::TimerWheel;
fn main() {
tif_supervisor();
#[cfg(feature = "hierarchical")]
hierarchical_gtd();
#[cfg(feature = "concurrent")]
concurrent_quote_timeouts();
#[cfg(feature = "deadline-scheduler")]
deadline_session_idle();
#[cfg(feature = "cron")]
cron_risk_snapshot();
#[cfg(feature = "metrics")]
metered_expiry_wheel();
}
enum Event {
Rest(&'static str, usize),
Fill(&'static str),
Amend(&'static str, usize),
}
fn tif_supervisor() {
println!("== base: order time-in-force supervisor ==");
let tape = [
(0usize, Event::Rest("ORD-A", 3)),
(0, Event::Rest("ORD-B", 5)),
(0, Event::Rest("ORD-C", 9)),
(0, Event::Rest("ORD-D", 12)),
(2, Event::Fill("ORD-B")),
(4, Event::Amend("ORD-C", 6)),
];
let mut expiries: TimerWheel<&'static str> = TimerWheel::new(256);
let mut timer_of: Vec<(&'static str, u64)> = Vec::new();
let lookup = |map: &Vec<(&'static str, u64)>, ord: &str| {
map.iter().find(|(o, _)| *o == ord).map(|(_, t)| *t)
};
let session_secs = 11;
for second in 0..=session_secs {
for (at, ev) in tape.iter() {
if *at != second {
continue;
}
match ev {
Event::Rest(ord, tif) => {
let id = expiries.schedule(*tif, ord);
timer_of.push((ord, id));
println!(" t={second}s rest {ord} tif={tif}s");
}
Event::Fill(ord) => {
let id = lookup(&timer_of, ord).expect("a resting order");
expiries.cancel(id);
println!(" t={second}s fill {ord} -> expiry cancelled");
}
Event::Amend(ord, tif) => {
let id = lookup(&timer_of, ord).expect("a resting order");
expiries.reschedule(id, *tif);
println!(" t={second}s amend {ord} tif -> {tif}s from now");
}
}
}
if second == session_secs {
break;
}
for ord in expiries.tick() {
println!(" t={}s expire {ord}", second + 1);
}
}
let unfilled = expiries.drain();
println!(
" session close: {} orders still resting {:?}",
unfilled.len(),
unfilled
);
println!(" pending after drain: {}", expiries.pending());
assert_eq!(
unfilled,
vec!["ORD-D"],
"only the 12s TIF outlives the session"
);
assert_eq!(expiries.pending(), 0);
}
#[cfg(feature = "hierarchical")]
fn hierarchical_gtd() {
use subms_timer_wheel::HierarchicalTimerWheel;
println!("\n== hierarchical: good-til-date across horizons ==");
let mut gtd: HierarchicalTimerWheel<&'static str> = HierarchicalTimerWheel::new();
gtd.schedule(30, "GTD-near"); let far = gtd.schedule(5000, "GTD-far"); println!(" armed 2 GTD orders, {} pending", gtd.pending());
gtd.reschedule(far, 300);
println!(" GTD-far pulled in to t=300");
let mut near_at = None;
let mut far_at = None;
for t in 1..=300 {
for id in gtd.tick() {
match id {
"GTD-near" => near_at = Some(t),
"GTD-far" => far_at = Some(t),
_ => {}
}
}
}
println!(
" near fired at t={:?}, far fired at t={:?}",
near_at, far_at
);
println!(" cascade events: {}", gtd.cascades());
assert_eq!(near_at, Some(30), "near GTD fires on its deadline");
assert_eq!(
far_at,
Some(300),
"the rescheduled GTD fires on its new deadline"
);
assert!(gtd.cascades() >= 1, "the far order cascaded down a level");
assert_eq!(gtd.pending(), 0);
}
#[cfg(feature = "concurrent")]
fn concurrent_quote_timeouts() {
use std::thread;
use subms_timer_wheel::ConcurrentTimerWheel;
println!("\n== concurrent: quote timeouts from many feeds ==");
let wheel: ConcurrentTimerWheel<usize> = ConcurrentTimerWheel::new(256);
let feeds = 4;
let per_feed = 50;
let mut handles = Vec::new();
for feed in 0..feeds {
let wheel = wheel.clone();
handles.push(thread::spawn(move || {
for i in 0..per_feed {
wheel.schedule(1 + (i % 8), feed * 1000 + i);
}
}));
}
for h in handles {
h.join().unwrap();
}
println!(
" {} quote timeouts armed across {feeds} feeds",
wheel.pending()
);
let fired = wheel.advance(16).len();
println!(" {feeds} feeds x {per_feed} quotes -> {fired} timeouts fired");
assert_eq!(
fired,
feeds * per_feed,
"every armed timeout fired exactly once"
);
assert!(wheel.is_empty());
}
#[cfg(feature = "deadline-scheduler")]
fn deadline_session_idle() {
use std::time::Duration;
use subms_timer_wheel::{DeadlineScheduler, TestClock};
println!("\n== deadline-scheduler: FIX session idle timeout ==");
let idle = Duration::from_millis(30);
let mut sched: DeadlineScheduler<&'static str, TestClock> =
DeadlineScheduler::new(256, TestClock::new(), Duration::from_millis(1));
let session = sched.schedule_after(idle, "SESSION-1");
let mut elapsed = 0u64;
for gap in [10u64, 15] {
sched.clock().advance(Duration::from_millis(gap));
elapsed += gap;
assert!(sched.poll().is_empty(), "traffic keeps the session alive");
sched.reschedule_after(session, idle);
println!(
" inbound msg at +{elapsed}ms, idle deadline now +{}ms",
elapsed + 30
);
}
sched.clock().advance(Duration::from_millis(30));
let dead = sched.poll();
println!(" no traffic for {}ms -> {:?}", idle.as_millis(), dead);
assert_eq!(dead, vec!["SESSION-1"], "the idle timeout fires");
}
#[cfg(feature = "cron")]
fn cron_risk_snapshot() {
use subms_timer_wheel::{CronSchedule, CronScheduler};
println!("\n== cron: mark-to-market every 5 minutes ==");
let schedule = CronSchedule::parse("*/5 * * * *").expect("valid cron");
let start = 1_704_067_201;
let mut scheduler = CronScheduler::new(schedule, start);
let first = scheduler.next_fire(start).expect("a next fire exists");
scheduler.record_fire(first);
let second = scheduler.next_fire(first).expect("a next fire exists");
println!(" first snapshot at epoch {first}, next at {second}");
assert_eq!(
first, 1_704_067_500,
"first fire lands on the 5-minute grid"
);
assert_eq!(second, first + 300, "re-arms exactly 5 minutes later");
}
#[cfg(feature = "metrics")]
fn metered_expiry_wheel() {
use subms_timer_wheel::MeteredTimerWheel;
println!("\n== metrics: self-reporting expiry counters ==");
let mut wheel: MeteredTimerWheel<&'static str> = MeteredTimerWheel::new(64);
let a = wheel.schedule(2, "ORD-A");
let b = wheel.schedule(2, "ORD-B");
let c = wheel.schedule(2, "ORD-C");
wheel.cancel(b);
wheel.reschedule(c, 20);
let _ = a;
let fired = wheel.advance(3).len();
let left = wheel.drain();
let m = wheel.metrics();
println!(
" scheduled={} fired={} cancelled={} rescheduled={} drained={} ticks={}",
m.scheduled, m.fired, m.cancelled, m.rescheduled, m.drained, m.ticks
);
assert_eq!(m.scheduled, 3);
assert_eq!(m.cancelled, 1);
assert_eq!(m.rescheduled, 1);
assert_eq!(fired, 1, "only the untouched order fired");
assert_eq!(m.fired, 1);
assert_eq!(left, vec!["ORD-C"]);
assert_eq!(m.drained, 1);
assert_eq!(m.ticks, 3);
}