1pub mod config;
2pub mod messages;
3
4use std::{
5 cell::Cell,
6 collections::VecDeque,
7 marker::PhantomData,
8 sync::{
9 atomic::{AtomicU64, Ordering},
10 Arc,
11 },
12};
13
14pub use config::{Buffer, BufferConfig, SharedBufferConfig};
15use log::{debug, error, info};
16pub use messages::PrefetchHandle;
17use parking_lot::RwLock;
18use tokio::{
19 sync::{
20 mpsc::{self, UnboundedReceiver, UnboundedSender},
21 oneshot,
22 },
23 task::JoinHandle,
24};
25
26use crate::correlated_randomness::{
27 generator::CorrelationGenerator,
28 stream::{
29 buffered::{config::try_read_config, messages::Command},
30 errors::CorrelatedStreamError,
31 futures::Next,
32 CorrelatedStream,
33 NextVec,
34 ResyncHandle,
35 },
36 CorrelatedBatch,
37};
38
39enum Work {
41 Generate(usize),
43 Skip(usize),
45}
46
47pub struct BufferedStream<PB: CorrelatedBatch, E> {
52 command_sender: UnboundedSender<Command<PB, E>>,
53 _unsync_marker: PhantomData<Cell<()>>,
54 config: Arc<RwLock<BufferConfig>>,
55 position: Arc<AtomicU64>,
59 buffered: Arc<AtomicU64>,
63 dispatcher_handle: JoinHandle<()>,
64 generator_handle: JoinHandle<()>,
65}
66
67impl<PB: CorrelatedBatch, E> Buffer for BufferedStream<PB, E> {
68 fn config(&self) -> &Arc<RwLock<BufferConfig>> {
69 &self.config
70 }
71}
72
73impl<
74 PB: CorrelatedBatch,
75 E: From<CorrelatedStreamError> + Clone + Send + std::fmt::Debug + 'static,
76 > BufferedStream<PB, E>
77{
78 pub fn new<G: CorrelationGenerator<PB> + Send + 'static>(
80 generator: G,
81 net: G::Net,
82 config: BufferConfig,
83 ) -> Self
84 where
85 E: From<G::Error>,
86 {
87 Self::new_with_shared_config(generator, net, Arc::new(RwLock::new(config)))
88 }
89
90 pub fn new_on_demand<G: CorrelationGenerator<PB> + Send + 'static>(
94 generator: G,
95 net: G::Net,
96 ) -> Self
97 where
98 E: From<G::Error>,
99 {
100 Self::new(
101 generator,
102 net,
103 BufferConfig::lazy(BufferConfig::UNBOUNDED, 0),
104 )
105 }
106
107 pub fn new_with_shared_config<G: CorrelationGenerator<PB> + Send + 'static>(
109 generator: G,
110 net: G::Net,
111 config: Arc<RwLock<BufferConfig>>,
112 ) -> Self
113 where
114 E: From<G::Error>,
115 {
116 let (cmd_tx, cmd_rx) = mpsc::unbounded_channel::<Command<PB, E>>();
117 let (work_tx, work_rx) = mpsc::unbounded_channel::<Work>();
118 let (items_tx, items_rx) = mpsc::unbounded_channel::<Result<Vec<PB::Item>, E>>();
119 let (skip_tx, skip_rx) = mpsc::unbounded_channel::<Result<(), E>>();
120 let position = Arc::new(AtomicU64::new(0));
121 let buffered = Arc::new(AtomicU64::new(0));
122 let generator_handle =
123 tokio::spawn(generator_loop(generator, net, work_rx, items_tx, skip_tx));
124 let dispatcher_handle = tokio::spawn(dispatcher_loop(
125 cmd_rx,
126 work_tx,
127 items_rx,
128 skip_rx,
129 config.clone(),
130 position.clone(),
131 buffered.clone(),
132 G::SUPPORTS_UNILATERAL_SKIP,
133 ));
134 Self {
135 command_sender: cmd_tx,
136 _unsync_marker: PhantomData,
137 config,
138 position,
139 buffered,
140 dispatcher_handle,
141 generator_handle,
142 }
143 }
144
145 pub async fn stop(self) {
150 let Self {
151 command_sender,
152 dispatcher_handle,
153 generator_handle,
154 ..
155 } = self;
156 drop(command_sender);
157 let _ = dispatcher_handle.await;
158 let _ = generator_handle.await;
159 }
160}
161
162async fn generator_loop<
167 PB: CorrelatedBatch,
168 G: CorrelationGenerator<PB> + Send,
169 E: From<G::Error> + Send,
170>(
171 mut generator: G,
172 mut net: G::Net,
173 mut work_rx: UnboundedReceiver<Work>,
174 items_tx: UnboundedSender<Result<Vec<PB::Item>, E>>,
175 skip_tx: UnboundedSender<Result<(), E>>,
176) {
177 let log_prefix = format!("<Generator<{}>>", std::any::type_name::<PB>());
178 while let Some(work) = work_rx.recv().await {
179 match work {
180 Work::Generate(n) => {
181 debug!("{log_prefix} generating {n} elements");
182 let result = generator.run_for(n, &mut net).await.map_err(E::from);
183 let stop = result.is_err();
184 if items_tx.send(result).is_err() || stop {
186 break;
187 }
188 }
189 Work::Skip(n) => {
190 debug!("{log_prefix} skipping {n} elements");
191 let result = generator.skip(n, &mut net).await.map_err(E::from);
192 let stop = result.is_err();
193 if skip_tx.send(result).is_err() || stop {
194 break;
195 }
196 }
197 }
198 }
199 info!("{log_prefix} exiting");
200}
201
202type ItemsCollected<PB> = Vec<<PB as IntoIterator>::Item>;
207type TotalNeeded = usize;
208type BatchSender<PB, E> = oneshot::Sender<Result<Vec<<PB as IntoIterator>::Item>, E>>;
209
210fn maybe_skip(
212 pending_skip: &mut usize,
213 work_in_flight: &mut bool,
214 work_tx: &UnboundedSender<Work>,
215 log_prefix: &str,
216) {
217 if *pending_skip > 0 && !*work_in_flight {
218 debug!("{log_prefix} requesting skip of {} elements", *pending_skip);
219 *work_in_flight = true;
220 let _ = work_tx.send(Work::Skip(*pending_skip));
221 *pending_skip = 0;
222 }
223}
224
225#[allow(clippy::too_many_arguments)]
229fn maybe_generate<PB: CorrelatedBatch, E: From<CorrelatedStreamError>>(
230 work_in_flight: &mut bool,
231 pending_resync: &Option<(usize, oneshot::Sender<Result<(), E>>)>,
232 pending_skip: usize,
233 config: &Arc<RwLock<BufferConfig>>,
234 pending_batches: &VecDeque<(ItemsCollected<PB>, TotalNeeded, BatchSender<PB, E>)>,
235 buffer_len: usize,
236 prefetch_demand: usize,
237 work_tx: &UnboundedSender<Work>,
238 log_prefix: &str,
239) -> Result<(), E> {
240 if *work_in_flight || pending_resync.is_some() || pending_skip != 0 {
241 return Ok(());
242 }
243 let batch_shortfall: usize = pending_batches
244 .iter()
245 .map(|(collected, needed, _)| needed.saturating_sub(collected.len() + buffer_len))
246 .sum();
247 let buf_after = buffer_len.saturating_sub(batch_shortfall);
248 let need = batch_shortfall
249 + try_read_config(config)?
250 .refill_threshold()
251 .saturating_sub(buf_after)
252 .max(prefetch_demand);
253 if need > 0 {
254 debug!("{log_prefix} requesting generation of {need} items");
255 *work_in_flight = true;
256 let _ = work_tx.send(Work::Generate(need));
257 }
258 Ok(())
259}
260
261#[allow(clippy::too_many_arguments)]
262async fn dispatcher_loop<
263 PB: CorrelatedBatch,
264 E: From<CorrelatedStreamError> + Clone + Send + std::fmt::Debug,
265>(
266 mut cmd_rx: UnboundedReceiver<Command<PB, E>>,
267 work_tx: UnboundedSender<Work>,
268 mut items_rx: UnboundedReceiver<Result<Vec<PB::Item>, E>>,
269 mut skip_rx: UnboundedReceiver<Result<(), E>>,
270 config: Arc<RwLock<BufferConfig>>,
271 shared_position: Arc<AtomicU64>,
272 shared_buffered: Arc<AtomicU64>,
273 supports_skip: bool,
274) {
275 let log_prefix = format!("<Dispatcher<{}>>", std::any::type_name::<PB>());
276
277 let initial_cap = try_read_config(&config)
280 .map(|c| c.capacity())
281 .unwrap_or(0)
282 .min(1 << 12);
283 let mut buffer: VecDeque<PB::Item> = VecDeque::with_capacity(initial_cap);
284 let mut prefetch_demand: usize = 0;
286 let mut prefetch_completions: VecDeque<(usize, oneshot::Sender<Result<(), E>>)> =
288 VecDeque::new();
289 let mut work_in_flight = false;
291 let mut pending_batches: VecDeque<(ItemsCollected<PB>, TotalNeeded, BatchSender<PB, E>)> =
293 VecDeque::new();
294 let mut pending_resync: Option<(usize, oneshot::Sender<Result<(), E>>)> = None;
300 let mut pending_skip: usize = 0;
302 let mut shutdown_err: Option<E> = None;
304
305 macro_rules! lock_cfg {
307 () => {
308 match try_read_config(&config) {
309 Ok(g) => g,
310 Err(e) => {
311 error!("{log_prefix} config lock timeout, shutting down");
312 shutdown_err = Some(e.into());
313 break;
314 }
315 }
316 };
317 }
318
319 macro_rules! outstanding_demand {
321 () => {{
322 let batch_shortfall: usize = pending_batches
323 .iter()
324 .map(|(collected, needed, _)| needed.saturating_sub(collected.len()))
325 .sum();
326 (batch_shortfall + prefetch_demand).saturating_sub(buffer.len())
327 }};
328 }
329
330 let set_buffered =
332 |buffer_len: usize| shared_buffered.store(buffer_len as u64, Ordering::Release);
333
334 loop {
335 tokio::select! {
336 cmd = cmd_rx.recv() => {
337 let Some(cmd) = cmd else {
338 info!("{log_prefix} command channel closed, shutting down");
339 break;
340 };
341 match cmd {
342 Command::RequestN { n_elements, completion } => {
343 debug!("{log_prefix} batch request for {n_elements} items");
344 let cap = lock_cfg!().capacity();
345 if outstanding_demand!() + n_elements > cap {
346 let _ = completion.send(Err(CorrelatedStreamError::RateLimitExceeded.into()));
348 } else if buffer.len() >= n_elements {
349 let items: Vec<_> = buffer.drain(..n_elements).collect();
351 shared_position.fetch_add(n_elements as u64, Ordering::Release);
352 set_buffered(buffer.len());
353 let _ = completion.send(Ok(items));
354 } else {
355 let collected: Vec<_> = buffer.drain(..).collect();
358 shared_position.fetch_add(n_elements as u64, Ordering::Release);
359 set_buffered(buffer.len());
360 pending_batches.push_back((collected, n_elements, completion));
361 }
362 }
363 Command::Resync { target, completion } => {
364 let position = shared_position.load(Ordering::Relaxed);
366 debug!("{log_prefix} resync to {target} (position {position})");
367 if pending_resync.is_some() {
368 let _ = completion.send(Err(CorrelatedStreamError::ResyncInProgress.into()));
371 } else if target < position {
372 let _ = completion.send(Err(CorrelatedStreamError::ResyncRewind {
373 current: position,
374 target,
375 }
376 .into()));
377 } else {
378 let skip = (target - position) as usize;
380 let drained = skip.min(buffer.len());
381 buffer.drain(..drained);
382 shared_position.fetch_add(drained as u64, Ordering::Release);
383 set_buffered(buffer.len());
384 let deficit = skip - drained;
385 if deficit == 0 {
386 let _ = completion.send(Ok(()));
387 } else if !supports_skip {
388 let _ = completion.send(Err(CorrelatedStreamError::ResyncUnsupported {
391 generated: position + drained as u64,
392 target,
393 }
394 .into()));
395 } else {
396 pending_resync = Some((deficit, completion));
400 pending_skip = deficit;
401 maybe_skip(&mut pending_skip, &mut work_in_flight, &work_tx, &log_prefix);
402 }
403 }
404 }
405 Command::Prefetch { n_elements, completion } => {
406 debug!("{log_prefix} prefetch {n_elements} items");
407 if buffer.len() >= n_elements {
408 let _ = completion.send(Ok(()));
410 } else {
411 let cap = lock_cfg!().capacity();
412 if outstanding_demand!() + n_elements > cap {
413 let _ = completion.send(Err(CorrelatedStreamError::RateLimitExceeded.into()));
414 } else {
415 let deficit = n_elements - buffer.len();
416 prefetch_demand += deficit;
417 prefetch_completions.push_back((deficit, completion));
418 }
419 }
420 }
421 }
422 }
423
424 result = items_rx.recv() => {
425 let Some(result) = result else {
426 info!("{log_prefix} generator channel closed, shutting down");
427 break;
428 };
429 match result {
430 Ok(items) => {
431 work_in_flight = false;
432 let generated = items.len();
433 debug!("{log_prefix} received {generated} items (pending_batches: {}, buffer: {})",
434 pending_batches.iter().map(|(c, n, _)| n - c.len()).sum::<usize>(),
435 buffer.len());
436 let mut iter = items.into_iter();
437 while let Some((ref mut collected, needed, _)) = pending_batches.front_mut() {
439 let shortfall = *needed - collected.len();
440 collected.extend(iter.by_ref().take(shortfall));
441 if collected.len() < *needed { break; } let (collected, _, tx) = pending_batches.pop_front().unwrap();
443 let _ = tx.send(Ok(collected));
444 }
445 buffer.extend(iter);
447 set_buffered(buffer.len());
448 prefetch_demand = prefetch_demand.saturating_sub(generated);
450 let mut credit = generated;
451 while credit > 0 {
452 let Some((deficit, _)) = prefetch_completions.front_mut() else { break; };
453 let used = (*deficit).min(credit);
454 *deficit -= used;
455 credit -= used;
456 if *deficit > 0 { break; }
457 let (_, tx) = prefetch_completions.pop_front().unwrap();
458 let _ = tx.send(Ok(()));
459 }
460 maybe_skip(&mut pending_skip, &mut work_in_flight, &work_tx, &log_prefix);
462 }
463 Err(e) => {
464 error!("{log_prefix} generation error, shutting down: {e:?}");
465 for (_, _, tx) in pending_batches.drain(..) { let _ = tx.send(Err(e.clone())); }
466 for (_, tx) in prefetch_completions.drain(..) { let _ = tx.send(Err(e.clone())); }
467 if let Some((_, tx)) = pending_resync.take() { let _ = tx.send(Err(e.clone())); }
468 return;
469 }
470 }
471 }
472
473 result = skip_rx.recv() => {
474 let Some(result) = result else {
475 info!("{log_prefix} skip channel closed, shutting down");
476 break;
477 };
478 work_in_flight = false;
479 match result {
480 Ok(()) => {
481 if let Some((deficit, tx)) = pending_resync.take() {
485 shared_position.fetch_add(deficit as u64, Ordering::Release);
486 let _ = tx.send(Ok(()));
487 }
488 }
489 Err(e) => {
490 error!("{log_prefix} skip error, shutting down: {e:?}");
491 for (_, _, tx) in pending_batches.drain(..) { let _ = tx.send(Err(e.clone())); }
492 for (_, tx) in prefetch_completions.drain(..) { let _ = tx.send(Err(e.clone())); }
493 if let Some((_, tx)) = pending_resync.take() { let _ = tx.send(Err(e.clone())); }
494 return;
495 }
496 }
497 }
498 }
499
500 if let Err(e) = maybe_generate::<PB, E>(
502 &mut work_in_flight,
503 &pending_resync,
504 pending_skip,
505 &config,
506 &pending_batches,
507 buffer.len(),
508 prefetch_demand,
509 &work_tx,
510 &log_prefix,
511 ) {
512 error!("{log_prefix} config lock timeout, shutting down");
513 shutdown_err = Some(e);
514 break;
515 }
516 }
517
518 let final_err: E = shutdown_err.unwrap_or_else(|| CorrelatedStreamError::StreamClosed.into());
521 for (_, _, tx) in pending_batches.drain(..) {
522 let _ = tx.send(Err(final_err.clone()));
523 }
524 for (_, tx) in prefetch_completions.drain(..) {
525 let _ = tx.send(Err(final_err.clone()));
526 }
527 if let Some((_, tx)) = pending_resync.take() {
528 let _ = tx.send(Err(final_err.clone()));
529 }
530}
531
532impl<
533 PB: CorrelatedBatch,
534 E: From<CorrelatedStreamError> + Clone + Send + std::fmt::Debug + 'static,
535 > CorrelatedStream<PB::Item> for BufferedStream<PB, E>
536{
537 type Error = E;
538
539 fn next_n(&self, n_elements: usize) -> Result<NextVec<PB::Item, E>, CorrelatedStreamError> {
540 if n_elements == 0 {
541 return Ok(NextVec::default());
542 }
543 let max_allowed = self.max_request_size()?;
544 if n_elements > max_allowed {
545 return Err(CorrelatedStreamError::RequestTooLarge {
546 requested: n_elements,
547 max_allowed,
548 });
549 }
550 let (tx, rx) = oneshot::channel();
551 self.command_sender
552 .send(Command::RequestN {
553 n_elements,
554 completion: tx,
555 })
556 .map_err(|e| CorrelatedStreamError::SendError(e.to_string()))?;
557 Ok(NextVec {
558 future: Next(rx),
559 size: n_elements,
560 })
561 }
562
563 fn prefetch_n(&self, n_elements: usize) -> PrefetchHandle<E> {
564 let (tx, rx) = oneshot::channel();
565 let max = match self.max_request_size() {
566 Ok(m) => m,
567 Err(e) => {
568 let _ = tx.send(Err(e.into()));
569 return PrefetchHandle::from(rx);
570 }
571 };
572 if n_elements > max {
573 let _ = tx.send(Err(CorrelatedStreamError::RequestTooLarge {
574 requested: n_elements,
575 max_allowed: max,
576 }
577 .into()));
578 return PrefetchHandle::from(rx);
579 }
580 let cmd = Command::Prefetch {
582 n_elements,
583 completion: tx,
584 };
585 if let Err(e) = self.command_sender.send(cmd) {
586 if let Command::Prefetch { completion, .. } = e.0 {
587 let _ = completion.send(Err(CorrelatedStreamError::StreamClosed.into()));
588 }
589 }
590 PrefetchHandle::from(rx)
591 }
592
593 fn position(&self) -> u64 {
594 self.position.load(Ordering::Acquire)
595 }
596
597 fn buffered(&self) -> u64 {
598 self.buffered.load(Ordering::Acquire)
599 }
600
601 fn resync(&self, target: u64) -> ResyncHandle<E> {
602 let (tx, rx) = oneshot::channel();
603 let cmd = Command::Resync {
604 target,
605 completion: tx,
606 };
607 if let Err(e) = self.command_sender.send(cmd) {
608 if let Command::Resync { completion, .. } = e.0 {
609 let _ = completion.send(Err(CorrelatedStreamError::StreamClosed.into()));
610 }
611 }
612 ResyncHandle::from(rx)
613 }
614}
615
616#[cfg(test)]
617mod tests {
618 use std::{
619 sync::{
620 atomic::{AtomicUsize, Ordering},
621 Arc,
622 },
623 time::Duration,
624 };
625
626 use rand::{rngs::StdRng, SeedableRng};
627 use typenum::U2;
628
629 use crate::{
630 algebra::elliptic_curve::{Curve25519Ristretto, ScalarField},
631 correlated_randomness::{
632 generator::CorrelationGenerator,
633 singlets::{Singlet, Singlets},
634 stream::{
635 buffered::{Buffer, BufferConfig, BufferedStream},
636 errors::CorrelatedStreamError,
637 CorrelatedStream,
638 },
639 },
640 random::Random,
641 utils::TryFuture,
642 };
643
644 type Fq = ScalarField<Curve25519Ristretto>;
645 type TestPB = Singlets<Fq, U2>;
646 type TestItem = Singlet<Fq>;
647 type TestErr = CorrelatedStreamError;
648
649 #[derive(Clone)]
654 struct MockGenConfig {
655 delay: Duration,
657 fail_after: Option<usize>,
660 }
661
662 impl Default for MockGenConfig {
663 fn default() -> Self {
664 Self {
665 delay: Duration::from_millis(0),
666 fail_after: None,
667 }
668 }
669 }
670
671 struct MockGen {
672 rng: StdRng,
673 cfg: MockGenConfig,
674 items_produced: Arc<AtomicUsize>,
676 batches: Arc<AtomicUsize>,
678 }
679
680 impl MockGen {
681 fn new(cfg: MockGenConfig) -> (Self, Arc<AtomicUsize>, Arc<AtomicUsize>) {
682 let items_produced = Arc::new(AtomicUsize::new(0));
683 let batches = Arc::new(AtomicUsize::new(0));
684 let gen = Self {
685 rng: StdRng::from_seed([0u8; 32]),
686 cfg,
687 items_produced: items_produced.clone(),
688 batches: batches.clone(),
689 };
690 (gen, items_produced, batches)
691 }
692 }
693
694 impl CorrelationGenerator<TestPB> for MockGen {
695 type Net = ();
696 type Error = TestErr;
697
698 fn run(&mut self, _net: &mut ()) -> impl TryFuture<Ok = TestPB, Error = Self::Error> {
699 async move { Err(CorrelatedStreamError::StreamClosed) }
700 }
701
702 fn run_for(
703 &mut self,
704 n: usize,
705 _net: &mut (),
706 ) -> impl TryFuture<Ok = Vec<TestItem>, Error = Self::Error> {
707 async move {
708 self.batches.fetch_add(1, Ordering::SeqCst);
709 if self.cfg.delay > Duration::ZERO {
710 tokio::time::sleep(self.cfg.delay).await;
711 }
712 if let Some(threshold) = self.cfg.fail_after {
713 if self.items_produced.load(Ordering::SeqCst) + n > threshold {
714 return Err(CorrelatedStreamError::StreamClosed);
715 }
716 }
717 let items: Vec<TestItem> = (0..n)
718 .map(|_| {
719 Singlet::<Fq>::random_n::<Vec<_>>(&mut self.rng, 1)
720 .into_iter()
721 .next()
722 .unwrap()
723 })
724 .collect();
725 self.items_produced.fetch_add(n, Ordering::SeqCst);
726 Ok(items)
727 }
728 }
729 }
730
731 fn make_stream(
732 cfg: MockGenConfig,
733 buf_cfg: BufferConfig,
734 ) -> (
735 BufferedStream<TestPB, TestErr>,
736 Arc<AtomicUsize>,
737 Arc<AtomicUsize>,
738 ) {
739 let (gen, produced, batches) = MockGen::new(cfg);
740 let stream = BufferedStream::<TestPB, TestErr>::new(gen, (), buf_cfg);
741 (stream, produced, batches)
742 }
743
744 struct SkipMockGen {
751 rng: StdRng,
752 produced: Arc<AtomicUsize>,
753 skipped: Arc<AtomicUsize>,
754 }
755
756 impl SkipMockGen {
757 fn new() -> (Self, Arc<AtomicUsize>, Arc<AtomicUsize>) {
758 let produced = Arc::new(AtomicUsize::new(0));
759 let skipped = Arc::new(AtomicUsize::new(0));
760 let gen = Self {
761 rng: StdRng::from_seed([7u8; 32]),
762 produced: produced.clone(),
763 skipped: skipped.clone(),
764 };
765 (gen, produced, skipped)
766 }
767 }
768
769 impl CorrelationGenerator<TestPB> for SkipMockGen {
770 type Net = ();
771 type Error = TestErr;
772
773 const SUPPORTS_UNILATERAL_SKIP: bool = true;
774
775 fn run(&mut self, _net: &mut ()) -> impl TryFuture<Ok = TestPB, Error = Self::Error> {
776 async move { Err(CorrelatedStreamError::StreamClosed) }
777 }
778
779 fn run_for(
780 &mut self,
781 n: usize,
782 _net: &mut (),
783 ) -> impl TryFuture<Ok = Vec<TestItem>, Error = Self::Error> {
784 async move {
785 let items: Vec<TestItem> = Singlet::<Fq>::random_n::<Vec<_>>(&mut self.rng, n);
786 self.produced.fetch_add(n, Ordering::SeqCst);
787 Ok(items)
788 }
789 }
790
791 fn skip(
792 &mut self,
793 n: usize,
794 _net: &mut (),
795 ) -> impl TryFuture<Ok = (), Error = Self::Error> {
796 async move {
797 self.skipped.fetch_add(n, Ordering::SeqCst);
799 self.produced.fetch_add(n, Ordering::SeqCst);
800 Ok(())
801 }
802 }
803 }
804
805 struct NoSkipMockGen {
808 rng: StdRng,
809 }
810
811 impl CorrelationGenerator<TestPB> for NoSkipMockGen {
812 type Net = ();
813 type Error = TestErr;
814
815 fn run(&mut self, _net: &mut ()) -> impl TryFuture<Ok = TestPB, Error = Self::Error> {
816 async move { Err(CorrelatedStreamError::StreamClosed) }
817 }
818
819 fn run_for(
820 &mut self,
821 n: usize,
822 _net: &mut (),
823 ) -> impl TryFuture<Ok = Vec<TestItem>, Error = Self::Error> {
824 async move { Ok(Singlet::<Fq>::random_n::<Vec<_>>(&mut self.rng, n)) }
825 }
826 }
827
828 #[tokio::test]
833 async fn next_n_resolves_batch_future() {
834 let (stream, _, _) = make_stream(MockGenConfig::default(), BufferConfig::eager(16));
835 let fut = stream.next_n(7).expect("request accepted");
836 let items = fut.await.expect("batch resolves");
837 assert_eq!(items.len(), 7);
838 }
839
840 #[tokio::test]
841 async fn request_too_large_rejected() {
842 let (stream, _, _) = make_stream(
843 MockGenConfig::default(),
844 BufferConfig::eager_with(8, 4), );
846 match stream.next_n(5) {
847 Err(CorrelatedStreamError::RequestTooLarge {
848 requested: 5,
849 max_allowed: 4,
850 }) => {}
851 Ok(_) => panic!("must reject n>max"),
852 Err(e) => panic!("unexpected error: {e:?}"),
853 }
854 }
855
856 #[tokio::test]
857 async fn rate_limit_when_exceeding_capacity() {
858 let cfg = MockGenConfig {
861 delay: Duration::from_millis(200),
862 ..Default::default()
863 };
864 let (stream, _, _) = make_stream(cfg, BufferConfig::eager(4));
865 let _f1 = stream.next_n(4).unwrap();
866 tokio::time::sleep(Duration::from_millis(20)).await;
868 let f2 = stream.next_n(4).unwrap();
869 let results = futures::future::join_all(f2).await;
870 assert!(
871 results
872 .iter()
873 .all(|r| matches!(r, Err(CorrelatedStreamError::RateLimitExceeded))),
874 "expected all four to be rate-limited, got {results:?}"
875 );
876 }
877
878 #[tokio::test]
879 async fn prefetch_completes_and_serves_subsequent_requests_quickly() {
880 let cfg = MockGenConfig {
881 delay: Duration::from_millis(100),
882 ..Default::default()
883 };
884 let (stream, produced, _) = make_stream(cfg, BufferConfig::eager(32));
885 let handle = stream.prefetch_n(10);
886 handle.await.expect("prefetch completes");
887 assert!(produced.load(Ordering::SeqCst) >= 10);
888 let start = std::time::Instant::now();
891 let items = stream.next_n(10).unwrap().await.expect("served");
892 assert_eq!(items.len(), 10);
893 assert!(
894 start.elapsed() < Duration::from_millis(80),
895 "request should be served from prefetched buffer (took {:?})",
896 start.elapsed()
897 );
898 }
899
900 #[tokio::test]
901 async fn sequential_prefetches_all_resolve() {
902 let (stream, _, _) = make_stream(MockGenConfig::default(), BufferConfig::lazy(16, 0));
906 for i in 0..2 {
907 tokio::time::timeout(Duration::from_secs(1), stream.prefetch_n(4))
908 .await
909 .unwrap_or_else(|_| panic!("prefetch {i} timed out"))
910 .expect("prefetch completes");
911 }
912 tokio::time::timeout(Duration::from_secs(1), stream.prefetch_n(8))
914 .await
915 .expect("partially covered prefetch timed out")
916 .expect("prefetch completes");
917 }
918
919 #[tokio::test]
920 async fn generator_error_propagates_to_pending_consumers() {
921 let cfg = MockGenConfig {
922 delay: Duration::from_millis(20),
923 fail_after: Some(0), };
925 let (stream, _, _) = make_stream(cfg, BufferConfig::eager(16));
926 let futs = stream.next_n(4).unwrap();
927 let results = futures::future::join_all(futs).await;
928 assert!(
929 results
930 .iter()
931 .all(|r| matches!(r, Err(CorrelatedStreamError::StreamClosed))),
932 "all consumers should receive the generator error"
933 );
934 }
935
936 #[tokio::test]
937 async fn fifo_order_across_two_batches() {
938 let cfg = MockGenConfig {
940 delay: Duration::from_millis(40),
941 ..Default::default()
942 };
943 let (stream, _, batches) = make_stream(cfg, BufferConfig::eager(32));
944 let f1 = stream.next_n(3).unwrap();
945 let f2 = stream.next_n(3).unwrap();
946 let (a, b) = tokio::join!(f1, f2);
947 let a = a.expect("first batch resolves");
948 let b = b.expect("second batch resolves");
949 assert_eq!(a.len(), 3);
950 assert_eq!(b.len(), 3);
951 assert!(batches.load(Ordering::SeqCst) >= 1);
953 }
954
955 #[tokio::test]
956 async fn buffer_config_setters_are_visible() {
957 let (stream, _, _) = make_stream(MockGenConfig::default(), BufferConfig::eager(16));
958 assert_eq!(stream.capacity().unwrap(), 16);
959 stream.set_capacity(32).unwrap();
960 assert_eq!(stream.capacity().unwrap(), 32);
961 }
962
963 #[tokio::test]
967 async fn next_n_rejects_too_large() {
968 let (stream, _, _) = make_stream(
969 MockGenConfig::default(),
970 BufferConfig::eager_with(8, 4), );
972 match stream.next_n(5) {
973 Err(CorrelatedStreamError::RequestTooLarge {
974 requested: 5,
975 max_allowed: 4,
976 }) => {}
977 Ok(_) => panic!("must reject n > max"),
978 Err(e) => panic!("unexpected error: {e:?}"),
979 }
980 }
981
982 #[tokio::test]
983 async fn next_n_rate_limit_when_exceeding_capacity() {
984 let cfg = MockGenConfig {
987 delay: Duration::from_millis(200),
988 ..Default::default()
989 };
990 let (stream, _, _) = make_stream(cfg, BufferConfig::eager(4));
991 let _f1 = stream.next_n(4).unwrap();
992 tokio::time::sleep(Duration::from_millis(20)).await;
994 let f2 = stream.next_n(4).unwrap();
995 assert!(
996 matches!(f2.await, Err(CorrelatedStreamError::RateLimitExceeded)),
997 "second next_n should be rate-limited"
998 );
999 }
1000
1001 #[tokio::test]
1002 async fn next_n_admitted_when_shortfall_fits_capacity() {
1003 let cfg = MockGenConfig {
1006 delay: Duration::from_millis(50),
1007 ..Default::default()
1008 };
1009 let (stream, _, _) = make_stream(cfg, BufferConfig::lazy(8, 0));
1010 let f1 = stream.next_n(4).unwrap();
1011 tokio::time::sleep(Duration::from_millis(20)).await;
1012 let f2 = stream.next_n(4).unwrap();
1013 let (a, b) = tokio::join!(f1, f2);
1014 assert_eq!(a.expect("first batch resolves").len(), 4);
1015 assert_eq!(b.expect("second batch resolves").len(), 4);
1016 }
1017
1018 #[tokio::test]
1019 async fn next_n_error_propagates() {
1020 let cfg = MockGenConfig {
1022 delay: Duration::from_millis(20), fail_after: Some(0),
1024 };
1025 let (stream, _, _) = make_stream(cfg, BufferConfig::eager(16));
1026 let result = stream.next_n(4).unwrap().await;
1027 assert!(
1028 matches!(result, Err(CorrelatedStreamError::StreamClosed)),
1029 "expected generator error to propagate through BatchFuture, got {result:?}"
1030 );
1031 }
1032
1033 #[tokio::test]
1036 async fn refill_threshold_drives_proactive_generation() {
1037 let (stream, produced, _) =
1040 make_stream(MockGenConfig::default(), BufferConfig::lazy(16, 8));
1041 let _ = stream.next_n(4).unwrap().await.expect("served");
1042 tokio::time::sleep(Duration::from_millis(50)).await;
1044 let total = produced.load(Ordering::SeqCst);
1047 assert!(total >= 8, "expected >= 8 items generated, got {total}");
1048 }
1049
1050 #[tokio::test]
1053 async fn position_tracks_delivered_not_prefetched() {
1054 let (stream, _, _) = make_stream(MockGenConfig::default(), BufferConfig::eager(32));
1055 stream.prefetch_n(10).await.expect("prefetch completes");
1057 assert_eq!(stream.position(), 0, "prefetch must not advance position");
1058 let _ = stream.next_n(7).unwrap().await.expect("served");
1060 assert_eq!(stream.position(), 7);
1061 let _ = stream.next_n(3).unwrap().await.expect("served");
1062 assert_eq!(stream.position(), 10);
1063 }
1064
1065 #[tokio::test]
1066 async fn buffered_tracks_ready_elements() {
1067 let (stream, _, _) = make_stream(MockGenConfig::default(), BufferConfig::lazy(32, 0));
1069 assert_eq!(stream.buffered(), 0);
1070 stream.prefetch_n(10).await.expect("prefetch completes");
1071 assert_eq!(stream.buffered(), 10, "prefetch fills the buffer");
1072 let _ = stream.next_n(4).unwrap().await.expect("served");
1073 assert_eq!(stream.buffered(), 6, "delivery drains the buffer");
1074 }
1075
1076 #[tokio::test]
1077 async fn position_does_not_advance_on_rejected_request() {
1078 let cfg = MockGenConfig {
1081 delay: Duration::from_millis(200),
1082 ..Default::default()
1083 };
1084 let (stream, _, _) = make_stream(cfg, BufferConfig::eager(4));
1085 let _f1 = stream.next_n(4).unwrap();
1086 tokio::time::sleep(Duration::from_millis(20)).await;
1087 let f2 = stream.next_n(4).unwrap();
1088 assert!(matches!(
1089 f2.await,
1090 Err(CorrelatedStreamError::RateLimitExceeded)
1091 ));
1092 assert_eq!(stream.position(), 4);
1094 }
1095
1096 #[tokio::test]
1097 async fn resync_drains_buffer_and_advances_position() {
1098 let (stream, produced, _) =
1101 make_stream(MockGenConfig::default(), BufferConfig::lazy(32, 0));
1102 stream.prefetch_n(10).await.expect("prefetch completes");
1103 let before = produced.load(Ordering::SeqCst);
1104 stream.resync(6).await.expect("resync completes");
1106 assert_eq!(stream.position(), 6);
1107 assert_eq!(
1108 produced.load(Ordering::SeqCst),
1109 before,
1110 "drain-only resync must not generate"
1111 );
1112 let _ = stream.next_n(2).unwrap().await.expect("served");
1114 assert_eq!(stream.position(), 8);
1115 }
1116
1117 #[tokio::test]
1118 async fn resync_noop_when_already_at_target() {
1119 let (stream, _, _) = make_stream(MockGenConfig::default(), BufferConfig::eager(16));
1120 let _ = stream.next_n(5).unwrap().await.expect("served");
1121 stream.resync(5).await.expect("no-op resync completes");
1122 assert_eq!(stream.position(), 5);
1123 }
1124
1125 #[tokio::test]
1126 async fn resync_rewind_is_rejected() {
1127 let (stream, _, _) = make_stream(MockGenConfig::default(), BufferConfig::eager(16));
1128 let _ = stream.next_n(5).unwrap().await.expect("served");
1129 match stream.resync(3).await {
1130 Err(CorrelatedStreamError::ResyncRewind {
1131 current: 5,
1132 target: 3,
1133 }) => {}
1134 other => panic!("expected ResyncRewind, got {other:?}"),
1135 }
1136 assert_eq!(stream.position(), 5);
1138 }
1139
1140 #[tokio::test]
1141 async fn resync_unsupported_when_deficit_and_no_skip() {
1142 let gen = NoSkipMockGen {
1145 rng: StdRng::from_seed([0u8; 32]),
1146 };
1147 let stream = BufferedStream::<TestPB, TestErr>::new(gen, (), BufferConfig::lazy(16, 0));
1148 match stream.resync(10).await {
1149 Err(CorrelatedStreamError::ResyncUnsupported {
1150 generated: 0,
1151 target: 10,
1152 }) => {}
1153 other => panic!("expected ResyncUnsupported, got {other:?}"),
1154 }
1155 assert_eq!(stream.position(), 0);
1157 }
1158
1159 #[tokio::test]
1160 async fn resync_skips_at_generator_when_supported() {
1161 let (gen, produced, skipped) = SkipMockGen::new();
1162 let stream = BufferedStream::<TestPB, TestErr>::new(gen, (), BufferConfig::lazy(64, 0));
1164 stream.resync(10).await.expect("resync via skip completes");
1165 assert_eq!(stream.position(), 10);
1166 assert_eq!(
1167 skipped.load(Ordering::SeqCst),
1168 10,
1169 "deficit should be skipped"
1170 );
1171 assert_eq!(
1172 produced.load(Ordering::SeqCst),
1173 10,
1174 "skip advances the generator without delivering items"
1175 );
1176 let items = stream.next_n(3).unwrap().await.expect("served");
1178 assert_eq!(items.len(), 3);
1179 assert_eq!(stream.position(), 13);
1180 }
1181
1182 #[tokio::test]
1183 async fn resync_partial_buffer_then_skip_remainder() {
1184 let (gen, produced, skipped) = SkipMockGen::new();
1185 let stream = BufferedStream::<TestPB, TestErr>::new(gen, (), BufferConfig::lazy(64, 0));
1186 stream.prefetch_n(4).await.expect("prefetch completes");
1188 let produced_after_prefetch = produced.load(Ordering::SeqCst);
1189 assert_eq!(produced_after_prefetch, 4);
1190 stream.resync(10).await.expect("resync completes");
1191 assert_eq!(stream.position(), 10);
1192 assert_eq!(
1193 skipped.load(Ordering::SeqCst),
1194 6,
1195 "only the unbuffered remainder is skipped at the generator"
1196 );
1197 }
1198}