1use crate::cache;
18use crate::frame::{self, Frame, FrameBuf};
19use crate::{Cap, Timescale, stats, track};
20use std::collections::VecDeque;
21use std::mem::MaybeUninit;
22use std::ops::{Bound, RangeBounds};
23use std::sync::Arc;
24use std::sync::atomic::{AtomicBool, Ordering};
25use std::task::{Poll, ready};
26
27use crate::{Error, IntoBytes, Result, Timestamp};
28
29pub const MAX_CACHE_BYTES: u64 = 32 * 1024 * 1024; pub const MAX_GROUP_FRAMES: usize = 8192;
41
42const FRAME_SLOTS: usize = 4;
47
48pub(crate) const CACHE_OVERHEAD: u64 = (kio::Producer::<GroupState>::HEAP
56 + 2 * size_of::<usize>()
59 + size_of::<Alive>()
60 + FRAME_SLOTS * size_of::<Frame>()) as u64;
61
62#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
66pub struct Info {
67 pub sequence: u64,
70}
71
72impl Info {
73 #[cfg(test)]
79 pub(crate) fn produce(self) -> Producer {
80 Producer::new(self, track::Info::default(), Default::default())
81 }
82}
83
84impl From<usize> for Info {
85 fn from(sequence: usize) -> Self {
86 Self {
87 sequence: sequence as u64,
88 }
89 }
90}
91
92impl From<u64> for Info {
93 fn from(sequence: u64) -> Self {
94 Self { sequence }
95 }
96}
97
98impl From<u32> for Info {
99 fn from(sequence: u32) -> Self {
100 Self {
101 sequence: sequence as u64,
102 }
103 }
104}
105
106impl From<u16> for Info {
107 fn from(sequence: u16) -> Self {
108 Self {
109 sequence: sequence as u64,
110 }
111 }
112}
113
114pub(crate) struct Partial {
117 timestamp: Timestamp,
118 buf: FrameBuf,
119 charged: u64,
121}
122
123#[derive(Default)]
126pub(crate) struct GroupState {
127 pub(crate) frames: VecDeque<Frame>,
130
131 pub(crate) partial: Option<Partial>,
133
134 pub(crate) offset: usize,
138
139 next_index: usize,
143
144 committed: usize,
148
149 pub(crate) cache: u64,
152
153 charge: cache::Charge,
156
157 timestamp: Option<Timestamp>,
163
164 latest: Option<Timestamp>,
168
169 pub(crate) fin: Option<usize>,
172
173 pub(crate) abort: Option<Error>,
177}
178
179impl GroupState {
180 fn content(&self) -> stats::Content {
185 let unwritten = self
186 .partial
187 .as_ref()
188 .map_or(0, |partial| partial.buf.size() as u64 - partial.charged);
189 stats::Content {
190 bytes: self.cache + unwritten,
191 frames: self.next_index.saturating_sub(self.offset) as u64,
192 groups: 1,
193 datagrams: 0,
194 }
195 }
196
197 pub(crate) fn content_range(&self, start: usize, end: usize) -> stats::Content {
199 let start = start.max(self.offset);
200 let end = end.min(self.next_index);
201 if start >= end {
202 return stats::Content::default();
203 }
204
205 let local_start = start.saturating_sub(self.offset).min(self.frames.len());
206 let local_end = end.saturating_sub(self.offset).min(self.frames.len());
207 let mut bytes = self
208 .frames
209 .range(local_start..local_end)
210 .map(|frame| frame.payload.len() as u64)
211 .sum();
212 if start <= self.committed
213 && self.committed < end
214 && let Some(partial) = &self.partial
215 {
216 bytes += partial.buf.size() as u64;
217 }
218
219 stats::Content {
220 bytes,
221 frames: (end - start) as u64,
222 groups: 0,
223 datagrams: 0,
224 }
225 }
226
227 fn poll_frame_source(&self, index: usize) -> Poll<Result<Option<(frame::Info, frame::Source)>>> {
230 if index < self.offset {
231 return Poll::Ready(Err(Error::Lagged));
232 }
233 let local = index - self.offset;
234 if let Some(f) = self.frames.get(local) {
235 self.charge.refresh();
238 let info = frame::Info {
239 size: f.payload.len() as u64,
240 timestamp: f.timestamp,
241 };
242 return Poll::Ready(Ok(Some((info, frame::Source::Complete(f.payload.clone())))));
243 }
244 if local == self.frames.len()
245 && let Some(p) = &self.partial
246 {
247 self.charge.refresh();
248 let info = frame::Info {
249 size: p.buf.size() as u64,
250 timestamp: p.timestamp,
251 };
252 return Poll::Ready(Ok(Some((info, frame::Source::Partial(p.buf.clone())))));
253 }
254 ready!(self.poll_terminal(index))?;
255 Poll::Ready(Ok(None))
256 }
257
258 fn poll_terminal(&self, index: usize) -> Poll<Result<()>> {
265 match (self.fin, &self.abort) {
266 (Some(total), Some(err)) if index < total => Poll::Ready(Err(err.clone())),
267 (Some(_), _) => Poll::Ready(Ok(())),
268 (None, Some(err)) => Poll::Ready(Err(err.clone())),
269 (None, None) => Poll::Pending,
270 }
271 }
272
273 fn poll_end(&self, index: usize) -> Poll<Result<()>> {
276 if index < self.offset {
277 return Poll::Ready(Err(Error::Lagged));
278 }
279 self.poll_terminal(index)
280 }
281
282 fn stamp(&mut self, timestamp: Timestamp) {
285 self.timestamp.get_or_insert(timestamp);
286 self.latest = Some(timestamp);
287 }
288
289 fn would_overflow(&self, extra_frames: usize, extra_bytes: u64) -> bool {
291 self.next_index.saturating_sub(self.offset).saturating_add(extra_frames) > MAX_GROUP_FRAMES
292 || self.cache.saturating_add(extra_bytes) > MAX_CACHE_BYTES
293 }
294
295 fn charge_partial(&mut self) -> Option<u64> {
299 let written = match &mut self.partial {
300 Some(partial) => {
301 let written = partial.buf.written(Ordering::Acquire) as u64;
302 written - std::mem::replace(&mut partial.charged, written)
303 }
304 None => 0,
305 };
306 self.cache += written;
307 self.charge.add(written)
308 }
309
310 fn release(&mut self) {
312 self.frames.clear();
313 self.partial = None;
314 self.cache = 0;
315 self.charge.clear();
316 }
317}
318
319fn modify(state: &kio::Producer<GroupState>) -> Result<kio::Mut<'_, GroupState>> {
320 state.write().map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
321}
322
323pub struct Producer {
329 state: kio::Producer<GroupState>,
331
332 info: Info,
335
336 track: track::Info,
341
342 cache: Arc<cache::Track>,
346
347 stats: stats::Meter,
350
351 alive: Arc<Alive>,
354}
355
356struct Alive {
364 info: Info,
365 state: kio::Producer<GroupState>,
366 aborted: AtomicBool,
374 access: Arc<cache::Access>,
378}
379
380impl Drop for Alive {
381 fn drop(&mut self) {
382 match self.state.write() {
388 Ok(mut state) => {
389 if state.fin.is_some() || state.abort.is_some() {
390 return;
391 }
392 tracing::warn!(
393 sequence = self.info.sequence,
394 "group::Producer dropped without finish() or abort()"
395 );
396 state.release();
397 }
398 Err(state) => {
399 if state.fin.is_some() || state.abort.is_some() {
400 return;
401 }
402 tracing::warn!(
403 sequence = self.info.sequence,
404 "group::Producer dropped without finish() or abort()"
405 );
406 }
407 }
408 }
409}
410
411impl std::ops::Deref for Producer {
412 type Target = Info;
413
414 fn deref(&self) -> &Self::Target {
415 &self.info
416 }
417}
418
419impl Producer {
420 pub(crate) fn new(info: Info, track: track::Info, cache: Arc<cache::Track>) -> Self {
431 let state = kio::Producer::<GroupState>::default();
432 let charge = cache.charge();
433 let access = charge.access();
434 state.write().ok().expect("a new group is open").charge = charge;
435 let alive = Arc::new(Alive {
436 info,
437 state: state.clone(),
438 aborted: AtomicBool::new(false),
439 access,
440 });
441 Self {
442 info,
443 state,
444 track,
445 cache,
446 stats: stats::Meter::default(),
447 alive,
448 }
449 }
450
451 pub(crate) fn with_meter(mut self, meter: stats::Meter) -> Self {
454 meter.group();
455 self.stats = meter;
456 self
457 }
458
459 pub(crate) fn info(&self) -> Info {
461 self.info
462 }
463
464 pub fn timescale(&self) -> Timescale {
466 self.track.timescale
467 }
468
469 pub fn start_at(&mut self, index: u64) -> Result<()> {
483 let index = usize::try_from(index).map_err(|_| Error::BoundsExceeded(crate::coding::BoundsExceeded))?;
484 if index == usize::MAX {
485 return Err(Error::BoundsExceeded(crate::coding::BoundsExceeded));
486 }
487
488 let mut state = modify(&self.state)?;
489 if state.fin.is_some() || state.next_index != state.offset {
492 return Err(Error::Closed);
493 }
494 state.offset = index;
495 state.next_index = index;
496 state.committed = index;
497 Ok(())
498 }
499
500 pub fn write_frame<B: IntoBytes>(&mut self, timestamp: Timestamp, data: B) -> Result<()> {
508 let timestamp = timestamp
509 .convert(self.track.timescale)
510 .map_err(|_| Error::TimestampMismatch)?;
511 let payload = data.into_bytes();
512 if payload.len() as u64 > MAX_CACHE_BYTES {
513 return Err(Error::FrameTooLarge);
514 }
515
516 let mut state = modify(&self.state)?;
517 if state.fin.is_some() {
518 return Err(Error::Closed);
519 }
520 if state.partial.is_some() {
521 return Err(Error::FrameOpen);
522 }
523 let next_index = state
524 .next_index
525 .checked_add(1)
526 .ok_or(Error::BoundsExceeded(crate::coding::BoundsExceeded))?;
527 debug_assert!(state.partial.is_none(), "a frame is already open");
528 let size = payload.len() as u64;
529 if state.would_overflow(1, size) {
530 return Err(self.abort_too_large(state));
531 }
532 state.cache += size;
533 let now = state.charge.add(size);
534 state.frames.push_back(Frame { timestamp, payload });
535 state.next_index = next_index;
536 state.committed = state.next_index;
537 state.stamp(timestamp);
538 drop(state);
539
540 self.cache.settle(now);
543
544 self.stats.frames(1);
546 self.stats.bytes(size);
547 Ok(())
548 }
549
550 pub fn write_frames<const N: usize>(&mut self, frames: &mut frame::Buffer<N>) -> Result<()> {
561 for frame in frames.filled() {
562 frame
563 .timestamp
564 .convert(self.track.timescale)
565 .map_err(|_| Error::TimestampMismatch)?;
566 if frame.payload.len() as u64 > MAX_CACHE_BYTES {
567 return Err(Error::FrameTooLarge);
568 }
569 }
570
571 let count = frames.len();
572 let bytes: u64 = frames.filled().iter().map(|frame| frame.payload.len() as u64).sum();
573 let mut state = modify(&self.state)?;
574 if state.fin.is_some() {
575 return Err(Error::Closed);
576 }
577 if state.partial.is_some() {
578 return Err(Error::FrameOpen);
579 }
580 let next_index = state
581 .next_index
582 .checked_add(count)
583 .ok_or(Error::BoundsExceeded(crate::coding::BoundsExceeded))?;
584 if state.would_overflow(count, bytes) {
585 return Err(self.abort_too_large(state));
586 }
587
588 let mut now = None;
590 for mut frame in frames.drain() {
591 frame.timestamp = frame
592 .timestamp
593 .convert(self.track.timescale)
594 .expect("timestamp scale checked above");
595 let size = frame.payload.len() as u64;
596 state.cache += size;
597 now = state.charge.add(size);
598 state.stamp(frame.timestamp);
599 state.frames.push_back(frame);
600 }
601 state.next_index = next_index;
602 state.committed = next_index;
603 drop(state);
604
605 self.cache.settle(now);
606 self.stats.frames(count as u64);
607 self.stats.bytes(bytes);
608 Ok(())
609 }
610
611 pub fn create_frame(&mut self, frame: frame::Info) -> Result<frame::Producer<'_>> {
620 let buf = FrameBuf::new(frame.size as usize);
622 let info = self.open_frame(frame, &buf)?;
623 let meter = self.stats.clone();
624 Ok(frame::Producer::new(self, buf, info).with_meter(meter))
625 }
626
627 pub(crate) fn create_frame_owned(
635 &mut self,
636 frame: frame::Info,
637 budget: &frame::Budget,
638 ) -> Result<frame::ProducerOwned> {
639 let size = frame.size as usize;
640 let reserved = budget.reserve(size);
641 let buf = match reserved {
642 Some(_) => FrameBuf::new(size),
643 None => FrameBuf::growing(size),
644 };
645 let info = self.open_frame(frame, &buf)?;
646 let meter = self.stats.clone();
647 Ok(frame::ProducerOwned::new(self.clone(), buf, info, reserved).with_meter(meter))
648 }
649
650 fn open_frame(&mut self, frame: frame::Info, buf: &FrameBuf) -> Result<frame::Info> {
655 let timestamp = frame
656 .timestamp
657 .convert(self.track.timescale)
658 .map_err(|_| Error::TimestampMismatch)?;
659 if frame.size > MAX_CACHE_BYTES {
660 return Err(Error::FrameTooLarge);
661 }
662
663 let mut state = modify(&self.state)?;
664 if state.fin.is_some() {
665 return Err(Error::Closed);
666 }
667 if state.partial.is_some() {
668 return Err(Error::FrameOpen);
669 }
670 let next_index = state
671 .next_index
672 .checked_add(1)
673 .ok_or(Error::BoundsExceeded(crate::coding::BoundsExceeded))?;
674 if state.would_overflow(1, frame.size) {
677 return Err(self.abort_too_large(state));
678 }
679 let now = state.charge.record_write();
680 state.partial = Some(Partial {
681 timestamp,
682 buf: buf.clone(),
683 charged: 0,
684 });
685 state.next_index = next_index;
686 state.stamp(timestamp);
689 drop(state);
690
691 self.cache.settle(now);
694
695 self.stats.frames(1);
698
699 Ok(frame::Info {
700 size: frame.size,
701 timestamp,
702 })
703 }
704
705 pub(crate) fn frame_notify(&self) {
707 let now = self.state.write().ok().and_then(|mut state| state.charge_partial());
713 self.cache.settle(now);
716 }
717
718 pub(crate) fn frame_commit(&mut self, frame: Frame) -> Result<()> {
720 let mut state = modify(&self.state)?;
721 let now = state.charge_partial();
728 state.partial = None;
729 state.frames.push_back(frame);
730 state.committed = state.next_index;
731 drop(state);
732
733 self.cache.settle(now);
736 Ok(())
737 }
738
739 pub(crate) fn frame_abort(&mut self, err: Error) {
742 let _ = self.clone().abort(err);
743 }
744
745 pub fn frame_count(&self) -> usize {
751 self.state.read().next_index
752 }
753
754 pub fn finish(&self) -> Result<()> {
759 let mut state = modify(&self.state)?;
760 if state.partial.is_some() {
761 return Err(Error::FrameOpen);
762 }
763 state.fin = Some(state.next_index);
764 Ok(())
765 }
766
767 pub fn abort(self, err: Error) -> Result<()> {
773 self.close_aborted(err)
774 }
775
776 pub(crate) fn abort_if_last(&self, err: Error) -> Result<()> {
780 if Arc::strong_count(&self.alive) > 1 {
781 return Ok(());
782 }
783 self.close_aborted(err)
784 }
785
786 pub(crate) fn abort_unused(&self, err: Error) -> bool {
790 match self.state.write_unused() {
791 kio::Unused::Idle(guard) => {
792 self.commit_abort(guard, err);
793 true
794 }
795 kio::Unused::Closed => true,
796 kio::Unused::Used => false,
797 }
798 }
799
800 fn close_aborted(&self, err: Error) -> Result<()> {
801 self.commit_abort(modify(&self.state)?, err);
802 Ok(())
803 }
804
805 fn commit_abort(&self, mut guard: kio::Mut<'_, GroupState>, err: Error) {
806 guard.abort = Some(err);
807 self.alive.aborted.store(true, Ordering::Release);
808 guard.release();
809 guard.close();
810 }
811
812 fn abort_too_large(&self, mut state: kio::Mut<'_, GroupState>) -> Error {
815 let err = Error::GroupTooLarge;
816 state.abort = Some(err.clone());
817 self.alive.aborted.store(true, Ordering::Release);
818 state.release();
819 state.close();
820 err
821 }
822
823 pub(crate) fn is_aborted(&self) -> bool {
831 self.alive.aborted.load(Ordering::Acquire)
832 }
833
834 pub(crate) fn is_finished(&self) -> bool {
836 self.state.read().fin.is_some()
837 }
838
839 pub(crate) fn live_first_frame(&self) -> Option<usize> {
849 let state = self.state.read();
850 state.abort.is_none().then_some(state.offset)
851 }
852
853 pub(crate) fn resume_frame(&self) -> Option<usize> {
865 let state = self.state.read();
866 if state.fin.is_some() {
867 return None;
868 }
869 (state.committed > state.offset).then_some(state.committed)
870 }
871
872 pub(crate) fn timestamp(&self) -> Option<Timestamp> {
882 self.state.read().timestamp
883 }
884
885 pub(crate) fn latest(&self) -> Option<Timestamp> {
894 self.state.read().latest
895 }
896
897 pub(crate) fn cache_size(&self) -> u64 {
900 self.state.read().charge.size()
901 }
902
903 pub(crate) fn cache_accessed(&self) -> u64 {
906 self.alive.access.get()
907 }
908
909 pub(crate) fn cache_accessed_tick(&self, now: Option<u64>) -> Option<u64> {
911 self.alive.access.tick(now)
912 }
913
914 pub(crate) fn cache_protect(&self) -> cache::Protection {
916 cache::Protection::new(&self.alive.access)
917 }
918
919 pub(crate) fn cache_protected(&self) -> bool {
921 self.alive.access.is_protected()
922 }
923
924 pub(crate) fn cache_demote(&self) {
927 if let Ok(mut state) = self.state.write() {
928 state.charge.demote();
929 }
930 }
931
932 pub(crate) fn cache_refresh(&self) {
938 self.state.read().charge.refresh();
939 }
940
941 pub fn consume(&self) -> Consumer {
943 self.consumer(self.state.consume())
944 }
945
946 pub(crate) fn try_consume(&self) -> Option<Consumer> {
950 self.state.weak().try_consume().map(|state| self.consumer(state))
951 }
952
953 fn consumer(&self, state: kio::Consumer<GroupState>) -> Consumer {
954 Consumer {
955 info: self.info,
956 track: self.track.clone(),
957 inner: ConsumerKind::Plain(Plain {
958 state,
959 index: 0,
960 end: None,
961 prefetch: Prefetch::default(),
962 cache: self.cache.clone(),
963 access: self.alive.access.clone(),
964 refreshed: self.cache.pool().now(),
965 }),
966 stats: stats::Meter::default(),
969 stale_stats: stats::Meter::default(),
970 expiry: None,
971 expired: false,
972 ended: false,
973 stale_counted: Arc::default(),
974 }
975 }
976
977 pub(crate) fn poll_timestamp(&self, waiter: &kio::Waiter) -> Poll<()> {
979 match self.state.poll(waiter, |state| {
980 if state.timestamp.is_some() || state.fin.is_some() || state.abort.is_some() {
981 Poll::Ready(())
982 } else {
983 Poll::Pending
984 }
985 }) {
986 Poll::Ready(_) => Poll::Ready(()),
987 Poll::Pending => Poll::Pending,
988 }
989 }
990
991 pub async fn closed(&self) -> Error {
993 kio::wait(|waiter| self.poll_closed(waiter)).await
994 }
995
996 pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<Error> {
998 self.state.poll_closed(waiter).map(|()| self.abort_reason())
999 }
1000
1001 pub async fn used(&self) -> Result<()> {
1003 self.state.used().await.map_err(|_| self.abort_reason())
1004 }
1005
1006 pub async fn unused(&self) -> Result<()> {
1008 self.state.unused().await.map_err(|_| self.abort_reason())
1009 }
1010
1011 pub(crate) fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
1013 self.state.poll_unused(waiter).map(|_| ())
1014 }
1015
1016 fn abort_reason(&self) -> Error {
1018 self.state.read().abort.clone().unwrap_or(Error::Dropped)
1019 }
1020}
1021
1022impl Clone for Producer {
1023 fn clone(&self) -> Self {
1024 Self {
1025 info: self.info,
1026 state: self.state.clone(),
1027 track: self.track.clone(),
1028 cache: self.cache.clone(),
1029 stats: self.stats.clone(),
1030 alive: self.alive.clone(),
1031 }
1032 }
1033}
1034
1035struct Prefetch {
1043 frames: [MaybeUninit<Frame>; Self::CAP],
1045 pos: usize,
1046 len: usize,
1047}
1048
1049impl Prefetch {
1050 const CAP: usize = 8;
1051
1052 fn pop(&mut self) -> Option<Frame> {
1054 if self.pos == self.len {
1055 return None;
1056 }
1057 let frame = unsafe { self.frames[self.pos].assume_init_read() };
1059 self.pos += 1;
1060 Some(frame)
1061 }
1062
1063 fn fill(&mut self, frames: impl Iterator<Item = Frame>) {
1065 debug_assert_eq!(self.pos, self.len, "fill on a non-empty batch would leak frames");
1066 self.pos = 0;
1067 self.len = 0;
1068 for frame in frames.take(Self::CAP) {
1069 self.frames[self.len].write(frame);
1070 self.len += 1;
1071 }
1072 }
1073
1074 fn buffered(&self) -> (u64, u64) {
1077 let mut bytes = 0u64;
1078 for slot in &self.frames[self.pos..self.len] {
1079 bytes += unsafe { slot.assume_init_ref() }.payload.len() as u64;
1081 }
1082 ((self.len - self.pos) as u64, bytes)
1083 }
1084}
1085
1086impl Default for Prefetch {
1087 fn default() -> Self {
1088 Self {
1089 frames: [const { MaybeUninit::uninit() }; Self::CAP],
1090 pos: 0,
1091 len: 0,
1092 }
1093 }
1094}
1095
1096impl Drop for Prefetch {
1097 fn drop(&mut self) {
1098 for slot in &mut self.frames[self.pos..self.len] {
1099 unsafe { slot.assume_init_drop() };
1101 }
1102 }
1103}
1104
1105pub struct Consumer {
1111 inner: ConsumerKind,
1112
1113 info: Info,
1115
1116 track: track::Info,
1119
1120 stats: stats::Meter,
1123 stale_stats: stats::Meter,
1126
1127 expiry: Option<Arc<dyn Expiry>>,
1130 expired: bool,
1131 ended: bool,
1136 stale_counted: Arc<AtomicBool>,
1139}
1140
1141pub(crate) trait Expiry: Send + Sync + std::panic::UnwindSafe + std::panic::RefUnwindSafe {
1146 fn is_expired(&self, waiter: &kio::Waiter) -> bool;
1149}
1150
1151#[expect(clippy::large_enum_variant)]
1154enum ConsumerKind {
1155 Plain(Plain),
1156 Spliced(Box<super::resume::Group>),
1158}
1159
1160struct Plain {
1162 state: kio::Consumer<GroupState>,
1164
1165 index: usize,
1168
1169 end: Option<usize>,
1171
1172 prefetch: Prefetch,
1174
1175 cache: Arc<cache::Track>,
1177 access: Arc<cache::Access>,
1178 refreshed: u64,
1179}
1180
1181impl Clone for Plain {
1182 fn clone(&self) -> Self {
1183 Self {
1186 state: self.state.clone(),
1187 index: self.index,
1188 end: self.end,
1189 prefetch: Prefetch::default(),
1190 cache: self.cache.clone(),
1191 access: self.access.clone(),
1192 refreshed: self.refreshed,
1193 }
1194 }
1195}
1196
1197impl Clone for Consumer {
1198 fn clone(&self) -> Self {
1199 Self {
1200 inner: match &self.inner {
1201 ConsumerKind::Plain(plain) => ConsumerKind::Plain(plain.clone()),
1202 ConsumerKind::Spliced(spliced) => ConsumerKind::Spliced(Box::new((**spliced).clone())),
1203 },
1204 info: self.info,
1205 track: self.track.clone(),
1206 stats: self.stats.clone(),
1209 stale_stats: self.stale_stats.clone(),
1210 expiry: self.expiry.clone(),
1211 expired: self.expired,
1212 ended: self.ended,
1213 stale_counted: self.stale_counted.clone(),
1214 }
1215 }
1216}
1217
1218impl std::ops::Deref for Consumer {
1219 type Target = Info;
1220
1221 fn deref(&self) -> &Self::Target {
1222 &self.info
1223 }
1224}
1225
1226impl Consumer {
1227 pub(crate) fn content(&self) -> stats::Content {
1229 match &self.inner {
1230 ConsumerKind::Plain(plain) => plain.state.read().content(),
1231 ConsumerKind::Spliced(_) => stats::Content {
1234 groups: 1,
1235 ..Default::default()
1236 },
1237 }
1238 }
1239
1240 fn unread_content(&self) -> stats::Content {
1242 match &self.inner {
1243 ConsumerKind::Plain(plain) => plain.unread_content(),
1244 ConsumerKind::Spliced(_) => stats::Content::default(),
1246 }
1247 }
1248
1249 pub(crate) fn into_spliced(self, mut spliced: super::resume::Group) -> Self {
1252 spliced.set_stale_meter(self.stale_stats.clone());
1253 Self {
1254 inner: ConsumerKind::Spliced(Box::new(spliced)),
1255 info: self.info,
1256 track: self.track,
1257 stats: self.stats,
1258 stale_stats: self.stale_stats,
1259 expiry: None,
1263 expired: false,
1264 ended: false,
1265 stale_counted: self.stale_counted,
1266 }
1267 }
1268
1269 pub(crate) fn with_meter(mut self, meter: stats::Meter) -> Self {
1272 meter.group();
1273 self.stats = meter.clone();
1274 self.set_stale_meter(meter);
1275 self
1276 }
1277
1278 pub(crate) fn set_stale_meter(&mut self, meter: stats::Meter) {
1280 if let ConsumerKind::Spliced(spliced) = &mut self.inner {
1281 spliced.set_stale_meter(meter.clone());
1282 }
1283 self.stale_stats = meter;
1284 }
1285
1286 pub(crate) fn with_expiry(mut self, expiry: Arc<dyn Expiry>) -> Self {
1288 self.expiry = Some(expiry);
1289 self
1290 }
1291
1292 pub(crate) fn poll_expired(&mut self, waiter: &kio::Waiter) -> bool {
1294 self.poll_expired_while_pending(waiter, false)
1295 }
1296
1297 fn poll_expired_if_blocked(&mut self, waiter: &kio::Waiter) -> Option<bool> {
1312 if self.ended {
1313 return Some(false);
1314 }
1315 if !self.poll_expired(waiter) {
1316 return None;
1317 }
1318 let truncates = self.expired_truncates();
1319 self.ended = !truncates;
1320 Some(truncates)
1321 }
1322
1323 fn expired_truncates(&self) -> bool {
1332 let unread = self.unread_content();
1333 unread.frames > 0 || unread.bytes > 0
1334 }
1335
1336 pub(crate) fn poll_expired_while_pending(&mut self, waiter: &kio::Waiter, pending: bool) -> bool {
1338 if !self.expired
1339 && (pending || self.expiry_pending())
1340 && self.expiry.as_ref().is_some_and(|expiry| expiry.is_expired(waiter))
1341 {
1342 self.expired = true;
1343 if !self.stale_counted.swap(true, Ordering::Relaxed) {
1344 self.stale_stats.stale(self.unread_content());
1345 }
1346 }
1347 self.expired
1348 }
1349
1350 fn expiry_pending(&self) -> bool {
1352 match &self.inner {
1353 ConsumerKind::Plain(plain) => plain.expiry_pending(),
1354 ConsumerKind::Spliced(_) => false,
1356 }
1357 }
1358
1359 pub(crate) fn latency_expired(&self) -> bool {
1361 self.expired
1362 }
1363
1364 pub(crate) fn is_aborted(&self) -> bool {
1370 match &self.inner {
1371 ConsumerKind::Plain(plain) => plain.state.read().abort.is_some(),
1372 ConsumerKind::Spliced(_) => false,
1373 }
1374 }
1375
1376 pub fn keep_alive(&self) {
1378 if let ConsumerKind::Plain(plain) = &self.inner {
1379 plain.state.read().charge.refresh();
1380 }
1381 }
1382
1383 pub(crate) fn cache_refresh(&self) {
1386 self.keep_alive();
1387 }
1388
1389 pub(crate) fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<()> {
1397 match &self.inner {
1398 ConsumerKind::Plain(plain) => plain.state.poll_closed(waiter),
1399 ConsumerKind::Spliced(_) => Poll::Ready(()),
1400 }
1401 }
1402
1403 pub fn timescale(&self) -> Timescale {
1405 self.track.timescale
1406 }
1407
1408 pub fn index(&self) -> u64 {
1413 match &self.inner {
1414 ConsumerKind::Plain(plain) => plain.index as u64,
1415 ConsumerKind::Spliced(spliced) => spliced.index(),
1416 }
1417 }
1418
1419 pub fn set_frames(&mut self, frames: impl RangeBounds<u64>) {
1425 let (start, end) = super::subscription::sequence_bounds(frames);
1426 self.start_at(start);
1427 self.end_at(end.map_or(Bound::Unbounded, Bound::Excluded));
1428 }
1429
1430 pub(crate) fn start_at(&mut self, index: u64) {
1440 match &mut self.inner {
1441 ConsumerKind::Plain(plain) => plain.start_at(index),
1442 ConsumerKind::Spliced(spliced) => spliced.start_at(index),
1443 }
1444 }
1445
1446 pub fn skip_to(&mut self, index: u64) {
1452 match &mut self.inner {
1453 ConsumerKind::Plain(plain) => plain.skip_to(index),
1454 ConsumerKind::Spliced(spliced) => spliced.start_at(index),
1455 }
1456 }
1457
1458 pub(crate) fn end_at(&mut self, end: impl Into<Cap>) {
1465 let end = end.into().exclusive();
1466 match &mut self.inner {
1467 ConsumerKind::Plain(plain) => {
1468 plain.end = end.map(|end| usize::try_from(end).unwrap_or(usize::MAX));
1469 }
1470 ConsumerKind::Spliced(spliced) => spliced.end_at(end),
1471 }
1472 }
1473
1474 pub fn frame_count(&self) -> usize {
1477 match &self.inner {
1478 ConsumerKind::Plain(plain) => {
1479 let state = plain.state.read();
1480 state.fin.unwrap_or(state.next_index)
1481 }
1482 ConsumerKind::Spliced(spliced) => spliced.frame_count(),
1483 }
1484 }
1485
1486 pub async fn next_frame(&mut self) -> Result<Option<frame::Consumer>> {
1488 kio::wait(|waiter| self.poll_next_frame(waiter)).await
1489 }
1490
1491 pub fn poll_next_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Consumer>>> {
1496 if self.ended {
1497 return Poll::Ready(Ok(None));
1498 }
1499 if self.expired {
1500 return Poll::Ready(Err(Error::Old));
1501 }
1502 let stats = self.stats.clone();
1503 let expiry = self
1504 .expiry
1505 .as_ref()
1506 .map(|policy| frame::Expiry::new(policy.clone(), self.stale_stats.clone(), self.stale_counted.clone()));
1507 let res = match &mut self.inner {
1508 ConsumerKind::Plain(plain) => plain.poll_next_frame(waiter, &stats, expiry),
1509 ConsumerKind::Spliced(spliced) => {
1510 let res = ready!(spliced.poll_next_frame(waiter))?;
1513 if res.is_some() {
1514 stats.frames(1);
1515 }
1516 Poll::Ready(Ok(res.map(|frame| frame.with_meter(stats))))
1517 }
1518 };
1519 match res.is_pending().then(|| self.poll_expired_if_blocked(waiter)).flatten() {
1520 Some(true) => Poll::Ready(Err(Error::Old)),
1521 Some(false) => Poll::Ready(Ok(None)),
1522 None => res,
1523 }
1524 }
1525
1526 pub fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Frame>>> {
1528 if self.ended {
1529 return Poll::Ready(Ok(None));
1530 }
1531 if self.expired {
1532 return Poll::Ready(Err(Error::Old));
1533 }
1534 let stats = self.stats.clone();
1535 let res = match &mut self.inner {
1536 ConsumerKind::Plain(plain) => plain.poll_read_frame(waiter, &stats),
1537 ConsumerKind::Spliced(spliced) => {
1538 let res = ready!(spliced.poll_read_frame(waiter))?;
1539 if let Some(frame) = &res {
1540 stats.frames(1);
1541 stats.bytes(frame.payload.len() as u64);
1542 }
1543 Poll::Ready(Ok(res))
1544 }
1545 };
1546 match res.is_pending().then(|| self.poll_expired_if_blocked(waiter)).flatten() {
1547 Some(true) => Poll::Ready(Err(Error::Old)),
1548 Some(false) => Poll::Ready(Ok(None)),
1549 None => res,
1550 }
1551 }
1552
1553 pub async fn read_frame(&mut self) -> Result<Option<frame::Frame>> {
1555 if !self.expired
1558 && let ConsumerKind::Plain(plain) = &mut self.inner
1559 {
1560 if !plain.capped()
1562 && let Some(frame) = plain.prefetch.pop()
1563 {
1564 plain.refresh_if_stale();
1565 plain.index += 1;
1566 return Ok(Some(frame));
1567 }
1568 }
1569 kio::wait(|waiter| self.poll_read_frame(waiter)).await
1570 }
1571
1572 pub fn poll_read_frames<const N: usize>(
1578 &mut self,
1579 waiter: &kio::Waiter,
1580 out: &mut frame::Buffer<N>,
1581 ) -> Poll<Result<usize>> {
1582 out.clear();
1583 if out.capacity() == 0 {
1584 return Poll::Ready(Ok(0));
1585 }
1586
1587 while !out.is_full() {
1588 match self.poll_read_frame(waiter) {
1589 Poll::Ready(Ok(Some(frame))) => out.push(frame).expect("buffer capacity checked"),
1590 Poll::Ready(Ok(None)) => break,
1591 Poll::Ready(Err(err)) => {
1592 if out.is_empty() {
1593 return Poll::Ready(Err(err));
1594 }
1595 break;
1596 }
1597 Poll::Pending if !out.is_empty() => break,
1598 Poll::Pending => return Poll::Pending,
1599 }
1600 }
1601
1602 Poll::Ready(Ok(out.len()))
1603 }
1604
1605 pub async fn read_frames<'a, const N: usize>(
1608 &mut self,
1609 out: &'a mut frame::Buffer<N>,
1610 ) -> Result<&'a mut [frame::Frame]> {
1611 kio::wait(|waiter| self.poll_read_frames(waiter, out)).await?;
1612 Ok(out.filled_mut())
1613 }
1614
1615 pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
1617 if self.ended {
1618 return Poll::Ready(Ok(self.index()));
1619 }
1620 if self.expired {
1621 return Poll::Ready(Err(Error::Old));
1622 }
1623 let res = match &mut self.inner {
1624 ConsumerKind::Plain(plain) => {
1625 let index = plain.index;
1626 plain
1627 .poll(waiter, |state| state.poll_end(index))
1628 .map(|res| res.map(|()| index as u64))
1629 }
1630 ConsumerKind::Spliced(spliced) => spliced.poll_finished(waiter),
1631 };
1632 match res.is_pending().then(|| self.poll_expired_if_blocked(waiter)).flatten() {
1633 Some(true) => Poll::Ready(Err(Error::Old)),
1634 Some(false) => Poll::Ready(Ok(self.index())),
1636 None => res,
1637 }
1638 }
1639
1640 pub async fn finished(&mut self) -> Result<u64> {
1647 kio::wait(|waiter| self.poll_finished(waiter)).await
1648 }
1649}
1650
1651impl Plain {
1652 fn expiry_pending(&self) -> bool {
1654 if self.capped() {
1655 return false;
1656 }
1657
1658 let state = self.state.read();
1659 state.abort.is_none() && state.fin.is_none_or(|fin| self.index < fin)
1660 }
1661
1662 fn unread_content(&self) -> stats::Content {
1664 let prefetched = self.prefetch.buffered().0 as usize;
1665 let start = self.index.saturating_add(prefetched);
1666 let end = self.end.unwrap_or(usize::MAX);
1667 self.state.read().content_range(start, end)
1668 }
1669
1670 fn refresh_if_stale(&mut self) {
1672 self.access.touch();
1673 let tick = self.cache.pool().now();
1674 if tick != self.refreshed {
1675 self.state.read().charge.refresh();
1676 self.refreshed = tick;
1677 }
1678 }
1679
1680 fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
1682 where
1683 F: Fn(&kio::Ref<'_, GroupState>) -> Poll<Result<R>>,
1684 {
1685 Poll::Ready(match ready!(self.state.poll(waiter, f)) {
1686 Ok(res) => res,
1687 Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
1689 })
1690 }
1691
1692 fn capped(&self) -> bool {
1694 self.end.is_some_and(|end| self.index >= end)
1695 }
1696
1697 fn start_at(&mut self, index: u64) {
1698 let index = usize::try_from(index).unwrap_or(usize::MAX);
1699 let index = index.max(self.state.read().offset);
1700 if index <= self.index {
1701 return;
1702 }
1703 self.index = index;
1704 self.prefetch = Prefetch::default();
1706 }
1707
1708 fn skip_to(&mut self, index: u64) {
1709 let index = usize::try_from(index).unwrap_or(usize::MAX);
1710 if index <= self.index {
1711 return;
1712 }
1713 self.index = index;
1714 self.prefetch = Prefetch::default();
1715 }
1716
1717 fn poll_next_frame(
1718 &mut self,
1719 waiter: &kio::Waiter,
1720 stats: &stats::Meter,
1721 expiry: Option<frame::Expiry>,
1722 ) -> Poll<Result<Option<frame::Consumer>>> {
1723 if self.capped() {
1724 return Poll::Ready(Ok(None));
1725 }
1726 let end = self.end.unwrap_or(usize::MAX);
1727
1728 if let Some(frame) = self.prefetch.pop() {
1732 self.refresh_if_stale();
1733 self.index += 1;
1734 let tail = self.index.saturating_add(self.prefetch.buffered().0 as usize)..end;
1735 let info = frame::Info {
1736 size: frame.payload.len() as u64,
1737 timestamp: frame.timestamp,
1738 };
1739 let source = frame::Source::Complete(frame.payload);
1740 let frame = frame::Consumer::new(self.state.clone(), info, source);
1741 return Poll::Ready(Ok(Some(match expiry {
1742 Some(expiry) => frame.with_expiry(expiry.for_frame(tail, false)),
1743 None => frame,
1744 })));
1745 }
1746
1747 let index = self.index;
1748 let Some((info, source)) = ready!(self.poll(waiter, |state| state.poll_frame_source(index))?) else {
1749 return Poll::Ready(Ok(None));
1750 };
1751
1752 self.index += 1;
1753 stats.frames(1);
1756 let frame = frame::Consumer::new(self.state.clone(), info, source).with_meter(stats.clone());
1757 Poll::Ready(Ok(Some(match expiry {
1758 Some(expiry) => frame.with_expiry(expiry.for_frame(self.index..end, true)),
1759 None => frame,
1760 })))
1761 }
1762
1763 fn poll_read_frame(&mut self, waiter: &kio::Waiter, stats: &stats::Meter) -> Poll<Result<Option<frame::Frame>>> {
1764 if self.capped() {
1765 return Poll::Ready(Ok(None));
1766 }
1767
1768 if let Some(frame) = self.prefetch.pop() {
1770 self.refresh_if_stale();
1771 self.index += 1;
1772 return Poll::Ready(Ok(Some(frame)));
1773 }
1774
1775 let index = self.index;
1778 let budget = self.end.map_or(usize::MAX, |end| end.saturating_sub(index));
1781 let prefetch = &mut self.prefetch;
1782 let res = self.state.poll(waiter, |state| {
1783 if index < state.offset {
1784 return Poll::Ready(Err(Error::Lagged));
1785 }
1786 let local = (index - state.offset).min(state.frames.len());
1791 prefetch.fill(state.frames.range(local..).take(budget).cloned());
1792 if prefetch.len > 0 {
1793 state.charge.refresh();
1796 return Poll::Ready(Ok(()));
1797 }
1798 state.poll_terminal(index)
1801 });
1802
1803 match ready!(res) {
1804 Ok(Ok(())) => {}
1805 Ok(Err(err)) => return Poll::Ready(Err(err)),
1806 Err(state) => return Poll::Ready(Err(state.abort.clone().unwrap_or(Error::Dropped))),
1807 }
1808
1809 self.refreshed = self.cache.pool().now();
1811
1812 let (frames, bytes) = self.prefetch.buffered();
1815 stats.frames(frames);
1816 stats.bytes(bytes);
1817
1818 Poll::Ready(Ok(self.prefetch.pop().inspect(|_| {
1819 self.index += 1;
1820 })))
1821 }
1822}
1823
1824#[derive(Clone, Debug, Default)]
1826#[non_exhaustive]
1827pub struct Fetch {
1828 pub priority: u8,
1830
1831 pub frame_start: u64,
1842}
1843
1844impl Fetch {
1845 pub fn with_priority(mut self, priority: u8) -> Self {
1847 self.priority = priority;
1848 self
1849 }
1850
1851 pub fn with_frame_start(mut self, frame_start: u64) -> Self {
1853 self.frame_start = frame_start;
1854 self
1855 }
1856}
1857
1858pub struct Request {
1869 pub(crate) state: kio::Producer<track::TrackState>,
1870 pub(crate) fetch: kio::Shared<track::FetchState>,
1871 pub(crate) sequence: u64,
1872 pub(crate) priority: u8,
1873 pub(crate) frame_start: u64,
1874 pub(crate) result: kio::Producer<track::FetchOutcome>,
1875 pub(crate) done: bool,
1876}
1877
1878#[cfg(test)]
1879mod test {
1880 use super::*;
1881 use crate::model::test_tracing::count_drop_warnings;
1882 use bytes::Bytes;
1883 use futures::FutureExt;
1884
1885 #[test]
1888 fn one_frame_fits_the_charged_slots() {
1889 let mut frames: VecDeque<Frame> = VecDeque::new();
1890 frames.push_back(Frame {
1891 timestamp: Timestamp::ZERO,
1892 payload: Bytes::new(),
1893 });
1894 let capacity = frames.capacity();
1895 assert!(
1896 capacity <= FRAME_SLOTS,
1897 "a one-frame deque now allocates {capacity} slots"
1898 );
1899 }
1900
1901 #[test]
1902 fn basic_frame_reading() {
1903 let mut producer = Info { sequence: 0 }.produce();
1904 producer
1905 .write_frame(Timestamp::ZERO, Bytes::from_static(b"frame0"))
1906 .unwrap();
1907 producer
1908 .write_frame(Timestamp::ZERO, Bytes::from_static(b"frame1"))
1909 .unwrap();
1910 producer.finish().unwrap();
1911
1912 let mut consumer = producer.consume();
1913 let f0 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1914 assert_eq!(f0.size, 6);
1915 let f1 = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1916 assert_eq!(f1.size, 6);
1917 let end = consumer.next_frame().now_or_never().unwrap().unwrap();
1918 assert!(end.is_none());
1919 }
1920
1921 #[test]
1922 fn read_frame_all_at_once() {
1923 let mut producer = Info { sequence: 0 }.produce();
1924 producer
1925 .write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
1926 .unwrap();
1927 producer.finish().unwrap();
1928
1929 let mut consumer = producer.consume();
1930 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1931 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
1932 }
1933
1934 #[test]
1935 fn read_frame_preserves_timestamp() {
1936 let mut producer = Info { sequence: 0 }.produce();
1937 let timestamp = Timestamp::from_micros(20_000).unwrap();
1938 producer.write_frame(timestamp, Bytes::from_static(b"hello")).unwrap();
1939 producer.finish().unwrap();
1940
1941 let mut consumer = producer.consume();
1942 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1943 assert_eq!(frame.timestamp.as_micros(), 20_000);
1944 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
1945 }
1946
1947 #[test]
1948 fn chunked_frame_reads_whole() {
1949 let mut producer = Info { sequence: 0 }.produce();
1950 {
1951 let mut frame = producer
1952 .create_frame(frame::Info {
1953 size: 10,
1954 timestamp: Timestamp::ZERO,
1955 })
1956 .unwrap();
1957 frame.write(Bytes::from_static(b"hello")).unwrap();
1958 frame.write(Bytes::from_static(b"world")).unwrap();
1959 frame.finish().unwrap();
1960 }
1961 producer.finish().unwrap();
1962
1963 let mut consumer = producer.consume();
1966 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
1967 assert_eq!(frame.payload, Bytes::from_static(b"helloworld"));
1968 }
1969
1970 #[test]
1971 fn chunked_frame_streams_partial() {
1972 let mut producer = Info { sequence: 0 }.produce();
1973 let mut consumer = producer.consume();
1974
1975 let mut frame = producer
1976 .create_frame(frame::Info {
1977 size: 6,
1978 timestamp: Timestamp::ZERO,
1979 })
1980 .unwrap();
1981 frame.write(Bytes::from_static(b"foo")).unwrap();
1982
1983 let mut f = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
1985 let c1 = f.read_chunk().now_or_never().unwrap().unwrap();
1986 assert_eq!(c1, Some(Bytes::from_static(b"foo")));
1987 assert!(f.read_chunk().now_or_never().is_none());
1988
1989 frame.write(Bytes::from_static(b"bar")).unwrap();
1990 frame.finish().unwrap();
1991
1992 let c2 = f.read_chunk().now_or_never().unwrap().unwrap();
1993 assert_eq!(c2, Some(Bytes::from_static(b"bar")));
1994 let c3 = f.read_chunk().now_or_never().unwrap().unwrap();
1995 assert_eq!(c3, None);
1996 }
1997
1998 #[test]
1999 fn group_finish_returns_none() {
2000 let producer = Info { sequence: 0 }.produce();
2001 producer.finish().unwrap();
2002
2003 let mut consumer = producer.consume();
2004 let end = consumer.next_frame().now_or_never().unwrap().unwrap();
2005 assert!(end.is_none());
2006 }
2007
2008 #[test]
2009 fn abort_propagates() {
2010 let producer = Info { sequence: 0 }.produce();
2011 let mut consumer = producer.consume();
2012 producer.abort(crate::Error::Cancel).unwrap();
2013
2014 let result = consumer.next_frame().now_or_never().unwrap();
2015 assert!(matches!(result, Err(crate::Error::Cancel)));
2016 }
2017
2018 #[test]
2019 fn abort_unused_pairs_with_try_consume() {
2020 let producer = Info { sequence: 0 }.produce();
2021
2022 let consumer = producer.try_consume().expect("open");
2023 assert!(
2024 !producer.abort_unused(crate::Error::Cancel),
2025 "a reader declines the abort"
2026 );
2027 drop(consumer);
2028
2029 assert!(producer.abort_unused(crate::Error::Cancel));
2030 assert!(producer.try_consume().is_none(), "an aborted group mints no reader");
2031 }
2032
2033 #[test]
2034 fn abort_clears_cached_frames() {
2035 let mut producer = Info { sequence: 0 }.produce();
2036 producer
2037 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
2038 .unwrap();
2039
2040 let _consumer = producer.consume();
2042 assert_eq!(producer.state.read().frames.len(), 1);
2043
2044 producer.clone().abort(crate::Error::Cancel).unwrap();
2045
2046 let state = producer.state.read();
2047 assert!(state.frames.is_empty(), "cached frames should be dropped on abort");
2048 assert_eq!(state.cache, 0);
2049 }
2050
2051 #[test]
2052 fn drop_unfinished_clears_cached_frames() {
2053 let producer = Info { sequence: 0 }.produce();
2054 let mut writer = producer.clone();
2055 writer
2056 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
2057 .unwrap();
2058
2059 let mut consumer = producer.consume();
2061 assert_eq!(producer.state.read().frames.len(), 1);
2062
2063 drop(writer);
2065 drop(producer);
2066
2067 let result = consumer.next_frame().now_or_never().unwrap();
2068 assert!(matches!(result, Err(crate::Error::Dropped)));
2069 }
2070
2071 #[test]
2072 fn drop_after_abort_does_not_warn() {
2073 let warns = count_drop_warnings("group::Producer dropped without finish", || {
2074 let producer = Info { sequence: 0 }.produce();
2075 let keep = producer.clone();
2076 let mut writer = producer.clone();
2077 writer
2078 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
2079 .unwrap();
2080 let _consumer = producer.consume();
2081 writer.abort(crate::Error::Cancel).unwrap();
2082 drop(keep);
2083 });
2084 assert_eq!(warns, 0, "abort-then-drop must not emit unfinished-producer WARN");
2085 }
2086
2087 #[test]
2088 fn drop_unfinished_warns() {
2089 let warns = count_drop_warnings("group::Producer dropped without finish", || {
2090 let producer = Info { sequence: 0 }.produce();
2091 let mut writer = producer.clone();
2092 writer
2093 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
2094 .unwrap();
2095 let _consumer = producer.consume();
2096 drop(writer);
2097 drop(producer);
2098 });
2099 assert!(warns >= 1, "unfinished drop must emit unfinished-producer WARN");
2100 }
2101
2102 #[test]
2103 fn drop_finished_keeps_cached_frames() {
2104 let mut producer = Info { sequence: 0 }.produce();
2105 producer
2106 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
2107 .unwrap();
2108 producer.finish().unwrap();
2109
2110 let mut consumer = producer.consume();
2111 drop(producer);
2112
2113 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
2115 assert_eq!(frame.payload, Bytes::from_static(b"data"));
2116 }
2117
2118 #[tokio::test]
2119 async fn pending_then_ready() {
2120 let mut producer = Info { sequence: 0 }.produce();
2121 let mut consumer = producer.consume();
2122
2123 assert!(consumer.next_frame().now_or_never().is_none());
2125
2126 producer
2127 .write_frame(Timestamp::ZERO, Bytes::from_static(b"data"))
2128 .unwrap();
2129 producer.finish().unwrap();
2130
2131 let frame = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
2132 assert_eq!(frame.size, 4);
2133 }
2134
2135 #[test]
2136 fn overflow_aborts_the_group() {
2137 let mut producer = Info { sequence: 0 }.produce();
2138 let mut consumer = producer.consume();
2139
2140 let big = Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize]);
2141 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
2142 assert!(matches!(
2143 producer.write_frame(Timestamp::ZERO, big),
2144 Err(Error::GroupTooLarge)
2145 ));
2146
2147 {
2148 let state = producer.state.read();
2149 assert!(matches!(state.abort, Some(Error::GroupTooLarge)));
2150 assert!(state.frames.is_empty());
2151 assert_eq!(state.offset, 0);
2152 }
2153
2154 let result = consumer.next_frame().now_or_never().unwrap();
2155 assert!(matches!(result, Err(Error::GroupTooLarge)));
2156 }
2157
2158 #[test]
2159 fn no_overflow_under_budget() {
2160 let mut producer = Info { sequence: 0 }.produce();
2161 for _ in 0..MAX_GROUP_FRAMES {
2163 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
2164 }
2165 producer.finish().unwrap();
2166
2167 let state = producer.state.read();
2168 assert_eq!(state.offset, 0);
2169 assert_eq!(state.frames.len(), MAX_GROUP_FRAMES);
2170 assert!(state.abort.is_none());
2171 }
2172
2173 #[test]
2174 fn writer_sees_group_too_large_on_the_8193rd_frame() {
2175 let mut producer = Info { sequence: 0 }.produce();
2176 for _ in 0..MAX_GROUP_FRAMES {
2177 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
2178 }
2179 assert!(matches!(
2180 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")),
2181 Err(Error::GroupTooLarge)
2182 ));
2183 assert!(matches!(producer.state.read().abort, Some(Error::GroupTooLarge)));
2184 }
2185
2186 #[test]
2187 fn clone_consumer_independent() {
2188 let mut producer = Info { sequence: 0 }.produce();
2189 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
2190
2191 let mut c1 = producer.consume();
2192 let _ = c1.next_frame().now_or_never().unwrap().unwrap().unwrap();
2194
2195 let mut c2 = c1.clone();
2197
2198 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
2199 producer.finish().unwrap();
2200
2201 let f = c2.next_frame().now_or_never().unwrap().unwrap().unwrap();
2203 assert_eq!(f.size, 1); let end = c2.next_frame().now_or_never().unwrap().unwrap();
2206 assert!(end.is_none());
2207 }
2208
2209 fn prefetched_consumer(pool: &cache::Pool, max_age: std::time::Duration) -> (Producer, Consumer) {
2210 let cache = cache::Track::new(pool.clone(), kio::Weak::new());
2211 let track = track::Info::default().with_max_age(max_age);
2212 let mut producer = Producer::new(Info { sequence: 0 }, track, cache);
2213 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
2214 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
2215 producer.finish().unwrap();
2216
2217 let mut consumer = producer.consume();
2218 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
2219 (producer, consumer)
2220 }
2221
2222 #[test]
2223 fn prefetch_refresh_honors_pool_expiry() {
2224 let config = cache::Config::default().with_expiry(std::time::Duration::from_secs(1));
2225 let pool = cache::Pool::new(config);
2226 let (producer, mut consumer) = prefetched_consumer(&pool, std::time::Duration::MAX);
2227 let before = producer.cache_accessed();
2228
2229 crate::model::clock::advance(std::time::Duration::from_millis(600));
2230 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
2231
2232 assert!(producer.cache_accessed() > before, "the pool cadence is used");
2233 }
2234
2235 #[test]
2236 fn prefetch_refresh_honors_track_max_age() {
2237 let config = cache::Config::default().with_expiry(std::time::Duration::from_secs(30));
2238 let pool = cache::Pool::new(config);
2239 let (producer, mut consumer) = prefetched_consumer(&pool, std::time::Duration::from_secs(1));
2240 let before = producer.cache_accessed();
2241
2242 crate::model::clock::advance(std::time::Duration::from_millis(600));
2243 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
2244
2245 assert!(producer.cache_accessed() > before, "the track cadence remains in force");
2246 }
2247
2248 #[test]
2251 fn read_frame_crosses_prefetch_batches() {
2252 let n = Prefetch::CAP * 3 + 5;
2253 let mut producer = Info { sequence: 0 }.produce();
2254 for i in 0..n {
2255 producer
2256 .write_frame(Timestamp::ZERO, Bytes::from(vec![i as u8; 4]))
2257 .unwrap();
2258 }
2259 producer.finish().unwrap();
2260
2261 let mut consumer = producer.consume();
2262 for i in 0..n {
2263 let frame = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
2264 assert_eq!(frame.payload, Bytes::from(vec![i as u8; 4]));
2265 }
2266 assert!(consumer.read_frame().now_or_never().unwrap().unwrap().is_none());
2267 }
2268
2269 #[test]
2273 fn abort_after_finish_keeps_the_clean_end_for_a_drained_reader() {
2274 let mut producer = Info { sequence: 0 }.produce();
2275 producer
2276 .write_frame(Timestamp::ZERO, Bytes::from_static(b"hello"))
2277 .unwrap();
2278 producer.finish().unwrap();
2279
2280 let mut drained = producer.consume();
2281 let mut behind = producer.consume();
2282 let frame = drained.read_frame().now_or_never().unwrap().unwrap().unwrap();
2283 assert_eq!(frame.payload, Bytes::from_static(b"hello"));
2284
2285 producer.abort(Error::Old).unwrap();
2286
2287 assert!(drained.read_frame().now_or_never().unwrap().unwrap().is_none());
2289 assert!(drained.next_frame().now_or_never().unwrap().unwrap().is_none());
2290
2291 assert!(matches!(behind.read_frame().now_or_never().unwrap(), Err(Error::Old)));
2293 }
2294
2295 #[test]
2299 fn finished_answers_for_the_cursor() {
2300 let mut producer = Info { sequence: 0 }.produce();
2301 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
2302 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
2303 producer.finish().unwrap();
2304
2305 let mut drained = producer.consume();
2306 let mut behind = producer.consume();
2307 while drained.read_frame().now_or_never().unwrap().unwrap().is_some() {}
2308 behind.read_frame().now_or_never().unwrap().unwrap().unwrap();
2309
2310 producer.abort(Error::Old).unwrap();
2311
2312 assert_eq!(drained.finished().now_or_never().unwrap().unwrap(), 2);
2313 assert!(matches!(behind.finished().now_or_never().unwrap(), Err(Error::Old)));
2314 assert_eq!(behind.frame_count(), 2);
2315 }
2316
2317 #[test]
2320 fn finished_reports_a_group_too_large() {
2321 let mut producer = Info { sequence: 0 }.produce();
2322 let mut consumer = producer.consume();
2323
2324 let big = Bytes::from(vec![0u8; MAX_CACHE_BYTES as usize]);
2325 producer.write_frame(Timestamp::ZERO, big.clone()).unwrap();
2326 assert!(matches!(
2327 producer.write_frame(Timestamp::ZERO, big),
2328 Err(Error::GroupTooLarge)
2329 ));
2330
2331 assert!(matches!(
2332 consumer.finished().now_or_never().unwrap(),
2333 Err(Error::GroupTooLarge)
2334 ));
2335 }
2336
2337 #[test]
2339 fn interleave_read_and_next_frame() {
2340 let mut producer = Info { sequence: 0 }.produce();
2341 for i in 0..5u8 {
2342 producer.write_frame(Timestamp::ZERO, Bytes::from(vec![i; 1])).unwrap();
2343 }
2344 producer.finish().unwrap();
2345
2346 let mut consumer = producer.consume();
2347 let f0 = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
2349 assert_eq!(f0.payload, Bytes::from(vec![0u8; 1]));
2350
2351 for i in 1..5u8 {
2353 let mut f = consumer.next_frame().now_or_never().unwrap().unwrap().unwrap();
2354 let data = f.read_all().now_or_never().unwrap().unwrap();
2355 assert_eq!(data, Bytes::from(vec![i; 1]));
2356 }
2357 assert!(consumer.next_frame().now_or_never().unwrap().unwrap().is_none());
2358 }
2359
2360 #[test]
2363 fn read_frame_past_cleared_frames_does_not_panic() {
2364 let mut producer = Info { sequence: 0 }.produce();
2365 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
2366 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"b")).unwrap();
2367
2368 let mut consumer = producer.consume();
2369 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
2370 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
2371
2372 producer.abort(Error::Cancel).unwrap();
2375
2376 let result = consumer.read_frame().now_or_never().unwrap();
2377 assert!(matches!(result, Err(Error::Cancel)), "expected Cancel, got {result:?}");
2378 }
2379
2380 #[test]
2383 fn drop_with_partial_batch() {
2384 let mut producer = Info { sequence: 0 }.produce();
2385 for _ in 0..Prefetch::CAP {
2386 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
2387 }
2388 producer.finish().unwrap();
2389
2390 let mut consumer = producer.consume();
2391 let _ = consumer.read_frame().now_or_never().unwrap().unwrap().unwrap();
2393 drop(consumer);
2394 }
2395
2396 #[tokio::test]
2401 async fn chunk_write_wakes_parked_reader() {
2402 let mut producer = Info { sequence: 0 }.produce();
2403 let mut consumer = producer.consume();
2404 let mut frame = producer
2405 .create_frame(frame::Info {
2406 size: 6,
2407 timestamp: Timestamp::ZERO,
2408 })
2409 .unwrap();
2410 let mut f = consumer.next_frame().await.unwrap().unwrap();
2411 let handle = tokio::spawn(async move { f.read_chunk().await });
2412 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
2414 frame.write(Bytes::from_static(b"foo")).unwrap();
2415 let chunk = tokio::time::timeout(std::time::Duration::from_secs(2), handle)
2416 .await
2417 .expect("parked chunk reader was never woken by the chunk write")
2418 .unwrap()
2419 .unwrap();
2420 assert_eq!(chunk, Some(Bytes::from_static(b"foo")));
2421 }
2422
2423 #[test]
2426 fn create_frame_converts_mismatched_scale() {
2427 use crate::{Timescale, Timestamp};
2428
2429 let mut producer = Producer::new(
2430 Info { sequence: 0 },
2431 track::Info::default().with_timescale(Timescale::MICRO),
2432 Default::default(),
2433 );
2434 let frame = frame::Info {
2435 size: 3,
2436 timestamp: Timestamp::from_millis(1).unwrap(), };
2438 let writer = producer.create_frame(frame).unwrap();
2439 assert_eq!(writer.timestamp.scale(), Timescale::MICRO);
2440 assert_eq!(writer.timestamp.value(), 1000);
2441 }
2442
2443 #[tokio::test]
2445 async fn create_frame_converts_current_timestamp() {
2446 use crate::Timescale;
2447
2448 let mut producer = Producer::new(
2449 Info { sequence: 0 },
2450 track::Info::default().with_timescale(Timescale::MICRO),
2451 Default::default(),
2452 );
2453 let writer = producer
2454 .create_frame(frame::Info {
2455 size: 3,
2456 timestamp: Timestamp::now(),
2457 })
2458 .unwrap();
2459 assert_eq!(writer.timestamp.scale(), Timescale::MICRO);
2460 assert!(!writer.timestamp.is_zero(), "local clock should be non-zero");
2461 }
2462
2463 #[test]
2466 fn start_at_starts_the_group_later() {
2467 let mut producer = Info { sequence: 0 }.produce();
2468 producer.start_at(3).unwrap();
2469 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"d")).unwrap();
2470 producer.finish().unwrap();
2471
2472 assert_eq!(producer.frame_count(), 4);
2474
2475 let mut consumer = producer.consume();
2476 assert_eq!(consumer.frame_count(), 4);
2477
2478 assert!(matches!(
2481 consumer.finished().now_or_never().unwrap(),
2482 Err(Error::Lagged)
2483 ));
2484 assert!(matches!(
2485 consumer.read_frame().now_or_never().unwrap(),
2486 Err(Error::Lagged)
2487 ));
2488 }
2489
2490 #[test]
2493 fn start_at_clamps_up_to_the_first_frame() {
2494 let mut producer = Info { sequence: 0 }.produce();
2495 producer.start_at(3).unwrap();
2496 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"d")).unwrap();
2497 producer.finish().unwrap();
2498
2499 let mut consumer = producer.consume();
2500 consumer.start_at(1);
2501 assert_eq!(consumer.index(), 3, "clamped up to the first frame that exists");
2502 assert_eq!(
2503 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap().payload,
2504 Bytes::from_static(b"d")
2505 );
2506 }
2507
2508 #[test]
2511 fn end_at_caps_and_reopens() {
2512 let mut producer = Info { sequence: 0 }.produce();
2513 for i in 0..4u8 {
2514 producer.write_frame(Timestamp::ZERO, Bytes::from(vec![i])).unwrap();
2515 }
2516 producer.finish().unwrap();
2517
2518 let mut consumer = producer.consume();
2519 consumer.set_frames(..2);
2520 assert_eq!(
2521 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
2522 0
2523 );
2524 assert_eq!(
2525 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
2526 1
2527 );
2528 assert!(
2529 consumer.read_frame().now_or_never().unwrap().unwrap().is_none(),
2530 "capped reads end cleanly"
2531 );
2532
2533 consumer.set_frames(..);
2534 assert_eq!(
2535 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
2536 2
2537 );
2538 }
2539
2540 #[test]
2541 fn frame_ranges_preserve_progress_and_make_inclusion_explicit() {
2542 let mut producer = Info { sequence: 0 }.produce();
2543 for i in 0..4u8 {
2544 producer.write_frame(Timestamp::ZERO, Bytes::from(vec![i])).unwrap();
2545 }
2546 producer.finish().unwrap();
2547 let mut consumer = producer.consume();
2548 consumer.set_frames(1..=1);
2549 assert_eq!(
2550 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
2551 1
2552 );
2553 assert!(consumer.read_frame().now_or_never().unwrap().unwrap().is_none());
2554 consumer.set_frames(..3);
2555 assert_eq!(
2556 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
2557 2
2558 );
2559 assert!(consumer.read_frame().now_or_never().unwrap().unwrap().is_none());
2560 consumer.set_frames(0..=3);
2561 assert_eq!(
2562 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
2563 3
2564 );
2565 }
2566
2567 #[test]
2570 fn end_at_zero_is_empty() {
2571 let mut producer = Info { sequence: 0 }.produce();
2572 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"x")).unwrap();
2573 producer.finish().unwrap();
2574
2575 let mut consumer = producer.consume();
2576 consumer.set_frames(..0);
2577 assert!(
2578 consumer.read_frame().now_or_never().unwrap().unwrap().is_none(),
2579 "empty cap delivers nothing"
2580 );
2581
2582 consumer.set_frames(..1);
2583 assert_eq!(
2584 consumer.read_frame().now_or_never().unwrap().unwrap().unwrap().payload,
2585 Bytes::from_static(b"x")
2586 );
2587 }
2588
2589 #[test]
2591 fn start_at_rejected_after_a_frame() {
2592 let mut producer = Info { sequence: 0 }.produce();
2593 producer.start_at(2).unwrap();
2595 producer.start_at(3).unwrap();
2596
2597 producer.write_frame(Timestamp::ZERO, Bytes::from_static(b"a")).unwrap();
2598 assert!(matches!(producer.start_at(4), Err(Error::Closed)));
2599 assert_eq!(producer.frame_count(), 4, "the frame landed at index 3");
2600
2601 let mut producer = Info { sequence: 1 }.produce();
2603 producer.finish().unwrap();
2604 assert!(matches!(producer.start_at(1), Err(Error::Closed)));
2605 }
2606
2607 #[test]
2609 fn start_at_rejects_the_largest_index() {
2610 let mut producer = Info { sequence: 0 }.produce();
2611 assert!(matches!(
2612 producer.start_at(usize::MAX as u64),
2613 Err(Error::BoundsExceeded(_))
2614 ));
2615 }
2616
2617 #[test]
2619 fn create_frame_rejects_oversized() {
2620 let mut producer = Info { sequence: 0 }.produce();
2621 let result = producer.create_frame(frame::Info {
2622 size: MAX_CACHE_BYTES + 1,
2623 timestamp: Timestamp::ZERO,
2624 });
2625 assert!(matches!(result, Err(Error::FrameTooLarge)));
2626 }
2627
2628 fn sized(size: u64) -> frame::Info {
2629 frame::Info {
2630 size,
2631 timestamp: Timestamp::ZERO,
2632 }
2633 }
2634
2635 #[test]
2639 fn frame_is_charged_as_written() {
2640 let pool = cache::Pool::unbounded();
2641 let cache = cache::Track::new(pool.clone(), kio::Weak::new());
2642 let mut producer = Producer::new(Info { sequence: 0 }, track::Info::default(), cache);
2643 let before = pool.used();
2644
2645 let mut frame = producer
2646 .create_frame_owned(sized(MAX_CACHE_BYTES), &frame::Budget::default())
2647 .unwrap();
2648 assert_eq!(pool.used(), before, "the declared size was charged");
2649
2650 frame.write(Bytes::from(vec![0u8; 100])).unwrap();
2651 assert_eq!(pool.used(), before, "charged before the write was published");
2652 frame.notify();
2653 assert_eq!(pool.used(), before + 100);
2654 assert_eq!(producer.state.read().cache, 100);
2655 assert_eq!(producer.state.read().content().bytes, MAX_CACHE_BYTES);
2657
2658 frame.abort(Error::Cancel).unwrap();
2660 assert_eq!(producer.state.read().cache, 0);
2661 assert!(pool.used() <= before);
2662 }
2663
2664 #[test]
2666 fn frame_commit_charges_the_tail() {
2667 let pool = cache::Pool::unbounded();
2668 let cache = cache::Track::new(pool.clone(), kio::Weak::new());
2669 let mut producer = Producer::new(Info { sequence: 0 }, track::Info::default(), cache);
2670 let before = pool.used();
2671
2672 let mut frame = producer
2673 .create_frame_owned(sized(300), &frame::Budget::default())
2674 .unwrap();
2675 frame.write(Bytes::from(vec![0u8; 100])).unwrap();
2676 frame.notify();
2677 frame.write(Bytes::from(vec![0u8; 200])).unwrap();
2678 frame.finish().unwrap();
2679 assert_eq!(pool.used(), before + 300);
2680 assert_eq!(producer.state.read().cache, 300);
2681 }
2682
2683 #[test]
2687 fn frame_budget_bounds_upfront_allocation() {
2688 let budget = frame::Budget::new(1024);
2689 let mut a = Info { sequence: 0 }.produce();
2690 let mut b = Info { sequence: 1 }.produce();
2691
2692 let mut first = a.create_frame_owned(sized(1024), &budget).unwrap();
2693 first.write(Bytes::from_static(b"x")).unwrap();
2694 assert_eq!(first.allocated(), 1024, "a frame within budget is allocated up front");
2695
2696 let mut second = b.create_frame_owned(sized(MAX_CACHE_BYTES), &budget).unwrap();
2697 second.write(Bytes::from(vec![0u8; 100])).unwrap();
2698 assert_eq!(
2699 second.allocated(),
2700 100,
2701 "a frame past budget allocated its declared size"
2702 );
2703 second.abort(Error::Cancel).unwrap();
2704
2705 first.write(Bytes::from(vec![0u8; 1023])).unwrap();
2706 first.finish().unwrap();
2707 a.finish().unwrap();
2708
2709 let mut a = Info { sequence: 2 }.produce();
2710 let mut third = a.create_frame_owned(sized(1024), &budget).unwrap();
2711 third.write(Bytes::from_static(b"x")).unwrap();
2712 assert_eq!(third.allocated(), 1024, "the finished frame did not return its share");
2713 }
2714}