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