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, _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 n = 50_000u64;
73 let (mut tx, mut rx) = SpscRingBuffer::with_capacity::<Tick>(1024);
74 let feed = thread::spawn(move || {
75 for seq in 0..n {
76 let tick = Tick {
77 seq,
78 price_cents: 10_000 + (seq % 500) as u32,
79 };
80 while tx.try_push(tick).is_err() {
81 std::hint::spin_loop();
82 }
83 }
84 });
85 let strategy = thread::spawn(move || {
86 let mut expected = 0u64;
87 while expected < n {
88 if let Some(tick) = rx.try_pop() {
89 assert_eq!(tick.seq, expected, "ticks arrive in feed order");
90 expected += 1;
91 }
92 }
93 expected
94 });
95 feed.join().unwrap();
96 let received = strategy.join().unwrap();
97 println!(" streamed {received} ticks in order, zero loss when drained");
98 assert_eq!(received, n);
99}
100
101#[cfg(feature = "bulk")]
105fn bulk_batch_ingest() {
106 println!("\n== bulk: batch a NIC receive into the ring ==");
107 let (mut tx, mut rx) = SpscRingBuffer::with_capacity::<Tick>(16);
108 let batch: Vec<Tick> = (0..10)
109 .map(|seq| Tick {
110 seq,
111 price_cents: 20_000 + seq as u32,
112 })
113 .collect();
114
115 let pushed = tx.try_enqueue_bulk(&batch);
116 println!(
117 " offered {} ticks, took {pushed} in one fenced call",
118 batch.len()
119 );
120 assert_eq!(pushed, 10);
121
122 let mut out = [Tick {
123 seq: 0,
124 price_cents: 0,
125 }; 10];
126 let drained = rx.try_dequeue_bulk(&mut out);
127 println!(" drained {drained} in one fenced call");
128 assert_eq!(drained, 10);
129 assert_eq!(out.as_slice(), batch.as_slice(), "bulk preserves order");
130}
131
132#[cfg(feature = "wait-strategies")]
136fn blocking_handoff() {
137 use subms_spsc_ring_buffer::{BlockingSpscConsumer, BlockingSpscProducer, YieldStrategy};
138
139 println!("\n== wait-strategies: blocking handoff (yield backoff) ==");
140 let (tx, rx) = SpscRingBuffer::with_capacity::<Tick>(8);
141 let mut producer = BlockingSpscProducer::new(tx, YieldStrategy);
142 let mut consumer = BlockingSpscConsumer::new(rx, YieldStrategy);
143
144 let n = 5_000u64;
145 let feed = thread::spawn(move || {
146 for seq in 0..n {
147 producer.push(Tick {
148 seq,
149 price_cents: 30_000,
150 });
151 }
152 });
153 let strategy = thread::spawn(move || {
154 for expected in 0..n {
155 let tick = consumer.pop();
156 assert_eq!(tick.seq, expected, "blocking pop keeps feed order");
157 }
158 n
159 });
160 feed.join().unwrap();
161 let got = strategy.join().unwrap();
162 println!(" handed off {got} ticks, producer blocked on full instead of dropping");
163 assert_eq!(got, n);
164}
165
166#[cfg(feature = "mpsc-fan-in")]
170fn many_venue_fan_in() {
171 use subms_spsc_ring_buffer::MpscFanIn;
172
173 println!("\n== mpsc-fan-in: three venue feeds -> one strategy ==");
174 let venues = 3usize;
175 let per_venue = 20_000u64;
176 let (mut producers, mut consumer) = MpscFanIn::with_capacity::<Tick>(venues, 256);
177
178 let mut feeds = Vec::new();
179 for venue in 0..venues {
180 let mut p = producers.remove(0);
181 feeds.push(thread::spawn(move || {
182 for seq in 0..per_venue {
183 let tick = Tick {
184 seq,
185 price_cents: 40_000 + venue as u32,
186 };
187 while p.try_push(tick).is_err() {
188 std::hint::spin_loop();
189 }
190 }
191 }));
192 }
193 let total = per_venue * venues as u64;
194 let strategy = thread::spawn(move || {
195 let mut got = 0u64;
196 while got < total {
197 if consumer.try_pop().is_some() {
198 got += 1;
199 }
200 }
201 got
202 });
203 for f in feeds {
204 f.join().unwrap();
205 }
206 let got = strategy.join().unwrap();
207 println!(" {venues} feeds x {per_venue} ticks -> consumer drained {got}");
208 assert_eq!(got, total);
209}
210
211#[cfg(feature = "mpmc-disruptor")]
215fn broadcast_to_strategy_and_risk() {
216 use subms_spsc_ring_buffer::MpmcDisruptor;
217
218 println!("\n== mpmc-disruptor: broadcast to strategy + risk ==");
219 let n = 8u64;
220 let (producer, mut consumers) = MpmcDisruptor::with_consumers::<Tick>(16, 2);
221 let (strategy, rest) = consumers.split_at_mut(1);
222 let strategy = &mut strategy[0];
223 let risk = &mut rest[0];
224
225 let mut published = 0u64;
228 let mut strat_seen = Vec::new();
229 let mut risk_seen = Vec::new();
230 while published < n {
231 while published < n
232 && producer
233 .try_publish(Tick {
234 seq: published,
235 price_cents: 50_000,
236 })
237 .is_ok()
238 {
239 published += 1;
240 }
241 while let Some(t) = strategy.try_consume() {
242 strat_seen.push(t.seq);
243 }
244 while let Some(t) = risk.try_consume() {
245 risk_seen.push(t.seq);
246 }
247 }
248 println!(
249 " published {published}; strategy saw {}, risk saw {}",
250 strat_seen.len(),
251 risk_seen.len()
252 );
253 let expected: Vec<u64> = (0..n).collect();
254 assert_eq!(strat_seen, expected, "strategy sees every tick");
255 assert_eq!(risk_seen, expected, "risk monitor sees every tick too");
256}
257
258#[cfg(feature = "metrics")]
262fn instrumented_handoff() {
263 use subms_spsc_ring_buffer::InstrumentedSpsc;
264
265 println!("\n== metrics: instrumented feed handoff ==");
266 let (tx, rx) = SpscRingBuffer::with_capacity::<Tick>(4);
267 let (mut tx, mut rx, metrics) = InstrumentedSpsc::wrap(tx, rx);
268
269 for seq in 0..4u64 {
271 tx.try_push(Tick {
272 seq,
273 price_cents: 60_000,
274 })
275 .unwrap();
276 }
277 assert!(
278 tx.try_push(Tick {
279 seq: 99,
280 price_cents: 0
281 })
282 .is_err()
283 );
284 for _ in 0..4 {
285 rx.try_pop().unwrap();
286 }
287 assert!(rx.try_pop().is_none());
288
289 let snap = metrics.snapshot();
290 println!(
291 " enqueued {} (dropped {}), dequeued {} (empty {}), peak depth {}",
292 snap.enqueue_success,
293 snap.enqueue_fail,
294 snap.dequeue_success,
295 snap.dequeue_fail,
296 snap.max_depth_observed
297 );
298 assert_eq!(snap.enqueue_success, 4);
299 assert_eq!(snap.enqueue_fail, 1);
300 assert_eq!(snap.dequeue_success, 4);
301 assert_eq!(snap.dequeue_fail, 1);
302 assert_eq!(snap.max_depth_observed, 4);
303}