Skip to main content

moq_net/model/
track.rs

1//! A track is a collection of semi-reliable and semi-ordered streams, split into a [TrackProducer] and [TrackConsumer] handle.
2//!
3//! A [TrackProducer] creates streams with a sequence number and priority.
4//! The sequence number is used to determine the order of streams, while the priority is used to determine which stream to transmit first.
5//! This may seem counter-intuitive, but is designed for live streaming where the newest streams may be higher priority.
6//! A cloned [TrackProducer] can be used to create streams in parallel, but will error if a duplicate sequence number is used.
7//!
8//! A [TrackConsumer] may not receive all streams in order or at all.
9//! These streams are meant to be transmitted over congested networks and the key to MoQ Transport is to not block on them.
10//! Streams will be cached for a potentially limited duration added to the unreliable nature.
11//! A cloned [TrackConsumer] will receive a copy of all new streams going forward (fanout).
12//!
13//! The track is closed with [Error] when all writers or readers are dropped.
14
15use crate::{Error, Result, coding};
16
17use super::{Group, GroupConsumer, GroupProducer};
18
19use std::{
20	collections::{HashSet, VecDeque},
21	task::{Poll, ready},
22	time::Duration,
23};
24
25/// Groups older than this are evicted from the track cache (unless they are the max_sequence group).
26// TODO: Replace with a configurable cache size.
27const MAX_GROUP_AGE: Duration = Duration::from_secs(5);
28
29/// A track is a collection of groups, delivered out-of-order until expired.
30#[derive(Clone, Debug, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Track {
33	/// Identifier within a broadcast. Unique per [`crate::Broadcast`].
34	pub name: String,
35	/// Delivery priority. Higher values preempt lower ones when bandwidth is constrained.
36	pub priority: u8,
37}
38
39impl Track {
40	/// Create a track with the given name and default priority (`0`).
41	pub fn new<T: Into<String>>(name: T) -> Self {
42		Self {
43			name: name.into(),
44			priority: 0,
45		}
46	}
47
48	/// Set the delivery priority, returning `self` for chaining.
49	pub fn with_priority(mut self, priority: u8) -> Self {
50		self.priority = priority;
51		self
52	}
53
54	/// Consume this [`Track`] to create a producer that owns its metadata.
55	pub fn produce(self) -> TrackProducer {
56		TrackProducer::new(self)
57	}
58}
59
60#[derive(Default)]
61struct State {
62	/// Groups in arrival order. `None` entries are tombstones for evicted groups.
63	groups: VecDeque<Option<(GroupProducer, web_async::time::Instant)>>,
64	duplicates: HashSet<u64>,
65	offset: usize,
66	max_sequence: Option<u64>,
67	final_sequence: Option<u64>,
68	abort: Option<Error>,
69}
70
71impl State {
72	/// Find the next non-tombstoned group at or after `index` in arrival order.
73	///
74	/// Returns the group and its absolute index so the consumer can advance past it.
75	fn poll_recv_group(&self, index: usize, min_sequence: u64) -> Poll<Result<Option<(GroupConsumer, usize)>>> {
76		let start = index.saturating_sub(self.offset);
77		for (i, slot) in self.groups.iter().enumerate().skip(start) {
78			if let Some((group, _)) = slot
79				&& group.sequence >= min_sequence
80			{
81				return Poll::Ready(Ok(Some((group.consume(), self.offset + i))));
82			}
83		}
84
85		// TODO once we have drop notifications, check if index == final_sequence.
86		if self.final_sequence.is_some() {
87			Poll::Ready(Ok(None))
88		} else if let Some(err) = &self.abort {
89			Poll::Ready(Err(err.clone()))
90		} else {
91			Poll::Pending
92		}
93	}
94
95	/// Scan groups at or after `index` in arrival order, looking for the first with sequence
96	/// `>= next_sequence` that has a fully-buffered next frame. Returns the frame plus the
97	/// winning slot's absolute index and sequence so the consumer can advance past it.
98	fn poll_read_frame(
99		&self,
100		index: usize,
101		next_sequence: u64,
102		waiter: &kio::Waiter,
103	) -> Poll<Result<Option<(bytes::Bytes, usize, u64)>>> {
104		let start = index.saturating_sub(self.offset);
105		let mut pending_seen = false;
106		for (i, slot) in self.groups.iter().enumerate().skip(start) {
107			let Some((group, _)) = slot else { continue };
108			if group.sequence < next_sequence {
109				continue;
110			}
111
112			let mut consumer = group.consume();
113			match consumer.poll_read_frame(waiter) {
114				Poll::Ready(Ok(Some(frame))) => {
115					return Poll::Ready(Ok(Some((frame, self.offset + i, group.sequence))));
116				}
117				Poll::Ready(Ok(None)) => continue,
118				Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
119				Poll::Pending => {
120					pending_seen = true;
121					continue;
122				}
123			}
124		}
125
126		// A pending group can still produce a frame even after finish() — finish only
127		// blocks new groups at/above final_sequence, not frames on existing groups.
128		if pending_seen {
129			Poll::Pending
130		} else if self.final_sequence.is_some() {
131			Poll::Ready(Ok(None))
132		} else if let Some(err) = &self.abort {
133			Poll::Ready(Err(err.clone()))
134		} else {
135			Poll::Pending
136		}
137	}
138
139	fn poll_get_group(&self, sequence: u64) -> Poll<Result<Option<GroupConsumer>>> {
140		// Search for the group with the matching sequence, skipping tombstones.
141		for (group, _) in self.groups.iter().flatten() {
142			if group.sequence == sequence {
143				return Poll::Ready(Ok(Some(group.consume())));
144			}
145		}
146
147		// Once final_sequence is set, groups at or past it can never exist.
148		if let Some(fin) = self.final_sequence
149			&& sequence >= fin
150		{
151			return Poll::Ready(Ok(None));
152		}
153
154		if let Some(err) = &self.abort {
155			return Poll::Ready(Err(err.clone()));
156		}
157
158		Poll::Pending
159	}
160
161	fn poll_closed(&self) -> Poll<Result<()>> {
162		if self.final_sequence.is_some() {
163			Poll::Ready(Ok(()))
164		} else if let Some(err) = &self.abort {
165			Poll::Ready(Err(err.clone()))
166		} else {
167			Poll::Pending
168		}
169	}
170
171	/// Evict groups older than MAX_GROUP_AGE, never evicting the max_sequence group.
172	///
173	/// Groups are in arrival order, so we can stop early when we hit a non-expired,
174	/// non-max_sequence group (everything after it arrived even later).
175	/// When max_sequence is at the front, we skip past it and tombstone expired groups
176	/// behind it.
177	fn evict_expired(&mut self, now: web_async::time::Instant) {
178		for slot in self.groups.iter_mut() {
179			let Some((group, created_at)) = slot else { continue };
180
181			if Some(group.sequence) == self.max_sequence {
182				continue;
183			}
184
185			if now.duration_since(*created_at) <= MAX_GROUP_AGE {
186				break;
187			}
188
189			self.duplicates.remove(&group.sequence);
190			*slot = None;
191		}
192
193		// Trim leading tombstones to advance the offset.
194		while let Some(None) = self.groups.front() {
195			self.groups.pop_front();
196			self.offset += 1;
197		}
198	}
199
200	fn poll_finished(&self) -> Poll<Result<u64>> {
201		if let Some(fin) = self.final_sequence {
202			Poll::Ready(Ok(fin))
203		} else if let Some(err) = &self.abort {
204			Poll::Ready(Err(err.clone()))
205		} else {
206			Poll::Pending
207		}
208	}
209}
210
211/// A producer for a track, used to create new groups.
212pub struct TrackProducer {
213	info: Track,
214	state: kio::Producer<State>,
215}
216
217impl std::ops::Deref for TrackProducer {
218	type Target = Track;
219
220	fn deref(&self) -> &Self::Target {
221		&self.info
222	}
223}
224
225impl TrackProducer {
226	/// Build a producer for the given track metadata. Prefer [`Track::produce`].
227	pub fn new(info: Track) -> Self {
228		Self {
229			info,
230			state: kio::Producer::default(),
231		}
232	}
233
234	/// Create a new group with the given sequence number.
235	pub fn create_group(&mut self, info: Group) -> Result<GroupProducer> {
236		let group = info.produce();
237
238		let mut state = self.modify()?;
239		if let Some(fin) = state.final_sequence
240			&& group.sequence >= fin
241		{
242			return Err(Error::Closed);
243		}
244
245		if !state.duplicates.insert(group.sequence) {
246			return Err(Error::Duplicate);
247		}
248
249		let now = web_async::time::Instant::now();
250		state.max_sequence = Some(state.max_sequence.unwrap_or(0).max(group.sequence));
251		state.groups.push_back(Some((group.clone(), now)));
252		state.evict_expired(now);
253
254		Ok(group)
255	}
256
257	/// Create a new group with the next sequence number.
258	pub fn append_group(&mut self) -> Result<GroupProducer> {
259		let mut state = self.modify()?;
260		let sequence = match state.max_sequence {
261			Some(s) => s.checked_add(1).ok_or(coding::BoundsExceeded)?,
262			None => 0,
263		};
264		if let Some(fin) = state.final_sequence
265			&& sequence >= fin
266		{
267			return Err(Error::Closed);
268		}
269
270		let group = Group { sequence }.produce();
271
272		let now = web_async::time::Instant::now();
273		state.duplicates.insert(sequence);
274		state.max_sequence = Some(sequence);
275		state.groups.push_back(Some((group.clone(), now)));
276		state.evict_expired(now);
277
278		Ok(group)
279	}
280
281	/// Create a group with a single frame.
282	pub fn write_frame<B: Into<bytes::Bytes>>(&mut self, frame: B) -> Result<()> {
283		let mut group = self.append_group()?;
284		group.write_frame(frame.into())?;
285		group.finish()?;
286		Ok(())
287	}
288
289	/// Mark the track as finished after the last appended group.
290	///
291	/// Sets the final sequence to one past the current max_sequence.
292	/// No new groups at or above this sequence can be appended.
293	/// NOTE: Old groups with lower sequence numbers can still arrive.
294	pub fn finish(&mut self) -> Result<()> {
295		let mut state = self.modify()?;
296		if state.final_sequence.is_some() {
297			return Err(Error::Closed);
298		}
299		state.final_sequence = Some(match state.max_sequence {
300			Some(max) => max.checked_add(1).ok_or(coding::BoundsExceeded)?,
301			None => 0,
302		});
303		Ok(())
304	}
305
306	/// Mark the track as finished at an exact final sequence.
307	///
308	/// The caller must pass the current max_sequence exactly.
309	/// Freezes the final boundary at one past the current max_sequence.
310	/// No new groups at or above that sequence can be created.
311	/// NOTE: Old groups with lower sequence numbers can still arrive.
312	pub fn finish_at(&mut self, sequence: u64) -> Result<()> {
313		let mut state = self.modify()?;
314		let max = state.max_sequence.ok_or(Error::Closed)?;
315		if state.final_sequence.is_some() || sequence != max {
316			return Err(Error::Closed);
317		}
318		state.final_sequence = Some(max.checked_add(1).ok_or(coding::BoundsExceeded)?);
319		Ok(())
320	}
321
322	/// Abort the track with the given error.
323	///
324	/// Drops the cached groups so a stale [`TrackConsumer`] can't pin them (and
325	/// their frame buffers) in memory forever. Consumers that haven't drained yet
326	/// surface the abort error instead of the leftover cache. Child groups are
327	/// independent: a consumer that already pulled a [`GroupConsumer`] keeps its
328	/// own handle and can finish reading it.
329	pub fn abort(&mut self, err: Error) -> Result<()> {
330		let mut guard = self.modify()?;
331		guard.abort = Some(err);
332		guard.groups.clear();
333		guard.duplicates.clear();
334		guard.close();
335		Ok(())
336	}
337
338	/// Create a new consumer for the track, starting at the beginning.
339	pub fn consume(&self) -> TrackConsumer {
340		TrackConsumer {
341			info: self.info.clone(),
342			state: self.state.consume(),
343			index: 0,
344			min_sequence: 0,
345			next_sequence: 0,
346		}
347	}
348
349	/// The track name.
350	pub fn name(&self) -> &str {
351		&self.info.name
352	}
353
354	/// A cloneable watch-only handle to subscriber demand.
355	pub fn demand(&self) -> TrackDemand {
356		TrackDemand {
357			name: self.info.name.clone(),
358			state: self.state.weak(),
359		}
360	}
361
362	/// Block until there are no active consumers.
363	pub async fn unused(&self) -> Result<()> {
364		self.state
365			.unused()
366			.await
367			.map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
368	}
369
370	/// Block until there is at least one active consumer.
371	pub async fn used(&self) -> Result<()> {
372		self.state
373			.used()
374			.await
375			.map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
376	}
377
378	/// Block until the track is closed or aborted, returning the cause.
379	pub async fn closed(&self) -> Error {
380		self.state.closed().await;
381		self.state.read().abort.clone().unwrap_or(Error::Dropped)
382	}
383
384	/// Return true if the track has been closed.
385	pub fn is_closed(&self) -> bool {
386		self.state.read().is_closed()
387	}
388
389	/// Return true if this is the same track.
390	pub fn is_clone(&self, other: &Self) -> bool {
391		self.state.same_channel(&other.state)
392	}
393
394	/// Create a weak reference that doesn't prevent auto-close.
395	pub(crate) fn weak(&self) -> TrackWeak {
396		TrackWeak {
397			info: self.info.clone(),
398			state: self.state.weak(),
399		}
400	}
401
402	fn modify(&self) -> Result<kio::Mut<'_, State>> {
403		self.state
404			.write()
405			.map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
406	}
407}
408
409impl Clone for TrackProducer {
410	fn clone(&self) -> Self {
411		Self {
412			info: self.info.clone(),
413			state: self.state.clone(),
414		}
415	}
416}
417
418impl Drop for TrackProducer {
419	fn drop(&mut self) {
420		// The last producer going away without finishing is an abrupt teardown:
421		// release the cached groups so a stale consumer can't pin them (and their
422		// frame buffers) forever, the same as an explicit abort. A cleanly
423		// finished track keeps its cache so consumers can still drain it.
424		if !self.state.is_last() {
425			return;
426		}
427		if let Ok(mut state) = self.state.write()
428			&& state.final_sequence.is_none()
429		{
430			state.groups.clear();
431			state.duplicates.clear();
432		}
433	}
434}
435
436impl From<Track> for TrackProducer {
437	fn from(info: Track) -> Self {
438		TrackProducer::new(info)
439	}
440}
441
442/// A weak reference to a track that doesn't prevent auto-close.
443#[derive(Clone)]
444pub(crate) struct TrackWeak {
445	pub(crate) info: Track,
446	state: kio::Weak<State>,
447}
448
449/// A cloneable, watch-only handle to a track's subscriber demand.
450#[derive(Clone)]
451pub struct TrackDemand {
452	name: String,
453	state: kio::Weak<State>,
454}
455
456impl TrackDemand {
457	/// The track name this handle is bound to.
458	pub fn name(&self) -> &str {
459		&self.name
460	}
461
462	/// Block until there is at least one active consumer.
463	pub async fn used(&self) -> Result<()> {
464		self.state
465			.used()
466			.await
467			.map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
468	}
469
470	/// Block until there are no active consumers.
471	pub async fn unused(&self) -> Result<()> {
472		self.state
473			.unused()
474			.await
475			.map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
476	}
477
478	/// Block until the track is closed or aborted, returning the cause.
479	pub async fn closed(&self) -> Error {
480		if let Some(state) = self.state.produce() {
481			state.closed().await;
482		}
483
484		self.state.read().abort.clone().unwrap_or(Error::Dropped)
485	}
486}
487
488impl TrackWeak {
489	pub fn is_closed(&self) -> bool {
490		self.state.is_closed()
491	}
492
493	pub fn consume(&self) -> TrackConsumer {
494		TrackConsumer {
495			info: self.info.clone(),
496			state: self.state.consume(),
497			index: 0,
498			min_sequence: 0,
499			next_sequence: 0,
500		}
501	}
502
503	pub async fn unused(&self) -> crate::Result<()> {
504		self.state
505			.unused()
506			.await
507			.map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
508	}
509
510	pub fn is_clone(&self, other: &Self) -> bool {
511		self.state.same_channel(&other.state)
512	}
513}
514
515/// A consumer for a track, used to read groups.
516#[derive(Clone)]
517pub struct TrackConsumer {
518	info: Track,
519	state: kio::Consumer<State>,
520	/// Arrival-order cursor used by [`Self::recv_group`].
521	index: usize,
522	/// Minimum sequence to return from any `recv` method. Set by [`Self::start_at`].
523	min_sequence: u64,
524	/// One past the highest sequence returned by [`Self::next_group`].
525	/// Used only by that method to skip late arrivals; does not affect [`Self::recv_group`].
526	next_sequence: u64,
527}
528
529impl std::ops::Deref for TrackConsumer {
530	type Target = Track;
531
532	fn deref(&self) -> &Self::Target {
533		&self.info
534	}
535}
536
537impl TrackConsumer {
538	/// The track name this handle is bound to.
539	pub fn name(&self) -> &str {
540		&self.info.name
541	}
542
543	// A helper to automatically apply Dropped if the state is closed without an error.
544	fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
545	where
546		F: Fn(&kio::Ref<'_, State>) -> Poll<Result<R>>,
547	{
548		Poll::Ready(match ready!(self.state.poll(waiter, f)) {
549			Ok(res) => res,
550			// We try to clone abort just in case the function forgot to check for terminal state.
551			Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
552		})
553	}
554
555	/// Poll for the next group in arrival order, without blocking.
556	///
557	/// Returns every group exactly once in the order it landed on the wire — which may be
558	/// out of sequence due to network reordering or loss. Use [`Self::poll_next_group`] if
559	/// you only want groups whose sequence number is higher than any previously returned.
560	///
561	/// Returns `Poll::Ready(Ok(Some(group)))` when a group is available,
562	/// `Poll::Ready(Ok(None))` when the track is finished,
563	/// `Poll::Ready(Err(e))` when the track has been aborted, or
564	/// `Poll::Pending` when no group is available yet.
565	pub fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<GroupConsumer>>> {
566		let Some((consumer, found_index)) =
567			ready!(self.poll(waiter, |state| state.poll_recv_group(self.index, self.min_sequence))?)
568		else {
569			return Poll::Ready(Ok(None));
570		};
571
572		self.index = found_index + 1;
573		Poll::Ready(Ok(Some(consumer)))
574	}
575
576	/// Receive the next group in arrival order.
577	///
578	/// Every group is returned exactly once, in the order it landed on the wire — which may
579	/// be out of sequence due to network reordering or loss. Use [`Self::next_group`] if you
580	/// only want groups whose sequence number is higher than any previously returned.
581	pub async fn recv_group(&mut self) -> Result<Option<GroupConsumer>> {
582		kio::wait(|waiter| self.poll_recv_group(waiter)).await
583	}
584
585	/// Poll for the next group with a higher sequence number than any previously returned.
586	///
587	/// Late arrivals (sequence at or below the last returned) are silently skipped, so this
588	/// produces a monotonically increasing sequence at the cost of dropping out-of-order
589	/// groups. Use [`Self::poll_recv_group`] to see every group in arrival order instead.
590	pub fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<GroupConsumer>>> {
591		loop {
592			let Some(group) = ready!(self.poll_recv_group(waiter)?) else {
593				return Poll::Ready(Ok(None));
594			};
595			if group.sequence < self.next_sequence {
596				// Late arrival; discard and keep looking.
597				continue;
598			}
599			self.next_sequence = group.sequence.saturating_add(1);
600			return Poll::Ready(Ok(Some(group)));
601		}
602	}
603
604	/// Return the next group with a higher sequence number than any previously returned.
605	///
606	/// Late arrivals (sequence at or below the last returned) are silently skipped, so this
607	/// produces a monotonically increasing sequence at the cost of dropping out-of-order
608	/// groups. Use [`Self::recv_group`] to see every group in arrival order instead.
609	pub async fn next_group(&mut self) -> Result<Option<GroupConsumer>> {
610		kio::wait(|waiter| self.poll_next_group(waiter)).await
611	}
612
613	/// A helper that calls [`Self::poll_next_group`] and returns its first frame,
614	/// skipping the rest of the group. Intended for single-frame groups (see
615	/// [`TrackProducer::write_frame`]).
616	pub fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<bytes::Bytes>>> {
617		let lower = self.min_sequence.max(self.next_sequence);
618		let Some((frame, found_index, sequence)) =
619			ready!(self.poll(waiter, |state| { state.poll_read_frame(self.index, lower, waiter) })?)
620		else {
621			return Poll::Ready(Ok(None));
622		};
623
624		self.index = found_index + 1;
625		self.next_sequence = sequence.saturating_add(1);
626		Poll::Ready(Ok(Some(frame)))
627	}
628
629	/// Read a single full frame from the next group in sequence order.
630	///
631	/// See [`Self::poll_read_frame`] for semantics.
632	pub async fn read_frame(&mut self) -> Result<Option<bytes::Bytes>> {
633		kio::wait(|waiter| self.poll_read_frame(waiter)).await
634	}
635
636	/// Poll for the group with the given sequence, without blocking.
637	pub fn poll_get_group(&self, waiter: &kio::Waiter, sequence: u64) -> Poll<Result<Option<GroupConsumer>>> {
638		self.poll(waiter, |state| state.poll_get_group(sequence))
639	}
640
641	/// Wait until the group with the given sequence becomes available.
642	///
643	/// Resolves to `Some(GroupConsumer)` once the group is in the cache.
644	/// Resolves to `None` only when `sequence` is at or past the track's
645	/// `final_sequence` (set by `finish()` / `finish_at()`), since such a
646	/// group can never be produced. Sequences below `final_sequence` still
647	/// wait, since older groups may still arrive out of order.
648	pub async fn get_group(&self, sequence: u64) -> Result<Option<GroupConsumer>> {
649		kio::wait(|waiter| self.poll_get_group(waiter, sequence)).await
650	}
651
652	/// Poll for track closure, without blocking.
653	pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
654		self.poll(waiter, |state| state.poll_closed())
655	}
656
657	/// Block until the track is closed.
658	///
659	/// Returns Ok() is the track was cleanly finished.
660	pub async fn closed(&self) -> Result<()> {
661		kio::wait(|waiter| self.poll_closed(waiter)).await
662	}
663
664	/// Whether `other` was cloned from this consumer (shares the same underlying state).
665	pub fn is_clone(&self, other: &Self) -> bool {
666		self.state.same_channel(&other.state)
667	}
668
669	/// Poll for the total number of groups in the track.
670	pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
671		self.poll(waiter, |state| state.poll_finished())
672	}
673
674	/// Block until the track is finished, returning the total number of groups.
675	pub async fn finished(&mut self) -> Result<u64> {
676		kio::wait(|waiter| self.poll_finished(waiter)).await
677	}
678
679	/// Start the consumer at the specified sequence.
680	pub fn start_at(&mut self, sequence: u64) {
681		self.min_sequence = sequence;
682	}
683
684	/// Return the latest sequence number in the track.
685	pub fn latest(&self) -> Option<u64> {
686		self.state.read().max_sequence
687	}
688
689	/// Create a weak reference that doesn't prevent auto-close.
690	pub(crate) fn weak(&self) -> TrackWeak {
691		TrackWeak {
692			info: self.info.clone(),
693			state: self.state.weak(),
694		}
695	}
696}
697
698#[cfg(test)]
699use futures::FutureExt;
700
701#[cfg(test)]
702impl TrackConsumer {
703	pub fn assert_group(&mut self) -> GroupConsumer {
704		self.recv_group()
705			.now_or_never()
706			.expect("group would have blocked")
707			.expect("would have errored")
708			.expect("track was closed")
709	}
710
711	pub fn assert_no_group(&mut self) {
712		assert!(
713			self.recv_group().now_or_never().is_none(),
714			"recv_group would not have blocked"
715		);
716	}
717
718	pub fn assert_not_closed(&self) {
719		assert!(self.closed().now_or_never().is_none(), "should not be closed");
720	}
721
722	pub fn assert_closed(&self) {
723		assert!(self.closed().now_or_never().is_some(), "should be closed");
724	}
725
726	// TODO assert specific errors after implementing PartialEq
727	pub fn assert_error(&self) {
728		assert!(
729			self.closed().now_or_never().expect("should not block").is_err(),
730			"should be error"
731		);
732	}
733
734	pub fn assert_is_clone(&self, other: &Self) {
735		assert!(self.is_clone(other), "should be clone");
736	}
737
738	pub fn assert_not_clone(&self, other: &Self) {
739		assert!(!self.is_clone(other), "should not be clone");
740	}
741}
742
743#[cfg(test)]
744mod test {
745	use super::*;
746
747	/// Helper: count non-tombstoned groups in state.
748	fn live_groups(state: &State) -> usize {
749		state.groups.iter().flatten().count()
750	}
751
752	/// Helper: get the sequence number of the first live group.
753	fn first_live_sequence(state: &State) -> u64 {
754		state.groups.iter().flatten().next().unwrap().0.sequence
755	}
756
757	#[tokio::test]
758	async fn evict_expired_groups() {
759		tokio::time::pause();
760
761		let mut producer = Track::new("test").produce();
762
763		// Create 3 groups at time 0.
764		producer.append_group().unwrap(); // seq 0
765		producer.append_group().unwrap(); // seq 1
766		producer.append_group().unwrap(); // seq 2
767
768		{
769			let state = producer.state.read();
770			assert_eq!(live_groups(&state), 3);
771			assert_eq!(state.offset, 0);
772		}
773
774		// Advance time past the eviction threshold.
775		tokio::time::advance(MAX_GROUP_AGE + Duration::from_secs(1)).await;
776
777		// Append a new group to trigger eviction.
778		producer.append_group().unwrap(); // seq 3
779
780		// Groups 0, 1, 2 are expired but seq 3 (max_sequence) is kept.
781		// Leading tombstones are trimmed, so only seq 3 remains.
782		{
783			let state = producer.state.read();
784			assert_eq!(live_groups(&state), 1);
785			assert_eq!(first_live_sequence(&state), 3);
786			assert_eq!(state.offset, 3);
787			assert!(!state.duplicates.contains(&0));
788			assert!(!state.duplicates.contains(&1));
789			assert!(!state.duplicates.contains(&2));
790			assert!(state.duplicates.contains(&3));
791		}
792	}
793
794	#[tokio::test]
795	async fn evict_keeps_max_sequence() {
796		tokio::time::pause();
797
798		let mut producer = Track::new("test").produce();
799		producer.append_group().unwrap(); // seq 0
800
801		// Advance time past threshold.
802		tokio::time::advance(MAX_GROUP_AGE + Duration::from_secs(1)).await;
803
804		// Append another group; seq 0 is expired and evicted.
805		producer.append_group().unwrap(); // seq 1
806
807		{
808			let state = producer.state.read();
809			assert_eq!(live_groups(&state), 1);
810			assert_eq!(first_live_sequence(&state), 1);
811			assert_eq!(state.offset, 1);
812		}
813	}
814
815	#[tokio::test]
816	async fn no_eviction_when_fresh() {
817		tokio::time::pause();
818
819		let mut producer = Track::new("test").produce();
820		producer.append_group().unwrap(); // seq 0
821		producer.append_group().unwrap(); // seq 1
822		producer.append_group().unwrap(); // seq 2
823
824		{
825			let state = producer.state.read();
826			assert_eq!(live_groups(&state), 3);
827			assert_eq!(state.offset, 0);
828		}
829	}
830
831	#[tokio::test]
832	async fn consumer_skips_evicted_groups() {
833		tokio::time::pause();
834
835		let mut producer = Track::new("test").produce();
836		producer.append_group().unwrap(); // seq 0
837
838		let mut consumer = producer.consume();
839
840		tokio::time::advance(MAX_GROUP_AGE + Duration::from_secs(1)).await;
841		producer.append_group().unwrap(); // seq 1
842
843		// Group 0 was evicted. Consumer should get group 1.
844		let group = consumer.assert_group();
845		assert_eq!(group.sequence, 1);
846	}
847
848	#[tokio::test]
849	async fn out_of_order_max_sequence_at_front() {
850		tokio::time::pause();
851
852		let mut producer = Track::new("test").produce();
853
854		// Arrive out of order: seq 5 first, then 3, then 4.
855		producer.create_group(Group { sequence: 5 }).unwrap();
856		producer.create_group(Group { sequence: 3 }).unwrap();
857		producer.create_group(Group { sequence: 4 }).unwrap();
858
859		// max_sequence = 5, which is at the front of the VecDeque.
860		{
861			let state = producer.state.read();
862			assert_eq!(state.max_sequence, Some(5));
863		}
864
865		// Expire all three groups.
866		tokio::time::advance(MAX_GROUP_AGE + Duration::from_secs(1)).await;
867
868		// Append seq 6 (becomes new max_sequence).
869		producer.append_group().unwrap(); // seq 6
870
871		// Seq 3, 4, 5 are all expired. Seq 5 was the old max_sequence but now 6 is.
872		// All old groups are evicted.
873		{
874			let state = producer.state.read();
875			assert_eq!(live_groups(&state), 1);
876			assert_eq!(first_live_sequence(&state), 6);
877			assert!(!state.duplicates.contains(&3));
878			assert!(!state.duplicates.contains(&4));
879			assert!(!state.duplicates.contains(&5));
880			assert!(state.duplicates.contains(&6));
881		}
882	}
883
884	#[tokio::test]
885	async fn max_sequence_at_front_blocks_trim() {
886		tokio::time::pause();
887
888		let mut producer = Track::new("test").produce();
889
890		// Arrive: seq 5, then seq 3.
891		producer.create_group(Group { sequence: 5 }).unwrap();
892
893		tokio::time::advance(MAX_GROUP_AGE + Duration::from_secs(1)).await;
894
895		// Seq 3 arrives late; max_sequence is still 5 (at front).
896		producer.create_group(Group { sequence: 3 }).unwrap();
897
898		// Seq 5 is max_sequence (protected). Seq 3 is not expired (just created).
899		// Nothing should be evicted.
900		{
901			let state = producer.state.read();
902			assert_eq!(live_groups(&state), 2);
903			assert_eq!(state.offset, 0);
904		}
905
906		// Expire seq 3 as well.
907		tokio::time::advance(MAX_GROUP_AGE + Duration::from_secs(1)).await;
908
909		// Seq 2 arrives late, triggering eviction.
910		producer.create_group(Group { sequence: 2 }).unwrap();
911
912		// Seq 5 is still max_sequence (protected, at front, blocks trim).
913		// Seq 3 is expired → tombstoned.
914		// Seq 2 is fresh → kept.
915		// VecDeque: [Some(5), None, Some(2)]. Leading entry is Some, so offset stays.
916		{
917			let state = producer.state.read();
918			assert_eq!(live_groups(&state), 2);
919			assert_eq!(state.offset, 0);
920			assert!(state.duplicates.contains(&5));
921			assert!(!state.duplicates.contains(&3));
922			assert!(state.duplicates.contains(&2));
923		}
924
925		// Consumer should still be able to read through the hole.
926		let mut consumer = producer.consume();
927		let group = consumer.assert_group();
928		// consume() starts at index 0, first non-tombstoned group is seq 5.
929		assert_eq!(group.sequence, 5);
930	}
931
932	#[tokio::test]
933	async fn abort_clears_cached_groups() {
934		let mut producer = Track::new("test").produce();
935		producer.append_group().unwrap();
936		producer.append_group().unwrap();
937
938		// A stale consumer that never drains must not pin the cached groups.
939		let mut consumer = producer.consume();
940		assert_eq!(live_groups(&producer.state.read()), 2);
941
942		producer.abort(Error::Cancel).unwrap();
943
944		{
945			let state = producer.state.read();
946			assert!(state.groups.is_empty(), "cached groups should be dropped on abort");
947			assert!(state.duplicates.is_empty());
948		}
949
950		// The consumer now surfaces the abort error rather than the leftover cache.
951		let result = consumer.recv_group().now_or_never().expect("should not block");
952		assert!(matches!(result, Err(Error::Cancel)));
953	}
954
955	#[tokio::test]
956	async fn drop_unfinished_clears_cached_groups() {
957		let producer = Track::new("test").produce();
958		let mut writer = producer.clone();
959		writer.append_group().unwrap();
960
961		// A stale consumer keeps the channel (and thus the cache) alive.
962		let mut consumer = producer.consume();
963		assert_eq!(live_groups(&producer.state.read()), 1);
964
965		// Drop every producer without finishing: the cache is released.
966		drop(writer);
967		drop(producer);
968
969		let result = consumer.recv_group().now_or_never().expect("should not block");
970		assert!(matches!(result, Err(Error::Dropped)));
971	}
972
973	#[tokio::test]
974	async fn drop_finished_keeps_cached_groups() {
975		let mut producer = Track::new("test").produce();
976		producer.append_group().unwrap();
977		producer.finish().unwrap();
978
979		let mut consumer = producer.consume();
980		drop(producer);
981
982		// A cleanly finished track keeps its cache so the consumer can still drain.
983		assert_eq!(consumer.assert_group().sequence, 0);
984		let done = consumer.recv_group().now_or_never().expect("should not block").unwrap();
985		assert!(done.is_none(), "consumer should drain then see clean finish");
986	}
987
988	#[test]
989	fn append_finish_cannot_be_rewritten() {
990		let mut producer = Track::new("test").produce();
991
992		// Finishing an empty track is valid (fin = 0, total groups = 0).
993		assert!(producer.finish().is_ok());
994		assert!(producer.finish().is_err());
995		assert!(producer.append_group().is_err());
996	}
997
998	#[test]
999	fn finish_after_groups() {
1000		let mut producer = Track::new("test").produce();
1001
1002		producer.append_group().unwrap();
1003		assert!(producer.finish().is_ok());
1004		assert!(producer.finish().is_err());
1005		assert!(producer.append_group().is_err());
1006	}
1007
1008	#[test]
1009	fn insert_finish_validates_sequence_and_freezes_to_max() {
1010		let mut producer = Track::new("test").produce();
1011		producer.create_group(Group { sequence: 5 }).unwrap();
1012
1013		assert!(producer.finish_at(4).is_err());
1014		assert!(producer.finish_at(10).is_err());
1015		assert!(producer.finish_at(5).is_ok());
1016
1017		{
1018			let state = producer.state.read();
1019			assert_eq!(state.final_sequence, Some(6));
1020		}
1021
1022		assert!(producer.finish_at(5).is_err());
1023		assert!(producer.create_group(Group { sequence: 4 }).is_ok());
1024		assert!(producer.create_group(Group { sequence: 5 }).is_err());
1025	}
1026
1027	#[tokio::test]
1028	async fn recv_group_finishes_without_waiting_for_gaps() {
1029		let mut producer = Track::new("test").produce();
1030		producer.create_group(Group { sequence: 1 }).unwrap();
1031		producer.finish_at(1).unwrap();
1032
1033		let mut consumer = producer.consume();
1034		assert_eq!(consumer.assert_group().sequence, 1);
1035
1036		let done = consumer
1037			.recv_group()
1038			.now_or_never()
1039			.expect("should not block")
1040			.expect("would have errored");
1041		assert!(done.is_none(), "track should finish without waiting for gaps");
1042	}
1043
1044	#[tokio::test]
1045	async fn next_group_skips_late_arrivals() {
1046		let mut producer = Track::new("test").produce();
1047		let mut consumer = producer.consume();
1048
1049		// Seq 5 arrives first.
1050		producer.create_group(Group { sequence: 5 }).unwrap();
1051		let group = consumer
1052			.next_group()
1053			.now_or_never()
1054			.expect("should not block")
1055			.expect("would have errored")
1056			.expect("track should not be closed");
1057		assert_eq!(group.sequence, 5);
1058
1059		// Seq 3 arrives late — skipped because 3 <= 5.
1060		producer.create_group(Group { sequence: 3 }).unwrap();
1061		// Seq 4 arrives late — also skipped.
1062		producer.create_group(Group { sequence: 4 }).unwrap();
1063		// Seq 7 arrives — returned.
1064		producer.create_group(Group { sequence: 7 }).unwrap();
1065
1066		let group = consumer
1067			.next_group()
1068			.now_or_never()
1069			.expect("should not block")
1070			.expect("would have errored")
1071			.expect("track should not be closed");
1072		assert_eq!(group.sequence, 7);
1073
1074		// No more groups — would block.
1075		assert!(
1076			consumer.next_group().now_or_never().is_none(),
1077			"should block waiting for a higher sequence"
1078		);
1079	}
1080
1081	#[tokio::test]
1082	async fn next_group_returns_arrivals_in_order() {
1083		let mut producer = Track::new("test").produce();
1084		let mut consumer = producer.consume();
1085
1086		// Seq 3 arrives first, then seq 5 — both should be returned in arrival order.
1087		producer.create_group(Group { sequence: 3 }).unwrap();
1088		producer.create_group(Group { sequence: 5 }).unwrap();
1089
1090		let group = consumer
1091			.next_group()
1092			.now_or_never()
1093			.expect("should not block")
1094			.expect("would have errored")
1095			.expect("track should not be closed");
1096		assert_eq!(group.sequence, 3);
1097
1098		let group = consumer
1099			.next_group()
1100			.now_or_never()
1101			.expect("should not block")
1102			.expect("would have errored")
1103			.expect("track should not be closed");
1104		assert_eq!(group.sequence, 5);
1105	}
1106
1107	#[tokio::test]
1108	async fn recv_group_after_next_group_sees_late_arrivals() {
1109		let mut producer = Track::new("test").produce();
1110		let mut consumer = producer.consume();
1111
1112		producer.create_group(Group { sequence: 5 }).unwrap();
1113		producer.create_group(Group { sequence: 3 }).unwrap();
1114
1115		// Ordered returns seq 5 and advances its internal cursor past it.
1116		let group = consumer
1117			.next_group()
1118			.now_or_never()
1119			.expect("should not block")
1120			.expect("would have errored")
1121			.expect("track should not be closed");
1122		assert_eq!(group.sequence, 5);
1123
1124		// Intermixing: recv_group on the same consumer still returns the late seq 3.
1125		// The ordered cursor is separate from the recv_group filter.
1126		assert_eq!(consumer.assert_group().sequence, 3);
1127	}
1128
1129	#[tokio::test]
1130	async fn read_frame_returns_single_frame_per_group() {
1131		let mut producer = Track::new("test").produce();
1132		let mut consumer = producer.consume();
1133
1134		producer.write_frame(b"hello".as_slice()).unwrap();
1135		producer.write_frame(b"world".as_slice()).unwrap();
1136
1137		let frame = consumer
1138			.read_frame()
1139			.now_or_never()
1140			.expect("should not block")
1141			.expect("would have errored")
1142			.expect("track should not be closed");
1143		assert_eq!(&frame[..], b"hello");
1144
1145		let frame = consumer
1146			.read_frame()
1147			.now_or_never()
1148			.expect("should not block")
1149			.expect("would have errored")
1150			.expect("track should not be closed");
1151		assert_eq!(&frame[..], b"world");
1152	}
1153
1154	#[tokio::test]
1155	async fn read_frame_skips_stalled_group_for_newer_ready_frame() {
1156		let mut producer = Track::new("test").produce();
1157		let mut consumer = producer.consume();
1158
1159		// Seq 3: group open, no frame yet (stalled).
1160		let _stalled = producer.create_group(Group { sequence: 3 }).unwrap();
1161		// Seq 5: fully-written group with a frame.
1162		let mut g5 = producer.create_group(Group { sequence: 5 }).unwrap();
1163		g5.write_frame(bytes::Bytes::from_static(b"later")).unwrap();
1164		g5.finish().unwrap();
1165
1166		// read_frame should not block on the stalled seq 3 — it returns seq 5's frame.
1167		let frame = consumer
1168			.read_frame()
1169			.now_or_never()
1170			.expect("should not block on stalled earlier group")
1171			.expect("would have errored")
1172			.expect("track should not be closed");
1173		assert_eq!(&frame[..], b"later");
1174	}
1175
1176	#[tokio::test]
1177	async fn read_frame_discards_rest_of_multi_frame_group() {
1178		let mut producer = Track::new("test").produce();
1179		let mut consumer = producer.consume();
1180
1181		// Group 0 has two frames; only the first is returned.
1182		let mut g0 = producer.create_group(Group { sequence: 0 }).unwrap();
1183		g0.write_frame(bytes::Bytes::from_static(b"one")).unwrap();
1184		g0.write_frame(bytes::Bytes::from_static(b"two")).unwrap();
1185		g0.finish().unwrap();
1186
1187		// Group 1 is a normal single-frame group.
1188		producer.write_frame(b"next".as_slice()).unwrap();
1189
1190		let frame = consumer
1191			.read_frame()
1192			.now_or_never()
1193			.expect("should not block")
1194			.expect("would have errored")
1195			.expect("track should not be closed");
1196		assert_eq!(&frame[..], b"one");
1197
1198		// The second frame of group 0 is discarded; the next read jumps to group 1.
1199		let frame = consumer
1200			.read_frame()
1201			.now_or_never()
1202			.expect("should not block")
1203			.expect("would have errored")
1204			.expect("track should not be closed");
1205		assert_eq!(&frame[..], b"next");
1206	}
1207
1208	#[tokio::test]
1209	async fn read_frame_waits_for_pending_group_after_finish() {
1210		// finish() sets final_sequence, but groups already created with lower sequences
1211		// can still produce frames. read_frame must not return None prematurely.
1212		let mut producer = Track::new("test").produce();
1213		let mut consumer = producer.consume();
1214
1215		let mut g0 = producer.create_group(Group { sequence: 0 }).unwrap();
1216		producer.finish().unwrap();
1217
1218		// Track is finished but group 0 has no frame yet — must block, not return None.
1219		assert!(
1220			consumer.read_frame().now_or_never().is_none(),
1221			"read_frame must block on a pending group even after finish()"
1222		);
1223
1224		// A late frame on the pending group is still delivered.
1225		g0.write_frame(bytes::Bytes::from_static(b"late")).unwrap();
1226		let frame = consumer
1227			.read_frame()
1228			.now_or_never()
1229			.expect("should not block once a frame is written")
1230			.expect("would have errored")
1231			.expect("track should not be closed");
1232		assert_eq!(&frame[..], b"late");
1233	}
1234
1235	#[tokio::test]
1236	async fn read_frame_respects_start_at() {
1237		// start_at sets min_sequence; read_frame must skip groups below it even though
1238		// next_sequence is still 0.
1239		let mut producer = Track::new("test").produce();
1240		let mut consumer = producer.consume();
1241		consumer.start_at(5);
1242
1243		// Seq 3 has a frame but is below min_sequence — must be skipped.
1244		let mut g3 = producer.create_group(Group { sequence: 3 }).unwrap();
1245		g3.write_frame(bytes::Bytes::from_static(b"skip-me")).unwrap();
1246		g3.finish().unwrap();
1247
1248		let mut g5 = producer.create_group(Group { sequence: 5 }).unwrap();
1249		g5.write_frame(bytes::Bytes::from_static(b"keep")).unwrap();
1250		g5.finish().unwrap();
1251
1252		let frame = consumer
1253			.read_frame()
1254			.now_or_never()
1255			.expect("should not block")
1256			.expect("would have errored")
1257			.expect("track should not be closed");
1258		assert_eq!(&frame[..], b"keep");
1259	}
1260
1261	#[tokio::test]
1262	async fn read_frame_returns_none_when_finished() {
1263		let mut producer = Track::new("test").produce();
1264		let mut consumer = producer.consume();
1265
1266		producer.write_frame(b"only".as_slice()).unwrap();
1267		producer.finish().unwrap();
1268
1269		let frame = consumer
1270			.read_frame()
1271			.now_or_never()
1272			.expect("should not block")
1273			.expect("would have errored")
1274			.expect("track should not be closed");
1275		assert_eq!(&frame[..], b"only");
1276
1277		let done = consumer
1278			.read_frame()
1279			.now_or_never()
1280			.expect("should not block")
1281			.expect("would have errored");
1282		assert!(done.is_none());
1283	}
1284
1285	#[tokio::test]
1286	async fn get_group_finishes_without_waiting_for_gaps() {
1287		let mut producer = Track::new("test").produce();
1288		producer.create_group(Group { sequence: 1 }).unwrap();
1289		producer.finish_at(1).unwrap();
1290
1291		let consumer = producer.consume();
1292		// get_group(0) blocks because group 0 is below final_sequence and could still arrive.
1293		assert!(
1294			consumer.get_group(0).now_or_never().is_none(),
1295			"sequence below fin should block (group could still arrive)"
1296		);
1297		assert!(
1298			consumer
1299				.get_group(2)
1300				.now_or_never()
1301				.expect("sequence at-or-after fin should resolve")
1302				.expect("should not error")
1303				.is_none(),
1304			"sequence at-or-after fin should not exist"
1305		);
1306	}
1307
1308	#[test]
1309	fn append_group_returns_bounds_exceeded_on_sequence_overflow() {
1310		let mut producer = Track::new("test").produce();
1311		{
1312			let mut state = producer.state.write().ok().unwrap();
1313			state.max_sequence = Some(u64::MAX);
1314		}
1315
1316		assert!(matches!(producer.append_group(), Err(Error::BoundsExceeded(_))));
1317	}
1318}