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; const FRAME_SLOTS: usize = 4;
32
33pub(crate) const CACHE_OVERHEAD: u64 = (kio::Producer::<GroupState>::HEAP
41 + 2 * size_of::<usize>()
44 + size_of::<Alive>()
45 + FRAME_SLOTS * size_of::<Frame>()) as u64;
46
47#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
51pub struct Info {
52 pub sequence: u64,
55}
56
57impl Info {
58 #[cfg(test)]
64 pub(crate) fn produce(self) -> Producer {
65 Producer::new(self, track::Info::default(), Default::default())
66 }
67}
68
69impl From<usize> for Info {
70 fn from(sequence: usize) -> Self {
71 Self {
72 sequence: sequence as u64,
73 }
74 }
75}
76
77impl From<u64> for Info {
78 fn from(sequence: u64) -> Self {
79 Self { sequence }
80 }
81}
82
83impl From<u32> for Info {
84 fn from(sequence: u32) -> Self {
85 Self {
86 sequence: sequence as u64,
87 }
88 }
89}
90
91impl From<u16> for Info {
92 fn from(sequence: u16) -> Self {
93 Self {
94 sequence: sequence as u64,
95 }
96 }
97}
98
99pub(crate) struct Partial {
102 timestamp: Timestamp,
103 buf: FrameBuf,
104}
105
106#[derive(Default)]
109pub(crate) struct GroupState {
110 pub(crate) frames: VecDeque<Frame>,
113
114 pub(crate) partial: Option<Partial>,
116
117 pub(crate) offset: usize,
119
120 pub(crate) cache: u64,
122
123 charge: cache::Charge,
126
127 pub(crate) fin: Option<usize>,
130
131 pub(crate) abort: Option<Error>,
133}
134
135impl GroupState {
136 fn poll_frame_source(&self, index: usize) -> Poll<Result<Option<(frame::Info, frame::Source)>>> {
139 if index < self.offset {
140 return Poll::Ready(Err(Error::Lagged));
141 }
142 let local = index - self.offset;
143 if let Some(f) = self.frames.get(local) {
144 self.charge.refresh();
147 let info = frame::Info {
148 size: f.payload.len() as u64,
149 timestamp: f.timestamp,
150 };
151 return Poll::Ready(Ok(Some((info, frame::Source::Complete(f.payload.clone())))));
152 }
153 if local == self.frames.len()
154 && let Some(p) = &self.partial
155 {
156 self.charge.refresh();
157 let info = frame::Info {
158 size: p.buf.capacity() as u64,
159 timestamp: p.timestamp,
160 };
161 return Poll::Ready(Ok(Some((info, frame::Source::Partial(p.buf.clone())))));
162 }
163 ready!(self.poll_terminal(index))?;
164 Poll::Ready(Ok(None))
165 }
166
167 fn poll_terminal(&self, index: usize) -> Poll<Result<()>> {
174 match (self.fin, &self.abort) {
175 (Some(total), Some(err)) if index < total => Poll::Ready(Err(err.clone())),
176 (Some(_), _) => Poll::Ready(Ok(())),
177 (None, Some(err)) => Poll::Ready(Err(err.clone())),
178 (None, None) => Poll::Pending,
179 }
180 }
181
182 fn poll_end(&self, index: usize) -> Poll<Result<()>> {
185 if index < self.offset {
186 return Poll::Ready(Err(Error::Lagged));
187 }
188 self.poll_terminal(index)
189 }
190
191 fn evict(&mut self) {
193 while self.cache > MAX_CACHE_BYTES {
194 let Some(frame) = self.frames.pop_front() else {
195 break;
196 };
197 let size = frame.payload.len() as u64;
198 self.cache -= size;
199 self.charge.sub(size);
200 self.offset += 1;
201 }
202 }
203
204 fn release(&mut self) {
206 self.frames.clear();
207 self.partial = None;
208 self.cache = 0;
209 self.charge.clear();
210 }
211}
212
213fn modify(state: &kio::Producer<GroupState>) -> Result<kio::Mut<'_, GroupState>> {
214 state.write().map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
215}
216
217pub struct Producer {
223 state: kio::Producer<GroupState>,
225
226 info: Info,
229
230 track: track::Info,
235
236 cache: Arc<cache::Track>,
240
241 stats: stats::Meter,
244
245 alive: Arc<Alive>,
248}
249
250struct Alive {
258 info: Info,
259 state: kio::Producer<GroupState>,
260}
261
262impl Drop for Alive {
263 fn drop(&mut self) {
264 match self.state.write() {
270 Ok(mut state) => {
271 if state.fin.is_some() || state.abort.is_some() {
272 return;
273 }
274 tracing::warn!(
275 sequence = self.info.sequence,
276 "group::Producer dropped without finish() or abort()"
277 );
278 state.release();
279 }
280 Err(state) => {
281 if state.fin.is_some() || state.abort.is_some() {
282 return;
283 }
284 tracing::warn!(
285 sequence = self.info.sequence,
286 "group::Producer dropped without finish() or abort()"
287 );
288 }
289 }
290 }
291}
292
293impl std::ops::Deref for Producer {
294 type Target = Info;
295
296 fn deref(&self) -> &Self::Target {
297 &self.info
298 }
299}
300
301impl Producer {
302 pub(crate) fn new(info: Info, track: track::Info, cache: Arc<cache::Track>) -> Self {
313 let state = kio::Producer::<GroupState>::default();
314 state.write().ok().expect("a new group is open").charge = cache.charge();
315 let alive = Arc::new(Alive {
316 info,
317 state: state.clone(),
318 });
319 Self {
320 info,
321 state,
322 track,
323 cache,
324 stats: stats::Meter::default(),
325 alive,
326 }
327 }
328
329 pub(crate) fn with_meter(mut self, meter: stats::Meter) -> Self {
332 meter.group();
333 self.stats = meter;
334 self
335 }
336
337 pub(crate) fn info(&self) -> Info {
339 self.info
340 }
341
342 pub fn timescale(&self) -> Timescale {
344 self.track.timescale
345 }
346
347 pub fn write_frame<B: IntoBytes>(&mut self, timestamp: Timestamp, data: B) -> Result<()> {
355 let timestamp = timestamp
356 .convert(self.track.timescale)
357 .map_err(|_| Error::TimestampMismatch)?;
358 let payload = data.into_bytes();
359 if payload.len() as u64 > MAX_CACHE_BYTES {
360 return Err(Error::FrameTooLarge);
361 }
362
363 let mut state = self.writable()?;
364 let size = payload.len() as u64;
365 state.cache += size;
366 state.charge.add(size);
367 state.frames.push_back(Frame { timestamp, payload });
368 state.evict();
369 drop(state);
370
371 self.cache.settle();
374
375 self.stats.frames(1);
377 self.stats.bytes(size);
378 Ok(())
379 }
380
381 fn writable(&self) -> Result<kio::Mut<'_, GroupState>> {
388 let state = modify(&self.state)?;
389 if state.fin.is_some() {
390 return Err(Error::Closed);
391 }
392 if state.partial.is_some() {
393 return Err(Error::FrameOpen);
394 }
395 Ok(state)
396 }
397
398 pub fn write_frames<const N: usize>(&mut self, frames: &mut frame::Buffer<N>) -> Result<()> {
409 for frame in frames.filled() {
414 frame
415 .timestamp
416 .convert(self.track.timescale)
417 .map_err(|_| Error::TimestampMismatch)?;
418 if frame.payload.len() as u64 > MAX_CACHE_BYTES {
419 return Err(Error::FrameTooLarge);
420 }
421 }
422
423 let count = frames.len() as u64;
424 let mut bytes = 0;
425
426 let mut state = self.writable()?;
427 for mut frame in frames.drain() {
430 frame.timestamp = frame
431 .timestamp
432 .convert(self.track.timescale)
433 .expect("timestamp scale checked above");
434 let size = frame.payload.len() as u64;
435 bytes += size;
436 state.cache += size;
437 state.charge.add(size);
438 state.frames.push_back(frame);
439 }
440 state.evict();
441 drop(state);
442
443 self.cache.settle();
446
447 self.stats.frames(count);
449 self.stats.bytes(bytes);
450 Ok(())
451 }
452
453 pub fn create_frame(&mut self, frame: frame::Info) -> Result<frame::Producer<'_>> {
462 let timestamp = frame
463 .timestamp
464 .convert(self.track.timescale)
465 .map_err(|_| Error::TimestampMismatch)?;
466 if frame.size > MAX_CACHE_BYTES {
467 return Err(Error::FrameTooLarge);
468 }
469 let buf = FrameBuf::new(frame.size as usize);
470
471 let mut state = self.writable()?;
472 state.cache += frame.size;
473 state.charge.add(frame.size);
474 state.partial = Some(Partial {
475 timestamp,
476 buf: buf.clone(),
477 });
478 state.evict();
479 drop(state);
480
481 self.cache.settle();
484
485 self.stats.frames(1);
488 let meter = self.stats.clone();
489
490 let info = frame::Info {
491 size: frame.size,
492 timestamp,
493 };
494 Ok(frame::Producer::new(self, buf, info).with_meter(meter))
495 }
496
497 pub(crate) fn frame_notify(&self) {
499 if let Ok(mut state) = self.state.write() {
506 state.charge.record_write();
507 }
508 }
509
510 pub(crate) fn frame_commit(&mut self, frame: Frame) -> Result<()> {
512 let mut state = modify(&self.state)?;
513 state.partial = None;
516 state.frames.push_back(frame);
517 Ok(())
518 }
519
520 pub(crate) fn frame_abort(&mut self, err: Error) {
523 let _ = self.clone().abort(err);
524 }
525
526 pub fn frame_count(&self) -> usize {
528 let state = self.state.read();
529 state.offset + state.frames.len() + state.partial.is_some() as usize
530 }
531
532 pub fn finish(&mut self) -> Result<()> {
537 let mut state = modify(&self.state)?;
538 if state.partial.is_some() {
543 return Err(Error::FrameOpen);
544 }
545 state.fin = Some(state.offset + state.frames.len());
546 Ok(())
547 }
548
549 pub fn abort(self, err: Error) -> Result<()> {
555 let mut guard = modify(&self.state)?;
556 guard.abort = Some(err);
557 guard.release();
558 guard.close();
559 Ok(())
560 }
561
562 pub(crate) fn is_aborted(&self) -> bool {
565 self.state.read().abort.is_some()
566 }
567
568 pub(crate) fn cache_size(&self) -> u64 {
571 self.state.read().charge.size()
572 }
573
574 pub(crate) fn cache_accessed(&self) -> u64 {
577 self.state.read().charge.accessed()
578 }
579
580 pub(crate) fn cache_demote(&self) {
583 if let Ok(mut state) = self.state.write() {
584 state.charge.demote();
585 }
586 }
587
588 pub(crate) fn cache_refresh(&self) {
594 self.state.read().charge.refresh();
595 }
596
597 pub fn consume(&self) -> Consumer {
599 Consumer {
600 info: self.info,
601 state: self.state.consume(),
602 track: self.track.clone(),
603 index: 0,
604 stats: stats::Meter::default(),
607 }
608 }
609
610 pub async fn closed(&self) -> Error {
612 kio::wait(|waiter| self.poll_closed(waiter)).await
613 }
614
615 pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<Error> {
617 self.state.poll_closed(waiter).map(|()| self.abort_reason())
618 }
619
620 pub async fn unused(&self) -> Result<()> {
622 self.state.unused().await.map_err(|_| self.abort_reason())
623 }
624
625 fn abort_reason(&self) -> Error {
627 self.state.read().abort.clone().unwrap_or(Error::Dropped)
628 }
629}
630
631impl Clone for Producer {
632 fn clone(&self) -> Self {
633 Self {
634 info: self.info,
635 state: self.state.clone(),
636 track: self.track.clone(),
637 cache: self.cache.clone(),
638 stats: self.stats.clone(),
639 alive: self.alive.clone(),
640 }
641 }
642}
643
644pub struct Consumer {
646 state: kio::Consumer<GroupState>,
648
649 info: Info,
651
652 track: track::Info,
655
656 index: usize,
659
660 stats: stats::Meter,
663}
664
665impl Clone for Consumer {
666 fn clone(&self) -> Self {
667 Self {
669 state: self.state.clone(),
670 info: self.info,
671 track: self.track.clone(),
672 index: self.index,
673 stats: self.stats.clone(),
676 }
677 }
678}
679
680impl std::ops::Deref for Consumer {
681 type Target = Info;
682
683 fn deref(&self) -> &Self::Target {
684 &self.info
685 }
686}
687
688impl Consumer {
689 pub(crate) fn with_meter(mut self, meter: stats::Meter) -> Self {
692 meter.group();
693 self.stats = meter;
694 self
695 }
696
697 pub(crate) fn is_aborted(&self) -> bool {
700 self.state.read().abort.is_some()
701 }
702
703 pub fn keep_alive(&self) {
715 self.state.read().charge.refresh();
716 }
717
718 pub(crate) fn cache_refresh(&self) {
721 self.keep_alive();
722 }
723
724 pub(crate) fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<()> {
728 self.state.poll_closed(waiter)
729 }
730
731 pub fn timescale(&self) -> Timescale {
733 self.track.timescale
734 }
735
736 pub fn frame_count(&self) -> usize {
739 let state = self.state.read();
740 state
741 .fin
742 .unwrap_or(state.offset + state.frames.len() + state.partial.is_some() as usize)
743 }
744
745 pub fn skip_to(&mut self, sequence: u64) {
752 let sequence = usize::try_from(sequence).unwrap_or(usize::MAX);
753 self.index = self.index.max(sequence);
754 }
755
756 fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
758 where
759 F: FnMut(&kio::Ref<'_, GroupState>) -> Poll<Result<R>>,
760 {
761 Poll::Ready(match ready!(self.state.poll(waiter, f)) {
762 Ok(res) => res,
763 Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
765 })
766 }
767
768 pub async fn next_frame(&mut self) -> Result<Option<frame::Consumer>> {
770 kio::wait(|waiter| self.poll_next_frame(waiter)).await
771 }
772
773 pub fn poll_next_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Consumer>>> {
777 let index = self.index;
778 let Some((info, source)) = ready!(self.poll(waiter, |state| state.poll_frame_source(index))?) else {
779 return Poll::Ready(Ok(None));
780 };
781
782 self.index += 1;
783 self.stats.frames(1);
786 Poll::Ready(Ok(Some(
787 frame::Consumer::new(self.state.clone(), info, source).with_meter(self.stats.clone()),
788 )))
789 }
790
791 pub fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Frame>>> {
796 let index = self.index;
797 let frame = ready!(self.poll(waiter, |state| {
798 if index < state.offset {
799 return Poll::Ready(Err(Error::Lagged));
800 }
801 if let Some(frame) = state.frames.get(index - state.offset) {
802 state.charge.refresh();
805 return Poll::Ready(Ok(Some(frame.clone())));
806 }
807 state.poll_terminal(index).map_ok(|()| None)
810 })?);
811
812 if let Some(frame) = &frame {
813 self.index += 1;
814 self.stats.frames(1);
815 self.stats.bytes(frame.payload.len() as u64);
816 }
817
818 Poll::Ready(Ok(frame))
819 }
820
821 pub async fn read_frame(&mut self) -> Result<Option<frame::Frame>> {
823 kio::wait(|waiter| self.poll_read_frame(waiter)).await
824 }
825
826 pub fn poll_read_frames<const N: usize>(
838 &mut self,
839 waiter: &kio::Waiter,
840 out: &mut frame::Buffer<N>,
841 ) -> Poll<Result<usize>> {
842 out.clear();
845
846 let index = self.index;
847 let res = self.poll(waiter, |state| {
848 if index < state.offset {
849 return Poll::Ready(Err(Error::Lagged));
850 }
851 let local = (index - state.offset).min(state.frames.len());
855 if out.fill(state.frames.range(local..).cloned()) > 0 {
856 state.charge.refresh();
858 return Poll::Ready(Ok(()));
859 }
860 state.poll_terminal(index)
864 });
865
866 ready!(res)?;
869
870 let filled = out.filled().len();
871 self.index += filled;
872 self.stats.frames(filled as u64);
874 self.stats
875 .bytes(out.filled().iter().map(|f| f.payload.len() as u64).sum());
876
877 Poll::Ready(Ok(filled))
878 }
879
880 pub async fn read_frames<'a, const N: usize>(
885 &mut self,
886 out: &'a mut frame::Buffer<N>,
887 ) -> Result<&'a mut [frame::Frame]> {
888 kio::wait(|waiter| self.poll_read_frames(waiter, out)).await?;
891 Ok(out.filled_mut())
892 }
893
894 pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
896 let index = self.index;
897 ready!(self.poll(waiter, |state| state.poll_end(index)))?;
898 Poll::Ready(Ok(index as u64))
899 }
900
901 pub async fn finished(&mut self) -> Result<u64> {
908 kio::wait(|waiter| self.poll_finished(waiter)).await
909 }
910}
911
912#[derive(Clone, Debug, Default)]
914#[non_exhaustive]
915pub struct Fetch {
916 pub priority: u8,
918}
919
920impl Fetch {
921 pub fn with_priority(mut self, priority: u8) -> Self {
923 self.priority = priority;
924 self
925 }
926}
927
928#[cfg(test)]
929mod test {
930 use super::*;
931 use crate::model::test_tracing::count_drop_warnings;
932 use bytes::Bytes;
933 use futures::FutureExt;
934
935 #[test]
938 fn one_frame_fits_the_charged_slots() {
939 let mut frames: VecDeque<Frame> = VecDeque::new();
940 frames.push_back(Frame {
941 timestamp: Timestamp::ZERO,
942 payload: Bytes::new(),
943 });
944 let capacity = frames.capacity();
945 assert!(
946 capacity <= FRAME_SLOTS,
947 "a one-frame deque now allocates {capacity} slots"
948 );
949 }
950
951 #[test]
952 fn basic_frame_reading() {
953 let mut producer = Info { sequence: 0 }.produce();
954 producer
955 .write_frame(Timestamp::ZERO, Bytes::from_static(b"frame0"))
956 .unwrap();
957 producer
958 .write_frame(Timestamp::ZERO, Bytes::from_static(b"frame1"))
959 .unwrap();
960 producer.finish().unwrap();
961
962 let mut consumer = producer.consume();
963 let f0 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
964 assert_eq!(f0.size, 6);
965 let f1 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
966 assert_eq!(f1.size, 6);
967 let end = consumer.next_frame().now_or_never().unwrap().unwrap();
968 assert!(end.is_none());
969 }
970
971 fn filled_group(n: usize) -> Producer {
973 let mut producer = Info { sequence: 0 }.produce();
974 for i in 0..n {
975 producer
976 .write_frame(Timestamp::ZERO, Bytes::from(i.to_string()))
977 .unwrap();
978 }
979 producer
980 }
981
982 fn payloads(frames: &[Frame]) -> Vec<String> {
984 frames
985 .iter()
986 .map(|frame| String::from_utf8(frame.payload.to_vec()).unwrap())
987 .collect()
988 }
989
990 fn drain<const N: usize>(consumer: &mut Consumer) -> Vec<String> {
992 let mut buf = frame::Buffer::<N>::new();
993 let mut seen = Vec::new();
994 loop {
995 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
996 if batch.is_empty() {
997 break;
998 }
999 seen.extend(payloads(batch));
1000 }
1001 seen
1002 }
1003
1004 #[test]
1009 fn writes_are_refused_while_a_frame_is_open() {
1010 let mut producer = Info { sequence: 0 }.produce();
1011 let mut other = producer.clone();
1012
1013 let mut open = producer
1015 .create_frame(frame::Info {
1016 size: 4,
1017 timestamp: Timestamp::ZERO,
1018 })
1019 .unwrap();
1020
1021 let mut buf = frame::Buffer::<4>::new();
1022 buf.push(frame::Frame {
1023 timestamp: Timestamp::ZERO,
1024 payload: Bytes::from_static(b"batch"),
1025 })
1026 .unwrap();
1027
1028 assert!(matches!(other.write_frames(&mut buf), Err(Error::FrameOpen)));
1029 assert_eq!(buf.len(), 1, "the batch is still the caller's");
1030 assert!(matches!(
1031 other.write_frame(Timestamp::ZERO, Bytes::from_static(b"single")),
1032 Err(Error::FrameOpen)
1033 ));
1034 assert!(matches!(
1035 other
1036 .create_frame(frame::Info {
1037 size: 1,
1038 timestamp: Timestamp::ZERO,
1039 })
1040 .err(),
1041 Some(Error::FrameOpen)
1042 ));
1043
1044 open.write(&b"open"[..]).unwrap();
1046 open.finish().unwrap();
1047 other.write_frames(&mut buf).unwrap();
1048 other.finish().unwrap();
1049
1050 let mut consumer = other.consume();
1051 assert_eq!(drain::<4>(&mut consumer), ["open", "batch"]);
1052 }
1053
1054 #[test]
1061 fn finish_is_refused_while_a_frame_is_open() {
1062 let mut producer = Info { sequence: 0 }.produce();
1063 let mut other = producer.clone();
1064 let mut consumer = producer.consume();
1065
1066 let mut frame = producer
1067 .create_frame(frame::Info {
1068 size: 4,
1069 timestamp: Timestamp::ZERO,
1070 })
1071 .unwrap();
1072
1073 assert!(matches!(other.finish(), Err(Error::FrameOpen)));
1074
1075 let mut buf = frame::Buffer::<4>::new();
1077 assert!(
1078 consumer.read_frames(&mut buf).now_or_never().is_none(),
1079 "an open frame must not read as the end of the group"
1080 );
1081
1082 frame.write(&b"open"[..]).unwrap();
1083 frame.finish().unwrap();
1084 other.finish().unwrap();
1085
1086 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1087 assert_eq!(batch.len(), 1);
1088 assert_eq!(batch[0].payload, Bytes::from_static(b"open"));
1089 }
1090
1091 #[test]
1092 fn write_frames_appends_the_whole_batch() {
1093 let mut producer = Info { sequence: 0 }.produce();
1094 let mut buf = frame::Buffer::<8>::new();
1095 for i in 0..5u8 {
1096 buf.push(frame::Frame {
1097 timestamp: Timestamp::ZERO,
1098 payload: Bytes::from(i.to_string()),
1099 })
1100 .unwrap();
1101 }
1102 producer.write_frames(&mut buf).unwrap();
1103 assert!(buf.is_empty(), "the batch was drained");
1104 producer.finish().unwrap();
1105
1106 let mut consumer = producer.consume();
1107 assert_eq!(drain::<8>(&mut consumer), ["0", "1", "2", "3", "4"]);
1108 }
1109
1110 #[test]
1113 fn write_frames_rejects_the_batch_atomically() {
1114 let mut producer = Info { sequence: 0 }.produce();
1115 let mut buf = frame::Buffer::<4>::new();
1116 buf.push(frame::Frame {
1117 timestamp: Timestamp::ZERO,
1118 payload: Bytes::from_static(b"ok"),
1119 })
1120 .unwrap();
1121 buf.push(frame::Frame {
1123 timestamp: Timestamp::ZERO,
1124 payload: Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize + 1]),
1125 })
1126 .unwrap();
1127
1128 assert!(matches!(producer.write_frames(&mut buf), Err(Error::FrameTooLarge)));
1129 assert_eq!(buf.len(), 2, "the batch is still the caller's");
1130
1131 producer.finish().unwrap();
1132 let mut consumer = producer.consume();
1133 assert!(drain::<4>(&mut consumer).is_empty(), "nothing was written");
1134 }
1135
1136 #[test]
1140 fn write_frames_leaves_a_rejected_batch_unconverted() {
1141 use crate::Timescale;
1142
1143 let mut producer = Producer::new(
1144 Info { sequence: 0 },
1145 track::Info::default().with_timescale(Timescale::MICRO),
1146 Default::default(),
1147 );
1148
1149 let mut buf = frame::Buffer::<4>::new();
1150 buf.push(frame::Frame {
1151 timestamp: Timestamp::from_millis(1).unwrap(),
1152 payload: Bytes::from_static(b"ok"),
1153 })
1154 .unwrap();
1155 buf.push(frame::Frame {
1157 timestamp: Timestamp::from_millis(2).unwrap(),
1158 payload: Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize + 1]),
1159 })
1160 .unwrap();
1161
1162 assert!(matches!(producer.write_frames(&mut buf), Err(Error::FrameTooLarge)));
1163 let kept = buf.filled();
1164 assert_eq!(kept.len(), 2, "the batch is still the caller's");
1165 assert_eq!(kept[0].timestamp.scale(), Timescale::MILLI, "timestamp was rewritten");
1166 assert_eq!(kept[0].timestamp.value(), 1);
1167 }
1168
1169 #[test]
1171 fn write_frames_converts_into_the_track_scale() {
1172 use crate::Timescale;
1173
1174 let mut producer = Producer::new(
1175 Info { sequence: 0 },
1176 track::Info::default().with_timescale(Timescale::MICRO),
1177 Default::default(),
1178 );
1179
1180 let mut buf = frame::Buffer::<4>::new();
1181 buf.push(frame::Frame {
1182 timestamp: Timestamp::from_millis(1).unwrap(),
1183 payload: Bytes::from_static(b"x"),
1184 })
1185 .unwrap();
1186 producer.write_frames(&mut buf).unwrap();
1187 producer.finish().unwrap();
1188
1189 let frame = producer
1190 .consume()
1191 .read_frame()
1192 .now_or_never()
1193 .unwrap()
1194 .unwrap()
1195 .unwrap();
1196 assert_eq!(frame.timestamp.scale(), Timescale::MICRO);
1197 assert_eq!(frame.timestamp.value(), 1000);
1198 }
1199
1200 #[test]
1201 fn buffer_push_refuses_past_capacity() {
1202 let mut buf = frame::Buffer::<2>::new();
1203 let frame = || frame::Frame {
1204 timestamp: Timestamp::ZERO,
1205 payload: Bytes::from_static(b"x"),
1206 };
1207 buf.push(frame()).unwrap();
1208 buf.push(frame()).unwrap();
1209 assert!(buf.is_full());
1210 assert!(buf.push(frame()).is_err(), "a full buffer hands the frame back");
1211 }
1212
1213 #[test]
1215 fn buffer_drain_empties_even_when_abandoned() {
1216 let mut buf = frame::Buffer::<4>::new();
1217 for i in 0..4u8 {
1218 buf.push(frame::Frame {
1219 timestamp: Timestamp::ZERO,
1220 payload: Bytes::from(vec![i; 1]),
1221 })
1222 .unwrap();
1223 }
1224 let taken: Vec<_> = buf.drain().take(2).collect();
1225 assert_eq!(taken.len(), 2);
1226 assert!(buf.is_empty(), "an abandoned drain still empties the buffer");
1227 }
1228
1229 #[test]
1230 fn read_frames_fills_whole_batch() {
1231 let mut producer = filled_group(5);
1232 producer.finish().unwrap();
1233
1234 let mut consumer = producer.consume();
1235 let mut buf = frame::Buffer::<8>::new();
1236
1237 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1238 assert_eq!(payloads(batch), ["0", "1", "2", "3", "4"]);
1239
1240 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1242 assert!(batch.is_empty());
1243 }
1244
1245 #[test]
1246 fn read_frames_bounded_by_capacity() {
1247 let mut producer = filled_group(5);
1248 producer.finish().unwrap();
1249
1250 let mut consumer = producer.consume();
1251 assert_eq!(drain::<2>(&mut consumer), ["0", "1", "2", "3", "4"]);
1252 }
1253
1254 #[test]
1255 fn read_frames_resumes_after_a_single_read() {
1256 let mut producer = filled_group(12);
1257 producer.finish().unwrap();
1258
1259 let mut consumer = producer.consume();
1260 let first = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1261 assert_eq!(first.payload, Bytes::from_static(b"0"));
1262
1263 assert_eq!(
1264 drain::<8>(&mut consumer),
1265 ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"]
1266 );
1267 }
1268
1269 #[test]
1270 fn read_frames_returns_short_instead_of_waiting() {
1271 let mut producer = filled_group(2);
1272
1273 let mut consumer = producer.consume();
1274 let mut buf = frame::Buffer::<8>::new();
1275
1276 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1278 assert_eq!(payloads(batch), ["0", "1"]);
1279
1280 assert!(consumer.read_frames(&mut buf).now_or_never().is_none());
1282
1283 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"2")).unwrap();
1284 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1285 assert_eq!(payloads(batch), ["2"]);
1286 }
1287
1288 #[test]
1289 fn read_frames_reports_an_abort() {
1290 let producer = filled_group(2);
1291 let mut consumer = producer.consume();
1292 producer.abort(Error::Cancel).unwrap();
1293
1294 let mut buf = frame::Buffer::<8>::new();
1296 let res = consumer.read_frames(&mut buf).now_or_never().unwrap();
1297 assert!(matches!(res, Err(Error::Cancel)));
1298 }
1299
1300 #[test]
1302 fn read_frames_refill_replaces_the_previous_batch() {
1303 let mut producer = filled_group(3);
1304 producer.finish().unwrap();
1305
1306 let mut consumer = producer.consume();
1307 let mut buf = frame::Buffer::<2>::new();
1308
1309 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1310 assert_eq!(payloads(batch), ["0", "1"]);
1311
1312 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1313 assert_eq!(payloads(batch), ["2"]);
1314 assert_eq!(buf.filled().len(), 1, "the buffer holds only the latest batch");
1315 }
1316
1317 #[test]
1318 fn read_frames_zero_capacity_reads_nothing() {
1319 let mut producer = filled_group(2);
1320 producer.finish().unwrap();
1321
1322 let mut consumer = producer.consume();
1323 let mut buf = frame::Buffer::<0>::new();
1324 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1325 assert!(batch.is_empty());
1326
1327 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1329 assert_eq!(frame.payload, Bytes::from_static(b"0"));
1330 }
1331
1332 #[test]
1333 fn read_frame_all_at_once() {
1334 let mut producer = Info { sequence: 0 }.produce();
1335 producer
1336 .write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
1337 .unwrap();
1338 producer.finish().unwrap();
1339
1340 let mut consumer = producer.consume();
1341 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1342 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
1343 }
1344
1345 #[test]
1346 fn read_frame_preserves_timestamp() {
1347 let mut producer = Info { sequence: 0 }.produce();
1348 let timestamp = Timestamp::from_micros(20_000).unwrap();
1349 producer.write_frame(timestamp, Bytes::from_static(b"hello")).unwrap();
1350 producer.finish().unwrap();
1351
1352 let mut consumer = producer.consume();
1353 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1354 assert_eq!(frame.timestamp.as_micros(), 20_000);
1355 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
1356 }
1357
1358 #[test]
1359 fn chunked_frame_reads_whole() {
1360 let mut producer = Info { sequence: 0 }.produce();
1361 {
1362 let mut frame = producer
1363 .create_frame(frame::Info {
1364 size: 10,
1365 timestamp: Timestamp::ZERO,
1366 })
1367 .unwrap();
1368 frame.write(Bytes::from_static(b"hello")).unwrap();
1369 frame.write(Bytes::from_static(b"world")).unwrap();
1370 frame.finish().unwrap();
1371 }
1372 producer.finish().unwrap();
1373
1374 let mut consumer = producer.consume();
1377 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1378 assert_eq!(frame.payload, Bytes::from_static(b"helloworld"));
1379 }
1380
1381 #[test]
1382 fn chunked_frame_streams_partial() {
1383 let mut producer = Info { sequence: 0 }.produce();
1384 let mut consumer = producer.consume();
1385
1386 let mut frame = producer
1387 .create_frame(frame::Info {
1388 size: 6,
1389 timestamp: Timestamp::ZERO,
1390 })
1391 .unwrap();
1392 frame.write(Bytes::from_static(b"foo")).unwrap();
1393
1394 let mut f = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1396 let c1 = f.read_chunk().now_or_never().unwrap().unwrap();
1397 assert_eq!(c1, Some(Bytes::from_static(b"foo")));
1398 assert!(f.read_chunk().now_or_never().is_none());
1399
1400 frame.write(Bytes::from_static(b"bar")).unwrap();
1401 frame.finish().unwrap();
1402
1403 let c2 = f.read_chunk().now_or_never().unwrap().unwrap();
1404 assert_eq!(c2, Some(Bytes::from_static(b"bar")));
1405 let c3 = f.read_chunk().now_or_never().unwrap().unwrap();
1406 assert_eq!(c3, None);
1407 }
1408
1409 #[test]
1410 fn group_finish_returns_none() {
1411 let mut producer = Info { sequence: 0 }.produce();
1412 producer.finish().unwrap();
1413
1414 let mut consumer = producer.consume();
1415 let end = consumer.next_frame().now_or_never().unwrap().unwrap();
1416 assert!(end.is_none());
1417 }
1418
1419 #[test]
1420 fn abort_propagates() {
1421 let producer = Info { sequence: 0 }.produce();
1422 let mut consumer = producer.consume();
1423 producer.abort(crate::Error::Cancel).unwrap();
1424
1425 let result = consumer.next_frame().now_or_never().unwrap();
1426 assert!(matches!(result, Err(crate::Error::Cancel)));
1427 }
1428
1429 #[test]
1430 fn abort_clears_cached_frames() {
1431 let mut producer = Info { sequence: 0 }.produce();
1432 producer
1433 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1434 .unwrap();
1435
1436 let _consumer = producer.consume();
1438 assert_eq!(producer.state.read().frames.len(), 1);
1439
1440 producer.clone().abort(crate::Error::Cancel).unwrap();
1441
1442 let state = producer.state.read();
1443 assert!(state.frames.is_empty(), "cached frames should be dropped on abort");
1444 assert_eq!(state.cache, 0);
1445 }
1446
1447 #[test]
1448 fn drop_unfinished_clears_cached_frames() {
1449 let producer = Info { sequence: 0 }.produce();
1450 let mut writer = producer.clone();
1451 writer
1452 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1453 .unwrap();
1454
1455 let mut consumer = producer.consume();
1457 assert_eq!(producer.state.read().frames.len(), 1);
1458
1459 drop(writer);
1461 drop(producer);
1462
1463 let result = consumer.next_frame().now_or_never().unwrap();
1464 assert!(matches!(result, Err(crate::Error::Dropped)));
1465 }
1466
1467 #[test]
1468 fn drop_after_abort_does_not_warn() {
1469 let warns = count_drop_warnings("group::Producer dropped without finish", || {
1470 let producer = Info { sequence: 0 }.produce();
1471 let keep = producer.clone();
1472 let mut writer = producer.clone();
1473 writer
1474 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1475 .unwrap();
1476 let _consumer = producer.consume();
1477 writer.abort(crate::Error::Cancel).unwrap();
1478 drop(keep);
1479 });
1480 assert_eq!(warns, 0, "abort-then-drop must not emit unfinished-producer WARN");
1481 }
1482
1483 #[test]
1484 fn drop_unfinished_warns() {
1485 let warns = count_drop_warnings("group::Producer dropped without finish", || {
1486 let producer = Info { sequence: 0 }.produce();
1487 let mut writer = producer.clone();
1488 writer
1489 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1490 .unwrap();
1491 let _consumer = producer.consume();
1492 drop(writer);
1493 drop(producer);
1494 });
1495 assert!(warns >= 1, "unfinished drop must emit unfinished-producer WARN");
1496 }
1497
1498 #[test]
1499 fn drop_finished_keeps_cached_frames() {
1500 let mut producer = Info { sequence: 0 }.produce();
1501 producer
1502 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1503 .unwrap();
1504 producer.finish().unwrap();
1505
1506 let mut consumer = producer.consume();
1507 drop(producer);
1508
1509 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1511 assert_eq!(frame.payload, Bytes::from_static(b"data"));
1512 }
1513
1514 #[tokio::test]
1515 async fn pending_then_ready() {
1516 let mut producer = Info { sequence: 0 }.produce();
1517 let mut consumer = producer.consume();
1518
1519 assert!(consumer.next_frame().now_or_never().is_none());
1521
1522 producer
1523 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
1524 .unwrap();
1525 producer.finish().unwrap();
1526
1527 let frame = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1528 assert_eq!(frame.size, 4);
1529 }
1530
1531 #[test]
1532 fn eviction_drops_old_frames() {
1533 let mut producer = Info { sequence: 0 }.produce();
1534
1535 let big = Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize]);
1537 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
1538 producer.write_frame(Timestamp::ZERO, big).unwrap();
1539
1540 let state = producer.state.read();
1542 assert_eq!(state.offset, 1);
1543 assert_eq!(state.frames.len(), 1);
1544 assert_eq!(state.frames[0].payload.len(), MAX_CACHE_BYTES as usize);
1545 }
1546
1547 #[test]
1548 fn next_frame_returns_cache_full_on_tombstone() {
1549 let mut producer = Info { sequence: 0 }.produce();
1550
1551 let big = Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize]);
1552 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
1553 producer.write_frame(Timestamp::ZERO, big).unwrap();
1554
1555 let mut consumer = producer.consume();
1556 let result = consumer.next_frame().now_or_never().unwrap();
1558 assert!(matches!(result, Err(crate::Error::Lagged)));
1559 }
1560
1561 #[test]
1564 fn skip_to_tolerates_an_eviction_below_it() {
1565 let mut producer = Info { sequence: 0 }.produce();
1566
1567 let big = Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize]);
1570 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
1571 producer.write_frame(Timestamp::ZERO, big).unwrap();
1572 producer
1573 .write_frame(Timestamp::ZERO, Bytes::from_static(b"tail"))
1574 .unwrap();
1575 assert_eq!(producer.state.read().offset, 2);
1576
1577 let mut reader = producer.consume();
1578 reader.skip_to(2);
1579 let frame = reader.next_frame().now_or_never().unwrap().unwrap().unwrap();
1580 assert_eq!(frame.size, 4);
1581
1582 let mut behind = producer.consume();
1583 behind.skip_to(1);
1584 let result = behind.next_frame().now_or_never().unwrap();
1585 assert!(matches!(result, Err(crate::Error::Lagged)));
1586 }
1587
1588 #[test]
1589 fn no_eviction_under_budget() {
1590 let mut producer = Info { sequence: 0 }.produce();
1591 for _ in 0..100_000 {
1593 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
1594 }
1595 producer.finish().unwrap();
1596
1597 let state = producer.state.read();
1598 assert_eq!(state.offset, 0);
1599 assert_eq!(state.frames.len(), 100_000);
1600 }
1601
1602 #[test]
1603 fn clone_consumer_independent() {
1604 let mut producer = Info { sequence: 0 }.produce();
1605 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
1606
1607 let mut c1 = producer.consume();
1608 let _ = c1.next_frame().now_or_never().unwrap().unwrap().unwrap();
1610
1611 let mut c2 = c1.clone();
1613
1614 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
1615 producer.finish().unwrap();
1616
1617 let f = c2.next_frame().now_or_never().unwrap().unwrap().unwrap();
1619 assert_eq!(f.size, 1); let end = c2.next_frame().now_or_never().unwrap().unwrap();
1622 assert!(end.is_none());
1623 }
1624
1625 #[test]
1628 fn read_frames_crosses_batches() {
1629 const CAP: usize = 8;
1630 let n = CAP * 3 + 5;
1631 let mut producer = Info { sequence: 0 }.produce();
1632 for i in 0..n {
1633 producer
1634 .write_frame(Timestamp::ZERO, Bytes::from(vec![i as u8; 4]))
1635 .unwrap();
1636 }
1637 producer.finish().unwrap();
1638
1639 let mut consumer = producer.consume();
1640 let mut buf = frame::Buffer::<CAP>::new();
1641 let mut seen = 0;
1642 loop {
1643 let batch = consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap();
1644 if batch.is_empty() {
1645 break;
1646 }
1647 for frame in batch.iter() {
1648 assert_eq!(frame.payload, Bytes::from(vec![seen as u8; 4]));
1649 seen += 1;
1650 }
1651 }
1652 assert_eq!(seen, n);
1653 assert!(consumer.read_frame().now_or_never().unwrap().unwrap().is_none());
1654 }
1655
1656 #[test]
1660 fn abort_after_finish_keeps_the_clean_end_for_a_drained_reader() {
1661 let mut producer = Info { sequence: 0 }.produce();
1662 producer
1663 .write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
1664 .unwrap();
1665 producer.finish().unwrap();
1666
1667 let mut drained = producer.consume();
1668 let mut behind = producer.consume();
1669 let frame = drained.read_frame().now_or_never().unwrap().unwrap().unwrap();
1670 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
1671
1672 producer.abort(Error::Old).unwrap();
1673
1674 assert!(drained.read_frame().now_or_never().unwrap().unwrap().is_none());
1676 assert!(drained.next_frame().now_or_never().unwrap().unwrap().is_none());
1677
1678 assert!(matches!(behind.read_frame().now_or_never().unwrap(), Err(Error::Old)));
1680 }
1681
1682 #[test]
1686 fn finished_answers_for_the_cursor() {
1687 let mut producer = Info { sequence: 0 }.produce();
1688 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
1689 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
1690 producer.finish().unwrap();
1691
1692 let mut drained = producer.consume();
1693 let mut behind = producer.consume();
1694 while drained.read_frame().now_or_never().unwrap().unwrap().is_some() {}
1695 behind.read_frame().now_or_never().unwrap().unwrap().unwrap();
1696
1697 producer.abort(Error::Old).unwrap();
1698
1699 assert_eq!(drained.finished().now_or_never().unwrap().unwrap(), 2);
1700 assert!(matches!(behind.finished().now_or_never().unwrap(), Err(Error::Old)));
1701 assert_eq!(behind.frame_count(), 2);
1702 }
1703
1704 #[test]
1707 fn finished_reports_a_lagged_cursor() {
1708 let mut producer = Info { sequence: 0 }.produce();
1709 let mut consumer = producer.consume();
1710
1711 let big = Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize]);
1713 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
1714 producer.write_frame(Timestamp::ZERO, big).unwrap();
1715
1716 assert!(matches!(
1717 consumer.finished().now_or_never().unwrap(),
1718 Err(Error::Lagged)
1719 ));
1720 }
1721
1722 #[test]
1724 fn interleave_read_and_next_frame() {
1725 let mut producer = Info { sequence: 0 }.produce();
1726 for i in 0..5u8 {
1727 producer.write_frame(Timestamp::ZERO, Bytes::from(vec![i; 1])).unwrap();
1728 }
1729 producer.finish().unwrap();
1730
1731 let mut consumer = producer.consume();
1732 let f0 = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1733 assert_eq!(f0.payload, Bytes::from(vec![0u8; 1]));
1734
1735 for i in 1..5u8 {
1737 let mut f = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1738 let data = f.read_all().now_or_never().unwrap().unwrap();
1739 assert_eq!(data, Bytes::from(vec![i; 1]));
1740 }
1741 assert!(consumer.next_frame().now_or_never().unwrap().unwrap().is_none());
1742 }
1743
1744 #[test]
1747 fn read_frame_past_cleared_frames_does_not_panic() {
1748 let mut producer = Info { sequence: 0 }.produce();
1749 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
1750 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
1751
1752 let mut consumer = producer.consume();
1753 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1754 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1755
1756 producer.abort(Error::Cancel).unwrap();
1759
1760 let result = consumer.read_frame().now_or_never().unwrap();
1761 assert!(matches!(result, Err(Error::Cancel)), "expected Cancel, got {result:?}");
1762 }
1763
1764 #[test]
1767 fn drop_with_a_filled_buffer() {
1768 const CAP: usize = 8;
1769 let mut producer = Info { sequence: 0 }.produce();
1770 for _ in 0..CAP {
1771 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
1772 }
1773 producer.finish().unwrap();
1774
1775 let mut consumer = producer.consume();
1776 let mut buf = frame::Buffer::<CAP>::new();
1777 assert_eq!(
1779 consumer.read_frames(&mut buf).now_or_never().unwrap().unwrap().len(),
1780 CAP
1781 );
1782 drop(buf);
1783 }
1784
1785 #[tokio::test]
1790 async fn chunk_write_wakes_parked_reader() {
1791 let mut producer = Info { sequence: 0 }.produce();
1792 let mut consumer = producer.consume();
1793 let mut frame = producer
1794 .create_frame(frame::Info {
1795 size: 6,
1796 timestamp: Timestamp::ZERO,
1797 })
1798 .unwrap();
1799 let mut f = consumer.next_frame().await.unwrap().unwrap();
1800 let handle = tokio::spawn(async move { f.read_chunk().await });
1801 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
1803 frame.write(Bytes::from_static(b"foo")).unwrap();
1804 let chunk = tokio::time::timeout(std::time::Duration::from_secs(2), handle)
1805 .await
1806 .expect("parked chunk reader was never woken by the chunk write")
1807 .unwrap()
1808 .unwrap();
1809 assert_eq!(chunk, Some(Bytes::from_static(b"foo")));
1810 }
1811
1812 #[test]
1815 fn create_frame_converts_mismatched_scale() {
1816 use crate::{Timescale, Timestamp};
1817
1818 let mut producer = Producer::new(
1819 Info { sequence: 0 },
1820 track::Info::default().with_timescale(Timescale::MICRO),
1821 Default::default(),
1822 );
1823 let frame = frame::Info {
1824 size: 3,
1825 timestamp: Timestamp::from_millis(1).unwrap(), };
1827 let writer = producer.create_frame(frame).unwrap();
1828 assert_eq!(writer.timestamp.scale(), Timescale::MICRO);
1829 assert_eq!(writer.timestamp.value(), 1000);
1830 }
1831
1832 #[tokio::test]
1834 async fn create_frame_converts_current_timestamp() {
1835 use crate::Timescale;
1836
1837 let mut producer = Producer::new(
1838 Info { sequence: 0 },
1839 track::Info::default().with_timescale(Timescale::MICRO),
1840 Default::default(),
1841 );
1842 let writer = producer
1843 .create_frame(frame::Info {
1844 size: 3,
1845 timestamp: Timestamp::now(),
1846 })
1847 .unwrap();
1848 assert_eq!(writer.timestamp.scale(), Timescale::MICRO);
1849 assert!(!writer.timestamp.is_zero(), "local clock should be non-zero");
1850 }
1851
1852 #[test]
1854 fn create_frame_rejects_oversized() {
1855 let mut producer = Info { sequence: 0 }.produce();
1856 let result = producer.create_frame(frame::Info {
1857 size: MAX_CACHE_BYTES + 1,
1858 timestamp: Timestamp::ZERO,
1859 });
1860 assert!(matches!(result, Err(Error::FrameTooLarge)));
1861 }
1862}