1use std::thread;
18
19use subms_spsc_ring_buffer::SpscRingBuffer;
20
21#[derive(Clone, Copy, Debug, PartialEq)]
22struct Tick {
23 seq: u64,
24 price_cents: u32,
25}
26
27fn main() {
28 base_feed_to_strategy();
29
30 #[cfg(feature = "bulk")]
31 bulk_batch_ingest();
32
33 #[cfg(feature = "wait-strategies")]
34 blocking_handoff();
35
36 #[cfg(feature = "mpsc-fan-in")]
37 many_venue_fan_in();
38
39 #[cfg(feature = "mpmc-disruptor")]
40 broadcast_to_strategy_and_risk();
41
42 #[cfg(feature = "metrics")]
43 instrumented_handoff();
44}
45
46fn base_feed_to_strategy() {
50 println!("== base: feed-handler -> strategy handoff ==");
51
52 let (mut tx, mut rx) = SpscRingBuffer::with_capacity::<Tick>(4);
55 let mut dropped = 0usize;
56 for seq in 0..6u64 {
57 let tick = Tick {
58 seq,
59 price_cents: 10_000 + seq as u32,
60 };
61 if tx.try_push(tick).is_err() {
62 dropped += 1;
63 }
64 }
65 println!(" cap-4 ring, 6 offered -> {dropped} dropped under backpressure");
66 assert_eq!(
67 dropped, 2,
68 "two ticks past capacity are dropped, not blocked"
69 );
70
71 let oldest = rx.peek().expect("ring is full").seq;
74 println!(
75 " depth {}/{} full={}, oldest queued seq {oldest}",
76 rx.len(),
77 rx.capacity(),
78 tx.is_full()
79 );
80 println!(" dropped {} stale ticks on resync", rx.clear());
81
82 let n = 50_000u64;
84 let (mut tx, mut rx) = SpscRingBuffer::with_capacity::<Tick>(1024);
85 let feed = thread::spawn(move || {
86 for seq in 0..n {
87 let tick = Tick {
88 seq,
89 price_cents: 10_000 + (seq % 500) as u32,
90 };
91 while tx.try_push(tick).is_err() {
92 std::hint::spin_loop();
93 }
94 }
95 });
96 let strategy = thread::spawn(move || {
97 let mut expected = 0u64;
98 while expected < n {
99 if let Some(tick) = rx.try_pop() {
100 assert_eq!(tick.seq, expected, "ticks arrive in feed order");
101 expected += 1;
102 }
103 }
104 expected
105 });
106 feed.join().unwrap();
107 let received = strategy.join().unwrap();
108 println!(" streamed {received} ticks in order, zero loss when drained");
109 assert_eq!(received, n);
110}
111
112#[cfg(feature = "bulk")]
116fn bulk_batch_ingest() {
117 println!("\n== bulk: batch a NIC receive into the ring ==");
118 let (mut tx, mut rx) = SpscRingBuffer::with_capacity::<Tick>(16);
119 let batch: Vec<Tick> = (0..10)
120 .map(|seq| Tick {
121 seq,
122 price_cents: 20_000 + seq as u32,
123 })
124 .collect();
125
126 let pushed = tx.try_enqueue_bulk(&batch);
127 println!(
128 " offered {} ticks, took {pushed} in one fenced call",
129 batch.len()
130 );
131 assert_eq!(pushed, 10);
132
133 let mut out = [Tick {
134 seq: 0,
135 price_cents: 0,
136 }; 10];
137 let drained = rx.try_dequeue_bulk(&mut out);
138 println!(" drained {drained} in one fenced call");
139 assert_eq!(drained, 10);
140 assert_eq!(out.as_slice(), batch.as_slice(), "bulk preserves order");
141}
142
143#[cfg(feature = "wait-strategies")]
147fn blocking_handoff() {
148 use subms_spsc_ring_buffer::{BlockingSpscConsumer, BlockingSpscProducer, YieldStrategy};
149
150 println!("\n== wait-strategies: blocking handoff (yield backoff) ==");
151 let (tx, rx) = SpscRingBuffer::with_capacity::<Tick>(8);
152 let mut producer = BlockingSpscProducer::new(tx, YieldStrategy);
153 let mut consumer = BlockingSpscConsumer::new(rx, YieldStrategy);
154
155 let n = 5_000u64;
156 let feed = thread::spawn(move || {
157 for seq in 0..n {
158 producer.push(Tick {
159 seq,
160 price_cents: 30_000,
161 });
162 }
163 });
164 let strategy = thread::spawn(move || {
165 for expected in 0..n {
166 let tick = consumer.pop();
167 assert_eq!(tick.seq, expected, "blocking pop keeps feed order");
168 }
169 n
170 });
171 feed.join().unwrap();
172 let got = strategy.join().unwrap();
173 println!(" handed off {got} ticks, producer blocked on full instead of dropping");
174 assert_eq!(got, n);
175}
176
177#[cfg(feature = "mpsc-fan-in")]
181fn many_venue_fan_in() {
182 use subms_spsc_ring_buffer::MpscFanIn;
183
184 println!("\n== mpsc-fan-in: three venue feeds -> one strategy ==");
185 let venues = 3usize;
186 let per_venue = 20_000u64;
187 let (mut producers, mut consumer) = MpscFanIn::with_capacity::<Tick>(venues, 256);
188
189 let mut feeds = Vec::new();
190 for venue in 0..venues {
191 let mut p = producers.remove(0);
192 feeds.push(thread::spawn(move || {
193 for seq in 0..per_venue {
194 let tick = Tick {
195 seq,
196 price_cents: 40_000 + venue as u32,
197 };
198 while p.try_push(tick).is_err() {
199 std::hint::spin_loop();
200 }
201 }
202 }));
203 }
204 let total = per_venue * venues as u64;
205 let strategy = thread::spawn(move || {
206 let mut got = 0u64;
207 while got < total {
208 if consumer.try_pop().is_some() {
209 got += 1;
210 }
211 }
212 got
213 });
214 for f in feeds {
215 f.join().unwrap();
216 }
217 let got = strategy.join().unwrap();
218 println!(" {venues} feeds x {per_venue} ticks -> consumer drained {got}");
219 assert_eq!(got, total);
220}
221
222#[cfg(feature = "mpmc-disruptor")]
226fn broadcast_to_strategy_and_risk() {
227 use subms_spsc_ring_buffer::MpmcDisruptor;
228
229 println!("\n== mpmc-disruptor: broadcast to strategy + risk ==");
230 let n = 8u64;
231 let (producer, mut consumers) = MpmcDisruptor::with_consumers::<Tick>(16, 2);
232 let (strategy, rest) = consumers.split_at_mut(1);
233 let strategy = &mut strategy[0];
234 let risk = &mut rest[0];
235
236 let mut published = 0u64;
239 let mut strat_seen = Vec::new();
240 let mut risk_seen = Vec::new();
241 while published < n {
242 while published < n
243 && producer
244 .try_publish(Tick {
245 seq: published,
246 price_cents: 50_000,
247 })
248 .is_ok()
249 {
250 published += 1;
251 }
252 while let Some(t) = strategy.try_consume() {
253 strat_seen.push(t.seq);
254 }
255 while let Some(t) = risk.try_consume() {
256 risk_seen.push(t.seq);
257 }
258 }
259 println!(
260 " published {published}; strategy saw {}, risk saw {}",
261 strat_seen.len(),
262 risk_seen.len()
263 );
264 let expected: Vec<u64> = (0..n).collect();
265 assert_eq!(strat_seen, expected, "strategy sees every tick");
266 assert_eq!(risk_seen, expected, "risk monitor sees every tick too");
267}
268
269#[cfg(feature = "metrics")]
273fn instrumented_handoff() {
274 use subms_spsc_ring_buffer::InstrumentedSpsc;
275
276 println!("\n== metrics: instrumented feed handoff ==");
277 let (tx, rx) = SpscRingBuffer::with_capacity::<Tick>(4);
278 let (mut tx, mut rx, metrics) = InstrumentedSpsc::wrap(tx, rx);
279
280 for seq in 0..4u64 {
282 tx.try_push(Tick {
283 seq,
284 price_cents: 60_000,
285 })
286 .unwrap();
287 }
288 assert!(
289 tx.try_push(Tick {
290 seq: 99,
291 price_cents: 0
292 })
293 .is_err()
294 );
295 for _ in 0..4 {
296 rx.try_pop().unwrap();
297 }
298 assert!(rx.try_pop().is_none());
299
300 let snap = metrics.snapshot();
301 println!(
302 " enqueued {} (dropped {}), dequeued {} (empty {}), peak depth {}",
303 snap.enqueue_success,
304 snap.enqueue_fail,
305 snap.dequeue_success,
306 snap.dequeue_fail,
307 snap.max_depth_observed
308 );
309 assert_eq!(snap.enqueue_success, 4);
310 assert_eq!(snap.enqueue_fail, 1);
311 assert_eq!(snap.dequeue_success, 4);
312 assert_eq!(snap.dequeue_fail, 1);
313 assert_eq!(snap.max_depth_observed, 4);
314}