1use subms_timer_wheel::TimerWheel;
18
19fn main() {
20 tif_supervisor();
21
22 #[cfg(feature = "hierarchical")]
23 hierarchical_gtd();
24
25 #[cfg(feature = "concurrent")]
26 concurrent_quote_timeouts();
27
28 #[cfg(feature = "deadline-scheduler")]
29 deadline_session_idle();
30
31 #[cfg(feature = "cron")]
32 cron_risk_snapshot();
33
34 #[cfg(feature = "metrics")]
35 metered_expiry_wheel();
36}
37
38enum Event {
40 Rest(&'static str, usize),
42 Fill(&'static str),
44 Amend(&'static str, usize),
46}
47
48fn tif_supervisor() {
52 println!("== base: order time-in-force supervisor ==");
53
54 let tape = [
55 (0usize, Event::Rest("ORD-A", 3)),
56 (0, Event::Rest("ORD-B", 5)),
57 (0, Event::Rest("ORD-C", 9)),
58 (0, Event::Rest("ORD-D", 12)),
59 (2, Event::Fill("ORD-B")),
60 (4, Event::Amend("ORD-C", 6)),
61 ];
62
63 let mut expiries: TimerWheel<&'static str> = TimerWheel::new(256);
64 let mut timer_of: Vec<(&'static str, u64)> = Vec::new();
65 let lookup = |map: &Vec<(&'static str, u64)>, ord: &str| {
66 map.iter().find(|(o, _)| *o == ord).map(|(_, t)| *t)
67 };
68
69 let session_secs = 11;
70 for second in 0..=session_secs {
71 for (at, ev) in tape.iter() {
72 if *at != second {
73 continue;
74 }
75 match ev {
76 Event::Rest(ord, tif) => {
77 let id = expiries.schedule(*tif, ord);
78 timer_of.push((ord, id));
79 println!(" t={second}s rest {ord} tif={tif}s");
80 }
81 Event::Fill(ord) => {
82 let id = lookup(&timer_of, ord).expect("a resting order");
83 expiries.cancel(id);
84 println!(" t={second}s fill {ord} -> expiry cancelled");
85 }
86 Event::Amend(ord, tif) => {
87 let id = lookup(&timer_of, ord).expect("a resting order");
88 expiries.reschedule(id, *tif);
89 println!(" t={second}s amend {ord} tif -> {tif}s from now");
90 }
91 }
92 }
93 if second == session_secs {
94 break;
95 }
96 for ord in expiries.tick() {
97 println!(" t={}s expire {ord}", second + 1);
98 }
99 }
100
101 let unfilled = expiries.drain();
102 println!(
103 " session close: {} orders still resting {:?}",
104 unfilled.len(),
105 unfilled
106 );
107 println!(" pending after drain: {}", expiries.pending());
108
109 assert_eq!(
110 unfilled,
111 vec!["ORD-D"],
112 "only the 12s TIF outlives the session"
113 );
114 assert_eq!(expiries.pending(), 0);
115}
116
117#[cfg(feature = "hierarchical")]
123fn hierarchical_gtd() {
124 use subms_timer_wheel::HierarchicalTimerWheel;
125 println!("\n== hierarchical: good-til-date across horizons ==");
126 let mut gtd: HierarchicalTimerWheel<&'static str> = HierarchicalTimerWheel::new();
127
128 gtd.schedule(30, "GTD-near"); let far = gtd.schedule(5000, "GTD-far"); println!(" armed 2 GTD orders, {} pending", gtd.pending());
131
132 gtd.reschedule(far, 300);
134 println!(" GTD-far pulled in to t=300");
135
136 let mut near_at = None;
137 let mut far_at = None;
138 for t in 1..=300 {
139 for id in gtd.tick() {
140 match id {
141 "GTD-near" => near_at = Some(t),
142 "GTD-far" => far_at = Some(t),
143 _ => {}
144 }
145 }
146 }
147 println!(
148 " near fired at t={:?}, far fired at t={:?}",
149 near_at, far_at
150 );
151 println!(" cascade events: {}", gtd.cascades());
152
153 assert_eq!(near_at, Some(30), "near GTD fires on its deadline");
154 assert_eq!(
155 far_at,
156 Some(300),
157 "the rescheduled GTD fires on its new deadline"
158 );
159 assert!(gtd.cascades() >= 1, "the far order cascaded down a level");
160 assert_eq!(gtd.pending(), 0);
161}
162
163#[cfg(feature = "concurrent")]
167fn concurrent_quote_timeouts() {
168 use std::thread;
169 use subms_timer_wheel::ConcurrentTimerWheel;
170 println!("\n== concurrent: quote timeouts from many feeds ==");
171 let wheel: ConcurrentTimerWheel<usize> = ConcurrentTimerWheel::new(256);
172
173 let feeds = 4;
174 let per_feed = 50;
175 let mut handles = Vec::new();
176 for feed in 0..feeds {
177 let wheel = wheel.clone();
178 handles.push(thread::spawn(move || {
179 for i in 0..per_feed {
180 wheel.schedule(1 + (i % 8), feed * 1000 + i);
181 }
182 }));
183 }
184 for h in handles {
185 h.join().unwrap();
186 }
187 println!(
188 " {} quote timeouts armed across {feeds} feeds",
189 wheel.pending()
190 );
191
192 let fired = wheel.advance(16).len();
193 println!(" {feeds} feeds x {per_feed} quotes -> {fired} timeouts fired");
194 assert_eq!(
195 fired,
196 feeds * per_feed,
197 "every armed timeout fired exactly once"
198 );
199 assert!(wheel.is_empty());
200}
201
202#[cfg(feature = "deadline-scheduler")]
209fn deadline_session_idle() {
210 use std::time::Duration;
211 use subms_timer_wheel::{DeadlineScheduler, TestClock};
212 println!("\n== deadline-scheduler: FIX session idle timeout ==");
213
214 let idle = Duration::from_millis(30);
215 let mut sched: DeadlineScheduler<&'static str, TestClock> =
216 DeadlineScheduler::new(256, TestClock::new(), Duration::from_millis(1));
217
218 let session = sched.schedule_after(idle, "SESSION-1");
219 let mut elapsed = 0u64;
220 for gap in [10u64, 15] {
221 sched.clock().advance(Duration::from_millis(gap));
222 elapsed += gap;
223 assert!(sched.poll().is_empty(), "traffic keeps the session alive");
224 sched.reschedule_after(session, idle);
225 println!(
226 " inbound msg at +{elapsed}ms, idle deadline now +{}ms",
227 elapsed + 30
228 );
229 }
230 sched.clock().advance(Duration::from_millis(30));
232 let dead = sched.poll();
233 println!(" no traffic for {}ms -> {:?}", idle.as_millis(), dead);
234 assert_eq!(dead, vec!["SESSION-1"], "the idle timeout fires");
235}
236
237#[cfg(feature = "cron")]
241fn cron_risk_snapshot() {
242 use subms_timer_wheel::{CronSchedule, CronScheduler};
243 println!("\n== cron: mark-to-market every 5 minutes ==");
244 let schedule = CronSchedule::parse("*/5 * * * *").expect("valid cron");
245
246 let start = 1_704_067_201;
248 let mut scheduler = CronScheduler::new(schedule, start);
249
250 let first = scheduler.next_fire(start).expect("a next fire exists");
251 scheduler.record_fire(first);
252 let second = scheduler.next_fire(first).expect("a next fire exists");
253 println!(" first snapshot at epoch {first}, next at {second}");
254
255 assert_eq!(
256 first, 1_704_067_500,
257 "first fire lands on the 5-minute grid"
258 );
259 assert_eq!(second, first + 300, "re-arms exactly 5 minutes later");
260}
261
262#[cfg(feature = "metrics")]
267fn metered_expiry_wheel() {
268 use subms_timer_wheel::MeteredTimerWheel;
269 println!("\n== metrics: self-reporting expiry counters ==");
270 let mut wheel: MeteredTimerWheel<&'static str> = MeteredTimerWheel::new(64);
271
272 let a = wheel.schedule(2, "ORD-A");
273 let b = wheel.schedule(2, "ORD-B");
274 let c = wheel.schedule(2, "ORD-C");
275 wheel.cancel(b);
276 wheel.reschedule(c, 20);
277 let _ = a;
278
279 let fired = wheel.advance(3).len();
280 let left = wheel.drain();
281 let m = wheel.metrics();
282 println!(
283 " scheduled={} fired={} cancelled={} rescheduled={} drained={} ticks={}",
284 m.scheduled, m.fired, m.cancelled, m.rescheduled, m.drained, m.ticks
285 );
286
287 assert_eq!(m.scheduled, 3);
288 assert_eq!(m.cancelled, 1);
289 assert_eq!(m.rescheduled, 1);
290 assert_eq!(fired, 1, "only the untouched order fired");
291 assert_eq!(m.fired, 1);
292 assert_eq!(left, vec!["ORD-C"]);
293 assert_eq!(m.drained, 1);
294 assert_eq!(m.ticks, 3);
295}