1use crate::cache;
12use crate::frame::{self, Frame, FrameBuf};
13use crate::{Timescale, stats, track};
14use std::collections::VecDeque;
15use std::sync::Arc;
16use std::task::{Poll, ready};
17
18use crate::{Error, IntoBytes, Result, Timestamp};
19
20pub const MAX_CACHE_BYTES: u64 = 32 * 1024 * 1024; #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
31pub struct Info {
32 pub sequence: u64,
35}
36
37impl Info {
38 #[cfg(test)]
44 pub(crate) fn produce(self) -> Producer {
45 Producer::new(self, track::Info::default(), Default::default())
46 }
47}
48
49impl From<usize> for Info {
50 fn from(sequence: usize) -> Self {
51 Self {
52 sequence: sequence as u64,
53 }
54 }
55}
56
57impl From<u64> for Info {
58 fn from(sequence: u64) -> Self {
59 Self { sequence }
60 }
61}
62
63impl From<u32> for Info {
64 fn from(sequence: u32) -> Self {
65 Self {
66 sequence: sequence as u64,
67 }
68 }
69}
70
71impl From<u16> for Info {
72 fn from(sequence: u16) -> Self {
73 Self {
74 sequence: sequence as u64,
75 }
76 }
77}
78
79pub(crate) struct Partial {
82 timestamp: Timestamp,
83 buf: FrameBuf,
84}
85
86#[derive(Default)]
89pub(crate) struct GroupState {
90 pub(crate) frames: VecDeque<Frame>,
93
94 pub(crate) partial: Option<Partial>,
96
97 pub(crate) offset: usize,
99
100 pub(crate) cache: u64,
102
103 charge: cache::Charge,
106
107 pub(crate) fin: Option<usize>,
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.refresh();
127 let info = frame::Info {
128 size: f.payload.len() as u64,
129 timestamp: f.timestamp,
130 };
131 return Poll::Ready(Ok(Some((info, frame::Source::Complete(f.payload.clone())))));
132 }
133 if local == self.frames.len()
134 && let Some(p) = &self.partial
135 {
136 self.charge.refresh();
137 let info = frame::Info {
138 size: p.buf.capacity() as u64,
139 timestamp: p.timestamp,
140 };
141 return Poll::Ready(Ok(Some((info, frame::Source::Partial(p.buf.clone())))));
142 }
143 ready!(self.poll_terminal(index))?;
144 Poll::Ready(Ok(None))
145 }
146
147 fn poll_terminal(&self, index: usize) -> Poll<Result<()>> {
154 match (self.fin, &self.abort) {
155 (Some(total), Some(err)) if index < total => Poll::Ready(Err(err.clone())),
156 (Some(_), _) => Poll::Ready(Ok(())),
157 (None, Some(err)) => Poll::Ready(Err(err.clone())),
158 (None, None) => Poll::Pending,
159 }
160 }
161
162 fn poll_finished(&self) -> Poll<Result<u64>> {
163 if let Some(total) = self.fin {
166 Poll::Ready(Ok(total as u64))
167 } else if let Some(err) = &self.abort {
168 Poll::Ready(Err(err.clone()))
169 } else {
170 Poll::Pending
171 }
172 }
173
174 fn evict(&mut self) {
176 while self.cache > MAX_CACHE_BYTES {
177 let Some(frame) = self.frames.pop_front() else {
178 break;
179 };
180 let size = frame.payload.len() as u64;
181 self.cache -= size;
182 self.charge.sub(size);
183 self.offset += 1;
184 }
185 }
186
187 fn release(&mut self) {
189 self.frames.clear();
190 self.partial = None;
191 self.cache = 0;
192 self.charge.clear();
193 }
194}
195
196fn modify(state: &kio::Producer<GroupState>) -> Result<kio::Mut<'_, GroupState>> {
197 state.write().map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
198}
199
200pub struct Producer {
206 state: kio::Producer<GroupState>,
208
209 info: Info,
212
213 track: track::Info,
218
219 cache: Arc<cache::Track>,
223
224 stats: stats::Meter,
227
228 alive: Arc<Alive>,
231}
232
233struct Alive {
241 info: Info,
242 state: kio::Producer<GroupState>,
243}
244
245impl Drop for Alive {
246 fn drop(&mut self) {
247 match self.state.write() {
253 Ok(mut state) => {
254 if state.fin.is_some() || state.abort.is_some() {
255 return;
256 }
257 tracing::warn!(
258 sequence = self.info.sequence,
259 "group::Producer dropped without finish() or abort()"
260 );
261 state.release();
262 }
263 Err(state) => {
264 if state.fin.is_some() || state.abort.is_some() {
265 return;
266 }
267 tracing::warn!(
268 sequence = self.info.sequence,
269 "group::Producer dropped without finish() or abort()"
270 );
271 }
272 }
273 }
274}
275
276impl std::ops::Deref for Producer {
277 type Target = Info;
278
279 fn deref(&self) -> &Self::Target {
280 &self.info
281 }
282}
283
284impl Producer {
285 pub(crate) fn new(info: Info, track: track::Info, cache: Arc<cache::Track>) -> Self {
296 let state = kio::Producer::<GroupState>::default();
297 state.write().ok().expect("a new group is open").charge = cache.charge();
298 let alive = Arc::new(Alive {
299 info,
300 state: state.clone(),
301 });
302 Self {
303 info,
304 state,
305 track,
306 cache,
307 stats: stats::Meter::default(),
308 alive,
309 }
310 }
311
312 pub(crate) fn with_meter(mut self, meter: stats::Meter) -> Self {
315 meter.group();
316 self.stats = meter;
317 self
318 }
319
320 pub(crate) fn info(&self) -> Info {
322 self.info
323 }
324
325 pub fn timescale(&self) -> Timescale {
327 self.track.timescale
328 }
329
330 pub fn write_frame<B: IntoBytes>(&mut self, timestamp: Timestamp, data: B) -> Result<()> {
338 let timestamp = timestamp
339 .convert(self.track.timescale)
340 .map_err(|_| Error::TimestampMismatch)?;
341 let payload = data.into_bytes();
342 if payload.len() as u64 > MAX_CACHE_BYTES {
343 return Err(Error::FrameTooLarge);
344 }
345
346 let mut state = self.writable()?;
347 let size = payload.len() as u64;
348 state.cache += size;
349 state.charge.add(size);
350 state.frames.push_back(Frame { timestamp, payload });
351 state.evict();
352 drop(state);
353
354 self.cache.settle();
357
358 self.stats.frames(1);
360 self.stats.bytes(size);
361 Ok(())
362 }
363
364 fn writable(&self) -> Result<kio::Mut<'_, GroupState>> {
371 let state = modify(&self.state)?;
372 if state.fin.is_some() {
373 return Err(Error::Closed);
374 }
375 if state.partial.is_some() {
376 return Err(Error::FrameOpen);
377 }
378 Ok(state)
379 }
380
381 pub fn write_frames<const N: usize>(&mut self, frames: &mut frame::Buffer<N>) -> Result<()> {
392 for frame in frames.filled() {
397 frame
398 .timestamp
399 .convert(self.track.timescale)
400 .map_err(|_| Error::TimestampMismatch)?;
401 if frame.payload.len() as u64 > MAX_CACHE_BYTES {
402 return Err(Error::FrameTooLarge);
403 }
404 }
405
406 let count = frames.len() as u64;
407 let mut bytes = 0;
408
409 let mut state = self.writable()?;
410 for mut frame in frames.drain() {
413 frame.timestamp = frame
414 .timestamp
415 .convert(self.track.timescale)
416 .expect("timestamp scale checked above");
417 let size = frame.payload.len() as u64;
418 bytes += size;
419 state.cache += size;
420 state.charge.add(size);
421 state.frames.push_back(frame);
422 }
423 state.evict();
424 drop(state);
425
426 self.cache.settle();
429
430 self.stats.frames(count);
432 self.stats.bytes(bytes);
433 Ok(())
434 }
435
436 pub fn create_frame(&mut self, frame: frame::Info) -> Result<frame::Producer<'_>> {
445 let timestamp = frame
446 .timestamp
447 .convert(self.track.timescale)
448 .map_err(|_| Error::TimestampMismatch)?;
449 if frame.size > MAX_CACHE_BYTES {
450 return Err(Error::FrameTooLarge);
451 }
452 let buf = FrameBuf::new(frame.size as usize);
453
454 let mut state = self.writable()?;
455 state.cache += frame.size;
456 state.charge.add(frame.size);
457 state.partial = Some(Partial {
458 timestamp,
459 buf: buf.clone(),
460 });
461 state.evict();
462 drop(state);
463
464 self.cache.settle();
467
468 self.stats.frames(1);
471 let meter = self.stats.clone();
472
473 let info = frame::Info {
474 size: frame.size,
475 timestamp,
476 };
477 Ok(frame::Producer::new(self, buf, info).with_meter(meter))
478 }
479
480 pub(crate) fn frame_notify(&self) {
482 if let Ok(mut state) = self.state.write() {
489 state.charge.record_write();
490 }
491 }
492
493 pub(crate) fn frame_commit(&mut self, frame: Frame) -> Result<()> {
495 let mut state = modify(&self.state)?;
496 state.partial = None;
499 state.frames.push_back(frame);
500 Ok(())
501 }
502
503 pub(crate) fn frame_abort(&mut self, err: Error) {
506 let _ = self.clone().abort(err);
507 }
508
509 pub fn frame_count(&self) -> usize {
511 let state = self.state.read();
512 state.offset + state.frames.len() + state.partial.is_some() as usize
513 }
514
515 pub fn finish(&mut self) -> Result<()> {
520 let mut state = modify(&self.state)?;
521 if state.partial.is_some() {
526 return Err(Error::FrameOpen);
527 }
528 state.fin = Some(state.offset + state.frames.len());
529 Ok(())
530 }
531
532 pub fn abort(self, err: Error) -> Result<()> {
538 let mut guard = modify(&self.state)?;
539 guard.abort = Some(err);
540 guard.release();
541 guard.close();
542 Ok(())
543 }
544
545 pub(crate) fn is_aborted(&self) -> bool {
548 self.state.read().abort.is_some()
549 }
550
551 pub(crate) fn cache_size(&self) -> u64 {
554 self.state.read().charge.size()
555 }
556
557 pub(crate) fn cache_accessed(&self) -> u64 {
560 self.state.read().charge.accessed()
561 }
562
563 pub(crate) fn cache_demote(&self) {
566 if let Ok(mut state) = self.state.write() {
567 state.charge.demote();
568 }
569 }
570
571 pub(crate) fn cache_refresh(&self) {
577 self.state.read().charge.refresh();
578 }
579
580 pub fn consume(&self) -> Consumer {
582 Consumer {
583 info: self.info,
584 state: self.state.consume(),
585 track: self.track.clone(),
586 index: 0,
587 stats: stats::Meter::default(),
590 }
591 }
592
593 pub async fn closed(&self) -> Error {
595 kio::wait(|waiter| self.poll_closed(waiter)).await
596 }
597
598 pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<Error> {
600 self.state.poll_closed(waiter).map(|()| self.abort_reason())
601 }
602
603 pub async fn unused(&self) -> Result<()> {
605 self.state.unused().await.map_err(|_| self.abort_reason())
606 }
607
608 fn abort_reason(&self) -> Error {
610 self.state.read().abort.clone().unwrap_or(Error::Dropped)
611 }
612}
613
614impl Clone for Producer {
615 fn clone(&self) -> Self {
616 Self {
617 info: self.info,
618 state: self.state.clone(),
619 track: self.track.clone(),
620 cache: self.cache.clone(),
621 stats: self.stats.clone(),
622 alive: self.alive.clone(),
623 }
624 }
625}
626
627pub struct Consumer {
629 state: kio::Consumer<GroupState>,
631
632 info: Info,
634
635 track: track::Info,
638
639 index: usize,
642
643 stats: stats::Meter,
646}
647
648impl Clone for Consumer {
649 fn clone(&self) -> Self {
650 Self {
652 state: self.state.clone(),
653 info: self.info,
654 track: self.track.clone(),
655 index: self.index,
656 stats: self.stats.clone(),
659 }
660 }
661}
662
663impl std::ops::Deref for Consumer {
664 type Target = Info;
665
666 fn deref(&self) -> &Self::Target {
667 &self.info
668 }
669}
670
671impl Consumer {
672 pub(crate) fn with_meter(mut self, meter: stats::Meter) -> Self {
675 meter.group();
676 self.stats = meter;
677 self
678 }
679
680 pub(crate) fn is_aborted(&self) -> bool {
683 self.state.read().abort.is_some()
684 }
685
686 pub fn keep_alive(&self) {
698 self.state.read().charge.refresh();
699 }
700
701 pub(crate) fn cache_refresh(&self) {
704 self.keep_alive();
705 }
706
707 pub(crate) fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<()> {
711 self.state.poll_closed(waiter)
712 }
713
714 pub fn timescale(&self) -> Timescale {
716 self.track.timescale
717 }
718
719 pub fn frame_count(&self) -> usize {
722 let state = self.state.read();
723 state
724 .fin
725 .unwrap_or(state.offset + state.frames.len() + state.partial.is_some() as usize)
726 }
727
728 fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
730 where
731 F: FnMut(&kio::Ref<'_, GroupState>) -> Poll<Result<R>>,
732 {
733 Poll::Ready(match ready!(self.state.poll(waiter, f)) {
734 Ok(res) => res,
735 Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
737 })
738 }
739
740 pub async fn next_frame(&mut self) -> Result<Option<frame::Consumer>> {
742 kio::wait(|waiter| self.poll_next_frame(waiter)).await
743 }
744
745 pub fn poll_next_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Consumer>>> {
749 let index = self.index;
750 let Some((info, source)) = ready!(self.poll(waiter, |state| state.poll_frame_source(index))?) else {
751 return Poll::Ready(Ok(None));
752 };
753
754 self.index += 1;
755 self.stats.frames(1);
758 Poll::Ready(Ok(Some(
759 frame::Consumer::new(self.state.clone(), info, source).with_meter(self.stats.clone()),
760 )))
761 }
762
763 pub fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Frame>>> {
768 let index = self.index;
769 let frame = ready!(self.poll(waiter, |state| {
770 if index < state.offset {
771 return Poll::Ready(Err(Error::Lagged));
772 }
773 if let Some(frame) = state.frames.get(index - state.offset) {
774 state.charge.refresh();
777 return Poll::Ready(Ok(Some(frame.clone())));
778 }
779 state.poll_terminal(index).map_ok(|()| None)
782 })?);
783
784 if let Some(frame) = &frame {
785 self.index += 1;
786 self.stats.frames(1);
787 self.stats.bytes(frame.payload.len() as u64);
788 }
789
790 Poll::Ready(Ok(frame))
791 }
792
793 pub async fn read_frame(&mut self) -> Result<Option<frame::Frame>> {
795 kio::wait(|waiter| self.poll_read_frame(waiter)).await
796 }
797
798 pub fn poll_read_frames<const N: usize>(
810 &mut self,
811 waiter: &kio::Waiter,
812 out: &mut frame::Buffer<N>,
813 ) -> Poll<Result<usize>> {
814 out.clear();
817
818 let index = self.index;
819 let res = self.poll(waiter, |state| {
820 if index < state.offset {
821 return Poll::Ready(Err(Error::Lagged));
822 }
823 let local = (index - state.offset).min(state.frames.len());
827 if out.fill(state.frames.range(local..).cloned()) > 0 {
828 state.charge.refresh();
830 return Poll::Ready(Ok(()));
831 }
832 state.poll_terminal(index)
836 });
837
838 ready!(res)?;
841
842 let filled = out.filled().len();
843 self.index += filled;
844 self.stats.frames(filled as u64);
846 self.stats
847 .bytes(out.filled().iter().map(|f| f.payload.len() as u64).sum());
848
849 Poll::Ready(Ok(filled))
850 }
851
852 pub async fn read_frames<'a, const N: usize>(
857 &mut self,
858 out: &'a mut frame::Buffer<N>,
859 ) -> Result<&'a mut [frame::Frame]> {
860 kio::wait(|waiter| self.poll_read_frames(waiter, out)).await?;
863 Ok(out.filled_mut())
864 }
865
866 pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
868 self.poll(waiter, |state| state.poll_finished())
869 }
870
871 pub async fn finished(&mut self) -> Result<u64> {
873 kio::wait(|waiter| self.poll_finished(waiter)).await
874 }
875}
876
877#[derive(Clone, Debug, Default)]
879#[non_exhaustive]
880pub struct Fetch {
881 pub priority: u8,
883}
884
885impl Fetch {
886 pub fn with_priority(mut self, priority: u8) -> Self {
888 self.priority = priority;
889 self
890 }
891}
892
893#[cfg(test)]
894mod test {
895 use super::*;
896 use crate::model::test_tracing::count_drop_warnings;
897 use bytes::Bytes;
898 use futures::FutureExt;
899
900 #[test]
901 fn basic_frame_reading() {
902 let mut producer = Info { sequence: 0 }.produce();
903 producer
904 .write_frame(Timestamp::ZERO, Bytes::from_static(b"frame0"))
905 .unwrap();
906 producer
907 .write_frame(Timestamp::ZERO, Bytes::from_static(b"frame1"))
908 .unwrap();
909 producer.finish().unwrap();
910
911 let mut consumer = producer.consume();
912 let f0 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
913 assert_eq!(f0.size, 6);
914 let f1 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
915 assert_eq!(f1.size, 6);
916 let end = consumer.next_frame().now_or_never().unwrap().unwrap();
917 assert!(end.is_none());
918 }
919
920 fn filled_group(n: usize) -> Producer {
922 let mut producer = Info { sequence: 0 }.produce();
923 for i in 0..n {
924 producer
925 .write_frame(Timestamp::ZERO, Bytes::from(i.to_string()))
926 .unwrap();
927 }
928 producer
929 }
930
931 fn payloads(frames: &[Frame]) -> Vec<String> {
933 frames
934 .iter()
935 .map(|frame| String::from_utf8(frame.payload.to_vec()).unwrap())
936 .collect()
937 }
938
939 fn drain<const N: usize>(consumer: &mut Consumer) -> Vec<String> {
941 let mut buf = frame::Buffer::<N>::new();
942 let mut seen = Vec::new();
943 loop {
944 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
945 if batch.is_empty() {
946 break;
947 }
948 seen.extend(payloads(batch));
949 }
950 seen
951 }
952
953 #[test]
958 fn writes_are_refused_while_a_frame_is_open() {
959 let mut producer = Info { sequence: 0 }.produce();
960 let mut other = producer.clone();
961
962 let mut open = producer
964 .create_frame(frame::Info {
965 size: 4,
966 timestamp: Timestamp::ZERO,
967 })
968 .unwrap();
969
970 let mut buf = frame::Buffer::<4>::new();
971 buf.push(frame::Frame {
972 timestamp: Timestamp::ZERO,
973 payload: Bytes::from_static(b"batch"),
974 })
975 .unwrap();
976
977 assert!(matches!(other.write_frames(&mut buf), Err(Error::FrameOpen)));
978 assert_eq!(buf.len(), 1, "the batch is still the caller's");
979 assert!(matches!(
980 other.write_frame(Timestamp::ZERO, Bytes::from_static(b"single")),
981 Err(Error::FrameOpen)
982 ));
983 assert!(matches!(
984 other
985 .create_frame(frame::Info {
986 size: 1,
987 timestamp: Timestamp::ZERO,
988 })
989 .err(),
990 Some(Error::FrameOpen)
991 ));
992
993 open.write(&b"open"[..]).unwrap();
995 open.finish().unwrap();
996 other.write_frames(&mut buf).unwrap();
997 other.finish().unwrap();
998
999 let mut consumer = other.consume();
1000 assert_eq!(drain::<4>(&mut consumer), ["open", "batch"]);
1001 }
1002
1003 #[test]
1010 fn finish_is_refused_while_a_frame_is_open() {
1011 let mut producer = Info { sequence: 0 }.produce();
1012 let mut other = producer.clone();
1013 let mut consumer = producer.consume();
1014
1015 let mut frame = producer
1016 .create_frame(frame::Info {
1017 size: 4,
1018 timestamp: Timestamp::ZERO,
1019 })
1020 .unwrap();
1021
1022 assert!(matches!(other.finish(), Err(Error::FrameOpen)));
1023
1024 let mut buf = frame::Buffer::<4>::new();
1026 assert!(
1027 consumer.read_frames(&mut buf).now_or_never().is_none(),
1028 "an open frame must not read as the end of the group"
1029 );
1030
1031 frame.write(&b"open"[..]).unwrap();
1032 frame.finish().unwrap();
1033 other.finish().unwrap();
1034
1035 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1036 assert_eq!(batch.len(), 1);
1037 assert_eq!(batch[0].payload, Bytes::from_static(b"open"));
1038 }
1039
1040 #[test]
1041 fn write_frames_appends_the_whole_batch() {
1042 let mut producer = Info { sequence: 0 }.produce();
1043 let mut buf = frame::Buffer::<8>::new();
1044 for i in 0..5u8 {
1045 buf.push(frame::Frame {
1046 timestamp: Timestamp::ZERO,
1047 payload: Bytes::from(i.to_string()),
1048 })
1049 .unwrap();
1050 }
1051 producer.write_frames(&mut buf).unwrap();
1052 assert!(buf.is_empty(), "the batch was drained");
1053 producer.finish().unwrap();
1054
1055 let mut consumer = producer.consume();
1056 assert_eq!(drain::<8>(&mut consumer), ["0", "1", "2", "3", "4"]);
1057 }
1058
1059 #[test]
1062 fn write_frames_rejects_the_batch_atomically() {
1063 let mut producer = Info { sequence: 0 }.produce();
1064 let mut buf = frame::Buffer::<4>::new();
1065 buf.push(frame::Frame {
1066 timestamp: Timestamp::ZERO,
1067 payload: Bytes::from_static(b"ok"),
1068 })
1069 .unwrap();
1070 buf.push(frame::Frame {
1072 timestamp: Timestamp::ZERO,
1073 payload: Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize + 1]),
1074 })
1075 .unwrap();
1076
1077 assert!(matches!(producer.write_frames(&mut buf), Err(Error::FrameTooLarge)));
1078 assert_eq!(buf.len(), 2, "the batch is still the caller's");
1079
1080 producer.finish().unwrap();
1081 let mut consumer = producer.consume();
1082 assert!(drain::<4>(&mut consumer).is_empty(), "nothing was written");
1083 }
1084
1085 #[test]
1089 fn write_frames_leaves_a_rejected_batch_unconverted() {
1090 use crate::Timescale;
1091
1092 let mut producer = Producer::new(
1093 Info { sequence: 0 },
1094 track::Info::default().with_timescale(Timescale::MICRO),
1095 Default::default(),
1096 );
1097
1098 let mut buf = frame::Buffer::<4>::new();
1099 buf.push(frame::Frame {
1100 timestamp: Timestamp::from_millis(1).unwrap(),
1101 payload: Bytes::from_static(b"ok"),
1102 })
1103 .unwrap();
1104 buf.push(frame::Frame {
1106 timestamp: Timestamp::from_millis(2).unwrap(),
1107 payload: Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize + 1]),
1108 })
1109 .unwrap();
1110
1111 assert!(matches!(producer.write_frames(&mut buf), Err(Error::FrameTooLarge)));
1112 let kept = buf.filled();
1113 assert_eq!(kept.len(), 2, "the batch is still the caller's");
1114 assert_eq!(kept[0].timestamp.scale(), Timescale::MILLI, "timestamp was rewritten");
1115 assert_eq!(kept[0].timestamp.value(), 1);
1116 }
1117
1118 #[test]
1120 fn write_frames_converts_into_the_track_scale() {
1121 use crate::Timescale;
1122
1123 let mut producer = Producer::new(
1124 Info { sequence: 0 },
1125 track::Info::default().with_timescale(Timescale::MICRO),
1126 Default::default(),
1127 );
1128
1129 let mut buf = frame::Buffer::<4>::new();
1130 buf.push(frame::Frame {
1131 timestamp: Timestamp::from_millis(1).unwrap(),
1132 payload: Bytes::from_static(b"x"),
1133 })
1134 .unwrap();
1135 producer.write_frames(&mut buf).unwrap();
1136 producer.finish().unwrap();
1137
1138 let frame = producer
1139 .consume()
1140 .read_frame()
1141 .now_or_never()
1142 .unwrap()
1143 .unwrap()
1144 .unwrap();
1145 assert_eq!(frame.timestamp.scale(), Timescale::MICRO);
1146 assert_eq!(frame.timestamp.value(), 1000);
1147 }
1148
1149 #[test]
1150 fn buffer_push_refuses_past_capacity() {
1151 let mut buf = frame::Buffer::<2>::new();
1152 let frame = || frame::Frame {
1153 timestamp: Timestamp::ZERO,
1154 payload: Bytes::from_static(b"x"),
1155 };
1156 buf.push(frame()).unwrap();
1157 buf.push(frame()).unwrap();
1158 assert!(buf.is_full());
1159 assert!(buf.push(frame()).is_err(), "a full buffer hands the frame back");
1160 }
1161
1162 #[test]
1164 fn buffer_drain_empties_even_when_abandoned() {
1165 let mut buf = frame::Buffer::<4>::new();
1166 for i in 0..4u8 {
1167 buf.push(frame::Frame {
1168 timestamp: Timestamp::ZERO,
1169 payload: Bytes::from(vec![i; 1]),
1170 })
1171 .unwrap();
1172 }
1173 let taken: Vec<_> = buf.drain().take(2).collect();
1174 assert_eq!(taken.len(), 2);
1175 assert!(buf.is_empty(), "an abandoned drain still empties the buffer");
1176 }
1177
1178 #[test]
1179 fn read_frames_fills_whole_batch() {
1180 let mut producer = filled_group(5);
1181 producer.finish().unwrap();
1182
1183 let mut consumer = producer.consume();
1184 let mut buf = frame::Buffer::<8>::new();
1185
1186 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1187 assert_eq!(payloads(batch), ["0", "1", "2", "3", "4"]);
1188
1189 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1191 assert!(batch.is_empty());
1192 }
1193
1194 #[test]
1195 fn read_frames_bounded_by_capacity() {
1196 let mut producer = filled_group(5);
1197 producer.finish().unwrap();
1198
1199 let mut consumer = producer.consume();
1200 assert_eq!(drain::<2>(&mut consumer), ["0", "1", "2", "3", "4"]);
1201 }
1202
1203 #[test]
1204 fn read_frames_resumes_after_a_single_read() {
1205 let mut producer = filled_group(12);
1206 producer.finish().unwrap();
1207
1208 let mut consumer = producer.consume();
1209 let first = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1210 assert_eq!(first.payload, Bytes::from_static(b"0"));
1211
1212 assert_eq!(
1213 drain::<8>(&mut consumer),
1214 ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"]
1215 );
1216 }
1217
1218 #[test]
1219 fn read_frames_returns_short_instead_of_waiting() {
1220 let mut producer = filled_group(2);
1221
1222 let mut consumer = producer.consume();
1223 let mut buf = frame::Buffer::<8>::new();
1224
1225 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1227 assert_eq!(payloads(batch), ["0", "1"]);
1228
1229 assert!(consumer.read_frames(&mut buf).now_or_never().is_none());
1231
1232 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"2")).unwrap();
1233 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1234 assert_eq!(payloads(batch), ["2"]);
1235 }
1236
1237 #[test]
1238 fn read_frames_reports_an_abort() {
1239 let producer = filled_group(2);
1240 let mut consumer = producer.consume();
1241 producer.abort(Error::Cancel).unwrap();
1242
1243 let mut buf = frame::Buffer::<8>::new();
1245 let res = consumer.read_frames(&mut buf).now_or_never().unwrap();
1246 assert!(matches!(res, Err(Error::Cancel)));
1247 }
1248
1249 #[test]
1251 fn read_frames_refill_replaces_the_previous_batch() {
1252 let mut producer = filled_group(3);
1253 producer.finish().unwrap();
1254
1255 let mut consumer = producer.consume();
1256 let mut buf = frame::Buffer::<2>::new();
1257
1258 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1259 assert_eq!(payloads(batch), ["0", "1"]);
1260
1261 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1262 assert_eq!(payloads(batch), ["2"]);
1263 assert_eq!(buf.filled().len(), 1, "the buffer holds only the latest batch");
1264 }
1265
1266 #[test]
1267 fn read_frames_zero_capacity_reads_nothing() {
1268 let mut producer = filled_group(2);
1269 producer.finish().unwrap();
1270
1271 let mut consumer = producer.consume();
1272 let mut buf = frame::Buffer::<0>::new();
1273 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1274 assert!(batch.is_empty());
1275
1276 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1278 assert_eq!(frame.payload, Bytes::from_static(b"0"));
1279 }
1280
1281 #[test]
1282 fn read_frame_all_at_once() {
1283 let mut producer = Info { sequence: 0 }.produce();
1284 producer
1285 .write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
1286 .unwrap();
1287 producer.finish().unwrap();
1288
1289 let mut consumer = producer.consume();
1290 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1291 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
1292 }
1293
1294 #[test]
1295 fn read_frame_preserves_timestamp() {
1296 let mut producer = Info { sequence: 0 }.produce();
1297 let timestamp = Timestamp::from_micros(20_000).unwrap();
1298 producer.write_frame(timestamp, Bytes::from_static(b"hello")).unwrap();
1299 producer.finish().unwrap();
1300
1301 let mut consumer = producer.consume();
1302 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1303 assert_eq!(frame.timestamp.as_micros(), 20_000);
1304 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
1305 }
1306
1307 #[test]
1308 fn chunked_frame_reads_whole() {
1309 let mut producer = Info { sequence: 0 }.produce();
1310 {
1311 let mut frame = producer
1312 .create_frame(frame::Info {
1313 size: 10,
1314 timestamp: Timestamp::ZERO,
1315 })
1316 .unwrap();
1317 frame.write(Bytes::from_static(b"hello")).unwrap();
1318 frame.write(Bytes::from_static(b"world")).unwrap();
1319 frame.finish().unwrap();
1320 }
1321 producer.finish().unwrap();
1322
1323 let mut consumer = producer.consume();
1326 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1327 assert_eq!(frame.payload, Bytes::from_static(b"helloworld"));
1328 }
1329
1330 #[test]
1331 fn chunked_frame_streams_partial() {
1332 let mut producer = Info { sequence: 0 }.produce();
1333 let mut consumer = producer.consume();
1334
1335 let mut frame = producer
1336 .create_frame(frame::Info {
1337 size: 6,
1338 timestamp: Timestamp::ZERO,
1339 })
1340 .unwrap();
1341 frame.write(Bytes::from_static(b"foo")).unwrap();
1342
1343 let mut f = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1345 let c1 = f.read_chunk().now_or_never().unwrap().unwrap();
1346 assert_eq!(c1, Some(Bytes::from_static(b"foo")));
1347 assert!(f.read_chunk().now_or_never().is_none());
1348
1349 frame.write(Bytes::from_static(b"bar")).unwrap();
1350 frame.finish().unwrap();
1351
1352 let c2 = f.read_chunk().now_or_never().unwrap().unwrap();
1353 assert_eq!(c2, Some(Bytes::from_static(b"bar")));
1354 let c3 = f.read_chunk().now_or_never().unwrap().unwrap();
1355 assert_eq!(c3, None);
1356 }
1357
1358 #[test]
1359 fn group_finish_returns_none() {
1360 let mut producer = Info { sequence: 0 }.produce();
1361 producer.finish().unwrap();
1362
1363 let mut consumer = producer.consume();
1364 let end = consumer.next_frame().now_or_never().unwrap().unwrap();
1365 assert!(end.is_none());
1366 }
1367
1368 #[test]
1369 fn abort_propagates() {
1370 let producer = Info { sequence: 0 }.produce();
1371 let mut consumer = producer.consume();
1372 producer.abort(crate::Error::Cancel).unwrap();
1373
1374 let result = consumer.next_frame().now_or_never().unwrap();
1375 assert!(matches!(result, Err(crate::Error::Cancel)));
1376 }
1377
1378 #[test]
1379 fn abort_clears_cached_frames() {
1380 let mut producer = Info { sequence: 0 }.produce();
1381 producer
1382 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1383 .unwrap();
1384
1385 let _consumer = producer.consume();
1387 assert_eq!(producer.state.read().frames.len(), 1);
1388
1389 producer.clone().abort(crate::Error::Cancel).unwrap();
1390
1391 let state = producer.state.read();
1392 assert!(state.frames.is_empty(), "cached frames should be dropped on abort");
1393 assert_eq!(state.cache, 0);
1394 }
1395
1396 #[test]
1397 fn drop_unfinished_clears_cached_frames() {
1398 let producer = Info { sequence: 0 }.produce();
1399 let mut writer = producer.clone();
1400 writer
1401 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1402 .unwrap();
1403
1404 let mut consumer = producer.consume();
1406 assert_eq!(producer.state.read().frames.len(), 1);
1407
1408 drop(writer);
1410 drop(producer);
1411
1412 let result = consumer.next_frame().now_or_never().unwrap();
1413 assert!(matches!(result, Err(crate::Error::Dropped)));
1414 }
1415
1416 #[test]
1417 fn drop_after_abort_does_not_warn() {
1418 let warns = count_drop_warnings("group::Producer dropped without finish", || {
1419 let producer = Info { sequence: 0 }.produce();
1420 let keep = producer.clone();
1421 let mut writer = producer.clone();
1422 writer
1423 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1424 .unwrap();
1425 let _consumer = producer.consume();
1426 writer.abort(crate::Error::Cancel).unwrap();
1427 drop(keep);
1428 });
1429 assert_eq!(warns, 0, "abort-then-drop must not emit unfinished-producer WARN");
1430 }
1431
1432 #[test]
1433 fn drop_unfinished_warns() {
1434 let warns = count_drop_warnings("group::Producer dropped without finish", || {
1435 let producer = Info { sequence: 0 }.produce();
1436 let mut writer = producer.clone();
1437 writer
1438 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1439 .unwrap();
1440 let _consumer = producer.consume();
1441 drop(writer);
1442 drop(producer);
1443 });
1444 assert!(warns >= 1, "unfinished drop must emit unfinished-producer WARN");
1445 }
1446
1447 #[test]
1448 fn drop_finished_keeps_cached_frames() {
1449 let mut producer = Info { sequence: 0 }.produce();
1450 producer
1451 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1452 .unwrap();
1453 producer.finish().unwrap();
1454
1455 let mut consumer = producer.consume();
1456 drop(producer);
1457
1458 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1460 assert_eq!(frame.payload, Bytes::from_static(b"data"));
1461 }
1462
1463 #[tokio::test]
1464 async fn pending_then_ready() {
1465 let mut producer = Info { sequence: 0 }.produce();
1466 let mut consumer = producer.consume();
1467
1468 assert!(consumer.next_frame().now_or_never().is_none());
1470
1471 producer
1472 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1473 .unwrap();
1474 producer.finish().unwrap();
1475
1476 let frame = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1477 assert_eq!(frame.size, 4);
1478 }
1479
1480 #[test]
1481 fn eviction_drops_old_frames() {
1482 let mut producer = Info { sequence: 0 }.produce();
1483
1484 let big = Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize]);
1486 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
1487 producer.write_frame(Timestamp::ZERO, big).unwrap();
1488
1489 let state = producer.state.read();
1491 assert_eq!(state.offset, 1);
1492 assert_eq!(state.frames.len(), 1);
1493 assert_eq!(state.frames[0].payload.len(), MAX_CACHE_BYTES as usize);
1494 }
1495
1496 #[test]
1497 fn next_frame_returns_cache_full_on_tombstone() {
1498 let mut producer = Info { sequence: 0 }.produce();
1499
1500 let big = Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize]);
1501 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
1502 producer.write_frame(Timestamp::ZERO, big).unwrap();
1503
1504 let mut consumer = producer.consume();
1505 let result = consumer.next_frame().now_or_never().unwrap();
1507 assert!(matches!(result, Err(crate::Error::Lagged)));
1508 }
1509
1510 #[test]
1511 fn no_eviction_under_budget() {
1512 let mut producer = Info { sequence: 0 }.produce();
1513 for _ in 0..100_000 {
1515 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
1516 }
1517 producer.finish().unwrap();
1518
1519 let state = producer.state.read();
1520 assert_eq!(state.offset, 0);
1521 assert_eq!(state.frames.len(), 100_000);
1522 }
1523
1524 #[test]
1525 fn clone_consumer_independent() {
1526 let mut producer = Info { sequence: 0 }.produce();
1527 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
1528
1529 let mut c1 = producer.consume();
1530 let _ = c1.next_frame().now_or_never().unwrap().unwrap().unwrap();
1532
1533 let mut c2 = c1.clone();
1535
1536 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
1537 producer.finish().unwrap();
1538
1539 let f = c2.next_frame().now_or_never().unwrap().unwrap().unwrap();
1541 assert_eq!(f.size, 1); let end = c2.next_frame().now_or_never().unwrap().unwrap();
1544 assert!(end.is_none());
1545 }
1546
1547 #[test]
1550 fn read_frames_crosses_batches() {
1551 const CAP: usize = 8;
1552 let n = CAP * 3 + 5;
1553 let mut producer = Info { sequence: 0 }.produce();
1554 for i in 0..n {
1555 producer
1556 .write_frame(Timestamp::ZERO, Bytes::from(vec![i as u8; 4]))
1557 .unwrap();
1558 }
1559 producer.finish().unwrap();
1560
1561 let mut consumer = producer.consume();
1562 let mut buf = frame::Buffer::<CAP>::new();
1563 let mut seen = 0;
1564 loop {
1565 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1566 if batch.is_empty() {
1567 break;
1568 }
1569 for frame in batch.iter() {
1570 assert_eq!(frame.payload, Bytes::from(vec![seen as u8; 4]));
1571 seen += 1;
1572 }
1573 }
1574 assert_eq!(seen, n);
1575 assert!(consumer.read_frame().now_or_never().unwrap().unwrap().is_none());
1576 }
1577
1578 #[test]
1582 fn abort_after_finish_keeps_the_clean_end_for_a_drained_reader() {
1583 let mut producer = Info { sequence: 0 }.produce();
1584 producer
1585 .write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
1586 .unwrap();
1587 producer.finish().unwrap();
1588
1589 let mut drained = producer.consume();
1590 let mut behind = producer.consume();
1591 let frame = drained.read_frame().now_or_never().unwrap().unwrap().unwrap();
1592 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
1593
1594 producer.abort(Error::Old).unwrap();
1595
1596 assert!(drained.read_frame().now_or_never().unwrap().unwrap().is_none());
1598 assert!(drained.next_frame().now_or_never().unwrap().unwrap().is_none());
1599
1600 assert!(matches!(behind.read_frame().now_or_never().unwrap(), Err(Error::Old)));
1602 }
1603
1604 #[test]
1607 fn finished_survives_a_later_abort() {
1608 let mut producer = Info { sequence: 0 }.produce();
1609 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
1610 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
1611 producer.finish().unwrap();
1612
1613 let mut consumer = producer.consume();
1614 producer.abort(Error::Old).unwrap();
1615
1616 assert_eq!(consumer.finished().now_or_never().unwrap().unwrap(), 2);
1617 }
1618
1619 #[test]
1621 fn interleave_read_and_next_frame() {
1622 let mut producer = Info { sequence: 0 }.produce();
1623 for i in 0..5u8 {
1624 producer.write_frame(Timestamp::ZERO, Bytes::from(vec![i; 1])).unwrap();
1625 }
1626 producer.finish().unwrap();
1627
1628 let mut consumer = producer.consume();
1629 let f0 = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1630 assert_eq!(f0.payload, Bytes::from(vec![0u8; 1]));
1631
1632 for i in 1..5u8 {
1634 let mut f = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1635 let data = f.read_all().now_or_never().unwrap().unwrap();
1636 assert_eq!(data, Bytes::from(vec![i; 1]));
1637 }
1638 assert!(consumer.next_frame().now_or_never().unwrap().unwrap().is_none());
1639 }
1640
1641 #[test]
1644 fn read_frame_past_cleared_frames_does_not_panic() {
1645 let mut producer = Info { sequence: 0 }.produce();
1646 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
1647 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
1648
1649 let mut consumer = producer.consume();
1650 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1651 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1652
1653 producer.abort(Error::Cancel).unwrap();
1656
1657 let result = consumer.read_frame().now_or_never().unwrap();
1658 assert!(matches!(result, Err(Error::Cancel)), "expected Cancel, got {result:?}");
1659 }
1660
1661 #[test]
1664 fn drop_with_a_filled_buffer() {
1665 const CAP: usize = 8;
1666 let mut producer = Info { sequence: 0 }.produce();
1667 for _ in 0..CAP {
1668 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
1669 }
1670 producer.finish().unwrap();
1671
1672 let mut consumer = producer.consume();
1673 let mut buf = frame::Buffer::<CAP>::new();
1674 assert_eq!(
1676 consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap().len(),
1677 CAP
1678 );
1679 drop(buf);
1680 }
1681
1682 #[tokio::test]
1687 async fn chunk_write_wakes_parked_reader() {
1688 let mut producer = Info { sequence: 0 }.produce();
1689 let mut consumer = producer.consume();
1690 let mut frame = producer
1691 .create_frame(frame::Info {
1692 size: 6,
1693 timestamp: Timestamp::ZERO,
1694 })
1695 .unwrap();
1696 let mut f = consumer.next_frame().await.unwrap().unwrap();
1697 let handle = tokio::spawn(async move { f.read_chunk().await });
1698 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1700 frame.write(Bytes::from_static(b"foo")).unwrap();
1701 let chunk = tokio::time::timeout(std::time::Duration::from_secs(2), handle)
1702 .await
1703 .expect("parked chunk reader was never woken by the chunk write")
1704 .unwrap()
1705 .unwrap();
1706 assert_eq!(chunk, Some(Bytes::from_static(b"foo")));
1707 }
1708
1709 #[test]
1712 fn create_frame_converts_mismatched_scale() {
1713 use crate::{Timescale, Timestamp};
1714
1715 let mut producer = Producer::new(
1716 Info { sequence: 0 },
1717 track::Info::default().with_timescale(Timescale::MICRO),
1718 Default::default(),
1719 );
1720 let frame = frame::Info {
1721 size: 3,
1722 timestamp: Timestamp::from_millis(1).unwrap(), };
1724 let writer = producer.create_frame(frame).unwrap();
1725 assert_eq!(writer.timestamp.scale(), Timescale::MICRO);
1726 assert_eq!(writer.timestamp.value(), 1000);
1727 }
1728
1729 #[tokio::test]
1731 async fn create_frame_converts_current_timestamp() {
1732 use crate::Timescale;
1733
1734 let mut producer = Producer::new(
1735 Info { sequence: 0 },
1736 track::Info::default().with_timescale(Timescale::MICRO),
1737 Default::default(),
1738 );
1739 let writer = producer
1740 .create_frame(frame::Info {
1741 size: 3,
1742 timestamp: Timestamp::now(),
1743 })
1744 .unwrap();
1745 assert_eq!(writer.timestamp.scale(), Timescale::MICRO);
1746 assert!(!writer.timestamp.is_zero(), "local clock should be non-zero");
1747 }
1748
1749 #[test]
1751 fn create_frame_rejects_oversized() {
1752 let mut producer = Info { sequence: 0 }.produce();
1753 let result = producer.create_frame(frame::Info {
1754 size: MAX_CACHE_BYTES + 1,
1755 timestamp: Timestamp::ZERO,
1756 });
1757 assert!(matches!(result, Err(Error::FrameTooLarge)));
1758 }
1759}