pub struct SpscRingBuffer;Expand description
Constructor namespace. Use SpscRingBuffer::with_capacity.
Implementations§
Source§impl SpscRingBuffer
impl SpscRingBuffer
Sourcepub fn with_capacity<T>(requested_capacity: usize) -> (Producer<T>, Consumer<T>)
pub fn with_capacity<T>(requested_capacity: usize) -> (Producer<T>, Consumer<T>)
Build a (producer, consumer) pair backed by a buffer of at least
requested_capacity slots, rounded up to the next power of two.
A floor of 2 is enforced.
Examples found in repository?
More examples
examples/demo.rs (line 5)
4fn main() {
5 let (mut tx, mut rx) = SpscRingBuffer::with_capacity::<u32>(16);
6
7 let producer = thread::spawn(move || {
8 for i in 0..10u32 {
9 while tx.try_push(i).is_err() {}
10 }
11 });
12
13 let consumer = thread::spawn(move || {
14 let mut seen = Vec::new();
15 while seen.len() < 10 {
16 if let Some(v) = rx.try_pop() {
17 seen.push(v);
18 }
19 }
20 seen
21 });
22
23 producer.join().unwrap();
24 let seen = consumer.join().unwrap();
25 println!("consumed: {seen:?}");
26}examples/sample_app.rs (line 54)
49fn base_feed_to_strategy() {
50 println!("== base: feed-handler -> strategy handoff ==");
51
52 // Drop-on-full is the caller's decision. Shown deterministically on a
53 // small ring: capacity 4, six ticks offered, the last two are dropped.
54 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 // Occupancy is what a queue-depth alarm reads, and peek lets the strategy
72 // inspect the oldest tick before deciding to consume it.
73 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 // Steady state: a drained ring loses nothing and preserves feed order.
83 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/// `bulk` feature: a feed handler often lifts a whole batch of ticks off one
113/// NIC receive. `try_enqueue_bulk` copies the run in behind a single release
114/// fence instead of one per tick; the strategy drains behind one acquire fence.
115#[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/// `wait-strategies` feature: wrap the non-blocking ends in a blocking handle
144/// with a backoff policy. `YieldStrategy` lets other threads run between
145/// retries, a sensible default when the feed and strategy share cores.
146#[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/// `mpsc-fan-in` feature: several venue feeds, one strategy consumer. Each feed
178/// owns an independent SPSC ring (still wait-free against its own counter); the
179/// consumer round-robins so a quiet venue never starves a busy one.
180#[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/// `mpmc-disruptor` feature: broadcast one tick stream to independent readers,
223/// a strategy and a risk monitor, each of which sees every published tick. (To
224/// have each tick handled by exactly one reader, reach for `mpsc-fan-in`.)
225#[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 // Small and single-threaded so the tour self-verifies; the threaded
237 // broadcast path is pinned in the tests.
238 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/// `metrics` feature: wrap a base pair to count enqueue/dequeue success + fail
270/// and track the high-water depth - operational stats for a feed, at the cost
271/// of one atomic increment per op.
272#[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 // Fill to capacity, one overflow drop, then drain plus one empty poll.
281 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}Auto Trait Implementations§
impl Freeze for SpscRingBuffer
impl RefUnwindSafe for SpscRingBuffer
impl Send for SpscRingBuffer
impl Sync for SpscRingBuffer
impl Unpin for SpscRingBuffer
impl UnsafeUnpin for SpscRingBuffer
impl UnwindSafe for SpscRingBuffer
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more