1use crate::cache;
12use crate::frame::{self, Frame, FrameBuf};
13use crate::{Timescale, stats, track};
14use std::collections::VecDeque;
15use std::mem::MaybeUninit;
16use std::sync::Arc;
17use std::task::{Poll, ready};
18
19use crate::{Error, IntoBytes, Result, Timestamp};
20
21const MAX_GROUP_CACHE: u64 = 32 * 1024 * 1024; #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
32pub struct Info {
33 pub sequence: u64,
36}
37
38impl Info {
39 #[cfg(test)]
45 pub(crate) fn produce(self) -> Producer {
46 Producer::new(self, track::Info::default())
47 }
48}
49
50impl From<usize> for Info {
51 fn from(sequence: usize) -> Self {
52 Self {
53 sequence: sequence as u64,
54 }
55 }
56}
57
58impl From<u64> for Info {
59 fn from(sequence: u64) -> Self {
60 Self { sequence }
61 }
62}
63
64impl From<u32> for Info {
65 fn from(sequence: u32) -> Self {
66 Self {
67 sequence: sequence as u64,
68 }
69 }
70}
71
72impl From<u16> for Info {
73 fn from(sequence: u16) -> Self {
74 Self {
75 sequence: sequence as u64,
76 }
77 }
78}
79
80pub(crate) struct Partial {
83 timestamp: Timestamp,
84 buf: FrameBuf,
85}
86
87#[derive(Default)]
90pub(crate) struct GroupState {
91 pub(crate) frames: VecDeque<Frame>,
94
95 pub(crate) partial: Option<Partial>,
97
98 pub(crate) offset: usize,
100
101 pub(crate) cache: u64,
103
104 charge: cache::Charge,
107
108 pub(crate) fin: bool,
110
111 pub(crate) abort: Option<Error>,
113}
114
115impl GroupState {
116 fn poll_frame_source(&self, index: usize) -> Poll<Result<Option<(frame::Info, frame::Source)>>> {
119 if index < self.offset {
120 return Poll::Ready(Err(Error::Lagged));
121 }
122 let local = index - self.offset;
123 if let Some(f) = self.frames.get(local) {
124 self.charge.touch();
125 let info = frame::Info {
126 size: f.payload.len() as u64,
127 timestamp: f.timestamp,
128 };
129 return Poll::Ready(Ok(Some((info, frame::Source::Complete(f.payload.clone())))));
130 }
131 if local == self.frames.len()
132 && let Some(p) = &self.partial
133 {
134 self.charge.touch();
135 let info = frame::Info {
136 size: p.buf.capacity() as u64,
137 timestamp: p.timestamp,
138 };
139 return Poll::Ready(Ok(Some((info, frame::Source::Partial(p.buf.clone())))));
140 }
141 if let Some(err) = &self.abort {
145 return Poll::Ready(Err(err.clone()));
146 }
147 if self.fin {
148 return Poll::Ready(Ok(None));
149 }
150 Poll::Pending
151 }
152
153 fn poll_finished(&self) -> Poll<Result<u64>> {
154 if let Some(err) = &self.abort {
155 Poll::Ready(Err(err.clone()))
158 } else if self.fin {
159 Poll::Ready(Ok((self.offset + self.frames.len()) as u64))
160 } else {
161 Poll::Pending
162 }
163 }
164
165 fn evict(&mut self) {
167 while self.cache > MAX_GROUP_CACHE {
168 let Some(frame) = self.frames.pop_front() else {
169 break;
170 };
171 let size = frame.payload.len() as u64;
172 self.cache -= size;
173 self.charge.sub(size);
174 self.offset += 1;
175 }
176 }
177
178 fn release(&mut self) {
180 self.frames.clear();
181 self.partial = None;
182 self.cache = 0;
183 self.charge.clear();
184 }
185}
186
187fn modify(state: &kio::Producer<GroupState>) -> Result<kio::Mut<'_, GroupState>> {
188 state.write().map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
189}
190
191fn evict(weak: &kio::ProducerWeak<GroupState>) {
194 let Some(producer) = weak.produce() else { return };
196 let Ok(mut state) = producer.write() else { return };
197 if state.abort.is_some() {
198 return;
199 }
200 state.abort = Some(Error::Evicted);
201 state.release();
202 state.close();
203}
204
205pub struct Producer {
211 state: kio::Producer<GroupState>,
213
214 info: Info,
217
218 track: track::Info,
223
224 stats: stats::Meter,
227}
228
229impl std::ops::Deref for Producer {
230 type Target = Info;
231
232 fn deref(&self) -> &Self::Target {
233 &self.info
234 }
235}
236
237impl Producer {
238 pub(crate) fn new(info: Info, track: track::Info) -> Self {
249 let state = kio::Producer::<GroupState>::default();
250 let weak = state.weak();
251 let charge = track.broadcast.origin.pool.register(Box::new(move || evict(&weak)));
252 state.write().ok().expect("a new group is open").charge = charge;
253 Self {
254 info,
255 state,
256 track,
257 stats: stats::Meter::default(),
258 }
259 }
260
261 pub(crate) fn with_meter(mut self, meter: stats::Meter) -> Self {
264 meter.group();
265 self.stats = meter;
266 self
267 }
268
269 pub(crate) fn info(&self) -> Info {
271 self.info
272 }
273
274 pub fn timescale(&self) -> Timescale {
276 self.track.timescale
277 }
278
279 pub fn write_frame<B: IntoBytes>(&mut self, timestamp: Timestamp, data: B) -> Result<()> {
287 let timestamp = timestamp
288 .convert(self.track.timescale)
289 .map_err(|_| Error::TimestampMismatch)?;
290 let payload = data.into_bytes();
291 if payload.len() as u64 > MAX_GROUP_CACHE {
292 return Err(Error::FrameTooLarge);
293 }
294
295 let mut state = modify(&self.state)?;
296 if state.fin {
297 return Err(Error::Closed);
298 }
299 debug_assert!(state.partial.is_none(), "a frame is already open");
300 let size = payload.len() as u64;
301 state.cache += size;
302 state.charge.add(size);
303 state.frames.push_back(Frame { timestamp, payload });
304 state.evict();
305
306 drop(state);
309 self.track.broadcast.origin.pool.evict();
310
311 self.stats.frames(1);
313 self.stats.bytes(size);
314 Ok(())
315 }
316
317 pub fn create_frame(&mut self, frame: frame::Info) -> Result<frame::Producer<'_>> {
326 let timestamp = frame
327 .timestamp
328 .convert(self.track.timescale)
329 .map_err(|_| Error::TimestampMismatch)?;
330 if frame.size > MAX_GROUP_CACHE {
331 return Err(Error::FrameTooLarge);
332 }
333 let buf = FrameBuf::new(frame.size as usize);
334
335 let mut state = modify(&self.state)?;
336 if state.fin {
337 return Err(Error::Closed);
338 }
339 debug_assert!(state.partial.is_none(), "a frame is already open");
340 state.cache += frame.size;
341 state.charge.add(frame.size);
342 state.partial = Some(Partial {
343 timestamp,
344 buf: buf.clone(),
345 });
346 state.evict();
347
348 drop(state);
351 self.track.broadcast.origin.pool.evict();
352
353 self.stats.frames(1);
356 let meter = self.stats.clone();
357
358 let info = frame::Info {
359 size: frame.size,
360 timestamp,
361 };
362 Ok(frame::Producer::new(self, buf, info).with_meter(meter))
363 }
364
365 pub(crate) fn frame_notify(&self) {
367 let _ = self.state.write();
369 }
370
371 pub(crate) fn frame_commit(&mut self, frame: Frame) -> Result<()> {
373 let mut state = modify(&self.state)?;
374 state.partial = None;
377 state.frames.push_back(frame);
378 Ok(())
379 }
380
381 pub(crate) fn frame_abort(&mut self, err: Error) {
384 let _ = self.clone().abort(err);
385 }
386
387 pub fn frame_count(&self) -> usize {
389 let state = self.state.read();
390 state.offset + state.frames.len() + state.partial.is_some() as usize
391 }
392
393 pub fn finish(&mut self) -> Result<()> {
398 let mut state = modify(&self.state)?;
399 state.fin = true;
400 Ok(())
401 }
402
403 pub fn abort(self, err: Error) -> Result<()> {
409 let mut guard = modify(&self.state)?;
410 guard.abort = Some(err);
411 guard.release();
412 guard.close();
413 Ok(())
414 }
415
416 pub(crate) fn is_aborted(&self) -> bool {
419 self.state.read().abort.is_some()
420 }
421
422 pub(crate) fn cache_entry(&self) -> Option<Arc<cache::Entry>> {
425 self.state.read().charge.entry()
426 }
427
428 pub fn consume(&self) -> Consumer {
430 Consumer {
431 info: self.info,
432 state: self.state.consume(),
433 track: self.track.clone(),
434 index: 0,
435 prefetch: Prefetch::default(),
436 stats: stats::Meter::default(),
439 }
440 }
441
442 pub async fn closed(&self) -> Error {
444 kio::wait(|waiter| self.poll_closed(waiter)).await
445 }
446
447 pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<Error> {
449 self.state.poll_closed(waiter).map(|()| self.abort_reason())
450 }
451
452 pub async fn unused(&self) -> Result<()> {
454 self.state.unused().await.map_err(|_| self.abort_reason())
455 }
456
457 fn abort_reason(&self) -> Error {
459 self.state.read().abort.clone().unwrap_or(Error::Dropped)
460 }
461}
462
463impl Clone for Producer {
464 fn clone(&self) -> Self {
465 Self {
466 info: self.info,
467 state: self.state.clone(),
468 track: self.track.clone(),
469 stats: self.stats.clone(),
470 }
471 }
472}
473
474impl Drop for Producer {
475 fn drop(&mut self) {
476 if !self.state.is_last() {
480 return;
481 }
482 if let Ok(mut state) = modify(&self.state)
483 && !state.fin
484 {
485 tracing::warn!(
488 sequence = self.info.sequence,
489 "group::Producer dropped without finish() or abort()"
490 );
491 state.release();
492 }
493 }
494}
495
496struct Prefetch {
504 frames: [MaybeUninit<Frame>; Self::CAP],
506 pos: usize,
507 len: usize,
508}
509
510impl Prefetch {
511 const CAP: usize = 8;
512
513 fn pop(&mut self) -> Option<Frame> {
515 if self.pos == self.len {
516 return None;
517 }
518 let frame = unsafe { self.frames[self.pos].assume_init_read() };
520 self.pos += 1;
521 Some(frame)
522 }
523
524 fn fill(&mut self, frames: impl Iterator<Item = Frame>) {
526 debug_assert_eq!(self.pos, self.len, "fill on a non-empty batch would leak frames");
527 self.pos = 0;
528 self.len = 0;
529 for frame in frames.take(Self::CAP) {
530 self.frames[self.len].write(frame);
531 self.len += 1;
532 }
533 }
534
535 fn buffered(&self) -> (u64, u64) {
538 let mut bytes = 0u64;
539 for slot in &self.frames[self.pos..self.len] {
540 bytes += unsafe { slot.assume_init_ref() }.payload.len() as u64;
542 }
543 ((self.len - self.pos) as u64, bytes)
544 }
545}
546
547impl Default for Prefetch {
548 fn default() -> Self {
549 Self {
550 frames: [const { MaybeUninit::uninit() }; Self::CAP],
551 pos: 0,
552 len: 0,
553 }
554 }
555}
556
557impl Drop for Prefetch {
558 fn drop(&mut self) {
559 for slot in &mut self.frames[self.pos..self.len] {
560 unsafe { slot.assume_init_drop() };
562 }
563 }
564}
565
566pub struct Consumer {
568 state: kio::Consumer<GroupState>,
570
571 info: Info,
573
574 track: track::Info,
577
578 index: usize,
581
582 prefetch: Prefetch,
584
585 stats: stats::Meter,
588}
589
590impl Clone for Consumer {
591 fn clone(&self) -> Self {
592 Self {
595 state: self.state.clone(),
596 info: self.info,
597 track: self.track.clone(),
598 index: self.index,
599 prefetch: Prefetch::default(),
600 stats: self.stats.clone(),
603 }
604 }
605}
606
607impl std::ops::Deref for Consumer {
608 type Target = Info;
609
610 fn deref(&self) -> &Self::Target {
611 &self.info
612 }
613}
614
615impl Consumer {
616 pub(crate) fn with_meter(mut self, meter: stats::Meter) -> Self {
619 meter.group();
620 self.stats = meter;
621 self
622 }
623
624 pub fn timescale(&self) -> Timescale {
626 self.track.timescale
627 }
628
629 fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
631 where
632 F: Fn(&kio::Ref<'_, GroupState>) -> Poll<Result<R>>,
633 {
634 Poll::Ready(match ready!(self.state.poll(waiter, f)) {
635 Ok(res) => res,
636 Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
638 })
639 }
640
641 pub async fn next_frame(&mut self) -> Result<Option<frame::Consumer>> {
643 kio::wait(|waiter| self.poll_next_frame(waiter)).await
644 }
645
646 pub fn poll_next_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Consumer>>> {
650 if let Some(frame) = self.prefetch.pop() {
654 self.index += 1;
655 let info = frame::Info {
656 size: frame.payload.len() as u64,
657 timestamp: frame.timestamp,
658 };
659 let source = frame::Source::Complete(frame.payload);
660 return Poll::Ready(Ok(Some(frame::Consumer::new(self.state.clone(), info, source))));
661 }
662
663 let index = self.index;
664 let Some((info, source)) = ready!(self.poll(waiter, |state| state.poll_frame_source(index))?) else {
665 return Poll::Ready(Ok(None));
666 };
667
668 self.index += 1;
669 self.stats.frames(1);
672 Poll::Ready(Ok(Some(
673 frame::Consumer::new(self.state.clone(), info, source).with_meter(self.stats.clone()),
674 )))
675 }
676
677 pub fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Frame>>> {
679 if let Some(frame) = self.prefetch.pop() {
681 self.index += 1;
682 return Poll::Ready(Ok(Some(frame)));
683 }
684
685 let index = self.index;
688 let prefetch = &mut self.prefetch;
689 let res = self.state.poll(waiter, |state| {
690 if index < state.offset {
691 return Poll::Ready(Err(Error::Lagged));
692 }
693 let local = (index - state.offset).min(state.frames.len());
698 prefetch.fill(state.frames.range(local..).cloned());
699 if prefetch.len > 0 {
700 state.charge.touch();
704 return Poll::Ready(Ok(()));
705 }
706 if let Some(err) = &state.abort {
709 return Poll::Ready(Err(err.clone()));
710 }
711 if state.fin {
712 return Poll::Ready(Ok(()));
713 }
714 Poll::Pending
715 });
716
717 match ready!(res) {
718 Ok(Ok(())) => {}
719 Ok(Err(err)) => return Poll::Ready(Err(err)),
720 Err(state) => return Poll::Ready(Err(state.abort.clone().unwrap_or(Error::Dropped))),
721 }
722
723 let (frames, bytes) = self.prefetch.buffered();
726 self.stats.frames(frames);
727 self.stats.bytes(bytes);
728
729 Poll::Ready(Ok(self.prefetch.pop().inspect(|_| {
730 self.index += 1;
731 })))
732 }
733
734 pub async fn read_frame(&mut self) -> Result<Option<frame::Frame>> {
736 if let Some(frame) = self.prefetch.pop() {
738 self.index += 1;
739 return Ok(Some(frame));
740 }
741 kio::wait(|waiter| self.poll_read_frame(waiter)).await
742 }
743
744 pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
746 self.poll(waiter, |state| state.poll_finished())
747 }
748
749 pub async fn finished(&mut self) -> Result<u64> {
751 kio::wait(|waiter| self.poll_finished(waiter)).await
752 }
753}
754
755#[derive(Clone, Debug, Default)]
757#[non_exhaustive]
758pub struct Fetch {
759 pub priority: u8,
761}
762
763impl Fetch {
764 pub fn with_priority(mut self, priority: u8) -> Self {
766 self.priority = priority;
767 self
768 }
769}
770
771#[cfg(test)]
772mod test {
773 use super::*;
774 use bytes::Bytes;
775 use futures::FutureExt;
776
777 #[test]
778 fn basic_frame_reading() {
779 let mut producer = Info { sequence: 0 }.produce();
780 producer
781 .write_frame(Timestamp::ZERO, Bytes::from_static(b"frame0"))
782 .unwrap();
783 producer
784 .write_frame(Timestamp::ZERO, Bytes::from_static(b"frame1"))
785 .unwrap();
786 producer.finish().unwrap();
787
788 let mut consumer = producer.consume();
789 let f0 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
790 assert_eq!(f0.size, 6);
791 let f1 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
792 assert_eq!(f1.size, 6);
793 let end = consumer.next_frame().now_or_never().unwrap().unwrap();
794 assert!(end.is_none());
795 }
796
797 #[test]
798 fn read_frame_all_at_once() {
799 let mut producer = Info { sequence: 0 }.produce();
800 producer
801 .write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
802 .unwrap();
803 producer.finish().unwrap();
804
805 let mut consumer = producer.consume();
806 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
807 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
808 }
809
810 #[test]
811 fn read_frame_preserves_timestamp() {
812 let mut producer = Info { sequence: 0 }.produce();
813 let timestamp = Timestamp::from_micros(20_000).unwrap();
814 producer.write_frame(timestamp, Bytes::from_static(b"hello")).unwrap();
815 producer.finish().unwrap();
816
817 let mut consumer = producer.consume();
818 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
819 assert_eq!(frame.timestamp.as_micros(), 20_000);
820 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
821 }
822
823 #[test]
824 fn chunked_frame_reads_whole() {
825 let mut producer = Info { sequence: 0 }.produce();
826 {
827 let mut frame = producer
828 .create_frame(frame::Info {
829 size: 10,
830 timestamp: Timestamp::ZERO,
831 })
832 .unwrap();
833 frame.write(Bytes::from_static(b"hello")).unwrap();
834 frame.write(Bytes::from_static(b"world")).unwrap();
835 frame.finish().unwrap();
836 }
837 producer.finish().unwrap();
838
839 let mut consumer = producer.consume();
842 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
843 assert_eq!(frame.payload, Bytes::from_static(b"helloworld"));
844 }
845
846 #[test]
847 fn chunked_frame_streams_partial() {
848 let mut producer = Info { sequence: 0 }.produce();
849 let mut consumer = producer.consume();
850
851 let mut frame = producer
852 .create_frame(frame::Info {
853 size: 6,
854 timestamp: Timestamp::ZERO,
855 })
856 .unwrap();
857 frame.write(Bytes::from_static(b"foo")).unwrap();
858
859 let mut f = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
861 let c1 = f.read_chunk().now_or_never().unwrap().unwrap();
862 assert_eq!(c1, Some(Bytes::from_static(b"foo")));
863 assert!(f.read_chunk().now_or_never().is_none());
864
865 frame.write(Bytes::from_static(b"bar")).unwrap();
866 frame.finish().unwrap();
867
868 let c2 = f.read_chunk().now_or_never().unwrap().unwrap();
869 assert_eq!(c2, Some(Bytes::from_static(b"bar")));
870 let c3 = f.read_chunk().now_or_never().unwrap().unwrap();
871 assert_eq!(c3, None);
872 }
873
874 #[test]
875 fn group_finish_returns_none() {
876 let mut producer = Info { sequence: 0 }.produce();
877 producer.finish().unwrap();
878
879 let mut consumer = producer.consume();
880 let end = consumer.next_frame().now_or_never().unwrap().unwrap();
881 assert!(end.is_none());
882 }
883
884 #[test]
885 fn abort_propagates() {
886 let producer = Info { sequence: 0 }.produce();
887 let mut consumer = producer.consume();
888 producer.abort(crate::Error::Cancel).unwrap();
889
890 let result = consumer.next_frame().now_or_never().unwrap();
891 assert!(matches!(result, Err(crate::Error::Cancel)));
892 }
893
894 #[test]
895 fn abort_clears_cached_frames() {
896 let mut producer = Info { sequence: 0 }.produce();
897 producer
898 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
899 .unwrap();
900
901 let _consumer = producer.consume();
903 assert_eq!(producer.state.read().frames.len(), 1);
904
905 producer.clone().abort(crate::Error::Cancel).unwrap();
906
907 let state = producer.state.read();
908 assert!(state.frames.is_empty(), "cached frames should be dropped on abort");
909 assert_eq!(state.cache, 0);
910 }
911
912 #[test]
913 fn drop_unfinished_clears_cached_frames() {
914 let producer = Info { sequence: 0 }.produce();
915 let mut writer = producer.clone();
916 writer
917 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
918 .unwrap();
919
920 let mut consumer = producer.consume();
922 assert_eq!(producer.state.read().frames.len(), 1);
923
924 drop(writer);
926 drop(producer);
927
928 let result = consumer.next_frame().now_or_never().unwrap();
929 assert!(matches!(result, Err(crate::Error::Dropped)));
930 }
931
932 #[test]
933 fn drop_finished_keeps_cached_frames() {
934 let mut producer = Info { sequence: 0 }.produce();
935 producer
936 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
937 .unwrap();
938 producer.finish().unwrap();
939
940 let mut consumer = producer.consume();
941 drop(producer);
942
943 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
945 assert_eq!(frame.payload, Bytes::from_static(b"data"));
946 }
947
948 #[tokio::test]
949 async fn pending_then_ready() {
950 let mut producer = Info { sequence: 0 }.produce();
951 let mut consumer = producer.consume();
952
953 assert!(consumer.next_frame().now_or_never().is_none());
955
956 producer
957 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
958 .unwrap();
959 producer.finish().unwrap();
960
961 let frame = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
962 assert_eq!(frame.size, 4);
963 }
964
965 #[test]
966 fn eviction_drops_old_frames() {
967 let mut producer = Info { sequence: 0 }.produce();
968
969 let big = Bytes::from(vec![0u8; MAX_GROUP_CACHE as usize]);
971 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
972 producer.write_frame(Timestamp::ZERO, big).unwrap();
973
974 let state = producer.state.read();
976 assert_eq!(state.offset, 1);
977 assert_eq!(state.frames.len(), 1);
978 assert_eq!(state.frames[0].payload.len(), MAX_GROUP_CACHE as usize);
979 }
980
981 #[test]
982 fn next_frame_returns_cache_full_on_tombstone() {
983 let mut producer = Info { sequence: 0 }.produce();
984
985 let big = Bytes::from(vec![0u8; MAX_GROUP_CACHE as usize]);
986 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
987 producer.write_frame(Timestamp::ZERO, big).unwrap();
988
989 let mut consumer = producer.consume();
990 let result = consumer.next_frame().now_or_never().unwrap();
992 assert!(matches!(result, Err(crate::Error::Lagged)));
993 }
994
995 #[test]
996 fn no_eviction_under_budget() {
997 let mut producer = Info { sequence: 0 }.produce();
998 for _ in 0..100_000 {
1000 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
1001 }
1002 producer.finish().unwrap();
1003
1004 let state = producer.state.read();
1005 assert_eq!(state.offset, 0);
1006 assert_eq!(state.frames.len(), 100_000);
1007 }
1008
1009 #[test]
1010 fn clone_consumer_independent() {
1011 let mut producer = Info { sequence: 0 }.produce();
1012 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
1013
1014 let mut c1 = producer.consume();
1015 let _ = c1.next_frame().now_or_never().unwrap().unwrap().unwrap();
1017
1018 let mut c2 = c1.clone();
1020
1021 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
1022 producer.finish().unwrap();
1023
1024 let f = c2.next_frame().now_or_never().unwrap().unwrap().unwrap();
1026 assert_eq!(f.size, 1); let end = c2.next_frame().now_or_never().unwrap().unwrap();
1029 assert!(end.is_none());
1030 }
1031
1032 #[test]
1035 fn read_frame_crosses_prefetch_batches() {
1036 let n = Prefetch::CAP * 3 + 5;
1037 let mut producer = Info { sequence: 0 }.produce();
1038 for i in 0..n {
1039 producer
1040 .write_frame(Timestamp::ZERO, Bytes::from(vec![i as u8; 4]))
1041 .unwrap();
1042 }
1043 producer.finish().unwrap();
1044
1045 let mut consumer = producer.consume();
1046 for i in 0..n {
1047 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1048 assert_eq!(frame.payload, Bytes::from(vec![i as u8; 4]));
1049 }
1050 assert!(consumer.read_frame().now_or_never().unwrap().unwrap().is_none());
1051 }
1052
1053 #[test]
1055 fn interleave_read_and_next_frame() {
1056 let mut producer = Info { sequence: 0 }.produce();
1057 for i in 0..5u8 {
1058 producer.write_frame(Timestamp::ZERO, Bytes::from(vec![i; 1])).unwrap();
1059 }
1060 producer.finish().unwrap();
1061
1062 let mut consumer = producer.consume();
1063 let f0 = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1065 assert_eq!(f0.payload, Bytes::from(vec![0u8; 1]));
1066
1067 for i in 1..5u8 {
1069 let mut f = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1070 let data = f.read_all().now_or_never().unwrap().unwrap();
1071 assert_eq!(data, Bytes::from(vec![i; 1]));
1072 }
1073 assert!(consumer.next_frame().now_or_never().unwrap().unwrap().is_none());
1074 }
1075
1076 #[test]
1079 fn read_frame_past_cleared_frames_does_not_panic() {
1080 let mut producer = Info { sequence: 0 }.produce();
1081 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
1082 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
1083
1084 let mut consumer = producer.consume();
1085 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1086 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1087
1088 producer.abort(Error::Cancel).unwrap();
1091
1092 let result = consumer.read_frame().now_or_never().unwrap();
1093 assert!(matches!(result, Err(Error::Cancel)), "expected Cancel, got {result:?}");
1094 }
1095
1096 #[test]
1099 fn drop_with_partial_batch() {
1100 let mut producer = Info { sequence: 0 }.produce();
1101 for _ in 0..Prefetch::CAP {
1102 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
1103 }
1104 producer.finish().unwrap();
1105
1106 let mut consumer = producer.consume();
1107 let _ = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1109 drop(consumer);
1110 }
1111
1112 #[test]
1115 fn create_frame_converts_mismatched_scale() {
1116 use crate::{Timescale, Timestamp};
1117
1118 let mut producer = Producer::new(
1119 Info { sequence: 0 },
1120 track::Info::default().with_timescale(Timescale::MICRO),
1121 );
1122 let frame = frame::Info {
1123 size: 3,
1124 timestamp: Timestamp::from_millis(1).unwrap(), };
1126 let writer = producer.create_frame(frame).unwrap();
1127 assert_eq!(writer.timestamp.scale(), Timescale::MICRO);
1128 assert_eq!(writer.timestamp.value(), 1000);
1129 }
1130
1131 #[tokio::test]
1133 async fn create_frame_converts_current_timestamp() {
1134 use crate::Timescale;
1135
1136 let mut producer = Producer::new(
1137 Info { sequence: 0 },
1138 track::Info::default().with_timescale(Timescale::MICRO),
1139 );
1140 let writer = producer
1141 .create_frame(frame::Info {
1142 size: 3,
1143 timestamp: Timestamp::now(),
1144 })
1145 .unwrap();
1146 assert_eq!(writer.timestamp.scale(), Timescale::MICRO);
1147 assert!(!writer.timestamp.is_zero(), "local clock should be non-zero");
1148 }
1149
1150 #[test]
1152 fn create_frame_rejects_oversized() {
1153 let mut producer = Info { sequence: 0 }.produce();
1154 let result = producer.create_frame(frame::Info {
1155 size: MAX_GROUP_CACHE + 1,
1156 timestamp: Timestamp::ZERO,
1157 });
1158 assert!(matches!(result, Err(Error::FrameTooLarge)));
1159 }
1160}