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 [Producer] and [Subscriber] handle.
2//!
3//! A [Producer] 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 [Producer] can be used to create streams in parallel, but will error if a duplicate sequence number is used.
7//!
8//! A [Subscriber] 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 [Consumer] is a cheap, cloneable handle; subscribing it multiple times fans the same
12//! cached streams out to each independent [Subscriber].
13//!
14//! The track is closed with [Error] when all writers or readers are dropped.
15
16use crate::{Error, Result, Timescale, Timestamp, coding};
17use crate::{broadcast, cache, frame, group, stats};
18
19use super::{Datagram, Requests};
20
21pub use super::subscription::Subscription;
22
23use std::{
24	collections::{BTreeMap, HashMap, VecDeque},
25	sync::Arc,
26	sync::OnceLock,
27	sync::atomic::{AtomicBool, Ordering},
28	task::{Poll, ready},
29	time::Duration,
30};
31
32/// Default [`Info::latency_max`] age when the publisher doesn't set one.
33pub const DEFAULT_LATENCY_MAX: Duration = Duration::from_secs(5);
34
35/// How long a datagram stays in the per-track buffer before it is dropped.
36///
37/// Datagrams are a best-effort send buffer, not a replay cache (unlike groups): only the last
38/// few tens of milliseconds are kept, so a consumer that stalls loses stale datagrams instead of
39/// replaying them. Sized like a typical send buffer for real-time audio/video.
40const MAX_DATAGRAM_AGE: Duration = Duration::from_millis(50);
41
42/// Slack before the eviction order is rebuilt, so a track holding just a few groups
43/// doesn't rebuild on every write.
44const EVICT_SLACK: usize = 64;
45
46/// How many live eviction candidates one debt payment examines (Redis-style
47/// bounded sampling): enough to step over a few protected (recently accessed)
48/// groups, small enough that a write never scans a long queue.
49const EVICT_SCAN: usize = 4;
50
51/// Publisher-side properties of a track.
52///
53/// These are fixed by the publisher when the track is created and don't change
54/// while the track is alive. A subscriber learns them via
55/// [`broadcast::Consumer::track`](broadcast::Consumer::track),
56/// which returns the publisher's [`Info`] once the subscription is accepted.
57//
58// Deliberately not `Copy`, even though it's now a plain value: adding `Copy` turns
59// every existing `info.clone()` in a consumer's code into a `clippy::clone_on_copy`
60// error under `-D warnings`.
61#[derive(Clone, Debug)]
62#[non_exhaustive]
63pub struct Info {
64	/// Units per second for per-frame timestamps on this track.
65	///
66	/// Every track is timed; this defaults to [`Timescale::MILLI`]. On Lite05+ it is
67	/// reported in TRACK_INFO and the publisher zigzag-delta encodes per-frame
68	/// timestamps at this scale on the wire. Protocols whose wire can't carry it
69	/// (pre-Lite05 moq-lite, IETF moq-transport) fall back to local monotonic milliseconds.
70	pub timescale: Timescale,
71	/// The maximum age of a non-latest group before the publisher evicts it (the
72	/// newest group is always retained). A subscriber's
73	/// [`Subscription::latency_max`] window is clamped to this, since a group can't be
74	/// waited for longer than it's kept around. Reported in TRACK_INFO so
75	/// relays re-serve with the same window. Defaults to [`DEFAULT_LATENCY_MAX`].
76	///
77	/// This is the `Publisher Max Latency` on the wire, the publisher-side half of
78	/// the same budget [`Subscription::latency_max`] sets for a subscriber.
79	pub latency_max: Duration,
80	/// The publisher's priority for this track, used only to break ties between
81	/// subscriptions of equal subscriber priority. Reported in TRACK_INFO (Lite05+).
82	pub priority: u8,
83	/// Whether groups are prioritized in sequence order. Groups may always arrive
84	/// out-of-order (or not at all) over the network. Used only to break ties,
85	/// reported in TRACK_INFO (Lite05+), and defaults to `false` (newest-first).
86	pub ordered: bool,
87}
88
89impl Default for Info {
90	fn default() -> Self {
91		Self {
92			timescale: Timescale::default(),
93			latency_max: DEFAULT_LATENCY_MAX,
94			priority: 0,
95			ordered: false,
96		}
97	}
98}
99
100impl Info {
101	/// Set the per-frame timestamp scale, returning `self` for chaining.
102	///
103	/// Defaults to [`Timescale::MILLI`]. On Lite05+ this scale is reported in TRACK_INFO
104	/// and used to encode per-frame timestamps on the wire.
105	pub fn with_timescale(mut self, timescale: Timescale) -> Self {
106		self.timescale = timescale;
107		self
108	}
109
110	/// Set the maximum age of a non-latest group before eviction, returning `self` for chaining.
111	pub fn with_latency_max(mut self, latency_max: Duration) -> Self {
112		self.latency_max = latency_max;
113		self
114	}
115
116	/// Set the publisher's tie-break priority, returning `self` for chaining.
117	pub fn with_priority(mut self, priority: u8) -> Self {
118		self.priority = priority;
119		self
120	}
121
122	/// Set whether groups are prioritized in sequence order, returning `self` for
123	/// chaining. Groups may always arrive out-of-order (or not at all) over the
124	/// network. Defaults to `false`.
125	pub fn with_ordered(mut self, ordered: bool) -> Self {
126		self.ordered = ordered;
127		self
128	}
129}
130
131#[derive(Default)]
132pub(crate) struct TrackState {
133	// The publisher's properties, once known; always Some for Subscriber/Producer.
134	// Copied by value into each group it creates.
135	info: Option<Info>,
136
137	// The broadcast this track belongs to. Supplies the cache pool its groups charge
138	// into and the `cache_duration` ceiling clamping `Info::latency_max`.
139	broadcast: Arc<broadcast::Info>,
140
141	// This track's account against the shared cache pool, shared with every group it
142	// creates (see `cache::Track`). Holds the gross-write counter `charge_debt` drains,
143	// and the weak link a frame write follows back here to settle its own debt.
144	cache: Arc<cache::Track>,
145
146	// Cached groups by sequence: the single source of truth for what is cached. The
147	// two orderings below hold bare sequences and validate against this map, so a
148	// removed or replaced group turns their entries into discarded-on-pop hints.
149	lookup: HashMap<u64, Slot>,
150
151	// Publisher-produced groups in arrival order as (sequence, stamp), walked by
152	// subscriptions; an entry only resolves while its stamp matches the slot's.
153	// Fetched backfill (`insert_group_request`) is deliberately absent: it is
154	// served by sequence, never replayed to arrival-order subscribers.
155	arrival: VecDeque<(u64, u32)>,
156
157	// Eviction order under memory pressure as (sequence, stamp): every cached
158	// group except the protected latest. `pay_debt` scans victims from the front;
159	// groups accessed more recently than the pool-wide average rotate to the back
160	// instead of dying, decoupling eviction order from arrival order. Entries are
161	// hints that only resolve while their stamp matches the slot's, so a re-served
162	// sequence can't accumulate duplicate hints that alias its replacement.
163	// Eviction is deliberately approximate: a bounded scan per write.
164	evict: VecDeque<(u64, u32)>,
165
166	// Outstanding eviction debt in bytes, accrued by writes while the shared pool
167	// is over capacity (see `cache::Pool::accrue`) and paid by aborting this
168	// track's own oldest groups. Per track, so eviction lands proportionally to
169	// what each track writes and never touches another track's cache.
170	debt: u64,
171
172	// Datagrams in arrival order paired with their arrival time, a best-effort send buffer
173	// evicted by age (see `MAX_DATAGRAM_AGE`). Shares the group `max_sequence` namespace but
174	// is otherwise independent.
175	datagrams: VecDeque<(Datagram, web_async::time::Instant)>,
176
177	// Number of datagrams dropped off the front (aged out), mapping a subscriber's absolute
178	// cursor to an index into `datagrams` (mirrors `offset` for groups).
179	datagram_offset: usize,
180
181	// We've popped the front of `arrival` this many times, mapping a subscriber's
182	// absolute cursor to an index.
183	offset: usize,
184
185	// The highest sequence number successfully appended to the track. Shared with
186	// datagrams, so it can run ahead of any cached group.
187	max_sequence: Option<u64>,
188
189	// The sequence of the newest cached group: the live edge, protected from
190	// eviction by never entering the eviction order. Tracked separately from
191	// `max_sequence` because datagrams advance that shared counter, and the live
192	// edge must still demote correctly when the next group lands past one.
193	latest_group: Option<u64>,
194
195	// Incarnation counter for `Slot::stamp`.
196	next_stamp: u32,
197
198	// Rotating position of the expiry scan over `evict`, so entries beyond one
199	// scan window can't be starved by fresh entries in front of them.
200	expire_cursor: usize,
201
202	// The sequence number at which the track was finalized.
203	final_sequence: Option<u64>,
204
205	// The error that caused the track to be aborted, if any.
206	abort: Option<Error>,
207
208	// Active subscriptions, in their own [`kio::Shared`] so a read-only `Consumer`
209	// registers under that lock instead of writing back into the track state.
210	// Kept here (rather than threaded through every handle) so any holder reaches it.
211	subscriptions: kio::Shared<Subscriptions>,
212
213	// The reverse fetch queue (see [`FetchState`]), same reasoning: cache-miss
214	// `fetch_group` calls enqueue here and a `Dynamic` drains.
215	fetch: kio::Shared<FetchState>,
216}
217
218/// A cached group plus its bookkeeping in the track's `lookup` map.
219///
220/// Access times and the evictable-population sample live in the group's own
221/// `cache::Charge`, so they share the group's lifecycle exactly: an abort from any
222/// handle releases the bytes and the sample together.
223struct Slot {
224	group: group::Producer,
225
226	// Incarnation stamp, echoed by this slot's arrival entry (if any). A re-served
227	// sequence (an aborted group re-created by the publisher or re-fetched as
228	// backfill) gets a fresh stamp, so a historical arrival entry can't resolve to
229	// the replacement and deliver it twice or at the wrong position.
230	stamp: u32,
231}
232
233/// The registered subscriptions, aggregated by the producer.
234type Subscriptions = Vec<kio::Consumer<Subscription>>;
235
236/// Reverse state for [`Consumer::fetch_group`], beside the track state in its own
237/// [`kio::Shared`]: consumers enqueue (coalescing per sequence, so a relay opens one
238/// upstream FETCH per group) and [`Dynamic`] handlers drain under one lock, without
239/// write access to the track itself.
240type FetchState = Requests<u64, PendingFetch>;
241
242/// One fetch attempt for a sequence, shared by every [`Fetching`] that joined it.
243struct PendingFetch {
244	// The most demanding delivery priority across the joined fetches.
245	priority: u8,
246
247	// Result channel back to the joined fetches. Written only on rejection; a
248	// successful accept resolves them through the track cache instead. Dropping
249	// every producer without writing (a vanished handler) closes the channel,
250	// which a [`Fetching`] reads as [`Error::NotFound`].
251	result: kio::Producer<FetchOutcome>,
252}
253
254/// The result of a fetch attempt. Stays empty on success (the group lands in the
255/// track cache); a handler writes `rejected` to fail every joined fetch.
256#[derive(Default)]
257struct FetchOutcome {
258	rejected: Option<Error>,
259}
260
261impl TrackState {
262	fn poll_info(&self) -> Poll<Result<Info>> {
263		if let Some(info) = &self.info {
264			Poll::Ready(Ok(info.clone()))
265		} else {
266			Poll::Pending
267		}
268	}
269
270	/// Find the next live group at or after `index` in arrival order.
271	///
272	/// Returns the group and its absolute index so the consumer can advance past it.
273	fn poll_recv_group(&self, index: usize, min_sequence: u64) -> Poll<Result<Option<(group::Consumer, usize)>>> {
274		let start = index.saturating_sub(self.offset);
275		for (i, (sequence, stamp)) in self.arrival.iter().enumerate().skip(start) {
276			if *sequence >= min_sequence
277				&& let Some(slot) = self.lookup.get(sequence)
278				&& slot.stamp == *stamp
279				&& !slot.group.is_aborted()
280			{
281				return Poll::Ready(Ok(Some((slot.group.consume(), self.offset + i))));
282			}
283		}
284
285		// TODO once we have drop notifications, check if index == final_sequence.
286		if self.is_complete() {
287			Poll::Ready(Ok(None))
288		} else if let Some(err) = &self.abort {
289			Poll::Ready(Err(err.clone()))
290		} else {
291			Poll::Pending
292		}
293	}
294
295	/// Find the next datagram at or after the subscriber's absolute `index`.
296	///
297	/// Returns the datagram and its absolute index so the consumer can advance past it. A
298	/// consumer whose `index` has fallen behind `datagram_offset` (older datagrams dropped)
299	/// resumes at the oldest still-buffered datagram, skipping the lost ones.
300	fn poll_recv_datagram(&self, index: usize) -> Poll<Result<Option<(Datagram, usize)>>> {
301		let start = index.saturating_sub(self.datagram_offset);
302		if let Some((datagram, _)) = self.datagrams.get(start) {
303			return Poll::Ready(Ok(Some((datagram.clone(), self.datagram_offset + start))));
304		}
305
306		// Nothing buffered at the cursor: the track ending terminates the datagram stream too.
307		if self.is_complete() {
308			Poll::Ready(Ok(None))
309		} else if let Some(err) = &self.abort {
310			Poll::Ready(Err(err.clone()))
311		} else {
312			Poll::Pending
313		}
314	}
315
316	/// Push a datagram onto the buffer, dropping any that have aged past [`MAX_DATAGRAM_AGE`].
317	fn push_datagram(&mut self, datagram: Datagram) {
318		let now = web_async::time::Instant::now();
319		self.datagrams.push_back((datagram, now));
320		while let Some((_, at)) = self.datagrams.front() {
321			if now.duration_since(*at) <= MAX_DATAGRAM_AGE {
322				break;
323			}
324			self.datagrams.pop_front();
325			self.datagram_offset += 1;
326		}
327	}
328
329	/// Scan groups at or after `index` in arrival order, looking for the first with sequence
330	/// `>= next_sequence` that has a fully-buffered next frame. Returns the frame plus the
331	/// winning slot's absolute index and sequence so the consumer can advance past it.
332	fn poll_read_frame(
333		&self,
334		index: usize,
335		next_sequence: u64,
336		waiter: &kio::Waiter,
337	) -> Poll<Result<Option<(frame::Frame, usize, u64)>>> {
338		let start = index.saturating_sub(self.offset);
339		let mut pending_seen = false;
340		for (i, (sequence, stamp)) in self.arrival.iter().enumerate().skip(start) {
341			if *sequence < next_sequence {
342				continue;
343			}
344			let Some(slot) = self.lookup.get(sequence) else {
345				continue;
346			};
347			if slot.stamp != *stamp {
348				// A historical entry; the sequence was re-served by a newer
349				// incarnation, delivered (if at all) at its own arrival position.
350				continue;
351			}
352
353			let mut consumer = slot.group.consume();
354			match consumer.poll_read_frame(waiter) {
355				Poll::Ready(Ok(Some(frame))) => {
356					return Poll::Ready(Ok(Some((frame, self.offset + i, *sequence))));
357				}
358				Poll::Ready(Ok(None)) => continue,
359				// A single group failing (aborted upstream, or evicted from the
360				// cache) doesn't poison the track; skip it like a gap.
361				Poll::Ready(Err(_)) => continue,
362				Poll::Pending => {
363					pending_seen = true;
364					continue;
365				}
366			}
367		}
368
369		// A pending group can still produce a frame even after finish(). Finish only
370		// blocks new groups at/above final_sequence, not frames on existing groups.
371		if pending_seen {
372			Poll::Pending
373		} else if self.is_complete() {
374			Poll::Ready(Ok(None))
375		} else if let Some(err) = &self.abort {
376			Poll::Ready(Err(err.clone()))
377		} else {
378			Poll::Pending
379		}
380	}
381
382	/// Find the smallest-sequence cached group satisfying
383	/// `next_sequence <= seq <= end_sequence (if set)`. Used by
384	/// [`Subscriber::next_group`] so the range can be widened (or unset)
385	/// after the fact and previously-skipped cached groups become available
386	/// without scanning past them in arrival order.
387	///
388	/// Returns `Poll::Pending` when no in-range group is currently cached but
389	/// future groups could still arrive in range; returns `Ok(None)` only when
390	/// the track is finalized and no further in-range group is possible.
391	fn poll_next_in_range(
392		&self,
393		next_sequence: u64,
394		end_sequence: Option<u64>,
395	) -> Poll<Result<Option<group::Consumer>>> {
396		// If the end cap is already below where we'd resume, no group can
397		// ever satisfy this call until the cap rises. Pending (not None) so
398		// the consumer is parked rather than told the stream is over.
399		if let Some(end) = end_sequence
400			&& end < next_sequence
401		{
402			if let Some(err) = &self.abort {
403				return Poll::Ready(Err(err.clone()));
404			}
405			return Poll::Pending;
406		}
407
408		let mut best: Option<&group::Producer> = None;
409		for slot in self.lookup.values() {
410			let group = &slot.group;
411			if group.sequence < next_sequence {
412				continue;
413			}
414			if let Some(end) = end_sequence
415				&& group.sequence > end
416			{
417				continue;
418			}
419			if group.is_aborted() {
420				continue;
421			}
422			if best.is_none_or(|b| group.sequence < b.sequence) {
423				best = Some(group);
424			}
425		}
426
427		if let Some(group) = best {
428			return Poll::Ready(Ok(Some(group.consume())));
429		}
430
431		// No in-range group is cached. Decide whether more could ever arrive.
432		if let Some(err) = &self.abort {
433			return Poll::Ready(Err(err.clone()));
434		}
435		// `final_sequence` is one past the last possible sequence. If our
436		// floor is already at/past it, nothing else can land in range.
437		if let Some(fin) = self.final_sequence
438			&& next_sequence >= fin
439		{
440			return Poll::Ready(Ok(None));
441		}
442		Poll::Pending
443	}
444
445	/// Find a cached group by sequence; an aborted (evicted) group is a miss, so a
446	/// fetch re-fetches it. Synchronous, never blocks. Test hook for `peek_group`;
447	/// the real fetch path is [`Self::poll_fetch_cached`], which also refreshes the
448	/// group.
449	#[cfg(test)]
450	fn cached_group(&self, sequence: u64) -> Option<group::Consumer> {
451		let slot = self.lookup.get(&sequence)?;
452		if slot.group.is_aborted() {
453			return None;
454		}
455		Some(slot.group.consume())
456	}
457
458	/// The publisher's latency window, or `None` while the info is unknown (an
459	/// unaccepted [`Request`]). Bounds the aggregate subscription; see [`clamp_combined`].
460	fn latency_bound(&self) -> Option<Duration> {
461		self.info.as_ref().map(|info| info.latency_max)
462	}
463
464	/// Resolve a one-shot fetch from the track side: the cached group, or an [`Error`]
465	/// once it can never be served. A missing group is a failure ([`Error::NotFound`]), not an
466	/// end-of-stream. The handler side (a rejection, or no [`Dynamic`] at all) lives
467	/// in [`FetchState`]; [`Fetching`] polls both.
468	fn poll_fetch_cached(&self, sequence: u64) -> Poll<Result<group::Consumer>> {
469		if let Some(slot) = self.lookup.get(&sequence)
470			&& !slot.group.is_aborted()
471		{
472			// A cache hit refreshes the group: it resets both its age (expiry keys
473			// off the last access) and its standing against the pool-wide average,
474			// so the eviction walk keeps it over never-read groups.
475			slot.group.cache_refresh();
476			return Poll::Ready(Ok(slot.group.consume()));
477		}
478
479		if let Some(err) = &self.abort {
480			return Poll::Ready(Err(err.clone()));
481		}
482
483		// Past the final sequence: the group can never exist.
484		if self.final_sequence.is_some_and(|fin| sequence >= fin) {
485			return Poll::Ready(Err(Error::NotFound));
486		}
487
488		Poll::Pending
489	}
490
491	/// Expire groups whose last access is older than `max_age`, never the latest.
492	///
493	/// One bounded, rotating scan over the eviction order, which holds every cached
494	/// group except the protected latest. The cursor persists across calls, so
495	/// entries beyond one scan window can't be starved by fresh (recently fetched
496	/// or written) entries in front of them: every position is revisited within a
497	/// few writes. Expiry throughput is therefore EVICT_SCAN groups per write; the
498	/// byte budget reclaims the remainder under memory pressure.
499	fn evict_expired(&mut self, max_age: Duration) {
500		let now = self.cache.pool().now();
501		let max_ticks = cache::Pool::ticks(max_age);
502
503		let len = self.evict.len();
504		if len > 0 {
505			let start = self.expire_cursor % len;
506			for step in 0..len.min(EVICT_SCAN) {
507				let (sequence, stamp) = self.evict[(start + step) % len];
508				let Some(slot) = self.lookup.get(&sequence) else {
509					continue;
510				};
511				if slot.stamp != stamp {
512					// A historical hint; the live entry is elsewhere in the queue.
513					continue;
514				}
515				// Already aborted: the frames are gone, reclaim the slot so a
516				// later fetch can serve the sequence again.
517				if slot.group.is_aborted() {
518					self.lookup.remove(&sequence);
519					continue;
520				}
521				if Some(sequence) == self.latest_group || now.saturating_sub(slot.group.cache_accessed()) <= max_ticks {
522					continue;
523				}
524				// Take the group out of the cache and abort it, so any consumer
525				// still reading surfaces `Error::Old` instead of blocking forever
526				// on a frame that will never arrive.
527				let slot = self.lookup.remove(&sequence).unwrap();
528				let _ = slot.group.abort(Error::Old);
529			}
530			self.expire_cursor = (start + EVICT_SCAN) % len;
531		}
532
533		// Trim dead leading arrival entries to advance the subscriber offset. An
534		// entry is dead once its slot is gone or re-stamped by a newer incarnation.
535		while let Some((sequence, stamp)) = self.arrival.front() {
536			if self.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp) {
537				break;
538			}
539			self.arrival.pop_front();
540			self.offset += 1;
541		}
542
543		// Drop dead leading eviction entries so scans stay over live candidates.
544		while let Some((sequence, stamp)) = self.evict.front() {
545			if self.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp) {
546				break;
547			}
548			self.evict.pop_front();
549		}
550
551		// Dead entries behind a live front can linger; rebuild once they clearly
552		// outnumber the live slots.
553		if self.evict.len() > 2 * self.lookup.len() + EVICT_SLACK {
554			let lookup = &self.lookup;
555			self.evict
556				.retain(|(sequence, stamp)| lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp));
557		}
558	}
559
560	/// Drop every cached group and reset the eviction bookkeeping. Each group's
561	/// access sample lives in its own charge, released when the group itself dies.
562	fn clear_cache(&mut self) {
563		self.lookup.clear();
564		self.arrival.clear();
565		self.evict.clear();
566		self.latest_group = None;
567		self.debt = 0;
568	}
569
570	/// Attach `info` to this track, clamping the publisher's window down to the
571	/// origin's [`cache_duration`](crate::origin::Info::cache_duration) ceiling so a
572	/// group is never retained longer than the origin allows. Every path that binds an
573	/// info to a track funnels through here, covering local publishers and relayed
574	/// (lite / IETF) tracks alike.
575	fn install(&mut self, mut info: Info) {
576		info.latency_max = info.latency_max.min(self.broadcast.origin.cache_duration);
577		self.info = Some(info);
578	}
579
580	/// Create the shared state for a track under `broadcast`, along with the cache
581	/// account it and its groups charge into.
582	///
583	/// The account holds a [`kio::Weak`] back to this state: a group must be able to
584	/// settle the track's eviction debt as it writes, but the track owns its cached
585	/// groups, so anything stronger would make the pair immortal.
586	fn spawn(broadcast: Arc<broadcast::Info>) -> kio::Producer<Self> {
587		let state = kio::Producer::new(Self {
588			broadcast: broadcast.clone(),
589			..Default::default()
590		});
591		let cache = cache::Track::new(broadcast.origin.pool.clone(), state.downgrade());
592		state.write().ok().expect("a new track is open").cache = cache;
593		state
594	}
595
596	/// Reject a sequence that is still cached; a dead (aborted or evicted)
597	/// incarnation is removed so a fresh group can serve the sequence again.
598	///
599	/// Best effort: nothing remembers a sequence whose slot is already gone, so a
600	/// publisher re-sending a long-evicted sequence is accepted as new.
601	fn claim_sequence(&mut self, sequence: u64) -> Result<()> {
602		if let Some(slot) = self.lookup.get(&sequence) {
603			if !slot.group.is_aborted() {
604				return Err(Error::Duplicate);
605			}
606			self.lookup.remove(&sequence);
607		}
608		Ok(())
609	}
610
611	/// Insert a freshly-created group into the cache.
612	///
613	/// Updates the live edge, demoting the previous latest into the eviction order;
614	/// the current latest is never enqueued, which is what protects it from
615	/// eviction. `visible` controls arrival-order delivery: publisher-produced
616	/// groups reach subscribers, fetched backfill is served by sequence only.
617	fn insert_group(&mut self, group: &group::Producer, visible: bool) {
618		let sequence = group.sequence;
619		self.next_stamp = self.next_stamp.wrapping_add(1);
620		let stamp = self.next_stamp;
621
622		// The live edge is tracked separately from `max_sequence`, which datagrams
623		// share and can push past any cached group: demotion must still fire when
624		// the next group lands beyond a datagram-advanced counter.
625		if self.latest_group.is_none_or(|latest| sequence >= latest) {
626			// Demote the previous latest: it joins the eviction order (and the
627			// pool's access average) like any other cached group.
628			if let Some(latest) = self.latest_group
629				&& sequence > latest
630				&& let Some(prev) = self.lookup.get(&latest)
631			{
632				prev.group.cache_demote();
633				self.evict.push_back((latest, prev.stamp));
634			}
635			self.latest_group = Some(sequence);
636		} else {
637			group.cache_demote();
638			self.evict.push_back((sequence, stamp));
639		}
640
641		self.max_sequence = Some(self.max_sequence.map_or(sequence, |max| max.max(sequence)));
642		self.lookup.insert(
643			sequence,
644			Slot {
645				group: group.clone(),
646				stamp,
647			},
648		);
649		if visible {
650			self.arrival.push_back((sequence, stamp));
651		}
652	}
653
654	/// Admit a freshly-created group: settle eviction debt first (so the newcomer
655	/// can never be a victim of the very write that created it), insert it, then
656	/// expire by age.
657	fn commit_group(&mut self, group: &group::Producer, visible: bool, latency_max: Duration) {
658		self.charge_debt();
659		self.insert_group(group, visible);
660		self.evict_expired(latency_max);
661	}
662
663	/// Accrue and pay eviction debt for everything written since the last charge:
664	/// this track's account, which the groups' charges feed on every frame (so
665	/// growth on already-demoted groups and backfill is billed too).
666	///
667	/// Runs BEFORE the new group is inserted, so a brand-new entry is never a
668	/// victim of the very write that created it. A track whose oldest content is
669	/// staler than the pool-wide average access time accrues at double rate, so
670	/// stale-heavy tracks drain first.
671	///
672	/// Also runs from the frame-write path via [`cache::Track::settle`], which is
673	/// why it's reachable from the account, so a track that only appends frames to
674	/// open groups still pays.
675	pub(super) fn charge_debt(&mut self) {
676		let written = self.cache.take_written();
677		let pool = self.cache.pool().clone();
678		match pool.accrue(written) {
679			Some(mut accrued) => {
680				if self.oldest_is_stale(&pool) {
681					accrued = accrued.saturating_mul(2);
682				}
683				// `used` bounds what eviction could ever free, keeping a track that
684				// can't pay (everything protected) from hoarding a stale schedule.
685				self.debt = self.debt.saturating_add(accrued).min(pool.used());
686				// Cap each payment at twice what was written so one write never dumps
687				// a deep backlog at once; the remainder carries to the next write.
688				self.pay_debt(&pool, written.saturating_mul(2));
689			}
690			// Under capacity there is nothing to work off, and stale debt would
691			// cause a spurious eviction burst at the next pressure spike.
692			None => self.debt = 0,
693		}
694	}
695
696	/// Whether this track's oldest evictable group was accessed at or before the
697	/// pool-wide average, doubling the debt it accrues. A dead entry at the front
698	/// just reads as not-stale until the next payment or expiry cleans it up.
699	fn oldest_is_stale(&self, pool: &cache::Pool) -> bool {
700		let Some(average) = pool.average() else {
701			return false;
702		};
703		let Some((sequence, stamp)) = self.evict.front() else {
704			return false;
705		};
706		let Some(slot) = self.lookup.get(sequence) else {
707			return false;
708		};
709		slot.stamp == *stamp && !slot.group.is_aborted() && slot.group.cache_accessed() <= average
710	}
711
712	/// Abort this track's stalest groups until the outstanding debt is paid, or
713	/// `cap` bytes have been freed by this call.
714	///
715	/// Deliberately approximate, Redis-style: at most a handful of live candidates
716	/// are examined per call, from the front of the eviction order. A group
717	/// accessed more recently than the pool-wide average is protected and rotates
718	/// to the back, so fresh content in this track never dies while staler content
719	/// survives elsewhere; the unfreed bytes keep the pool over budget, shifting
720	/// the debt onto the tracks holding that staler content. When the next victim
721	/// is larger than the remaining debt it is left in place and the debt carries
722	/// over, so a small write never evicts a huge group (once the debt does cover
723	/// it, that one victim may overshoot `cap`).
724	fn pay_debt(&mut self, pool: &cache::Pool, cap: u64) {
725		let average = pool.average().unwrap_or(0);
726		let mut paid = 0u64;
727		let mut scanned = 0usize;
728		for _ in 0..self.evict.len() {
729			if self.debt == 0 || paid >= cap || scanned >= EVICT_SCAN {
730				return;
731			}
732			let Some((sequence, stamp)) = self.evict.pop_front() else {
733				return;
734			};
735			let Some(slot) = self.lookup.get(&sequence) else {
736				// Evicted or expired; discard the dead entry.
737				continue;
738			};
739			if slot.stamp != stamp {
740				// A historical hint; the live entry is elsewhere in the queue.
741				continue;
742			}
743			if slot.group.is_aborted() {
744				// Aborted upstream: the frames are already gone, reclaim the slot.
745				self.lookup.remove(&sequence);
746				continue;
747			}
748			if Some(sequence) == self.latest_group {
749				// The live edge is never enqueued, but tolerate finding it anyway.
750				self.evict.push_back((sequence, stamp));
751				continue;
752			}
753
754			scanned += 1;
755			// Protected: accessed more recently than the average (a fresh insert or
756			// a FETCH hit, which also covers a backfill still being filled). Rotate
757			// to the back.
758			if slot.group.cache_accessed() > average {
759				self.evict.push_back((sequence, stamp));
760				continue;
761			}
762			// The full footprint including overhead, so even empty groups repay
763			// their share of the budget when evicted.
764			let size = slot.group.cache_size();
765			if size > self.debt {
766				self.evict.push_front((sequence, stamp));
767				return;
768			}
769
770			self.debt -= size;
771			paid = paid.saturating_add(size);
772			let slot = self.lookup.remove(&sequence).unwrap();
773			let _ = slot.group.abort(Error::Evicted);
774		}
775	}
776
777	/// Record the exclusive final sequence, rejecting a re-finish or a boundary that
778	/// would orphan already-produced groups.
779	fn set_final(&mut self, final_sequence: u64) -> Result<()> {
780		if self.final_sequence.is_some() {
781			return Err(Error::Closed);
782		}
783		if let Some(max) = self.max_sequence
784			&& final_sequence <= max
785		{
786			return Err(Error::ProtocolViolation);
787		}
788		self.final_sequence = Some(final_sequence);
789		Ok(())
790	}
791
792	/// Whether the track has reached its end: the final boundary is set and the live
793	/// edge has caught up to it, so no further group can arrive. A future boundary
794	/// (declared via [`Producer::finish_at`] ahead of the live edge) stays incomplete
795	/// until the remaining groups are produced. Drives the end-of-stream signal from
796	/// the read methods (`recv_group` / `next_group` / `read_frame` return `None`).
797	fn is_complete(&self) -> bool {
798		self.final_sequence
799			.is_some_and(|fin| self.max_sequence.map_or(0, |max| max.saturating_add(1)) >= fin)
800	}
801
802	fn poll_finished(&self) -> Poll<Result<u64>> {
803		if let Some(fin) = self.final_sequence {
804			Poll::Ready(Ok(fin))
805		} else if let Some(err) = &self.abort {
806			Poll::Ready(Err(err.clone()))
807		} else {
808			Poll::Pending
809		}
810	}
811
812	fn modify(producer: &kio::Producer<Self>) -> Result<kio::Mut<'_, Self>> {
813		producer.write().map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
814	}
815
816	/// Insert a group fetched for a [`GroupRequest`], setting the track's [`Info`]
817	/// if it isn't accepted yet. The group's timescale comes from that info, so a
818	/// fetch can serve an as-yet-unaccepted track (e.g. a relay with no live
819	/// subscription). The group lands in the cache so a waiting
820	/// [`Fetching`] resolves via [`Self::poll_fetch`].
821	fn insert_group_request(&mut self, sequence: u64, info: Option<Info>) -> Result<group::Producer> {
822		if let Some(err) = &self.abort {
823			return Err(err.clone());
824		}
825		if let Some(fin) = self.final_sequence
826			&& sequence >= fin
827		{
828			return Err(Error::Closed);
829		}
830
831		// Adopt the supplied info only if the track hasn't been accepted yet. Groups
832		// created here charge the same account as any other, so backfill written
833		// before the track is accepted settles its debt like the rest.
834		if self.info.is_none() {
835			self.install(info.unwrap_or_default());
836		}
837		let info = self.info.clone().unwrap();
838
839		// An evicted sequence can be re-fetched; a live one is a duplicate.
840		self.claim_sequence(sequence)?;
841
842		let latency_max = info.latency_max;
843		let group = group::Producer::new(group::Info { sequence }, info, self.cache.clone());
844		// A backfill exists because someone is fetching it right now: stamp that
845		// access so the eviction walk can't kill it before the fetch resolves.
846		// It is also invisible to arrival-order subscribers: fetched on demand,
847		// not produced live by the publisher.
848		group.cache_refresh();
849		self.commit_group(&group, false, latency_max);
850		Ok(group)
851	}
852}
853
854/// A producer for a track, used to create new groups.
855#[derive(Clone)]
856pub struct Producer {
857	name: Arc<str>,
858	// The parent broadcast's info, inherited from [`broadcast::Producer::create_track`].
859	// Top link of the ownership chain; carried for identity and future inheritance.
860	broadcast: Arc<broadcast::Info>,
861	state: kio::Producer<TrackState>,
862	prev_subscription: Option<Subscription>,
863	// Shared with every clone and every `Dynamic`: its `Drop` is the teardown.
864	alive: Arc<Alive>,
865	// Ingress stats scope, inherited from a tagged [`broadcast::Producer`]. Bumped as
866	// one subscription on tag and closed when the last producer clone drops. Empty
867	// (no-op) for an untagged broadcast.
868	stats: stats::Scope,
869}
870
871impl Producer {
872	/// Build a producer for the given track metadata.
873	///
874	/// Crate-private: tracks are born from their broadcast via
875	/// [`broadcast::Producer::create_track`] (or served on demand through a
876	/// [`Request`]), which threads the broadcast's `Arc<broadcast::Info>` down. The
877	/// track opens its cache account against that broadcast's origin pool, and every
878	/// group it creates charges into it.
879	pub(crate) fn new(
880		broadcast: Arc<broadcast::Info>,
881		name: impl Into<Arc<str>>,
882		info: impl Into<Option<Info>>,
883	) -> Self {
884		let name = name.into();
885		let state = TrackState::spawn(broadcast.clone());
886		state
887			.write()
888			.ok()
889			.expect("a new track is open")
890			.install(info.into().unwrap_or_default());
891		let alive = Alive::new(name.clone(), state.clone());
892		alive.publish(None);
893		Self {
894			name,
895			state,
896			broadcast,
897			prev_subscription: None,
898			alive,
899			stats: stats::Scope::default(),
900		}
901	}
902
903	/// Attach the parent broadcast's ingress stats scope, counting this track as one
904	/// ingress subscription (closed when the last producer clone drops). Called by a
905	/// tagged [`broadcast::Producer`] when it creates the track.
906	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
907		self.alive.publish(Some(&scope));
908		self.stats = scope;
909		self
910	}
911
912	/// The track's name, unique within its broadcast.
913	pub fn name(&self) -> &str {
914		&self.name
915	}
916
917	/// The parent broadcast this track belongs to.
918	pub fn broadcast(&self) -> &broadcast::Info {
919		&self.broadcast
920	}
921
922	/// Create a new group with the given sequence number.
923	pub fn create_group(&mut self, group: group::Info) -> Result<group::Producer> {
924		let mut state = self.modify()?;
925		if let Some(fin) = state.final_sequence
926			&& group.sequence >= fin
927		{
928			return Err(Error::Closed);
929		}
930		let track = state.info.clone().unwrap();
931		let latency_max = track.latency_max;
932
933		// An evicted sequence can be re-created; a live one is a duplicate.
934		state.claim_sequence(group.sequence)?;
935
936		let group = group::Producer::new(group, track, state.cache.clone()).with_meter(self.stats.meter());
937		state.commit_group(&group, true, latency_max);
938
939		Ok(group)
940	}
941
942	/// Create a new group with the next sequence number.
943	pub fn append_group(&mut self) -> Result<group::Producer> {
944		let mut state = self.modify()?;
945		let sequence = match state.max_sequence {
946			Some(s) => s.checked_add(1).ok_or(coding::BoundsExceeded)?,
947			None => 0,
948		};
949		if let Some(fin) = state.final_sequence
950			&& sequence >= fin
951		{
952			return Err(Error::Closed);
953		}
954
955		let track = state.info.clone().unwrap();
956		let latency_max = track.latency_max;
957
958		let group =
959			group::Producer::new(group::Info { sequence }, track, state.cache.clone()).with_meter(self.stats.meter());
960		state.commit_group(&group, true, latency_max);
961
962		Ok(group)
963	}
964
965	/// Append a datagram with the next sequence number, returning the assigned sequence.
966	///
967	/// A datagram is delivered best-effort over a single QUIC datagram, parallel to the
968	/// track's groups but drawing from the same sequence namespace (so interleaving with
969	/// [`Self::append_group`] never reuses a number). There is no group fallback: each
970	/// session drops (with a debug log) any datagram whose encoded body exceeds the
971	/// transport's datagram size, and sessions that can't carry datagrams at all (IETF
972	/// moq-transport, moq-lite before 05, or stream-only transports like WebSocket) never
973	/// deliver them. Keep payloads well under the 1200-byte minimum path MTU. An origin
974	/// publisher uses this; a relay preserving upstream numbering uses
975	/// [`Self::write_datagram`].
976	pub fn append_datagram<B: crate::IntoBytes>(&mut self, timestamp: Timestamp, payload: B) -> Result<u64> {
977		let payload = payload.into_bytes();
978		if payload.len() > super::datagram::MAX_DATAGRAM_PAYLOAD {
979			return Err(Error::FrameTooLarge);
980		}
981		// Resolved before the state guard borrows `self`.
982		let meter = self.stats.meter();
983		let mut state = self.modify()?;
984		// Normalize into the track's timescale, like frames (see `group::Producer::create_frame`).
985		let timescale = state.info.as_ref().unwrap().timescale;
986		let timestamp = timestamp.convert(timescale).map_err(|_| Error::TimestampMismatch)?;
987		let sequence = match state.max_sequence {
988			Some(s) => s.checked_add(1).ok_or(coding::BoundsExceeded)?,
989			None => 0,
990		};
991		if let Some(fin) = state.final_sequence
992			&& sequence >= fin
993		{
994			return Err(Error::Closed);
995		}
996		state.max_sequence = Some(sequence);
997		meter.datagram(payload.len() as u64);
998		state.push_datagram(Datagram {
999			sequence,
1000			timestamp,
1001			payload,
1002		});
1003		Ok(sequence)
1004	}
1005
1006	/// Write a datagram with an explicit sequence number.
1007	///
1008	/// Preserves the supplied sequence (bumping the shared `max_sequence` if needed), so a
1009	/// relay can forward a datagram without renumbering it. Most origin publishers want
1010	/// [`Self::append_datagram`] instead.
1011	pub fn write_datagram(&mut self, mut datagram: Datagram) -> Result<()> {
1012		if datagram.payload.len() > super::datagram::MAX_DATAGRAM_PAYLOAD {
1013			return Err(Error::FrameTooLarge);
1014		}
1015		// Resolved before the state guard borrows `self`.
1016		let meter = self.stats.meter();
1017		let mut state = self.modify()?;
1018		// Normalize into the track's timescale, like frames (see `group::Producer::create_frame`).
1019		let timescale = state.info.as_ref().unwrap().timescale;
1020		datagram.timestamp = datagram
1021			.timestamp
1022			.convert(timescale)
1023			.map_err(|_| Error::TimestampMismatch)?;
1024		if let Some(fin) = state.final_sequence
1025			&& datagram.sequence >= fin
1026		{
1027			return Err(Error::Closed);
1028		}
1029		state.max_sequence = Some(state.max_sequence.unwrap_or(0).max(datagram.sequence));
1030		meter.datagram(datagram.payload.len() as u64);
1031		state.push_datagram(datagram);
1032		Ok(())
1033	}
1034
1035	/// Create a group with a single frame, at the given presentation timestamp.
1036	///
1037	/// The timestamp is converted into the track's timescale. For data without
1038	/// a presentation time, pass [`Timestamp::now`] explicitly.
1039	pub fn write_frame<B: crate::IntoBytes>(&mut self, timestamp: Timestamp, frame: B) -> Result<()> {
1040		let frame = crate::IntoBytes::into_bytes(frame);
1041		if frame.len() as u64 > group::MAX_GROUP_CACHE {
1042			return Err(Error::FrameTooLarge);
1043		}
1044		let mut group = self.append_group()?;
1045		group.write_frame(timestamp, frame)?;
1046		group.finish()?;
1047		Ok(())
1048	}
1049
1050	/// Mark the track as finished after the last appended group.
1051	///
1052	/// Sets the final sequence to one past the current max_sequence.
1053	/// No new groups at or above this sequence can be appended.
1054	/// NOTE: Old groups with lower sequence numbers can still arrive.
1055	pub fn finish(&mut self) -> Result<()> {
1056		let mut state = self.modify()?;
1057		let final_sequence = match state.max_sequence {
1058			Some(max) => max.checked_add(1).ok_or(coding::BoundsExceeded)?,
1059			None => 0,
1060		};
1061		state.set_final(final_sequence)
1062	}
1063
1064	/// Declare the track's exclusive final sequence, possibly ahead of the live edge.
1065	///
1066	/// `final_sequence` is the first sequence that will never be produced, so a track
1067	/// whose last group is 89 finishes at `90`. Passing a boundary beyond the current
1068	/// max_sequence records a known ending before the remaining groups arrive (e.g.
1069	/// learning a track ends at group 89 while only 87 has been received). The boundary
1070	/// must be strictly greater than the highest produced group, otherwise it would
1071	/// orphan groups that already exist ([`Error::ProtocolViolation`]).
1072	///
1073	/// Groups below `final_sequence` may still be created afterwards; groups at or above
1074	/// it are rejected. Consumers only see end-of-stream once the live edge reaches the
1075	/// boundary. Use [`Self::finish`] to finish exactly at the live edge.
1076	pub fn finish_at(&mut self, final_sequence: u64) -> Result<()> {
1077		self.modify()?.set_final(final_sequence)
1078	}
1079
1080	/// The exclusive final sequence, once [`Self::finish`] or [`Self::finish_at`] declared one.
1081	///
1082	/// `None` while the track is still open ended. Both methods reject a second boundary, so
1083	/// callers that may have already declared one check here first.
1084	pub fn final_sequence(&self) -> Option<u64> {
1085		self.state.read().final_sequence
1086	}
1087
1088	/// Abort the track with the given error.
1089	///
1090	/// Consumes the handle, since nothing can be written to an aborted track. Drops the
1091	/// cached groups so a stale [`Consumer`] can't pin them (and their frame buffers) in
1092	/// memory forever. Consumers that haven't drained yet surface the abort error instead
1093	/// of the leftover cache. Child groups are independent: a consumer that already pulled
1094	/// a [`group::Consumer`] keeps its own handle and can finish reading it.
1095	///
1096	/// [`finish`](Self::finish) is deliberately not terminal: it declares the final
1097	/// sequence, and lower-numbered groups may still be written afterwards.
1098	pub fn abort(self, err: Error) -> Result<()> {
1099		let mut guard = self.modify()?;
1100		guard.abort = Some(err);
1101		guard.clear_cache();
1102		guard.datagrams.clear();
1103		guard.close();
1104		Ok(())
1105	}
1106
1107	/// Block until there are no active consumers.
1108	pub async fn unused(&self) -> Result<()> {
1109		self.state.unused().await.map_err(|_| self.abort_reason())
1110	}
1111
1112	/// Block until there is at least one active consumer.
1113	pub async fn used(&self) -> Result<()> {
1114		self.state.used().await.map_err(|_| self.abort_reason())
1115	}
1116
1117	/// Block until the track is closed or aborted, returning the cause.
1118	pub async fn closed(&self) -> Error {
1119		kio::wait(|waiter| self.poll_closed(waiter)).await
1120	}
1121
1122	/// Poll until the track is closed or aborted; ready with the cause.
1123	pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<Error> {
1124		self.state.poll_closed(waiter).map(|()| self.abort_reason())
1125	}
1126
1127	/// The recorded abort reason, or [`Error::Dropped`] if the track closed without one.
1128	fn abort_reason(&self) -> Error {
1129		self.state.read().abort.clone().unwrap_or(Error::Dropped)
1130	}
1131
1132	/// Return true if the track has been closed.
1133	pub fn is_closed(&self) -> bool {
1134		self.state.read().is_closed()
1135	}
1136
1137	/// Return the latest sequence number successfully appended to the track.
1138	pub fn latest(&self) -> Option<u64> {
1139		self.state.read().max_sequence
1140	}
1141
1142	/// Return true if this is the same track.
1143	pub fn is_clone(&self, other: &Self) -> bool {
1144		self.state.same_channel(&other.state)
1145	}
1146
1147	/// Create a weak reference that doesn't prevent auto-close.
1148	pub(crate) fn weak(&self) -> TrackWeak {
1149		TrackWeak {
1150			name: self.name.clone(),
1151			state: self.state.weak(),
1152		}
1153	}
1154
1155	/// Create a [`Demand`]: a cloneable, watch-only handle to this track's
1156	/// subscriber demand.
1157	///
1158	/// Lets a publisher gate work (e.g. on-demand capture) on whether anyone is
1159	/// subscribed, without the ability to publish frames or close the track. The
1160	/// handle is weak, so holding one neither keeps the track alive nor pins its
1161	/// cached groups.
1162	pub fn demand(&self) -> Demand {
1163		Demand {
1164			name: self.name.clone(),
1165			state: self.state.weak(),
1166		}
1167	}
1168
1169	/// Get a consumer handle for this in-process track.
1170	///
1171	/// Unlike a wire subscription, the info is already known, so a subscription
1172	/// opened from this handle resolves immediately.
1173	pub fn consume(&self) -> Consumer {
1174		Consumer::plain(self.name.clone(), self.state.consume())
1175	}
1176
1177	/// Subscribing to this in-process track, resolving synchronously.
1178	///
1179	/// The info is fixed at creation, so there's nothing to wait for (no
1180	/// SUBSCRIBE_OK round trip). Pass `None` for [`Subscription::default`].
1181	pub fn subscribe(&self, subscription: impl Into<Option<Subscription>>) -> Subscriber {
1182		let preferences = subscription.into().unwrap_or_default();
1183
1184		// Info is fixed at creation and survives a close/abort, so read it without
1185		// requiring a live producer state. If the track already ended, the returned
1186		// subscriber surfaces the close/abort on its first read; the preferences are
1187		// simply never registered (nothing aggregates them anymore).
1188		let info = self.state.read().info.clone().expect("producer always has info");
1189		let subscription = kio::Producer::new(preferences);
1190		register_subscription(self.state.read(), &subscription);
1191
1192		Subscriber {
1193			name: self.name.clone(),
1194			info,
1195			inner: SubscriberKind::Plain(PlainSubscriber {
1196				state: self.state.consume(),
1197				subscription,
1198				index: 0,
1199				datagram_index: 0,
1200				min_sequence: 0,
1201				next_sequence: 0,
1202				end_sequence: None,
1203				parked: BTreeMap::new(),
1204			}),
1205			// A producer-side (in-process) subscribe is not egress: stay untagged.
1206			stats: stats::Scope::default(),
1207			_stats_sub: stats::Subscription::default(),
1208		}
1209	}
1210
1211	/// Block until the aggregate subscription changes, then return the new value.
1212	///
1213	/// Yields the most demanding request across all live subscribers, or `None`
1214	/// once the last one drops. Used by relays to forward downstream demand
1215	/// upstream (e.g. SUBSCRIBE_UPDATE).
1216	pub async fn subscription_changed(&mut self) -> Result<Option<Subscription>> {
1217		kio::wait(|waiter| self.poll_subscription_changed(waiter)).await
1218	}
1219
1220	/// A non-blocking snapshot of the current aggregate subscription, or `None`
1221	/// when there are no live subscribers. Unlike [`Self::subscription`], this
1222	/// doesn't wait for a change or advance the change cursor.
1223	///
1224	/// The aggregate's [`Subscription::latency_max`] is clamped to this track's
1225	/// [`Info::latency_max`]: no subscriber can wait for a late group longer than the
1226	/// publisher keeps it.
1227	pub fn subscription(&self) -> Option<Subscription> {
1228		let state = self.state.read();
1229		let (subs, bound) = (state.subscriptions.clone(), state.latency_bound());
1230		drop(state);
1231		snapshot_subscription(&subs, bound)
1232	}
1233
1234	/// Poll counterpart to [`subscription_changed`](Self::subscription_changed): the
1235	/// aggregate subscription whenever it changes, or `None` once nobody is subscribed.
1236	/// Errors once the track is aborted.
1237	pub fn poll_subscription_changed(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Subscription>>> {
1238		// Surface an abort as the stream ending. `poll_closed` parks on the closed
1239		// waiters, so per-group churn on the track state never wakes this poll.
1240		if self.state.poll_closed(waiter).is_ready() {
1241			let abort = self.state.read().abort.clone();
1242			return Poll::Ready(Err(abort.unwrap_or(Error::Dropped)));
1243		}
1244
1245		// Read the bound before locking `subs`, so the aggregation never nests the two locks.
1246		let state = self.state.read();
1247		let (subs, bound) = (state.subscriptions.clone(), state.latency_bound());
1248		drop(state);
1249
1250		let prev = &self.prev_subscription;
1251		let mut combined = None;
1252		let mut guard = match subs.poll(waiter, |subs| {
1253			let next = combined_subscription(subs, bound, waiter);
1254			if &next == prev {
1255				Poll::Pending
1256			} else {
1257				combined = next;
1258				Poll::Ready(())
1259			}
1260		}) {
1261			Poll::Ready(guard) => guard,
1262			Poll::Pending => return Poll::Pending,
1263		};
1264		// The aggregate changed: prune any closed subscribers now that we hold the lock.
1265		guard.retain(|sub| !sub.is_closed());
1266		drop(guard);
1267		self.prev_subscription = combined.clone();
1268		Poll::Ready(Ok(combined))
1269	}
1270
1271	/// Poll for the producer becoming unused (every consumer dropped).
1272	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
1273		self.state.poll_unused(waiter).map(|_| ())
1274	}
1275
1276	/// Create a [`Dynamic`] handle that serves on-demand fetches of uncached
1277	/// (old) groups. Most producers never need this; a relay creates one to fetch
1278	/// past groups from upstream.
1279	pub fn dynamic(&self) -> Dynamic {
1280		Dynamic::new(self.name.clone(), self.state.clone(), self.alive.clone())
1281	}
1282
1283	fn modify(&self) -> Result<kio::Mut<'_, TrackState>> {
1284		TrackState::modify(&self.state)
1285	}
1286}
1287
1288/// Pop the next queued group fetch off the fetch queue and wrap it in a
1289/// [`GroupRequest`] bound to a fresh producer handle. Shared by every
1290/// [`Dynamic`] handle on the track.
1291fn poll_requested_group(
1292	state: &kio::Producer<TrackState>,
1293	fetch: &kio::Shared<FetchState>,
1294	waiter: &kio::Waiter,
1295) -> Poll<Result<GroupRequest>> {
1296	// Prefer serving a queued fetch, even if the track has since aborted.
1297	if let Poll::Ready(mut guard) = fetch.poll(waiter, |fetch| {
1298		if fetch.has_queued() {
1299			Poll::Ready(())
1300		} else {
1301			Poll::Pending
1302		}
1303	}) {
1304		let sequence = guard.pop().expect("predicate guaranteed a request");
1305		// The popped attempt stays pending, so a fetch in the window between hand-off
1306		// and accept joins it instead of queueing a duplicate.
1307		// `GroupRequest::{accept, reject, drop}` removes the entry.
1308		let pending = guard.get(&sequence).expect("popped key must be pending");
1309		let priority = pending.priority;
1310		let result = pending.result.clone();
1311		drop(guard);
1312		return Poll::Ready(Ok(GroupRequest {
1313			state: state.clone(),
1314			fetch: fetch.clone(),
1315			sequence,
1316			priority,
1317			result,
1318			done: false,
1319		}));
1320	}
1321
1322	// No fetch queued: surface a track abort so the handler loop can exit.
1323	match state.poll_ref(waiter, |state| match &state.abort {
1324		Some(err) => Poll::Ready(err.clone()),
1325		None => Poll::Pending,
1326	}) {
1327		Poll::Ready(Ok(err)) => Poll::Ready(Err(err)),
1328		Poll::Ready(Err(closed)) => Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped))),
1329		Poll::Pending => Poll::Pending,
1330	}
1331}
1332
1333/// Serves on-demand fetches of uncached (old) groups for a track, the group-level
1334/// analogue of [`broadcast::Dynamic`].
1335///
1336/// Most tracks never serve old content, so this capability lives on a dedicated
1337/// handle rather than [`Producer`]: a relay creates one (via
1338/// [`Producer::dynamic`] or [`Request::dynamic`]) to pull past groups
1339/// from upstream. While at least one is alive the track will block a cache-miss
1340/// [`Consumer::fetch_group`] waiting to be served; with none, an accepted track's
1341/// miss fails fast with [`Error::NotFound`].
1342pub struct Dynamic {
1343	name: Arc<str>,
1344	// Kept to insert served groups into the cache and observe track abort.
1345	state: kio::Producer<TrackState>,
1346	// The fetch queue this handle drains; its `dynamic` count gates `fetch_group`.
1347	fetch: kio::Shared<FetchState>,
1348	// Shared with the track's producers: a handler still serving fetches keeps the
1349	// track alive, like a producer clone does.
1350	alive: Arc<Alive>,
1351}
1352
1353impl Dynamic {
1354	fn new(name: Arc<str>, state: kio::Producer<TrackState>, alive: Arc<Alive>) -> Self {
1355		let fetch = state.read().fetch.clone();
1356		fetch.lock().add_handler();
1357		Self {
1358			name,
1359			state,
1360			fetch,
1361			alive,
1362		}
1363	}
1364
1365	/// The track's name, unique within its broadcast.
1366	pub fn name(&self) -> &str {
1367		&self.name
1368	}
1369
1370	/// Block until a consumer fetches a group that isn't cached, returning a
1371	/// [`GroupRequest`] to serve via [`GroupRequest::accept`].
1372	///
1373	/// A relay issues a wire FETCH first; an origin already has the group cached, so
1374	/// the fetch resolves without ever reaching here. Errors once the track is aborted.
1375	pub async fn requested_group(&self) -> Result<GroupRequest> {
1376		kio::wait(|waiter| self.poll_requested_group(waiter)).await
1377	}
1378
1379	/// Poll counterpart to [`requested_group`](Self::requested_group).
1380	pub fn poll_requested_group(&self, waiter: &kio::Waiter) -> Poll<Result<GroupRequest>> {
1381		poll_requested_group(&self.state, &self.fetch, waiter)
1382	}
1383
1384	/// Poll for the track becoming unused (every consumer dropped).
1385	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
1386		self.state.poll_unused(waiter).map(|_| ())
1387	}
1388}
1389
1390impl Clone for Dynamic {
1391	fn clone(&self) -> Self {
1392		// Count each live handle (mirrors `broadcast::Dynamic`).
1393		self.fetch.lock().add_handler();
1394		Self {
1395			name: self.name.clone(),
1396			state: self.state.clone(),
1397			fetch: self.fetch.clone(),
1398			alive: self.alive.clone(),
1399		}
1400	}
1401}
1402
1403impl Drop for Dynamic {
1404	fn drop(&mut self) {
1405		// Unlike `broadcast::Dynamic`, dropping the last handle doesn't abort the track:
1406		// a live `Producer` may still be serving the subscription. It just stops fetch
1407		// serving. Queued attempts no handler will ever pop are dropped, closing their
1408		// result channels so every joined `Fetching` resolves NotFound; an attempt
1409		// already handed to a handler stays, resolved by its `GroupRequest` instead.
1410		let mut fetch = self.fetch.lock();
1411		if fetch.remove_handler() {
1412			fetch.drain_queued();
1413		}
1414	}
1415}
1416
1417/// Ends the track when the last [`Producer`] or [`Dynamic`] drops.
1418///
1419/// A refcount rather than a "am I the last one?" check inside `Drop`: that answer is a
1420/// snapshot, and acting on it is exactly what invalidates it. The track state's own
1421/// producer count can't answer it either, since a group settling its eviction debt
1422/// upgrades the account's weak handle and counts there for the duration (see
1423/// [`cache::Track::settle`]). Holding a producer of its own also keeps the state
1424/// writable until the teardown has run, whatever order the last owner's fields drop in.
1425struct Alive {
1426	name: Arc<str>,
1427	state: kio::Producer<TrackState>,
1428
1429	// Set when a `Producer` is first minted, so a `Request` nobody accepted (its
1430	// `Dynamic` holds this guard too) isn't reported as an abandoned publisher.
1431	published: AtomicBool,
1432
1433	// Ingress subscription for this track, opened by the tagged producer that claimed
1434	// it and closed when this guard drops.
1435	stats: OnceLock<stats::Subscription>,
1436}
1437
1438impl Alive {
1439	fn new(name: Arc<str>, state: kio::Producer<TrackState>) -> Arc<Self> {
1440		Arc::new(Self {
1441			name,
1442			state,
1443			published: Default::default(),
1444			stats: Default::default(),
1445		})
1446	}
1447
1448	/// Note that a [`Producer`] was minted from this track, optionally under a tagged
1449	/// broadcast's ingress scope (counted as one subscription for as long as the track
1450	/// has a publisher).
1451	fn publish(&self, stats: Option<&stats::Scope>) {
1452		self.published.store(true, Ordering::Relaxed);
1453		if let Some(scope) = stats {
1454			// At most one scope ever arrives: a track is minted either through
1455			// `Producer::new` (+ `with_stats`) or through `Request::accept`, never both.
1456			let _ = self.stats.set(scope.subscribe());
1457		}
1458	}
1459}
1460
1461impl Drop for Alive {
1462	fn drop(&mut self) {
1463		// A request nobody accepted was never publishing; there's nothing to tear down.
1464		if !self.published.load(Ordering::Relaxed) {
1465			return;
1466		}
1467		// The last producer going away without finishing is an abrupt teardown:
1468		// release the cached groups so a stale consumer can't pin them (and their
1469		// frame buffers) forever, the same as an explicit abort. A cleanly
1470		// finished track keeps its cache so consumers can still drain it.
1471		//
1472		// `abort()` closes the channel, so `write()` returns `Err(Ref)`. `finish()`
1473		// leaves it open with `final_sequence` set, so inspect both outcomes.
1474		match self.state.write() {
1475			Ok(mut state) => {
1476				if state.final_sequence.is_some() || state.abort.is_some() {
1477					return;
1478				}
1479				tracing::warn!(
1480					track = %self.name,
1481					"track::Producer dropped without finish() or abort()"
1482				);
1483				state.clear_cache();
1484				state.datagrams.clear();
1485			}
1486			Err(state) => {
1487				if state.final_sequence.is_some() || state.abort.is_some() {
1488					return;
1489				}
1490				tracing::warn!(
1491					track = %self.name,
1492					"track::Producer dropped without finish() or abort()"
1493				);
1494			}
1495		}
1496	}
1497}
1498
1499/// Aggregate every live subscriber's preferences into the most demanding request.
1500///
1501/// Read-only: iterates the subscriptions immutably and registers `waiter` on each, so a
1502/// preference update (or a subscriber dropping) wakes the caller's poll. Callers decide
1503/// readiness from the returned value, then prune closed subscribers through the `Mut`.
1504fn combined_subscription(subs: &Subscriptions, bound: Option<Duration>, waiter: &kio::Waiter) -> Option<Subscription> {
1505	let mut combined = None;
1506	for sub in subs.iter() {
1507		// A closed consumer means the subscriber dropped: it holds no live demand.
1508		// `Consumer::poll` evaluates the closure before the closed flag, so it would
1509		// still replay the final value into the aggregate; skip it explicitly so a
1510		// departed subscriber can't keep the aggregate pinned to its last request.
1511		if sub.is_closed() {
1512			continue;
1513		}
1514		// Arm the closed waiter explicitly. `poll` below registers on the value
1515		// channel only when it returns Pending, so a subscriber that contributes
1516		// demand (always the case for the first one) would leave nothing watching
1517		// for its departure, and the last one leaving would never wake this poll.
1518		let _ = sub.poll_closed(waiter);
1519		if let Poll::Ready(Ok(sub)) = sub.poll(waiter, |sub| sub.poll_combined(&combined)) {
1520			combined = Some(sub);
1521		}
1522	}
1523	clamp_combined(combined, bound)
1524}
1525
1526/// A non-blocking aggregate of the current subscriptions, without arming any waiter.
1527fn snapshot_subscription(subs: &kio::Shared<Subscriptions>, bound: Option<Duration>) -> Option<Subscription> {
1528	let mut combined: Option<Subscription> = None;
1529	for sub in subs.read().iter() {
1530		// Skip dropped subscribers, matching `combined_subscription`.
1531		if sub.is_closed() {
1532			continue;
1533		}
1534		if let Poll::Ready(merged) = sub.read().poll_combined(&combined) {
1535			combined = Some(merged);
1536		}
1537	}
1538	clamp_combined(combined, bound)
1539}
1540
1541/// Clamp the aggregate's latency budget to the publisher's window: nobody can wait for a
1542/// late group longer than the publisher keeps it around.
1543///
1544/// The single clamp point. Subscribers hold their preferences verbatim, so what they asked
1545/// for stays readable, and clamping the aggregate is equivalent to clamping each subscriber
1546/// first (`min` distributes over the `max` that combines them). `bound` is `None` on a track
1547/// whose info isn't known yet (an unaccepted [`Request`]), which imposes no window.
1548fn clamp_combined(combined: Option<Subscription>, bound: Option<Duration>) -> Option<Subscription> {
1549	let mut combined = combined?;
1550	if let Some(bound) = bound {
1551		combined.latency_max = combined.latency_max.min(bound);
1552	}
1553	Some(combined)
1554}
1555
1556/// Register a subscription if the track is live: clone the shared list out of the
1557/// state, release the track lock, then push under the list's own lock. A closed
1558/// track skips the push; nothing aggregates the preferences anymore.
1559fn register_subscription(state: kio::Ref<'_, TrackState>, subscription: &kio::Producer<Subscription>) {
1560	if state.is_closed() {
1561		return;
1562	}
1563	let subs = state.subscriptions.clone();
1564	drop(state);
1565	subs.lock().push(subscription.consume());
1566}
1567
1568/// A weak reference to a track that doesn't prevent auto-close.
1569#[derive(Clone)]
1570pub(crate) struct TrackWeak {
1571	name: Arc<str>,
1572	state: kio::ProducerWeak<TrackState>,
1573}
1574
1575impl TrackWeak {
1576	pub fn consume(&self) -> Consumer {
1577		Consumer::plain(self.name.clone(), self.state.consume())
1578	}
1579
1580	/// The shared name handle, for use as a broadcast lookup key (clone is a
1581	/// refcount bump, and the same `Arc` is shared with the track's handles).
1582	pub(crate) fn name(&self) -> &Arc<str> {
1583		&self.name
1584	}
1585
1586	/// Whether anyone is consuming the track right now. A closed track doesn't
1587	/// count even if consumers linger to drain its cache: no new work is owed.
1588	pub(crate) fn is_used(&self) -> bool {
1589		!self.state.is_closed() && self.state.is_used()
1590	}
1591
1592	/// Park `waiter` for the next consumer appearing; a no-op once one exists.
1593	/// Feeds [`crate::broadcast::Demand`], which recomputes on wake.
1594	pub(crate) fn poll_used(&self, waiter: &kio::Waiter) {
1595		let _ = self.state.poll_used(waiter);
1596	}
1597
1598	/// Park `waiter` for the last consumer (or the track) going away; a no-op
1599	/// once none remain. Feeds [`crate::broadcast::Demand`].
1600	pub(crate) fn poll_unused(&self, waiter: &kio::Waiter) {
1601		let _ = self.state.poll_unused(waiter);
1602	}
1603}
1604
1605impl super::WeakEntry for TrackWeak {
1606	fn is_closed(&self) -> bool {
1607		self.state.is_closed()
1608	}
1609
1610	fn same_channel(&self, other: &Self) -> bool {
1611		self.state.same_channel(&other.state)
1612	}
1613}
1614
1615/// A cloneable, watch-only handle to a track's subscriber demand.
1616///
1617/// Obtained from [`Producer::demand`]. A publisher uses it to react to
1618/// whether anyone is subscribed (on-demand capture / encoding) without being able
1619/// to publish frames or close the track. It's a weak handle, so it neither keeps
1620/// the track alive nor pins its cached groups; once the owning [`Producer`]
1621/// goes away, [`used`](Self::used) / [`unused`](Self::unused) report the track's
1622/// closure.
1623#[derive(Clone)]
1624pub struct Demand {
1625	name: Arc<str>,
1626	state: kio::ProducerWeak<TrackState>,
1627}
1628
1629impl Demand {
1630	/// The track name this handle is bound to.
1631	pub fn name(&self) -> &str {
1632		&self.name
1633	}
1634
1635	/// Block until there is at least one active consumer.
1636	pub async fn used(&self) -> Result<()> {
1637		self.state.used().await.map_err(|_| self.abort_reason())
1638	}
1639
1640	/// Block until there are no active consumers.
1641	pub async fn unused(&self) -> Result<()> {
1642		self.state.unused().await.map_err(|_| self.abort_reason())
1643	}
1644
1645	/// Block until the track is closed or aborted, returning the cause.
1646	pub async fn closed(&self) -> Error {
1647		self.state.closed().await;
1648		self.abort_reason()
1649	}
1650
1651	/// The recorded abort reason, or [`Error::Dropped`] if the track closed without one.
1652	fn abort_reason(&self) -> Error {
1653		self.state.read().abort.clone().unwrap_or(Error::Dropped)
1654	}
1655}
1656
1657/// A handle to a single track within a broadcast.
1658///
1659/// Obtained from [`broadcast::Consumer::track`]. Holding it sends nothing
1660/// to the publisher; it just names a track you can [`subscribe`](Self::subscribe)
1661/// to (a live, ongoing stream of groups) later. The same handle can be subscribed
1662/// to multiple times, and clones are cheap.
1663///
1664/// A track reached through a route-fed broadcast is *spliced*: it is backed by one
1665/// or more per-session tracks joined at group boundaries, and this handle reads
1666/// across them transparently.
1667#[derive(Clone)]
1668pub struct Consumer {
1669	name: Arc<str>,
1670	inner: ConsumerKind,
1671	// Egress stats scope, set by a tagged [`broadcast::Consumer`] via
1672	// [`Self::with_stats`]. Empty (no-op) for an untagged track.
1673	stats: stats::Scope,
1674}
1675
1676#[derive(Clone)]
1677enum ConsumerKind {
1678	Plain(kio::Consumer<TrackState>),
1679	Spliced(super::resume::Consumer),
1680}
1681
1682impl Consumer {
1683	fn plain(name: Arc<str>, state: kio::Consumer<TrackState>) -> Self {
1684		Self {
1685			name,
1686			inner: ConsumerKind::Plain(state),
1687			stats: stats::Scope::default(),
1688		}
1689	}
1690
1691	/// A consumer over a spliced logical track (a route-fed broadcast's track).
1692	pub(crate) fn spliced(name: Arc<str>, resume: super::resume::Consumer) -> Self {
1693		Self {
1694			name,
1695			inner: ConsumerKind::Spliced(resume),
1696			stats: stats::Scope::default(),
1697		}
1698	}
1699
1700	/// Attach an egress stats scope, inherited by the subscriptions, fetches, and
1701	/// groups derived from this handle. Called by a tagged [`broadcast::Consumer`].
1702	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
1703		self.stats = scope;
1704		self
1705	}
1706
1707	/// The track name this handle is bound to.
1708	pub fn name(&self) -> &str {
1709		&self.name
1710	}
1711
1712	/// Open a live subscription.
1713	///
1714	/// Registers the subscription on the track and returns a [`kio::Pending`] that resolves to the
1715	/// [`Subscriber`] once the track info is available, or the track's abort error (or
1716	/// [`Error::Dropped`]) if it is already closed.
1717	pub fn subscribe(&self, subscription: impl Into<Option<Subscription>>) -> kio::Pending<Subscribing> {
1718		let subscription = kio::Producer::new(subscription.into().unwrap_or_default());
1719
1720		let inner = match &self.inner {
1721			ConsumerKind::Plain(state) => {
1722				// Register the subscription if the track is live. If it is already closed, the
1723				// returned future resolves to the abort error via `Subscribing::poll_ok`.
1724				register_subscription(state.read(), &subscription);
1725				SubscribingKind::Plain(state.clone())
1726			}
1727			// A spliced subscription registers per segment once the subscriber polls.
1728			ConsumerKind::Spliced(resume) => SubscribingKind::Spliced(resume.clone()),
1729		};
1730
1731		kio::Pending::new(Subscribing {
1732			name: self.name.clone(),
1733			inner,
1734			subscription,
1735			stats: self.stats.clone(),
1736		})
1737	}
1738
1739	// Peek at a cached group by sequence without blocking, or `None` if it isn't in the
1740	// cache. A test hook for asserting cache state; the library reads
1741	// `TrackState::cached_group` directly, and callers want `fetch_group`.
1742	#[cfg(test)]
1743	pub(crate) fn peek_group(&self, sequence: u64) -> Option<group::Consumer> {
1744		match &self.inner {
1745			ConsumerKind::Plain(state) => state.read().cached_group(sequence),
1746			// Spliced tracks have no cache of their own; peek the newest segment
1747			// via `fetch_group` instead.
1748			ConsumerKind::Spliced(_) => None,
1749		}
1750	}
1751
1752	/// Fetching a single past group, without holding a live subscription.
1753	///
1754	/// Returns a [`kio::Pending`] that resolves to the [`group::Consumer`]:
1755	/// immediately if the group is cached, otherwise once a [`Dynamic`] serves
1756	/// the request (a wire FETCH for a relay). `options` accepts `None`, a [`group::Fetch`],
1757	/// or `group::Fetch::default()`.
1758	///
1759	/// The returned future resolves to [`Error::NotFound`] when the group can never be served
1760	/// (past the final sequence, or no [`Dynamic`] on the track), or the track's abort error
1761	/// if it's already closed. Concurrent fetches for the same sequence coalesce onto one
1762	/// handler request.
1763	pub fn fetch_group(&self, sequence: u64, options: impl Into<Option<group::Fetch>>) -> kio::Pending<Fetching> {
1764		let options = options.into().unwrap_or_default();
1765
1766		// One fetch per calling context, counted here (coalesced upstream work is
1767		// still one request served). Independent of `subscriptions` and the viewer
1768		// refcount.
1769		self.stats.fetch();
1770
1771		let state = match &self.inner {
1772			ConsumerKind::Plain(state) => state,
1773			// Spliced: routed to the newest segment's (plain) track, waiting for a
1774			// segment to exist if no route has served the track yet.
1775			ConsumerKind::Spliced(resume) => {
1776				return kio::Pending::new(Fetching {
1777					inner: FetchingKind::Spliced(resume.fetch_group(sequence, options)),
1778					stats: self.stats.clone(),
1779				});
1780			}
1781		};
1782
1783		let mut result = None;
1784
1785		// Queue a request only when the group isn't already resolvable from the track
1786		// (cached, aborted, or past-final all resolve through `Fetching::poll` without
1787		// a queue entry).
1788		let (fetch, unresolved) = {
1789			let state = state.read();
1790			(state.fetch.clone(), state.poll_fetch_cached(sequence).is_pending())
1791		};
1792
1793		if unresolved {
1794			let mut fetch = fetch.lock();
1795			if let Some(pending) = fetch.join(&sequence) {
1796				// Join the in-flight attempt for this sequence (queued or already being
1797				// served): share its result channel, raising its priority if ours is higher.
1798				pending.priority = pending.priority.max(options.priority);
1799				result = Some(pending.result.consume());
1800			} else {
1801				// Queue a new attempt. The handler gate is atomic with a handler
1802				// dropping (no fetch stranded on a queue nobody drains); with no
1803				// handler, `Fetching::poll` fails fast instead.
1804				let producer = kio::Producer::<FetchOutcome>::default();
1805				let consumer = producer.consume();
1806				let attempt = PendingFetch {
1807					priority: options.priority,
1808					result: producer,
1809				};
1810				if fetch.insert(sequence, attempt).is_ok() {
1811					result = Some(consumer);
1812				}
1813			}
1814		}
1815
1816		kio::Pending::new(Fetching {
1817			inner: FetchingKind::Plain {
1818				state: state.clone(),
1819				fetch,
1820				sequence,
1821				result,
1822			},
1823			stats: self.stats.clone(),
1824		})
1825	}
1826
1827	/// Resolve the track's [`Info`] without subscribing.
1828	///
1829	/// A [`Consumer`] is a lazy handle, so the info may not be known yet: this waits
1830	/// for the producer to [`Request::accept`] the track (a wire TRACK_INFO round-trip
1831	/// for a relay), and errors with the track's abort error if it closes first.
1832	/// [`Subscriber::info`] is the already-resolved counterpart.
1833	pub fn info(&self) -> kio::Pending<Querying> {
1834		kio::Pending::new(Querying {
1835			inner: match &self.inner {
1836				ConsumerKind::Plain(state) => QueryingKind::Plain(state.clone()),
1837				ConsumerKind::Spliced(resume) => QueryingKind::Spliced(resume.clone()),
1838			},
1839		})
1840	}
1841
1842	/// Return the latest group sequence in the track, or `None` before any group.
1843	pub fn latest(&self) -> Option<u64> {
1844		match &self.inner {
1845			ConsumerKind::Plain(state) => state.read().max_sequence,
1846			ConsumerKind::Spliced(resume) => resume.latest(),
1847		}
1848	}
1849
1850	/// Poll for the track reaching a terminal state: `Ok(())` once it is complete
1851	/// (the final group was produced), `Err` once it closed or aborted before
1852	/// completing. The origin's dispatcher uses this to tell a track that truly
1853	/// ended from one whose serving route died mid-stream.
1854	pub(crate) fn poll_complete(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
1855		let ConsumerKind::Plain(state) = &self.inner else {
1856			// Spliced tracks are compositions; the dispatcher never monitors one.
1857			return Poll::Pending;
1858		};
1859		match ready!(state.poll(waiter, |state| {
1860			if state.is_complete() {
1861				Poll::Ready(())
1862			} else {
1863				Poll::Pending
1864			}
1865		})) {
1866			Ok(_) => Poll::Ready(Ok(())),
1867			// Closed before completing. Read through the returned guard: it holds
1868			// the lock, so re-locking the channel here would deadlock.
1869			Err(closed) => Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped))),
1870		}
1871	}
1872}
1873
1874/// The pollable state of a [`Consumer::subscribe`]; awaited via the
1875/// [`kio::Pending`] wrapper, whose `DerefMut` exposes [`Self::update`].
1876pub struct Subscribing {
1877	name: Arc<str>,
1878	inner: SubscribingKind,
1879	subscription: kio::Producer<Subscription>,
1880	stats: stats::Scope,
1881}
1882
1883enum SubscribingKind {
1884	Plain(kio::Consumer<TrackState>),
1885	Spliced(super::resume::Consumer),
1886}
1887
1888impl Subscribing {
1889	/// Poll until the peer confirms the subscription, yielding the [`Subscriber`].
1890	/// Errors if the track is aborted or not found.
1891	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<Subscriber>> {
1892		match &self.inner {
1893			SubscribingKind::Plain(state) => {
1894				// Wait until the track info is available
1895				let info = ready!(state.poll(waiter, |state| state.poll_info()))
1896					.map_err(|e| e.abort.clone().unwrap_or(Error::Dropped))??;
1897
1898				Poll::Ready(Ok(Subscriber {
1899					name: self.name.clone(),
1900					info,
1901					inner: SubscriberKind::Plain(PlainSubscriber {
1902						state: state.clone(),
1903						subscription: self.subscription.clone(),
1904						index: 0,
1905						datagram_index: 0,
1906						min_sequence: 0,
1907						next_sequence: 0,
1908						end_sequence: None,
1909						parked: BTreeMap::new(),
1910					}),
1911					stats: self.stats.clone(),
1912					_stats_sub: self.stats.subscribe(),
1913				}))
1914			}
1915			SubscribingKind::Spliced(resume) => {
1916				// Resolved from the first segment's track. The publisher's latency
1917				// window is applied to each per-session aggregate, not here.
1918				let info = ready!(resume.poll_info(waiter))?;
1919
1920				Poll::Ready(Ok(Subscriber {
1921					name: self.name.clone(),
1922					info,
1923					inner: SubscriberKind::Spliced(Box::new(resume.subscribe_shared(self.subscription.clone()))),
1924					stats: self.stats.clone(),
1925					_stats_sub: self.stats.subscribe(),
1926				}))
1927			}
1928		}
1929	}
1930
1931	/// Change the subscription preferences before (or after) it resolves.
1932	///
1933	/// Returns [`Error::Closed`] if the track already ended; the update is
1934	/// meaningless at that point and can usually be ignored.
1935	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
1936		let mut state = self.subscription.write().map_err(|_| Error::Closed)?;
1937		*state = subscription;
1938		Ok(())
1939	}
1940}
1941
1942impl kio::Pollable for Subscribing {
1943	type Output = Result<Subscriber>;
1944
1945	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
1946		self.poll_ok(waiter)
1947	}
1948}
1949
1950/// The pollable state of a [`Consumer::info`]; awaited via the
1951/// [`kio::Pending`] wrapper.
1952pub struct Querying {
1953	inner: QueryingKind,
1954}
1955
1956enum QueryingKind {
1957	Plain(kio::Consumer<TrackState>),
1958	Spliced(super::resume::Consumer),
1959}
1960
1961impl Querying {
1962	/// Poll until the track's [`Info`] is known, without subscribing to its groups.
1963	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<Info>> {
1964		match &self.inner {
1965			QueryingKind::Plain(state) => {
1966				// Wait until the track info is available
1967				let info = ready!(state.poll(waiter, |state| state.poll_info()))
1968					.map_err(|e| e.abort.clone().unwrap_or(Error::Dropped))??;
1969				Poll::Ready(Ok(info))
1970			}
1971			QueryingKind::Spliced(resume) => resume.poll_info(waiter),
1972		}
1973	}
1974}
1975
1976impl kio::Pollable for Querying {
1977	type Output = Result<Info>;
1978
1979	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
1980		self.poll_ok(waiter)
1981	}
1982}
1983
1984/// A consumer's request for a single past group, handed to a handler via
1985/// [`Dynamic::requested_group`].
1986///
1987/// The handler fulfills it by calling [`Self::accept`], which inserts the group
1988/// into the track cache (resolving every [`Consumer::fetch_group`] that joined the
1989/// attempt) and returns a [`group::Producer`] to fill. A relay typically opens a wire
1990/// FETCH, reads FETCH_OK, then accepts. The request carries its own producer handle,
1991/// so it works the same whether or not the track has been accepted yet.
1992pub struct GroupRequest {
1993	state: kio::Producer<TrackState>,
1994	// To remove this attempt from the fetch state once it resolves.
1995	fetch: kio::Shared<FetchState>,
1996	sequence: u64,
1997	priority: u8,
1998	// Rejections route back to every joined `Fetching`.
1999	result: kio::Producer<FetchOutcome>,
2000	done: bool,
2001}
2002
2003impl GroupRequest {
2004	/// The group sequence the consumer wants.
2005	pub fn sequence(&self) -> u64 {
2006		self.sequence
2007	}
2008
2009	/// The delivery priority the consumer requested for this group.
2010	pub fn priority(&self) -> u8 {
2011		self.priority
2012	}
2013
2014	/// Insert the fetched group into the track cache, resolving the waiting
2015	/// [`Consumer::fetch_group`], and return a [`group::Producer`] to fill.
2016	///
2017	/// The group's timescale comes from the track's [`Info`]. `info` sets that
2018	/// info if the track hasn't been accepted yet (a fetch with no live subscription),
2019	/// and is ignored once accepted. Returns [`Error::Duplicate`] if the group is
2020	/// already present, or the track's abort error if it closed while pending.
2021	pub fn accept(mut self, info: impl Into<Option<Info>>) -> Result<group::Producer> {
2022		self.done = true;
2023		// Cache the group before removing the attempt: the joined fetches resolve
2024		// through the cache, and removal closes their result channel (which alone
2025		// would read as NotFound).
2026		let res = TrackState::modify(&self.state)
2027			.and_then(|mut state| state.insert_group_request(self.sequence, info.into()));
2028		self.remove();
2029		res
2030	}
2031
2032	/// Reject the fetch, resolving every joined [`Consumer::fetch_group`] with `err`.
2033	pub fn reject(mut self, err: Error) {
2034		self.done = true;
2035		// Remove before writing, so a fetch arriving now starts a fresh attempt
2036		// instead of joining a rejected one.
2037		self.remove();
2038		if let Ok(mut outcome) = self.result.write() {
2039			outcome.rejected = Some(err);
2040		}
2041	}
2042
2043	/// Remove this attempt from the fetch state, unless a newer attempt for the same
2044	/// sequence has already replaced it.
2045	fn remove(&self) {
2046		self.fetch
2047			.lock()
2048			.remove_if(&self.sequence, |pending| pending.result.same_channel(&self.result));
2049	}
2050}
2051
2052impl Drop for GroupRequest {
2053	fn drop(&mut self) {
2054		if self.done {
2055			return;
2056		}
2057		self.remove();
2058		if let Ok(mut outcome) = self.result.write() {
2059			outcome.rejected = Some(Error::Dropped);
2060		}
2061	}
2062}
2063
2064/// The pollable state of a [`Consumer::fetch_group`].
2065///
2066/// Awaited via the [`kio::Pending`] wrapper; resolves to the
2067/// [`group::Consumer`] once the group lands in the track's cache (already present,
2068/// or produced after a wire FETCH), or [`Error::NotFound`] if it can never exist.
2069pub struct Fetching {
2070	inner: FetchingKind,
2071	// Egress stats scope, so the resolved group carries a payload meter (and counts
2072	// as one delivered group). Empty (no-op) for an untagged track.
2073	stats: stats::Scope,
2074}
2075
2076enum FetchingKind {
2077	Plain {
2078		state: kio::Consumer<TrackState>,
2079		fetch: kio::Shared<FetchState>,
2080		sequence: u64,
2081		// The joined attempt's result channel; `None` when no handler existed to queue on.
2082		result: Option<kio::Consumer<FetchOutcome>>,
2083	},
2084	/// A spliced track's fetch: waits for a segment, then fetches from it.
2085	Spliced(kio::Pending<super::resume::Fetching>),
2086}
2087
2088impl kio::Pollable for Fetching {
2089	type Output = Result<group::Consumer>;
2090
2091	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
2092		let (state, fetch, sequence, result) = match &self.inner {
2093			FetchingKind::Plain {
2094				state,
2095				fetch,
2096				sequence,
2097				result,
2098			} => (state, fetch, *sequence, result.as_ref()),
2099			FetchingKind::Spliced(spliced) => {
2100				// A fetched group is metered here (once), at the tagged handle: the
2101				// spliced source track it comes from is the origin's own, untagged.
2102				return kio::Pollable::poll(&**spliced, waiter)
2103					.map(|res| res.map(|group| group.with_meter(self.stats.meter())));
2104			}
2105		};
2106
2107		// Track side: the cached group, the abort error, or past-final. The outer
2108		// error is the channel closing without any of those.
2109		match state.poll(waiter, |state| state.poll_fetch_cached(sequence)) {
2110			Poll::Ready(Ok(res)) => return Poll::Ready(res.map(|group| group.with_meter(self.stats.meter()))),
2111			Poll::Ready(Err(closed)) => {
2112				return Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped)));
2113			}
2114			Poll::Pending => {}
2115		}
2116
2117		// Handler side.
2118		let Some(result) = result else {
2119			// Never queued: no handler existed when the fetch was made. Fail fast while
2120			// that's still true; a handler that appeared since may yet fill the cache.
2121			return match fetch.poll(waiter, |fetch| match fetch.has_handlers() {
2122				false => Poll::Ready(()),
2123				true => Poll::Pending,
2124			}) {
2125				Poll::Ready(_guard) => Poll::Ready(Err(Error::NotFound)),
2126				Poll::Pending => Poll::Pending,
2127			};
2128		};
2129
2130		// A written rejection fails every joined fetch. The channel closing without
2131		// one means the attempt was dropped unserved (its handlers went away).
2132		match result.poll(waiter, |outcome| match &outcome.rejected {
2133			Some(err) => Poll::Ready(err.clone()),
2134			None => Poll::Pending,
2135		}) {
2136			Poll::Ready(Ok(err)) => Poll::Ready(Err(err)),
2137			Poll::Ready(Err(_closed)) => Poll::Ready(Err(Error::NotFound)),
2138			Poll::Pending => Poll::Pending,
2139		}
2140	}
2141}
2142
2143/// A live subscription to a track, used to read its groups.
2144///
2145/// Created via [`Consumer::subscribe`](Consumer::subscribe), or
2146/// directly from a [`Producer`] for an in-process track. Carries this
2147/// subscriber's [`Subscription`] preferences, which feed the producer's aggregate.
2148///
2149/// # Local cursor vs wire preference
2150///
2151/// Group bounds exist at two levels, and setting one does not imply the other:
2152///
2153/// - [`Self::start_at`] / [`Self::end_at`] move **this subscriber's read cursor**. They
2154///   filter exactly what this handle returns and are invisible to the publisher.
2155/// - [`Subscription::group_start`] / [`Subscription::group_end`], set via [`Self::update`],
2156///   are a **request to the publisher**. They're aggregated across every live subscriber
2157///   (earliest start, widest end), so they say what the publisher should send, not what
2158///   this subscriber sees.
2159///
2160/// They stay separate because their scopes differ: a subscriber can't filter by the
2161/// aggregate, since another subscriber can widen it, and the publisher can't honor a
2162/// cursor it's never told about. So setting only the cursor still transfers the skipped
2163/// groups, and setting only the preference still returns groups another subscriber asked
2164/// for. Set both to skip them *and* avoid the transfer.
2165pub struct Subscriber {
2166	name: Arc<str>,
2167	info: Info,
2168	inner: SubscriberKind,
2169	// Egress stats scope, used to meter the groups this subscriber reads. Empty
2170	// (no-op) for an untagged track.
2171	stats: stats::Scope,
2172	// The subscription guard: bumps `subscriptions` (and the egress viewer refcount)
2173	// while held, closing them on drop. Empty (no-op) for an untagged track.
2174	_stats_sub: stats::Subscription,
2175}
2176
2177enum SubscriberKind {
2178	Plain(PlainSubscriber),
2179	// Boxed: the spliced cursor set dwarfs the plain cursor.
2180	Spliced(Box<super::resume::Subscriber>),
2181}
2182
2183/// The cursor state for a subscription over a single (per-session) track.
2184struct PlainSubscriber {
2185	state: kio::Consumer<TrackState>,
2186
2187	subscription: kio::Producer<Subscription>,
2188	/// Arrival-order cursor used by `recv_group`.
2189	index: usize,
2190	/// Arrival-order cursor used by `recv_datagram`, independent of groups.
2191	datagram_index: usize,
2192	/// Minimum sequence to return from any `recv` method. Set by `start_at`.
2193	min_sequence: u64,
2194	/// One past the highest sequence returned by `next_group`.
2195	/// Used only by that method to skip late arrivals; does not affect `recv_group`.
2196	next_sequence: u64,
2197	/// Inclusive upper sequence bound for `next_group` and `recv_group`. `None`
2198	/// means no cap. Set by `end_at`; can be raised, lowered, or unset at any time.
2199	/// Groups beyond the cap stay in the producer's cache and become eligible again
2200	/// when the cap rises (or is removed).
2201	end_sequence: Option<u64>,
2202	/// Groups received beyond the [`Self::end_sequence`] cap, held for `recv_group`
2203	/// until the cap rises (arrival-order reads consume the shared cursor, so they
2204	/// are parked here instead of dropped). Keyed by sequence so the lowest is
2205	/// re-offered first.
2206	parked: BTreeMap<u64, group::Consumer>,
2207}
2208
2209impl PlainSubscriber {
2210	// A helper to automatically apply Dropped if the state is closed without an error.
2211	fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
2212	where
2213		F: Fn(&kio::Ref<'_, TrackState>) -> Poll<Result<R>>,
2214	{
2215		Poll::Ready(match ready!(self.state.poll(waiter, f)) {
2216			Ok(res) => res,
2217			// We try to clone abort just in case the function forgot to check for terminal state.
2218			Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
2219		})
2220	}
2221
2222	fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
2223		// An eviction aborts a parked group without touching any cursor this
2224		// subscriber polls, so each entry needs a waiter or this poll would never
2225		// rerun. `poll_closed` observes-or-registers under one lock: `Pending`
2226		// parks the waiter while the group is open (an open group cannot be
2227		// aborted), and `Ready` means closed, where only an abort invalidates the
2228		// entry. Checking `is_aborted` separately from the registration would leave
2229		// a window where an abort lands between the two and wakes nobody.
2230		let watch = |group: &group::Consumer| match group.poll_closed(waiter) {
2231			Poll::Pending => true,
2232			Poll::Ready(()) => !group.is_aborted(),
2233		};
2234
2235		// A raised `start_at` drops parked groups it overtook, and eviction/expiry
2236		// (which aborts a cached group) drops its parked entry. The latter is what
2237		// bounds parking: a subscription capped indefinitely holds only what the
2238		// track's cache policy still retains, not every group it ever observed.
2239		let min_sequence = self.min_sequence;
2240		self.parked
2241			.retain(|sequence, group| *sequence >= min_sequence && watch(group));
2242
2243		// Re-offer the lowest parked group back inside the cap once it rises.
2244		if let Some(&sequence) = self.parked.keys().next()
2245			&& self.end_sequence.is_none_or(|end| sequence <= end)
2246		{
2247			return Poll::Ready(Ok(self.parked.remove(&sequence)));
2248		}
2249
2250		loop {
2251			let Some((consumer, found_index)) =
2252				ready!(self.poll(waiter, |state| state.poll_recv_group(self.index, self.min_sequence))?)
2253			else {
2254				// Parked groups survive a finished track: they become deliverable
2255				// again if the cap rises, so the stream isn't over while any are held.
2256				if self.parked.is_empty() {
2257					return Poll::Ready(Ok(None));
2258				}
2259				return Poll::Pending;
2260			};
2261			self.index = found_index + 1;
2262
2263			// Park a group beyond the cap instead of dropping it, and keep scanning
2264			// so an in-range group that arrived behind it still flows.
2265			if self.end_sequence.is_some_and(|end| consumer.sequence > end) {
2266				// Watch it from the moment it parks: the retain pass above already
2267				// ran, so an entry admitted here would otherwise sit unwatched for
2268				// the rest of this poll, and an abort could wake nobody.
2269				if watch(&consumer) {
2270					self.parked.insert(consumer.sequence, consumer);
2271				}
2272				continue;
2273			}
2274			return Poll::Ready(Ok(Some(consumer)));
2275		}
2276	}
2277
2278	fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
2279		let Some((datagram, found_index)) =
2280			ready!(self.poll(waiter, |state| state.poll_recv_datagram(self.datagram_index))?)
2281		else {
2282			return Poll::Ready(Ok(None));
2283		};
2284
2285		self.datagram_index = found_index + 1;
2286		self.next_sequence = self.next_sequence.max(datagram.sequence.saturating_add(1));
2287		Poll::Ready(Ok(Some(datagram)))
2288	}
2289
2290	fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
2291		let floor = self.next_sequence.max(self.min_sequence);
2292		let Some(group) = ready!(self.poll(waiter, |state| state.poll_next_in_range(floor, self.end_sequence))?) else {
2293			return Poll::Ready(Ok(None));
2294		};
2295		self.next_sequence = group.sequence.saturating_add(1);
2296		Poll::Ready(Ok(Some(group)))
2297	}
2298
2299	fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Frame>>> {
2300		let lower = self.min_sequence.max(self.next_sequence);
2301		let Some((frame, found_index, sequence)) =
2302			ready!(self.poll(waiter, |state| { state.poll_read_frame(self.index, lower, waiter) })?)
2303		else {
2304			return Poll::Ready(Ok(None));
2305		};
2306
2307		self.index = found_index + 1;
2308		self.next_sequence = sequence.saturating_add(1);
2309		Poll::Ready(Ok(Some(frame)))
2310	}
2311}
2312
2313/// A cloneable handle to a subscriber's delivery preferences.
2314///
2315/// This updates the same subscription as the owning [`Subscriber`] without
2316/// borrowing its read cursor, so callers can change delivery priority, group
2317/// ordering priority, or group bounds while another task is waiting for groups.
2318#[derive(Clone)]
2319pub struct SubscriberControl {
2320	subscription: kio::Producer<Subscription>,
2321}
2322
2323impl SubscriberControl {
2324	/// This subscriber's current preferences.
2325	pub fn subscription(&self) -> Subscription {
2326		self.subscription.read().clone()
2327	}
2328
2329	/// Replace this subscriber's preferences, updating the producer's aggregate.
2330	///
2331	/// Returns [`Error::Closed`] if the track already ended; the update is
2332	/// meaningless at that point and can usually be ignored.
2333	pub fn update(&self, subscription: Subscription) -> Result<()> {
2334		let mut state = self.subscription.write().map_err(|_| Error::Closed)?;
2335		*state = subscription;
2336		Ok(())
2337	}
2338}
2339
2340impl Subscriber {
2341	/// The track's [`Info`], resolved when the subscription was established.
2342	///
2343	/// Free, unlike [`Consumer::info`]: subscribing already waited for the info
2344	/// (SUBSCRIBE_OK on the wire), so a subscriber always has it.
2345	pub fn info(&self) -> &Info {
2346		&self.info
2347	}
2348
2349	/// The track's name, unique within its broadcast.
2350	pub fn name(&self) -> &str {
2351		&self.name
2352	}
2353
2354	/// Create a handle for updating this subscriber's delivery preferences.
2355	pub fn control(&self) -> SubscriberControl {
2356		SubscriberControl {
2357			subscription: match &self.inner {
2358				SubscriberKind::Plain(plain) => plain.subscription.clone(),
2359				SubscriberKind::Spliced(spliced) => spliced.prefs(),
2360			},
2361		}
2362	}
2363
2364	/// Poll for the next group in arrival order, without blocking.
2365	///
2366	/// Returns every group exactly once in the order it landed on the wire, which may be
2367	/// out of sequence due to network reordering or loss. Use [`Self::poll_next_group`] if
2368	/// you only want groups whose sequence number is higher than any previously returned.
2369	///
2370	/// Honors the floor set by [`Self::start_at`] and the cap set by [`Self::end_at`]:
2371	/// a group beyond the cap is parked (not dropped) and re-offered once the cap rises
2372	/// (lowest sequence first), without blocking in-range groups that arrive behind it.
2373	/// A parked group that the producer evicts or expires in the meantime is dropped,
2374	/// so parking never outlives the track's cache policy.
2375	///
2376	/// Returns `Poll::Ready(Ok(Some(group)))` when a group is available,
2377	/// `Poll::Ready(Ok(None))` when the track is finished,
2378	/// `Poll::Ready(Err(e))` when the track has been aborted, or
2379	/// `Poll::Pending` when no group is available yet.
2380	pub fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
2381		let meter = self.stats.meter();
2382		let res = match &mut self.inner {
2383			SubscriberKind::Plain(plain) => plain.poll_recv_group(waiter),
2384			SubscriberKind::Spliced(spliced) => spliced.poll_recv_group(waiter),
2385		};
2386		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
2387	}
2388
2389	/// Receive the next group in arrival order.
2390	///
2391	/// Every group is returned exactly once, in the order it landed on the wire, which may
2392	/// be out of sequence due to network reordering or loss. Use [`Self::next_group`] if you
2393	/// only want groups whose sequence number is higher than any previously returned.
2394	/// See [`Self::poll_recv_group`] for how [`Self::start_at`] and [`Self::end_at`] apply.
2395	pub async fn recv_group(&mut self) -> Result<Option<group::Consumer>> {
2396		kio::wait(|waiter| self.poll_recv_group(waiter)).await
2397	}
2398
2399	/// Poll for the next datagram in arrival order, without blocking.
2400	///
2401	/// Datagrams are a separate best-effort channel from groups (see
2402	/// [`Producer::append_datagram`]); they share only the sequence namespace. A consumer
2403	/// that falls too far behind silently loses the oldest datagrams.
2404	/// Returning a datagram advances [`Self::poll_next_group`] past that sequence.
2405	///
2406	/// Returns `Poll::Ready(Ok(Some(datagram)))` when one is available,
2407	/// `Poll::Ready(Ok(None))` when the track is finished, `Poll::Ready(Err(e))` when the track
2408	/// is aborted, or `Poll::Pending` when none is buffered yet.
2409	pub fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
2410		let meter = self.stats.meter();
2411		let res = match &mut self.inner {
2412			SubscriberKind::Plain(plain) => plain.poll_recv_datagram(waiter),
2413			SubscriberKind::Spliced(spliced) => spliced.poll_recv_datagram(waiter),
2414		};
2415		// Unlike a group (metered lazily as its frames are read), a datagram is
2416		// delivered whole here, so count it as the single-frame group it stands in for.
2417		if let Poll::Ready(Ok(Some(datagram))) = &res {
2418			meter.datagram(datagram.payload.len() as u64);
2419		}
2420		res
2421	}
2422
2423	/// Receive the next datagram in arrival order.
2424	///
2425	/// A best-effort channel parallel to [`Self::recv_group`]; the two share only the sequence
2426	/// namespace. To receive both concurrently from one subscriber, poll [`Self::poll_next_group`]
2427	/// (or [`Self::poll_recv_group`]) and [`Self::poll_recv_datagram`] together in a single `poll`
2428	/// closure (sequential `&mut` borrows), rather than awaiting the two `recv` futures at once.
2429	pub async fn recv_datagram(&mut self) -> Result<Option<Datagram>> {
2430		kio::wait(|waiter| self.poll_recv_datagram(waiter)).await
2431	}
2432
2433	/// Poll for the next group with a higher sequence number than any previously returned.
2434	///
2435	/// Late arrivals (sequence at or below the last returned) are silently skipped, so this
2436	/// produces a monotonically increasing sequence at the cost of dropping out-of-order
2437	/// groups. Use [`Self::poll_recv_group`] to see every group in arrival order instead.
2438	///
2439	/// Honors the cap set by [`Self::end_at`]: groups with sequence past the cap are left
2440	/// in the producer's cache and become eligible again if the cap is raised or removed.
2441	pub fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
2442		let meter = self.stats.meter();
2443		let res = match &mut self.inner {
2444			SubscriberKind::Plain(plain) => plain.poll_next_group(waiter),
2445			SubscriberKind::Spliced(spliced) => spliced.poll_next_group(waiter),
2446		};
2447		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
2448	}
2449
2450	/// Return the next group with a higher sequence number than any previously returned.
2451	///
2452	/// Late arrivals (sequence at or below the last returned) are silently skipped, so this
2453	/// produces a monotonically increasing sequence at the cost of dropping out-of-order
2454	/// groups. Use [`Self::recv_group`] to see every group in arrival order instead.
2455	pub async fn next_group(&mut self) -> Result<Option<group::Consumer>> {
2456		kio::wait(|waiter| self.poll_next_group(waiter)).await
2457	}
2458
2459	/// A helper that calls [`Self::poll_next_group`] and returns its first frame
2460	/// (timestamp and payload), skipping the rest of the group. Intended for
2461	/// single-frame groups (see [`Producer::write_frame`]).
2462	pub fn poll_read_frame(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<frame::Frame>>> {
2463		let meter = self.stats.meter();
2464		let res = match &mut self.inner {
2465			SubscriberKind::Plain(plain) => plain.poll_read_frame(waiter),
2466			SubscriberKind::Spliced(spliced) => spliced.poll_read_frame(waiter),
2467		};
2468		// This helper collapses a group to its first frame: count the group, the one
2469		// frame, and the bytes actually read.
2470		if let Poll::Ready(Ok(Some(frame))) = &res {
2471			meter.group();
2472			meter.frames(1);
2473			meter.bytes(frame.payload.len() as u64);
2474		}
2475		res
2476	}
2477
2478	/// Read a single full frame (timestamp and payload) from the next group in
2479	/// sequence order.
2480	///
2481	/// See [`Self::poll_read_frame`] for semantics.
2482	pub async fn read_frame(&mut self) -> Result<Option<frame::Frame>> {
2483		kio::wait(|waiter| self.poll_read_frame(waiter)).await
2484	}
2485
2486	/// Whether `other` was cloned from this subscriber (shares the same underlying state).
2487	pub fn is_clone(&self, other: &Self) -> bool {
2488		match (&self.inner, &other.inner) {
2489			(SubscriberKind::Plain(a), SubscriberKind::Plain(b)) => a.state.same_channel(&b.state),
2490			(SubscriberKind::Spliced(a), SubscriberKind::Spliced(b)) => a.is_clone(b),
2491			_ => false,
2492		}
2493	}
2494
2495	/// Poll for the track's declared final sequence, without blocking.
2496	pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
2497		match &mut self.inner {
2498			SubscriberKind::Plain(plain) => plain.poll(waiter, |state| state.poll_finished()),
2499			SubscriberKind::Spliced(spliced) => spliced.poll_finished(waiter),
2500		}
2501	}
2502
2503	/// Block until the track declares its end, returning the exclusive final sequence
2504	/// (also the total group count), or the cause on an abort.
2505	///
2506	/// Resolves as soon as the boundary is known, which may be ahead of the live edge
2507	/// when the producer finished via [`Producer::finish_at`]. This reports the declared
2508	/// end, not that every group has arrived: drive [`Self::recv_group`] /
2509	/// [`Self::next_group`] until they yield `None` to observe the track fully drained.
2510	pub async fn finished(&mut self) -> Result<u64> {
2511		kio::wait(|waiter| self.poll_finished(waiter)).await
2512	}
2513
2514	/// Start this subscriber's read cursor at the given sequence.
2515	///
2516	/// A local filter, not a request: it doesn't tell the publisher anything, so the
2517	/// skipped groups are still delivered and simply not returned. To ask the publisher
2518	/// to start there instead, set [`Subscription::group_start`] via [`Self::update`].
2519	/// See [Local cursor vs wire preference](Self#local-cursor-vs-wire-preference).
2520	pub fn start_at(&mut self, sequence: u64) {
2521		match &mut self.inner {
2522			SubscriberKind::Plain(plain) => plain.min_sequence = sequence,
2523			SubscriberKind::Spliced(spliced) => spliced.start_at(sequence),
2524		}
2525	}
2526
2527	/// Cap this subscriber's read cursor at the given sequence (inclusive), or remove the
2528	/// cap entirely.
2529	///
2530	/// Accepts a bare `u64` (cap), `Some(u64)`, or `None` (uncap).
2531	///
2532	/// A local filter, not a request; [`Subscription::group_end`] is the wire-level
2533	/// counterpart. See [Local cursor vs wire preference](Self#local-cursor-vs-wire-preference).
2534	///
2535	/// Affects [`Self::next_group`] and [`Self::recv_group`]: groups beyond the cap are
2536	/// held rather than skipped past, so a later call to [`Self::end_at`] with a higher
2537	/// value (or `None`) makes them available again. Lowering the cap below the
2538	/// consumer's current cursor parks the consumer until the cap is raised.
2539	pub fn end_at(&mut self, sequence: impl Into<Option<u64>>) {
2540		match &mut self.inner {
2541			SubscriberKind::Plain(plain) => plain.end_sequence = sequence.into(),
2542			SubscriberKind::Spliced(spliced) => spliced.end_at(sequence),
2543		}
2544	}
2545
2546	/// This subscriber's current preferences.
2547	pub fn subscription(&self) -> Subscription {
2548		self.control().subscription()
2549	}
2550
2551	/// Replace this subscriber's delivery preferences.
2552	///
2553	/// Stored verbatim; the publisher's latency window is applied to the aggregate, not
2554	/// here (see [`Producer::subscription`]). Returns [`Error::Closed`] if the track
2555	/// already ended; the update is meaningless at that point and can usually be ignored.
2556	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
2557		match &mut self.inner {
2558			SubscriberKind::Plain(plain) => {
2559				let mut state = plain.subscription.write().map_err(|_| Error::Closed)?;
2560				*state = subscription;
2561			}
2562			SubscriberKind::Spliced(spliced) => spliced.update(subscription),
2563		}
2564		Ok(())
2565	}
2566
2567	/// Return the latest sequence number in the track.
2568	pub fn latest(&self) -> Option<u64> {
2569		match &self.inner {
2570			SubscriberKind::Plain(plain) => plain.state.read().max_sequence,
2571			SubscriberKind::Spliced(spliced) => spliced.latest(),
2572		}
2573	}
2574}
2575
2576/// A subscriber asked for a track this broadcast doesn't have yet.
2577///
2578/// Yielded by [`broadcast::Dynamic::requested_track`](crate::broadcast::Dynamic::requested_track),
2579/// or created up front with [`broadcast::Producer::reserve_track`](crate::broadcast::Producer::reserve_track).
2580/// Subscribers block until the request is
2581/// resolved: call [`accept`](Self::accept) to serve it with a [`Producer`], or
2582/// [`reject`](Self::reject) to fail them. Dropping it without either rejects with
2583/// [`Error::Dropped`].
2584///
2585/// Concurrent requests for one name are coalesced, so exactly one of these exists per
2586/// name at a time.
2587pub struct Request {
2588	name: Arc<str>,
2589	// The parent broadcast's info, threaded into the [`Producer`] on accept.
2590	broadcast: Arc<broadcast::Info>,
2591	state: kio::Producer<TrackState>,
2592
2593	// The previous subscription that was combined, used to detect changes.
2594	prev_subscription: Option<Subscription>,
2595
2596	// Shared with the accepted [`Producer`] and every [`Dynamic`]: its `Drop` is the
2597	// teardown, and it stays inert until a producer is minted.
2598	alive: Arc<Alive>,
2599
2600	// A requested track is served on demand, so it counts as fetch-capable from
2601	// birth: a consumer's cache-miss `fetch_group` waits to be served instead of
2602	// racing the producer (e.g. a relay) into creating its own handler. Released
2603	// when the request is accepted or dropped; by then the relay holds its own.
2604	_dynamic: Dynamic,
2605
2606	// Ingress stats scope, threaded into the accepted [`Producer`]. Empty (no-op)
2607	// unless this request was reserved on a tagged broadcast.
2608	stats: stats::Scope,
2609}
2610
2611impl Request {
2612	pub(crate) fn new(broadcast: Arc<broadcast::Info>, name: impl Into<Arc<str>>) -> Self {
2613		let name = name.into();
2614		let state = TrackState::spawn(broadcast.clone());
2615		let alive = Alive::new(name.clone(), state.clone());
2616		let dynamic = Dynamic::new(name.clone(), state.clone(), alive.clone());
2617		Self {
2618			name,
2619			broadcast,
2620			state,
2621			prev_subscription: None,
2622			alive,
2623			_dynamic: dynamic,
2624			stats: stats::Scope::default(),
2625		}
2626	}
2627
2628	/// Attach an ingress stats scope, applied to the [`Producer`] on accept. Set by
2629	/// a tagged [`broadcast::Producer::reserve_track`].
2630	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
2631		self.stats = scope;
2632		self
2633	}
2634
2635	/// The requested track name.
2636	pub fn name(&self) -> &str {
2637		&self.name
2638	}
2639
2640	/// A [`Consumer`] for the eventual track, usable before the request is accepted.
2641	pub fn consume(&self) -> Consumer {
2642		Consumer::plain(self.name.clone(), self.state.consume())
2643	}
2644
2645	/// Create a [`Dynamic`] handle that serves on-demand fetches of uncached
2646	/// groups, before [`Self::accept`] is even called. A relay creates one to fetch
2647	/// past groups from upstream while (or instead of) serving a live subscription.
2648	pub fn dynamic(&self) -> Dynamic {
2649		Dynamic::new(self.name.clone(), self.state.clone(), self.alive.clone())
2650	}
2651
2652	/// Poll for the request becoming unused (every consumer dropped), so a relay can
2653	/// stop serving and drop the request.
2654	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
2655		self.state.poll_unused(waiter).map(|_| ())
2656	}
2657
2658	/// Serve the request with the given track, resolving every waiting subscriber.
2659	///
2660	/// The name is taken from [`Self::name`]; `info` supplies the remaining knobs
2661	/// (`None` for the defaults). If the track was already aborted, the returned
2662	/// [`Producer`] is inert: writes fail with the abort error, as if it had been
2663	/// aborted immediately after accepting.
2664	pub fn accept(self, info: impl Into<Option<Info>>) -> Producer {
2665		// A closed state means the track was aborted under us. Mirror `reject` and
2666		// tolerate it: the Producer we hand back simply can't write.
2667		if let Ok(mut state) = self.state.write() {
2668			state.install(info.into().unwrap_or_default());
2669		}
2670		// Accepting the request creates the track producer: count it as one ingress
2671		// subscription (closed when the last handle drops). No-op when untagged.
2672		self.alive.publish(Some(&self.stats));
2673		Producer {
2674			name: self.name,
2675			broadcast: self.broadcast,
2676			state: self.state,
2677			prev_subscription: None,
2678			alive: self.alive,
2679			stats: self.stats,
2680		}
2681	}
2682
2683	/// Reject the request, waking all waiting subscribers with `err`.
2684	pub fn reject(self, err: Error) {
2685		if let Ok(mut state) = self.state.write() {
2686			state.abort = Some(err);
2687		}
2688	}
2689
2690	/// The delivery preferences aggregated across everyone waiting on this request,
2691	/// or `None` if nobody is waiting. Useful for sizing the track before accepting.
2692	pub fn subscription(&self) -> Option<Subscription> {
2693		let state = self.state.read();
2694		let (subs, bound) = (state.subscriptions.clone(), state.latency_bound());
2695		drop(state);
2696		snapshot_subscription(&subs, bound)
2697	}
2698
2699	/// Block until the aggregate [`subscription`](Self::subscription) changes,
2700	/// yielding `None` once nobody is waiting.
2701	pub async fn subscription_changed(&mut self) -> Option<Subscription> {
2702		kio::wait(|waiter| self.poll_subscription_changed(waiter)).await
2703	}
2704
2705	/// Poll counterpart to [`subscription_changed`](Self::subscription_changed).
2706	pub fn poll_subscription_changed(&mut self, waiter: &kio::Waiter) -> Poll<Option<Subscription>> {
2707		let state = self.state.read();
2708		let (subs, bound) = (state.subscriptions.clone(), state.latency_bound());
2709		drop(state);
2710
2711		let prev = &self.prev_subscription;
2712		let mut combined = None;
2713		let mut guard = ready!(subs.poll(waiter, |subs| {
2714			let next = combined_subscription(subs, bound, waiter);
2715			if &next == prev {
2716				Poll::Pending
2717			} else {
2718				combined = next;
2719				Poll::Ready(())
2720			}
2721		}));
2722		// The aggregate changed: prune any closed subscribers now that we hold the lock.
2723		guard.retain(|sub| !sub.is_closed());
2724		drop(guard);
2725		self.prev_subscription = combined.clone();
2726		Poll::Ready(combined)
2727	}
2728
2729	pub(super) fn weak(&self) -> TrackWeak {
2730		TrackWeak {
2731			name: self.name.clone(),
2732			state: self.state.weak(),
2733		}
2734	}
2735}
2736
2737#[cfg(test)]
2738use futures::FutureExt;
2739
2740#[cfg(test)]
2741#[allow(missing_docs)] // test-only assertion helpers
2742impl Subscriber {
2743	pub fn assert_group(&mut self) -> group::Consumer {
2744		self.recv_group()
2745			.now_or_never()
2746			.expect("group would have blocked")
2747			.expect("would have errored")
2748			.expect("track was closed")
2749	}
2750
2751	pub fn assert_no_group(&mut self) {
2752		assert!(
2753			self.recv_group().now_or_never().is_none(),
2754			"recv_group would not have blocked"
2755		);
2756	}
2757
2758	pub fn assert_not_closed(&mut self) {
2759		assert!(self.finished().now_or_never().is_none(), "should not be closed");
2760	}
2761
2762	pub fn assert_closed(&mut self) {
2763		assert!(self.finished().now_or_never().is_some(), "should be closed");
2764	}
2765
2766	// TODO assert specific errors after implementing PartialEq
2767	pub fn assert_error(&mut self) {
2768		assert!(
2769			self.finished().now_or_never().expect("should not block").is_err(),
2770			"should be error"
2771		);
2772	}
2773
2774	pub fn assert_is_clone(&self, other: &Self) {
2775		assert!(self.is_clone(other), "should be clone");
2776	}
2777
2778	pub fn assert_not_clone(&self, other: &Self) {
2779		assert!(!self.is_clone(other), "should not be clone");
2780	}
2781}
2782
2783#[cfg(test)]
2784mod test {
2785	use super::*;
2786	use crate::model::test_tracing::count_drop_warnings;
2787
2788	/// Mint a track for tests with a default parent broadcast, since tracks are
2789	/// normally born from a [`broadcast::Producer`].
2790	fn track_producer(name: impl Into<Arc<str>>, info: impl Into<Option<Info>>) -> Producer {
2791		Producer::new(Arc::new(broadcast::Info::default()), name, info)
2792	}
2793
2794	/// Helper: count live cached groups in state.
2795	fn live_groups(state: &TrackState) -> usize {
2796		state.lookup.len()
2797	}
2798
2799	/// Helper: get the sequence number of the first live group in arrival order.
2800	fn first_live_sequence(state: &TrackState) -> u64 {
2801		state
2802			.arrival
2803			.iter()
2804			.find(|(sequence, stamp)| state.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp))
2805			.map(|(sequence, _)| *sequence)
2806			.unwrap()
2807	}
2808
2809	/// Helper: non-blocking datagram receive that must be ready with a datagram.
2810	fn recv_datagram(dg: &mut Subscriber) -> Datagram {
2811		dg.recv_datagram()
2812			.now_or_never()
2813			.expect("datagram would have blocked")
2814			.expect("would have errored")
2815			.expect("track was closed")
2816	}
2817
2818	#[tokio::test]
2819	async fn append_datagram_shares_group_sequence() {
2820		let mut producer = track_producer("test", None);
2821		let ts = Timestamp::from_millis(10).unwrap();
2822
2823		// Interleave groups and datagrams: they draw from one monotonic counter.
2824		assert_eq!(producer.append_group().unwrap().sequence, 0);
2825		assert_eq!(producer.append_datagram(ts, &b"a"[..]).unwrap(), 1);
2826		assert_eq!(producer.append_group().unwrap().sequence, 2);
2827		assert_eq!(producer.append_datagram(ts, &b"b"[..]).unwrap(), 3);
2828		assert_eq!(producer.latest(), Some(3));
2829	}
2830
2831	#[tokio::test]
2832	async fn append_datagram_roundtrip() {
2833		let mut producer = track_producer("test", None);
2834		let mut dg = producer.subscribe(None);
2835
2836		let ts = Timestamp::from_millis(42).unwrap();
2837		let seq = producer.append_datagram(ts, &b"hello"[..]).unwrap();
2838
2839		let got = recv_datagram(&mut dg);
2840		assert_eq!(got.sequence, seq);
2841		assert_eq!(got.timestamp, ts);
2842		assert_eq!(&got.payload[..], b"hello");
2843	}
2844
2845	#[tokio::test]
2846	async fn write_datagram_preserves_sequence() {
2847		let mut producer = track_producer("test", None);
2848		let mut dg = producer.subscribe(None);
2849
2850		let ts = Timestamp::from_millis(5).unwrap();
2851		// A relay forwarding an upstream datagram keeps its sequence number.
2852		producer
2853			.write_datagram(Datagram {
2854				sequence: 100,
2855				timestamp: ts,
2856				payload: bytes::Bytes::from_static(b"x"),
2857			})
2858			.unwrap();
2859
2860		assert_eq!(recv_datagram(&mut dg).sequence, 100);
2861		// max_sequence advanced, so the next appended group/datagram continues past it.
2862		assert_eq!(producer.append_group().unwrap().sequence, 101);
2863	}
2864
2865	#[tokio::test]
2866	async fn recv_datagram_advances_ordered_group_cursor() {
2867		let mut producer = track_producer("test", None);
2868		let mut subscriber = producer.subscribe(None);
2869		let ts = Timestamp::from_millis(5).unwrap();
2870
2871		producer
2872			.write_datagram(Datagram {
2873				sequence: 5,
2874				timestamp: ts,
2875				payload: bytes::Bytes::from_static(b"x"),
2876			})
2877			.unwrap();
2878		assert_eq!(recv_datagram(&mut subscriber).sequence, 5);
2879
2880		producer.create_group(group::Info { sequence: 3 }).unwrap();
2881		producer.create_group(group::Info { sequence: 6 }).unwrap();
2882
2883		let group = subscriber
2884			.next_group()
2885			.now_or_never()
2886			.expect("group would have blocked")
2887			.expect("would have errored")
2888			.expect("track was closed");
2889		assert_eq!(group.sequence, 6);
2890	}
2891
2892	#[tokio::test]
2893	async fn datagram_normalized_to_track_timescale() {
2894		let info = Info::default().with_timescale(Timescale::MICRO);
2895		let mut producer = track_producer("test", info);
2896		let mut dg = producer.subscribe(None);
2897
2898		// Supplied at millis; stored/emitted at the track's micro timescale.
2899		producer
2900			.append_datagram(Timestamp::from_millis(2).unwrap(), &b"z"[..])
2901			.unwrap();
2902		let got = recv_datagram(&mut dg);
2903		assert_eq!(got.timestamp.scale(), Timescale::MICRO);
2904		assert_eq!(got.timestamp.value(), 2_000);
2905	}
2906
2907	#[tokio::test]
2908	async fn datagram_rejects_oversized() {
2909		let mut producer = track_producer("test", None);
2910		let big = bytes::Bytes::from(vec![0u8; crate::model::datagram::MAX_DATAGRAM_PAYLOAD + 1]);
2911		let ts = Timestamp::from_millis(0).unwrap();
2912		assert!(matches!(
2913			producer.append_datagram(ts, big.clone()),
2914			Err(Error::FrameTooLarge)
2915		));
2916		assert!(matches!(
2917			producer.write_datagram(Datagram {
2918				sequence: 0,
2919				timestamp: ts,
2920				payload: big,
2921			}),
2922			Err(Error::FrameTooLarge)
2923		));
2924	}
2925
2926	#[tokio::test]
2927	async fn datagram_fanout_to_subscribers() {
2928		let mut producer = track_producer("test", None);
2929		// Two independent subscribers, each with its own datagram cursor.
2930		let mut a = producer.subscribe(None);
2931		let mut b = producer.subscribe(None);
2932		let ts = Timestamp::from_millis(1).unwrap();
2933
2934		producer.append_datagram(ts, &b"first"[..]).unwrap();
2935		producer.append_datagram(ts, &b"second"[..]).unwrap();
2936
2937		// Both receive every datagram in order, independently.
2938		assert_eq!(&recv_datagram(&mut a).payload[..], b"first");
2939		assert_eq!(&recv_datagram(&mut a).payload[..], b"second");
2940		assert_eq!(&recv_datagram(&mut b).payload[..], b"first");
2941		assert_eq!(&recv_datagram(&mut b).payload[..], b"second");
2942	}
2943
2944	#[tokio::test]
2945	async fn datagram_evicts_stale() {
2946		tokio::time::pause();
2947
2948		let mut producer = track_producer("test", None);
2949		let mut dg = producer.subscribe(None);
2950		let ts = Timestamp::from_millis(0).unwrap();
2951
2952		producer.append_datagram(ts, &b"old"[..]).unwrap(); // sequence 0
2953
2954		// Age past the send-buffer window, then push a fresh datagram: the stale one is evicted.
2955		tokio::time::advance(MAX_DATAGRAM_AGE + Duration::from_millis(10)).await;
2956		producer.append_datagram(ts, &b"new"[..]).unwrap(); // sequence 1
2957
2958		// A lagging consumer resumes at the oldest still-buffered datagram (the fresh one).
2959		let got = recv_datagram(&mut dg);
2960		assert_eq!(got.sequence, 1);
2961		assert_eq!(&got.payload[..], b"new");
2962	}
2963
2964	#[tokio::test]
2965	async fn datagram_recv_pends_until_written() {
2966		let mut producer = track_producer("test", None);
2967		let mut dg = producer.subscribe(None);
2968
2969		assert!(
2970			dg.recv_datagram().now_or_never().is_none(),
2971			"should block with no datagrams"
2972		);
2973
2974		producer
2975			.append_datagram(Timestamp::from_millis(0).unwrap(), &b"go"[..])
2976			.unwrap();
2977		assert_eq!(&recv_datagram(&mut dg).payload[..], b"go");
2978	}
2979
2980	/// Exercises the full producer -> publisher-encode -> subscriber-decode -> producer seam
2981	/// (everything but the QUIC datagram send/recv), catching any field-order mismatch between
2982	/// the wire codec and the model.
2983	#[tokio::test]
2984	async fn datagram_wire_roundtrip_between_tracks() {
2985		use crate::coding::{Decode, Encode};
2986		use crate::lite;
2987
2988		let version = lite::Version::Lite05;
2989
2990		// Origin publishes a datagram; the publisher reads it and encodes the wire body.
2991		let mut origin = track_producer("test", None);
2992		let mut origin_dg = origin.subscribe(None);
2993		let ts = Timestamp::from_millis(7).unwrap();
2994		let seq = origin.append_datagram(ts, &b"payload"[..]).unwrap();
2995
2996		let d = recv_datagram(&mut origin_dg);
2997		let body = lite::Datagram {
2998			subscribe: 5,
2999			sequence: d.sequence,
3000			timestamp: d.timestamp.value(),
3001			payload: d.payload.clone(),
3002		}
3003		.encode_bytes(version)
3004		.unwrap();
3005
3006		// Subscriber decodes the body and writes it downstream, preserving the sequence.
3007		let mut slice = &body[..];
3008		let wire = lite::Datagram::decode(&mut slice, version).unwrap();
3009		let mut downstream = track_producer("test", None);
3010		let mut downstream_dg = downstream.subscribe(None);
3011		downstream
3012			.write_datagram(Datagram {
3013				sequence: wire.sequence,
3014				timestamp: Timestamp::new(wire.timestamp, Timescale::MILLI).unwrap(),
3015				payload: wire.payload,
3016			})
3017			.unwrap();
3018
3019		let got = recv_datagram(&mut downstream_dg);
3020		assert_eq!(got.sequence, seq);
3021		assert_eq!(got.timestamp, ts);
3022		assert_eq!(&got.payload[..], b"payload");
3023	}
3024
3025	#[tokio::test]
3026	async fn evict_expired_groups() {
3027		tokio::time::pause();
3028
3029		let mut producer = track_producer("test", None);
3030
3031		// Create 3 groups at time 0.
3032		producer.append_group().unwrap(); // seq 0
3033		producer.append_group().unwrap(); // seq 1
3034		producer.append_group().unwrap(); // seq 2
3035
3036		{
3037			let state = producer.state.read();
3038			assert_eq!(live_groups(&state), 3);
3039			assert_eq!(state.offset, 0);
3040		}
3041
3042		// Advance time past the eviction threshold.
3043		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
3044
3045		// Append a new group to trigger eviction.
3046		producer.append_group().unwrap(); // seq 3
3047
3048		// Groups 0, 1, 2 are expired but seq 3 (the live edge) is kept. Their arrival
3049		// entries no longer resolve, so the leading ones are trimmed and the offset
3050		// advances past them.
3051		{
3052			let state = producer.state.read();
3053			assert_eq!(live_groups(&state), 1);
3054			assert_eq!(first_live_sequence(&state), 3);
3055			assert_eq!(state.offset, 3);
3056			assert!(!state.lookup.contains_key(&0));
3057			assert!(!state.lookup.contains_key(&1));
3058			assert!(!state.lookup.contains_key(&2));
3059			assert!(state.lookup.contains_key(&3));
3060		}
3061	}
3062
3063	/// A group whose frames outlive `latency_max` is aged out when the next group starts, but
3064	/// a subscriber that already drained it must still see the clean end of group. Otherwise a
3065	/// track with long groups (a per-minute rollup, say) fails its readers at every boundary.
3066	#[tokio::test]
3067	async fn aging_out_a_finished_group_keeps_the_clean_end() {
3068		tokio::time::pause();
3069
3070		let mut producer = track_producer("test", None);
3071		let mut group = producer.create_group(group::Info { sequence: 0 }).unwrap();
3072		let mut consumer = group.consume();
3073
3074		group
3075			.write_frame(Timestamp::from_millis(0).unwrap(), b"hello".as_slice())
3076			.unwrap();
3077		assert_eq!(consumer.next_frame().await.unwrap().unwrap().size, 5);
3078
3079		// The group stays open well past latency_max, then the next period starts.
3080		tokio::time::advance(DEFAULT_LATENCY_MAX * 12).await;
3081		group.finish().unwrap();
3082		let _next = producer.create_group(group::Info { sequence: 1 }).unwrap();
3083
3084		assert!(consumer.next_frame().await.unwrap().is_none());
3085	}
3086
3087	#[tokio::test]
3088	async fn evict_keeps_max_sequence() {
3089		tokio::time::pause();
3090
3091		let mut producer = track_producer("test", None);
3092		producer.append_group().unwrap(); // seq 0
3093
3094		// Advance time past threshold.
3095		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
3096
3097		// Append another group; seq 0 is expired and evicted.
3098		producer.append_group().unwrap(); // seq 1
3099
3100		{
3101			let state = producer.state.read();
3102			assert_eq!(live_groups(&state), 1);
3103			assert_eq!(first_live_sequence(&state), 1);
3104			assert_eq!(state.offset, 1);
3105		}
3106	}
3107
3108	#[tokio::test]
3109	async fn no_eviction_when_fresh() {
3110		tokio::time::pause();
3111
3112		let mut producer = track_producer("test", None);
3113		producer.append_group().unwrap(); // seq 0
3114		producer.append_group().unwrap(); // seq 1
3115		producer.append_group().unwrap(); // seq 2
3116
3117		{
3118			let state = producer.state.read();
3119			assert_eq!(live_groups(&state), 3);
3120			assert_eq!(state.offset, 0);
3121		}
3122	}
3123
3124	#[tokio::test]
3125	async fn consumer_skips_evicted_groups() {
3126		tokio::time::pause();
3127
3128		let mut producer = track_producer("test", None);
3129		producer.append_group().unwrap(); // seq 0
3130
3131		let mut consumer = producer.subscribe(None);
3132
3133		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
3134		producer.append_group().unwrap(); // seq 1
3135
3136		// Group 0 was evicted. Consumer should get group 1.
3137		let group = consumer.assert_group();
3138		assert_eq!(group.sequence, 1);
3139	}
3140
3141	#[tokio::test]
3142	async fn cache_age_controls_eviction() {
3143		tokio::time::pause();
3144
3145		// A shorter cache evicts sooner than the default.
3146		let mut producer = track_producer("test", Info::default().with_latency_max(Duration::from_secs(1)));
3147		producer.append_group().unwrap(); // seq 0
3148
3149		// Past the custom budget but well within DEFAULT_LATENCY_MAX.
3150		tokio::time::advance(Duration::from_secs(2)).await;
3151		producer.append_group().unwrap(); // seq 1
3152
3153		// Seq 0 is gone because the publisher only keeps groups for 1s.
3154		let state = producer.state.read();
3155		assert_eq!(live_groups(&state), 1);
3156		assert_eq!(first_live_sequence(&state), 1);
3157	}
3158
3159	#[test]
3160	fn latency_max_clamped_to_cache() {
3161		let producer = track_producer("test", Info::default().with_latency_max(Duration::from_secs(2)));
3162
3163		// A latency budget beyond the cache is capped in the aggregate; a group can't be
3164		// waited for longer than the publisher keeps it. The subscriber's own preference
3165		// is stored verbatim, so what it asked for stays readable.
3166		let mut subscriber = producer.subscribe(Subscription::default().with_latency_max(Duration::from_secs(10)));
3167		assert_eq!(subscriber.subscription().latency_max, Duration::from_secs(10));
3168		assert_eq!(producer.subscription().unwrap().latency_max, Duration::from_secs(2));
3169
3170		// A budget within the cache is left alone, and ZERO (skip immediately) stays ZERO.
3171		subscriber
3172			.update(Subscription::default().with_latency_max(Duration::from_millis(500)))
3173			.unwrap();
3174		assert_eq!(producer.subscription().unwrap().latency_max, Duration::from_millis(500));
3175
3176		subscriber
3177			.update(Subscription::default().with_latency_max(Duration::ZERO))
3178			.unwrap();
3179		assert_eq!(producer.subscription().unwrap().latency_max, Duration::ZERO);
3180	}
3181
3182	/// Mint a track under an origin whose retention ceiling is `cap`, so the
3183	/// track's own window is clamped down to it on bind.
3184	fn track_producer_capped(name: impl Into<Arc<str>>, info: Info, cap: Duration) -> Producer {
3185		let origin = crate::origin::Info::default().with_cache_duration(cap);
3186		Producer::new(Arc::new(broadcast::Info { origin }), name, info)
3187	}
3188
3189	#[test]
3190	fn origin_cache_duration_clamps_latency_max() {
3191		// A publisher asking to keep groups for a minute is capped to the origin's 1s
3192		// ceiling; a publisher already below the ceiling is left alone (it's a min).
3193		let capped = track_producer_capped(
3194			"test",
3195			Info::default().with_latency_max(Duration::from_secs(60)),
3196			Duration::from_secs(1),
3197		);
3198		assert_eq!(capped.state.read().latency_bound(), Some(Duration::from_secs(1)));
3199
3200		let under = track_producer_capped(
3201			"test",
3202			Info::default().with_latency_max(Duration::from_millis(500)),
3203			Duration::from_secs(1),
3204		);
3205		assert_eq!(under.state.read().latency_bound(), Some(Duration::from_millis(500)));
3206	}
3207
3208	#[tokio::test]
3209	async fn origin_cache_duration_caps_eviction() {
3210		tokio::time::pause();
3211
3212		// The publisher wants a 60s window, but the origin caps retention at 1s.
3213		let mut producer = track_producer_capped(
3214			"test",
3215			Info::default().with_latency_max(Duration::from_secs(60)),
3216			Duration::from_secs(1),
3217		);
3218		producer.append_group().unwrap(); // seq 0
3219
3220		// Past the origin ceiling but far within the publisher's own 60s window.
3221		tokio::time::advance(Duration::from_secs(2)).await;
3222		producer.append_group().unwrap(); // seq 1
3223
3224		// Seq 0 is evicted anyway: the origin ceiling wins over the larger publisher window.
3225		let state = producer.state.read();
3226		assert_eq!(live_groups(&state), 1);
3227		assert_eq!(first_live_sequence(&state), 1);
3228	}
3229
3230	#[test]
3231	fn latency_max_clamped_via_every_update_path() {
3232		let producer = track_producer("test", Info::default().with_latency_max(Duration::from_secs(2)));
3233		let over = Subscription::default().with_latency_max(Duration::from_secs(10));
3234
3235		// The clamp lives in the aggregation, so it applies no matter which entry point
3236		// wrote the raw preference. Previously only `Subscriber::update` clamped.
3237		let mut subscriber = producer.subscribe(over.clone());
3238		assert_eq!(producer.subscription().unwrap().latency_max, Duration::from_secs(2));
3239
3240		subscriber.control().update(over.clone()).unwrap();
3241		assert_eq!(producer.subscription().unwrap().latency_max, Duration::from_secs(2));
3242
3243		subscriber.update(over).unwrap();
3244		assert_eq!(producer.subscription().unwrap().latency_max, Duration::from_secs(2));
3245	}
3246
3247	#[test]
3248	fn latency_max_aggregate_clamps_the_max_across_subscribers() {
3249		let producer = track_producer("test", Info::default().with_latency_max(Duration::from_secs(2)));
3250
3251		// The aggregate takes the max, then clamps once. Equivalent to clamping each
3252		// subscriber first, since `min` distributes over `max`.
3253		let _a = producer.subscribe(Subscription::default().with_latency_max(Duration::from_millis(500)));
3254		let _b = producer.subscribe(Subscription::default().with_latency_max(Duration::from_secs(10)));
3255
3256		assert_eq!(producer.subscription().unwrap().latency_max, Duration::from_secs(2));
3257	}
3258
3259	#[test]
3260	fn subscriber_control_updates_while_read_future_is_pending() {
3261		let producer = track_producer("test", None);
3262		let mut subscriber = producer.subscribe(None);
3263		let control = subscriber.control();
3264
3265		let mut recv = Box::pin(subscriber.recv_group());
3266		assert!(recv.as_mut().now_or_never().is_none());
3267
3268		control
3269			.update(Subscription::default().with_priority(7).with_ordered(false))
3270			.unwrap();
3271
3272		let aggregate = producer.subscription().expect("expected an active subscription");
3273		assert_eq!(aggregate.priority, 7);
3274		assert!(!aggregate.ordered);
3275	}
3276
3277	#[test]
3278	fn dropped_subscriber_leaves_no_ghost_in_aggregate() {
3279		// Regression (#2351): a departed subscriber must not keep contributing its
3280		// last subscription to the aggregate. When it did, a relay's linger loop
3281		// never observed the track going idle, and an identical viewer reconnecting
3282		// within the linger window was reset when the stale timer fired.
3283		let mut producer = track_producer("test", None);
3284		let a = producer.subscribe(Subscription::default().with_priority(5));
3285
3286		// Prime the change cursor: the aggregate currently has one subscriber.
3287		let waiter = kio::Waiter::noop();
3288		assert!(
3289			matches!(producer.poll_subscription_changed(&waiter), Poll::Ready(Ok(Some(_)))),
3290			"one live subscriber should aggregate to Some",
3291		);
3292
3293		// The only subscriber leaves.
3294		drop(a);
3295
3296		// The aggregate must report the drop to None, not the ghost's last value.
3297		assert!(
3298			matches!(producer.poll_subscription_changed(&waiter), Poll::Ready(Ok(None))),
3299			"a dropped subscriber must not linger in the aggregate",
3300		);
3301
3302		// And the snapshot used by the linger loop must agree.
3303		assert!(
3304			producer.subscription().is_none(),
3305			"snapshot must exclude a dropped subscriber",
3306		);
3307	}
3308
3309	#[test]
3310	fn dropped_subscriber_wakes_the_aggregate() {
3311		// The value being right isn't enough: nothing re-polls the aggregate on its
3312		// own, so the drop has to wake the waiter. A subscriber contributing demand
3313		// takes `kio::Consumer::poll`'s Ready path, which registers no waiter, so
3314		// the departure needs the closed waiter armed explicitly. Without it a relay
3315		// never learns the last viewer left and holds the upstream subscription (and
3316		// the upstream's viewer count) open forever.
3317		use std::sync::atomic::{AtomicBool, Ordering};
3318
3319		let mut producer = track_producer("test", None);
3320		let a = producer.subscribe(Subscription::default().with_priority(5));
3321
3322		let woken = Arc::new(AtomicBool::new(false));
3323		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
3324
3325		// Prime the cursor, then confirm the next poll parks.
3326		assert!(matches!(
3327			producer.poll_subscription_changed(&waiter),
3328			Poll::Ready(Ok(Some(_)))
3329		));
3330		assert!(
3331			producer.poll_subscription_changed(&waiter).is_pending(),
3332			"the aggregate is unchanged, so this poll must park",
3333		);
3334		assert!(!woken.load(Ordering::SeqCst), "nothing happened yet");
3335
3336		drop(a);
3337		assert!(
3338			woken.load(Ordering::SeqCst),
3339			"the last subscriber leaving must wake the aggregate watcher",
3340		);
3341	}
3342
3343	/// An [`ArcWake`] that just records that it was woken.
3344	struct FlagWake(Arc<std::sync::atomic::AtomicBool>);
3345
3346	impl futures::task::ArcWake for FlagWake {
3347		fn wake_by_ref(arc_self: &Arc<Self>) {
3348			arc_self.0.store(true, std::sync::atomic::Ordering::SeqCst);
3349		}
3350	}
3351
3352	#[tokio::test]
3353	async fn out_of_order_max_sequence_at_front() {
3354		tokio::time::pause();
3355
3356		let mut producer = track_producer("test", None);
3357
3358		// Arrive out of order: seq 5 first, then 3, then 4.
3359		producer.create_group(group::Info { sequence: 5 }).unwrap();
3360		producer.create_group(group::Info { sequence: 3 }).unwrap();
3361		producer.create_group(group::Info { sequence: 4 }).unwrap();
3362
3363		// max_sequence = 5, which is at the front of the VecDeque.
3364		{
3365			let state = producer.state.read();
3366			assert_eq!(state.max_sequence, Some(5));
3367		}
3368
3369		// Expire all three groups.
3370		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
3371
3372		// Append seq 6 (becomes new max_sequence).
3373		producer.append_group().unwrap(); // seq 6
3374
3375		// Seq 3, 4, 5 are all expired. Seq 5 was the old max_sequence but now 6 is.
3376		// All old groups are evicted.
3377		{
3378			let state = producer.state.read();
3379			assert_eq!(live_groups(&state), 1);
3380			assert_eq!(first_live_sequence(&state), 6);
3381			assert!(!state.lookup.contains_key(&3));
3382			assert!(!state.lookup.contains_key(&4));
3383			assert!(!state.lookup.contains_key(&5));
3384			assert!(state.lookup.contains_key(&6));
3385		}
3386	}
3387
3388	#[tokio::test]
3389	async fn max_sequence_at_front_blocks_trim() {
3390		tokio::time::pause();
3391
3392		let mut producer = track_producer("test", None);
3393
3394		// Arrive: seq 5, then seq 3.
3395		producer.create_group(group::Info { sequence: 5 }).unwrap();
3396
3397		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
3398
3399		// Seq 3 arrives late; max_sequence is still 5 (at front).
3400		producer.create_group(group::Info { sequence: 3 }).unwrap();
3401
3402		// Seq 5 is max_sequence (protected). Seq 3 is not expired (just created).
3403		// Nothing should be evicted.
3404		{
3405			let state = producer.state.read();
3406			assert_eq!(live_groups(&state), 2);
3407			assert_eq!(state.offset, 0);
3408		}
3409
3410		// Expire seq 3 as well.
3411		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
3412
3413		// Seq 2 arrives late, triggering eviction.
3414		producer.create_group(group::Info { sequence: 2 }).unwrap();
3415
3416		// Seq 5 is the live edge (protected) and still resolves at the front of
3417		// `arrival`, so nothing is trimmed and the offset stays. Seq 3 expired out of
3418		// `lookup`, leaving a hole its arrival entry no longer resolves; seq 2 is
3419		// fresh and kept.
3420		{
3421			let state = producer.state.read();
3422			assert_eq!(live_groups(&state), 2);
3423			assert_eq!(state.offset, 0);
3424			assert!(state.lookup.contains_key(&5));
3425			assert!(!state.lookup.contains_key(&3));
3426			assert!(state.lookup.contains_key(&2));
3427		}
3428
3429		// Consumer should still be able to read through the hole.
3430		let mut consumer = producer.subscribe(None);
3431		let group = consumer.assert_group();
3432		// consume() starts at index 0; the first arrival entry that still resolves is seq 5.
3433		assert_eq!(group.sequence, 5);
3434	}
3435
3436	#[tokio::test]
3437	async fn abort_clears_cached_groups() {
3438		let mut producer = track_producer("test", None);
3439		producer.append_group().unwrap();
3440		producer.append_group().unwrap();
3441
3442		// A stale consumer that never drains must not pin the cached groups.
3443		let mut consumer = producer.subscribe(None);
3444		assert_eq!(live_groups(&producer.state.read()), 2);
3445
3446		producer.clone().abort(Error::Cancel).unwrap();
3447
3448		{
3449			let state = producer.state.read();
3450			assert!(state.lookup.is_empty(), "cached groups should be dropped on abort");
3451			assert!(state.arrival.is_empty());
3452			assert!(state.evict.is_empty());
3453		}
3454
3455		// The consumer now surfaces the abort error rather than the leftover cache.
3456		let result = consumer.recv_group().now_or_never().expect("should not block");
3457		assert!(matches!(result, Err(Error::Cancel)));
3458	}
3459
3460	#[tokio::test]
3461	async fn drop_unfinished_clears_cached_groups() {
3462		let producer = track_producer("test", None);
3463		let mut writer = producer.clone();
3464		writer.append_group().unwrap();
3465
3466		// A stale consumer keeps the channel (and thus the cache) alive.
3467		let mut consumer = producer.subscribe(None);
3468		assert_eq!(live_groups(&producer.state.read()), 1);
3469
3470		// Drop every producer without finishing: the cache is released.
3471		drop(writer);
3472		drop(producer);
3473
3474		let result = consumer.recv_group().now_or_never().expect("should not block");
3475		assert!(matches!(result, Err(Error::Dropped)));
3476	}
3477
3478	#[tokio::test]
3479	async fn drop_after_abort_does_not_warn() {
3480		// abort() closes the channel after recording `abort`. Drop must treat the
3481		// read-only guard returned by write() as clean or it emits a false WARN.
3482		let warns = count_drop_warnings("track::Producer dropped without finish", || {
3483			let producer = track_producer("test", None);
3484			let keep = producer.clone();
3485			let mut writer = producer.clone();
3486			let mut group = writer.append_group().unwrap();
3487			group.finish().unwrap();
3488			let _consumer = producer.subscribe(None);
3489			writer.abort(Error::Cancel).unwrap();
3490			drop(keep);
3491		});
3492		assert_eq!(warns, 0, "abort-then-drop must not emit unfinished-producer WARN");
3493	}
3494
3495	#[tokio::test]
3496	async fn drop_unfinished_warns() {
3497		let warns = count_drop_warnings("track::Producer dropped without finish", || {
3498			let producer = track_producer("test", None);
3499			let mut writer = producer.clone();
3500			writer.append_group().unwrap();
3501			let _consumer = producer.subscribe(None);
3502			drop(writer);
3503			drop(producer);
3504		});
3505		assert!(warns >= 1, "unfinished drop must emit unfinished-producer WARN");
3506	}
3507
3508	#[tokio::test]
3509	async fn drop_finished_keeps_cached_groups() {
3510		let mut producer = track_producer("test", None);
3511		producer.append_group().unwrap();
3512		producer.finish().unwrap();
3513
3514		let mut consumer = producer.subscribe(None);
3515		drop(producer);
3516
3517		// A cleanly finished track keeps its cache so the consumer can still drain.
3518		assert_eq!(consumer.assert_group().sequence, 0);
3519		let done = consumer.recv_group().now_or_never().expect("should not block").unwrap();
3520		assert!(done.is_none(), "consumer should drain then see clean finish");
3521	}
3522
3523	#[test]
3524	fn append_finish_cannot_be_rewritten() {
3525		let mut producer = track_producer("test", None);
3526
3527		// Finishing an empty track is valid (fin = 0, total groups = 0).
3528		assert!(producer.finish().is_ok());
3529		assert!(producer.finish().is_err());
3530		assert!(producer.append_group().is_err());
3531	}
3532
3533	#[test]
3534	fn finish_after_groups() {
3535		let mut producer = track_producer("test", None);
3536
3537		producer.append_group().unwrap();
3538		assert!(producer.finish().is_ok());
3539		assert!(producer.finish().is_err());
3540		assert!(producer.append_group().is_err());
3541	}
3542
3543	#[test]
3544	fn finish_at_rejects_a_boundary_at_or_below_the_live_edge() {
3545		let mut producer = track_producer("test", None);
3546		producer.create_group(group::Info { sequence: 5 }).unwrap();
3547
3548		// The boundary is exclusive, so it must be strictly above the highest produced
3549		// group. 5 or below would orphan groups that already exist.
3550		assert!(producer.finish_at(4).is_err());
3551		assert!(producer.finish_at(5).is_err());
3552		assert!(producer.finish_at(6).is_ok());
3553
3554		{
3555			let state = producer.state.read();
3556			assert_eq!(state.final_sequence, Some(6));
3557		}
3558
3559		// Re-finishing is rejected, and no group at or above the boundary can be created.
3560		assert!(producer.finish_at(6).is_err());
3561		assert!(producer.create_group(group::Info { sequence: 4 }).is_ok());
3562		assert!(producer.create_group(group::Info { sequence: 6 }).is_err());
3563	}
3564
3565	#[test]
3566	fn final_sequence_reports_the_declared_boundary() {
3567		let mut producer = track_producer("test", None);
3568		assert_eq!(producer.final_sequence(), None);
3569
3570		producer.create_group(group::Info { sequence: 5 }).unwrap();
3571		assert_eq!(producer.final_sequence(), None, "a group does not declare a boundary");
3572
3573		producer.finish_at(9).unwrap();
3574		assert_eq!(producer.final_sequence(), Some(9));
3575
3576		// finish() would try to declare a second boundary, so callers check first.
3577		assert!(producer.finish().is_err());
3578	}
3579
3580	#[test]
3581	fn final_sequence_reports_the_live_edge_after_finish() {
3582		let mut producer = track_producer("test", None);
3583		producer.create_group(group::Info { sequence: 5 }).unwrap();
3584		producer.finish().unwrap();
3585		assert_eq!(producer.final_sequence(), Some(6));
3586	}
3587
3588	#[tokio::test]
3589	async fn finish_at_declares_a_future_boundary() {
3590		let mut producer = track_producer("test", None);
3591		producer.create_group(group::Info { sequence: 5 }).unwrap();
3592
3593		// Learn the track ends at group 6 (exclusive 7) while the live edge is still 5.
3594		producer.finish_at(7).unwrap();
3595
3596		let mut consumer = producer.subscribe(None);
3597		assert_eq!(consumer.assert_group().sequence, 5);
3598
3599		// The boundary is known immediately, but the track isn't done: group 6 is still
3600		// outstanding, so the consumer parks rather than seeing end-of-stream.
3601		let boundary = consumer
3602			.finished()
3603			.now_or_never()
3604			.expect("boundary is known immediately")
3605			.expect("would have errored");
3606		assert_eq!(boundary, 7);
3607		assert!(
3608			consumer.recv_group().now_or_never().is_none(),
3609			"should wait for the outstanding group"
3610		);
3611
3612		// The trailing group arrives (below the boundary), then the track completes.
3613		producer.create_group(group::Info { sequence: 6 }).unwrap();
3614		assert_eq!(consumer.assert_group().sequence, 6);
3615		let done = consumer
3616			.recv_group()
3617			.now_or_never()
3618			.expect("should not block")
3619			.expect("would have errored");
3620		assert!(done.is_none(), "track completes once the boundary is reached");
3621	}
3622
3623	#[tokio::test]
3624	async fn recv_group_finishes_without_waiting_for_gaps() {
3625		let mut producer = track_producer("test", None);
3626		producer.create_group(group::Info { sequence: 1 }).unwrap();
3627		producer.finish().unwrap();
3628
3629		let mut consumer = producer.subscribe(None);
3630		assert_eq!(consumer.assert_group().sequence, 1);
3631
3632		let done = consumer
3633			.recv_group()
3634			.now_or_never()
3635			.expect("should not block")
3636			.expect("would have errored");
3637		assert!(done.is_none(), "track should finish without waiting for gaps");
3638	}
3639
3640	#[tokio::test]
3641	async fn next_group_skips_late_arrivals() {
3642		let mut producer = track_producer("test", None);
3643		let mut consumer = producer.subscribe(None);
3644
3645		// Seq 5 arrives first.
3646		producer.create_group(group::Info { sequence: 5 }).unwrap();
3647		let group = consumer
3648			.next_group()
3649			.now_or_never()
3650			.expect("should not block")
3651			.expect("would have errored")
3652			.expect("track should not be closed");
3653		assert_eq!(group.sequence, 5);
3654
3655		// Seq 3 arrives late, skipped because 3 <= 5.
3656		producer.create_group(group::Info { sequence: 3 }).unwrap();
3657		// Seq 4 arrives late and is also skipped.
3658		producer.create_group(group::Info { sequence: 4 }).unwrap();
3659		// Seq 7 arrives and is returned.
3660		producer.create_group(group::Info { sequence: 7 }).unwrap();
3661
3662		let group = consumer
3663			.next_group()
3664			.now_or_never()
3665			.expect("should not block")
3666			.expect("would have errored")
3667			.expect("track should not be closed");
3668		assert_eq!(group.sequence, 7);
3669
3670		// No more groups. This would block.
3671		assert!(
3672			consumer.next_group().now_or_never().is_none(),
3673			"should block waiting for a higher sequence"
3674		);
3675	}
3676
3677	#[tokio::test]
3678	async fn next_group_returns_arrivals_in_order() {
3679		let mut producer = track_producer("test", None);
3680		let mut consumer = producer.subscribe(None);
3681
3682		// Seq 3 arrives first, then seq 5. Both should be returned in arrival order.
3683		producer.create_group(group::Info { sequence: 3 }).unwrap();
3684		producer.create_group(group::Info { sequence: 5 }).unwrap();
3685
3686		let group = consumer
3687			.next_group()
3688			.now_or_never()
3689			.expect("should not block")
3690			.expect("would have errored")
3691			.expect("track should not be closed");
3692		assert_eq!(group.sequence, 3);
3693
3694		let group = consumer
3695			.next_group()
3696			.now_or_never()
3697			.expect("should not block")
3698			.expect("would have errored")
3699			.expect("track should not be closed");
3700		assert_eq!(group.sequence, 5);
3701	}
3702
3703	#[tokio::test]
3704	async fn next_group_and_recv_group_use_independent_cursors() {
3705		let mut producer = track_producer("test", None);
3706		let mut consumer = producer.subscribe(None);
3707
3708		// Out-of-order arrivals: seq 5 first, then seq 3.
3709		producer.create_group(group::Info { sequence: 5 }).unwrap();
3710		producer.create_group(group::Info { sequence: 3 }).unwrap();
3711
3712		// next_group is sequence-ordered: it returns the smallest sequence first,
3713		// regardless of arrival order.
3714		let group = consumer
3715			.next_group()
3716			.now_or_never()
3717			.expect("should not block")
3718			.expect("would have errored")
3719			.expect("track should not be closed");
3720		assert_eq!(group.sequence, 3);
3721
3722		// recv_group is arrival-ordered and uses an independent cursor, so it
3723		// still starts at the first arrival.
3724		assert_eq!(consumer.assert_group().sequence, 5);
3725	}
3726
3727	#[tokio::test]
3728	async fn end_at_caps_next_group() {
3729		let mut producer = track_producer("test", None);
3730		let mut consumer = producer.subscribe(None);
3731
3732		for s in 0..6 {
3733			producer.create_group(group::Info { sequence: s }).unwrap();
3734		}
3735
3736		consumer.end_at(2);
3737
3738		// Groups 0, 1, 2 are within the cap.
3739		assert_eq!(
3740			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3741			0
3742		);
3743		assert_eq!(
3744			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3745			1
3746		);
3747		assert_eq!(
3748			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3749			2
3750		);
3751
3752		// Group 3 is beyond the cap: next_group parks even though cached groups exist.
3753		assert!(
3754			consumer.next_group().now_or_never().is_none(),
3755			"capped consumer must block instead of returning out-of-range groups"
3756		);
3757	}
3758
3759	#[tokio::test]
3760	async fn end_at_release_drains_cached_groups() {
3761		let mut producer = track_producer("test", None);
3762		let mut consumer = producer.subscribe(None);
3763
3764		for s in 0..6 {
3765			producer.create_group(group::Info { sequence: s }).unwrap();
3766		}
3767
3768		consumer.end_at(1);
3769		assert_eq!(
3770			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3771			0
3772		);
3773		assert_eq!(
3774			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3775			1
3776		);
3777		assert!(consumer.next_group().now_or_never().is_none(), "capped at 1");
3778
3779		// Raise the cap; previously-blocked cached groups become available again.
3780		consumer.end_at(4);
3781		assert_eq!(
3782			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3783			2
3784		);
3785		assert_eq!(
3786			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3787			3
3788		);
3789		assert_eq!(
3790			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3791			4
3792		);
3793		assert!(consumer.next_group().now_or_never().is_none(), "capped at 4");
3794
3795		// Remove the cap; everything remaining flows.
3796		consumer.end_at(None);
3797		assert_eq!(
3798			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3799			5
3800		);
3801		assert!(consumer.next_group().now_or_never().is_none(), "no more groups");
3802	}
3803
3804	#[tokio::test]
3805	async fn end_at_lower_than_cursor_parks_consumer() {
3806		let mut producer = track_producer("test", None);
3807		let mut consumer = producer.subscribe(None);
3808
3809		for s in 0..3 {
3810			producer.create_group(group::Info { sequence: s }).unwrap();
3811		}
3812
3813		// Drain everything with no cap.
3814		assert_eq!(
3815			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3816			0
3817		);
3818		assert_eq!(
3819			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3820			1
3821		);
3822		assert_eq!(
3823			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3824			2
3825		);
3826
3827		// Lower the cap below the cursor. New groups beyond the cap are blocked.
3828		consumer.end_at(1);
3829		producer.create_group(group::Info { sequence: 3 }).unwrap();
3830		producer.create_group(group::Info { sequence: 4 }).unwrap();
3831		assert!(
3832			consumer.next_group().now_or_never().is_none(),
3833			"cap is below cursor; nothing returnable until cap rises"
3834		);
3835
3836		// Restoring the cap to no-limit (or any value >= cursor) releases them.
3837		consumer.end_at(None);
3838		assert_eq!(
3839			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3840			3
3841		);
3842		assert_eq!(
3843			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3844			4
3845		);
3846	}
3847
3848	#[tokio::test]
3849	async fn end_at_toggling_around_late_arrivals() {
3850		let mut producer = track_producer("test", None);
3851		let mut consumer = producer.subscribe(None);
3852
3853		consumer.end_at(5);
3854
3855		// Out-of-order arrivals all within the cap.
3856		producer.create_group(group::Info { sequence: 2 }).unwrap();
3857		producer.create_group(group::Info { sequence: 5 }).unwrap();
3858		producer.create_group(group::Info { sequence: 3 }).unwrap();
3859		// One beyond the cap; should be held even though it arrived in the middle.
3860		producer.create_group(group::Info { sequence: 8 }).unwrap();
3861		producer.create_group(group::Info { sequence: 4 }).unwrap();
3862
3863		// next_group walks in sequence order through everything <= cap.
3864		assert_eq!(
3865			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3866			2
3867		);
3868		assert_eq!(
3869			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3870			3
3871		);
3872		assert_eq!(
3873			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3874			4
3875		);
3876		assert_eq!(
3877			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3878			5
3879		);
3880		// Now blocked: 8 is still beyond the cap.
3881		assert!(consumer.next_group().now_or_never().is_none());
3882
3883		// Raise the cap; cached seq 8 is finally served.
3884		consumer.end_at(10);
3885		assert_eq!(
3886			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3887			8
3888		);
3889	}
3890
3891	/// `recv_group` (arrival order) honors the `end_at` cap by parking, like
3892	/// `next_group`: beyond-cap groups are held, not dropped, and a raised cap
3893	/// re-offers them, even after the track finishes.
3894	#[tokio::test]
3895	async fn end_at_parks_recv_group() {
3896		let mut producer = track_producer("test", None);
3897		let mut consumer = producer.subscribe(None);
3898
3899		for s in 0..3 {
3900			producer.create_group(group::Info { sequence: s }).unwrap();
3901		}
3902
3903		consumer.end_at(1);
3904		assert_eq!(
3905			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3906			0
3907		);
3908		assert_eq!(
3909			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3910			1
3911		);
3912		assert!(consumer.recv_group().now_or_never().is_none(), "capped at 1");
3913
3914		// A finished track keeps the parked group claimable: the cap may rise.
3915		producer.finish().unwrap();
3916		assert!(
3917			consumer.recv_group().now_or_never().is_none(),
3918			"still parked after finish"
3919		);
3920
3921		consumer.end_at(None);
3922		assert_eq!(
3923			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3924			2
3925		);
3926		assert!(
3927			matches!(consumer.recv_group().now_or_never(), Some(Ok(None))),
3928			"finished once the parked group drains"
3929		);
3930	}
3931
3932	/// A group beyond the cap must not block in-range groups that arrive behind
3933	/// it: a relay can ingest a burst micro-reordered (newest first).
3934	#[tokio::test]
3935	async fn recv_group_serves_arrivals_behind_the_cap() {
3936		let mut producer = track_producer("test", None);
3937		let mut consumer = producer.subscribe(None);
3938
3939		consumer.end_at(1);
3940
3941		// Reordered burst: the beyond-cap group arrives first.
3942		producer.create_group(group::Info { sequence: 2 }).unwrap();
3943		producer.create_group(group::Info { sequence: 0 }).unwrap();
3944		producer.create_group(group::Info { sequence: 1 }).unwrap();
3945
3946		assert_eq!(
3947			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3948			0
3949		);
3950		assert_eq!(
3951			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3952			1
3953		);
3954		assert!(consumer.recv_group().now_or_never().is_none(), "capped at 1");
3955
3956		consumer.end_at(2);
3957		assert_eq!(
3958			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3959			2
3960		);
3961	}
3962
3963	/// A raised `start_at` drops parked groups it overtook instead of re-offering
3964	/// them once the cap rises.
3965	#[tokio::test]
3966	async fn start_at_drops_parked_recv_groups() {
3967		let mut producer = track_producer("test", None);
3968		let mut consumer = producer.subscribe(None);
3969
3970		consumer.end_at(0);
3971		producer.create_group(group::Info { sequence: 1 }).unwrap();
3972		assert!(
3973			consumer.recv_group().now_or_never().is_none(),
3974			"group 1 parked at the cap"
3975		);
3976
3977		consumer.start_at(2);
3978		consumer.end_at(None);
3979		producer.create_group(group::Info { sequence: 2 }).unwrap();
3980		assert_eq!(
3981			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3982			2,
3983			"the overtaken parked group is dropped, not re-offered"
3984		);
3985	}
3986
3987	/// A parked group the producer aborts (eviction/expiry) is dropped: it is
3988	/// neither delivered once the cap rises nor allowed to hold the stream open
3989	/// after the track finishes. This is what bounds parking by the cache policy.
3990	#[tokio::test]
3991	async fn evicted_parked_recv_groups_are_dropped() {
3992		let mut producer = track_producer("test", None);
3993		let mut consumer = producer.subscribe(None);
3994
3995		producer.create_group(group::Info { sequence: 0 }).unwrap();
3996		assert_eq!(
3997			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
3998			0
3999		);
4000
4001		consumer.end_at(0);
4002		let straggler = producer.create_group(group::Info { sequence: 1 }).unwrap();
4003		assert!(
4004			consumer.recv_group().now_or_never().is_none(),
4005			"group 1 parked at the cap"
4006		);
4007
4008		// The cache evicts the parked group (abort-as-tombstone), then the track ends.
4009		straggler.abort(Error::Old).unwrap();
4010		producer.finish().unwrap();
4011
4012		consumer.end_at(None);
4013		assert!(
4014			matches!(consumer.recv_group().now_or_never(), Some(Ok(None))),
4015			"a dead parked group must not be delivered or hold the stream open"
4016		);
4017	}
4018
4019	/// Eviction aborts a parked group behind a sleeping subscriber's back. Nothing
4020	/// else will poll it (the track already finished), so the entry has to carry a
4021	/// waiter or the subscription sleeps forever holding its stream open.
4022	#[tokio::test]
4023	async fn evicted_parked_group_wakes_the_clean_end() {
4024		use std::sync::atomic::{AtomicUsize, Ordering};
4025		use std::task::{Context, Wake};
4026
4027		/// A waker that counts its wakes, for asserting a pending poll left a live
4028		/// registration behind.
4029		struct CountWaker(AtomicUsize);
4030		impl Wake for CountWaker {
4031			fn wake(self: std::sync::Arc<Self>) {
4032				self.0.fetch_add(1, Ordering::SeqCst);
4033			}
4034		}
4035
4036		let mut producer = track_producer("test", None);
4037		let mut consumer = producer.subscribe(None);
4038
4039		producer.create_group(group::Info { sequence: 0 }).unwrap();
4040		assert_eq!(
4041			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
4042			0
4043		);
4044
4045		consumer.end_at(0);
4046		let straggler = producer.create_group(group::Info { sequence: 1 }).unwrap();
4047		assert!(consumer.recv_group().now_or_never().is_none(), "parked at the cap");
4048		producer.finish().unwrap();
4049
4050		let counter = std::sync::Arc::new(CountWaker(AtomicUsize::new(0)));
4051		let waker = std::task::Waker::from(counter.clone());
4052		let mut cx = Context::from_waker(&waker);
4053		let mut fut = std::pin::pin!(consumer.recv_group());
4054		assert!(
4055			fut.as_mut().poll(&mut cx).is_pending(),
4056			"the parked group holds it open"
4057		);
4058
4059		straggler.abort(Error::Old).unwrap();
4060		assert!(counter.0.load(Ordering::SeqCst) > 0, "the eviction wakeup was lost");
4061		assert!(matches!(fut.as_mut().poll(&mut cx), Poll::Ready(Ok(None))));
4062	}
4063
4064	#[tokio::test]
4065	async fn read_frame_returns_single_frame_per_group() {
4066		let mut producer = track_producer("test", None);
4067		let mut consumer = producer.subscribe(None);
4068
4069		producer.write_frame(Timestamp::ZERO, b"hello".as_slice()).unwrap();
4070		producer.write_frame(Timestamp::ZERO, b"world".as_slice()).unwrap();
4071
4072		let frame = consumer
4073			.read_frame()
4074			.now_or_never()
4075			.expect("should not block")
4076			.expect("would have errored")
4077			.expect("track should not be closed");
4078		assert_eq!(&frame.payload[..], b"hello");
4079
4080		let frame = consumer
4081			.read_frame()
4082			.now_or_never()
4083			.expect("should not block")
4084			.expect("would have errored")
4085			.expect("track should not be closed");
4086		assert_eq!(&frame.payload[..], b"world");
4087	}
4088
4089	#[test]
4090	fn write_frame_rejects_an_oversized_frame_before_appending_its_group() {
4091		let mut producer = track_producer("test", None);
4092		let frame = bytes::Bytes::from(vec![0; group::MAX_GROUP_CACHE as usize + 1]);
4093
4094		assert!(matches!(
4095			producer.write_frame(Timestamp::ZERO, frame),
4096			Err(Error::FrameTooLarge)
4097		));
4098		assert_eq!(producer.latest(), None, "the rejected frame did not publish a group");
4099	}
4100
4101	#[tokio::test]
4102	async fn read_frame_preserves_timestamp() {
4103		let mut producer = track_producer("test", None);
4104		let mut consumer = producer.subscribe(None);
4105
4106		producer
4107			.write_frame(Timestamp::from_micros(20_000).unwrap(), b"hello".as_slice())
4108			.unwrap();
4109
4110		let frame = consumer
4111			.read_frame()
4112			.now_or_never()
4113			.expect("should not block")
4114			.expect("would have errored")
4115			.expect("track should not be closed");
4116		assert_eq!(frame.timestamp.as_micros(), 20_000);
4117		assert_eq!(&frame.payload[..], b"hello");
4118	}
4119
4120	#[tokio::test]
4121	async fn read_frame_skips_stalled_group_for_newer_ready_frame() {
4122		let mut producer = track_producer("test", None);
4123		let mut consumer = producer.subscribe(None);
4124
4125		// Seq 3: group open, no frame yet (stalled).
4126		let _stalled = producer.create_group(group::Info { sequence: 3 }).unwrap();
4127		// Seq 5: fully-written group with a frame.
4128		let mut g5 = producer.create_group(group::Info { sequence: 5 }).unwrap();
4129		g5.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"later"))
4130			.unwrap();
4131		g5.finish().unwrap();
4132
4133		// read_frame should not block on the stalled seq 3. It returns seq 5's frame.
4134		let frame = consumer
4135			.read_frame()
4136			.now_or_never()
4137			.expect("should not block on stalled earlier group")
4138			.expect("would have errored")
4139			.expect("track should not be closed");
4140		assert_eq!(&frame.payload[..], b"later");
4141	}
4142
4143	#[tokio::test]
4144	async fn read_frame_discards_rest_of_multi_frame_group() {
4145		let mut producer = track_producer("test", None);
4146		let mut consumer = producer.subscribe(None);
4147
4148		// Group 0 has two frames; only the first is returned.
4149		let mut g0 = producer.create_group(group::Info { sequence: 0 }).unwrap();
4150		g0.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"one"))
4151			.unwrap();
4152		g0.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"two"))
4153			.unwrap();
4154		g0.finish().unwrap();
4155
4156		// Group 1 is a normal single-frame group.
4157		producer.write_frame(Timestamp::ZERO, b"next".as_slice()).unwrap();
4158
4159		let frame = consumer
4160			.read_frame()
4161			.now_or_never()
4162			.expect("should not block")
4163			.expect("would have errored")
4164			.expect("track should not be closed");
4165		assert_eq!(&frame.payload[..], b"one");
4166
4167		// The second frame of group 0 is discarded; the next read jumps to group 1.
4168		let frame = consumer
4169			.read_frame()
4170			.now_or_never()
4171			.expect("should not block")
4172			.expect("would have errored")
4173			.expect("track should not be closed");
4174		assert_eq!(&frame.payload[..], b"next");
4175	}
4176
4177	#[tokio::test]
4178	async fn read_frame_waits_for_pending_group_after_finish() {
4179		// finish() sets final_sequence, but groups already created with lower sequences
4180		// can still produce frames. read_frame must not return None prematurely.
4181		let mut producer = track_producer("test", None);
4182		let mut consumer = producer.subscribe(None);
4183
4184		let mut g0 = producer.create_group(group::Info { sequence: 0 }).unwrap();
4185		producer.finish().unwrap();
4186
4187		// Track is finished but group 0 has no frame yet. It must block, not return None.
4188		assert!(
4189			consumer.read_frame().now_or_never().is_none(),
4190			"read_frame must block on a pending group even after finish()"
4191		);
4192
4193		// A late frame on the pending group is still delivered.
4194		g0.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"late"))
4195			.unwrap();
4196		let frame = consumer
4197			.read_frame()
4198			.now_or_never()
4199			.expect("should not block once a frame is written")
4200			.expect("would have errored")
4201			.expect("track should not be closed");
4202		assert_eq!(&frame.payload[..], b"late");
4203	}
4204
4205	#[tokio::test]
4206	async fn read_frame_respects_start_at() {
4207		// start_at sets min_sequence; read_frame must skip groups below it even though
4208		// next_sequence is still 0.
4209		let mut producer = track_producer("test", None);
4210		let mut consumer = producer.subscribe(None);
4211		consumer.start_at(5);
4212
4213		// Seq 3 has a frame but is below min_sequence, so it must be skipped.
4214		let mut g3 = producer.create_group(group::Info { sequence: 3 }).unwrap();
4215		g3.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"skip-me"))
4216			.unwrap();
4217		g3.finish().unwrap();
4218
4219		let mut g5 = producer.create_group(group::Info { sequence: 5 }).unwrap();
4220		g5.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"keep"))
4221			.unwrap();
4222		g5.finish().unwrap();
4223
4224		let frame = consumer
4225			.read_frame()
4226			.now_or_never()
4227			.expect("should not block")
4228			.expect("would have errored")
4229			.expect("track should not be closed");
4230		assert_eq!(&frame.payload[..], b"keep");
4231	}
4232
4233	#[tokio::test]
4234	async fn read_frame_returns_none_when_finished() {
4235		let mut producer = track_producer("test", None);
4236		let mut consumer = producer.subscribe(None);
4237
4238		producer.write_frame(Timestamp::ZERO, b"only".as_slice()).unwrap();
4239		producer.finish().unwrap();
4240
4241		let frame = consumer
4242			.read_frame()
4243			.now_or_never()
4244			.expect("should not block")
4245			.expect("would have errored")
4246			.expect("track should not be closed");
4247		assert_eq!(&frame.payload[..], b"only");
4248
4249		let done = consumer
4250			.read_frame()
4251			.now_or_never()
4252			.expect("should not block")
4253			.expect("would have errored");
4254		assert!(done.is_none());
4255	}
4256
4257	#[test]
4258	fn append_group_returns_bounds_exceeded_on_sequence_overflow() {
4259		let mut producer = track_producer("test", None);
4260		{
4261			let mut state = producer.state.write().ok().unwrap();
4262			state.max_sequence = Some(u64::MAX);
4263		}
4264
4265		assert!(matches!(producer.append_group(), Err(Error::BoundsExceeded(_))));
4266	}
4267
4268	#[tokio::test]
4269	async fn fetch_cache_hit() {
4270		let mut producer = track_producer("test", None);
4271
4272		// Produce a cached group.
4273		let mut group = producer.append_group().unwrap(); // seq 0
4274		group
4275			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hello"))
4276			.unwrap();
4277		group.finish().unwrap();
4278
4279		// A cached group resolves immediately and never queues a request. `peek_group`
4280		// also returns it synchronously.
4281		let dynamic = producer.dynamic();
4282		let consumer = producer.consume();
4283		assert!(consumer.peek_group(0).is_some());
4284		let mut g = consumer.fetch_group(0, None).await.unwrap();
4285		assert_eq!(g.sequence, 0);
4286		assert_eq!(&g.read_frame().await.unwrap().unwrap().payload[..], b"hello");
4287
4288		// Nothing was queued for the dynamic handler to serve.
4289		assert!(dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending());
4290	}
4291
4292	#[tokio::test]
4293	async fn fetch_miss_signals_dynamic() {
4294		let producer = track_producer("test", None);
4295		let dynamic = producer.dynamic();
4296		let consumer = producer.consume();
4297
4298		// A cache miss isn't in `peek_group`, but a dynamic handler exists, so
4299		// `fetch_group` stays pending and queues a request. `*pending` derefs the
4300		// wrapper to the inner `Fetching` (a `kio::Pollable`).
4301		assert!(consumer.peek_group(5).is_none());
4302		let pending = consumer.fetch_group(5, group::Fetch::default().with_priority(7));
4303		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
4304
4305		let req = dynamic
4306			.requested_group()
4307			.now_or_never()
4308			.expect("should not block")
4309			.unwrap();
4310		assert_eq!(req.sequence(), 5);
4311		assert_eq!(req.priority(), 7);
4312
4313		// Serve it by accepting the request; the fetch then resolves.
4314		let mut group = req.accept(None).unwrap();
4315		group
4316			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi"))
4317			.unwrap();
4318		group.finish().unwrap();
4319
4320		let mut g = pending.await.unwrap();
4321		assert_eq!(g.sequence, 5);
4322		assert_eq!(&g.read_frame().await.unwrap().unwrap().payload[..], b"hi");
4323	}
4324
4325	#[tokio::test]
4326	async fn fetch_miss_rejects() {
4327		let producer = track_producer("test", None);
4328		let dynamic = producer.dynamic();
4329		let consumer = producer.consume();
4330
4331		let pending = consumer.fetch_group(5, None);
4332		let req = dynamic
4333			.requested_group()
4334			.now_or_never()
4335			.expect("should not block")
4336			.unwrap();
4337
4338		req.reject(Error::Cancel);
4339		assert!(matches!(pending.await, Err(Error::Cancel)));
4340		let fetch = producer.state.read().fetch.clone();
4341		assert!(fetch.read().is_empty());
4342	}
4343
4344	#[tokio::test]
4345	async fn fetch_miss_drop_rejects() {
4346		let producer = track_producer("test", None);
4347		let dynamic = producer.dynamic();
4348		let consumer = producer.consume();
4349
4350		let pending = consumer.fetch_group(5, None);
4351		let req = dynamic
4352			.requested_group()
4353			.now_or_never()
4354			.expect("should not block")
4355			.unwrap();
4356
4357		drop(req);
4358		assert!(matches!(pending.await, Err(Error::Dropped)));
4359	}
4360
4361	#[tokio::test]
4362	async fn fetch_reject_does_not_poison_retry() {
4363		let producer = track_producer("test", None);
4364		let dynamic = producer.dynamic();
4365		let consumer = producer.consume();
4366
4367		let pending = consumer.fetch_group(5, None);
4368		let req = dynamic
4369			.requested_group()
4370			.now_or_never()
4371			.expect("should not block")
4372			.unwrap();
4373		req.reject(Error::Cancel);
4374		assert!(matches!(pending.await, Err(Error::Cancel)));
4375
4376		let retry = consumer.fetch_group(5, None);
4377		let req = dynamic
4378			.requested_group()
4379			.now_or_never()
4380			.expect("should not block")
4381			.unwrap();
4382		let mut group = req.accept(None).unwrap();
4383		group
4384			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"retry"))
4385			.unwrap();
4386		group.finish().unwrap();
4387
4388		let mut group = retry.await.unwrap();
4389		assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"retry");
4390	}
4391
4392	#[tokio::test]
4393	async fn fetch_coalesces_concurrent() {
4394		let producer = track_producer("test", None);
4395		let dynamic = producer.dynamic();
4396		let consumer = producer.consume();
4397
4398		// Two fetches for the same uncached group produce ONE handler request,
4399		// carrying the higher of the two priorities.
4400		let first = consumer.fetch_group(5, group::Fetch::default().with_priority(1));
4401		let second = consumer.fetch_group(5, group::Fetch::default().with_priority(7));
4402		assert!(kio::Pollable::poll(&*first, &kio::Waiter::noop()).is_pending());
4403
4404		let req = dynamic
4405			.requested_group()
4406			.now_or_never()
4407			.expect("should not block")
4408			.unwrap();
4409		assert_eq!(req.sequence(), 5);
4410		assert_eq!(req.priority(), 7);
4411		assert!(
4412			dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending(),
4413			"the second fetch queued a duplicate request"
4414		);
4415
4416		// A fetch arriving while the request is already in flight joins it too.
4417		let third = consumer.fetch_group(5, None);
4418
4419		// One accept resolves all of them.
4420		let mut group = req.accept(None).unwrap();
4421		group
4422			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi"))
4423			.unwrap();
4424		group.finish().unwrap();
4425
4426		assert_eq!(first.await.unwrap().sequence, 5);
4427		assert_eq!(second.await.unwrap().sequence, 5);
4428		assert_eq!(third.await.unwrap().sequence, 5);
4429	}
4430
4431	#[tokio::test]
4432	async fn fetch_coalesced_reject_fails_all() {
4433		let producer = track_producer("test", None);
4434		let dynamic = producer.dynamic();
4435		let consumer = producer.consume();
4436
4437		let first = consumer.fetch_group(5, None);
4438		let second = consumer.fetch_group(5, None);
4439		let req = dynamic
4440			.requested_group()
4441			.now_or_never()
4442			.expect("should not block")
4443			.unwrap();
4444		req.reject(Error::Cancel);
4445
4446		assert!(matches!(first.await, Err(Error::Cancel)));
4447		assert!(matches!(second.await, Err(Error::Cancel)));
4448
4449		// The rejected attempt is gone: a retry starts a fresh one.
4450		let retry = consumer.fetch_group(5, None);
4451		assert!(kio::Pollable::poll(&*retry, &kio::Waiter::noop()).is_pending());
4452		let req = dynamic
4453			.requested_group()
4454			.now_or_never()
4455			.expect("should not block")
4456			.unwrap();
4457		assert_eq!(req.sequence(), 5);
4458	}
4459
4460	#[tokio::test]
4461	async fn fetch_queued_fails_when_handlers_leave() {
4462		let producer = track_producer("test", None);
4463		let dynamic = producer.dynamic();
4464		let consumer = producer.consume();
4465
4466		// Queued but never popped: the last handler leaving fails it fast.
4467		let pending = consumer.fetch_group(5, None);
4468		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
4469		drop(dynamic);
4470		assert!(matches!(pending.await, Err(Error::NotFound)));
4471
4472		// And the attempt didn't leak.
4473		let fetch = producer.state.read().fetch.clone();
4474		assert!(fetch.read().is_empty());
4475	}
4476
4477	#[tokio::test]
4478	async fn fetch_miss_no_dynamic_not_found() {
4479		// A track with no `Dynamic` can't serve old content, so a cache miss
4480		// resolves to NotFound instead of blocking forever.
4481		let mut producer = track_producer("test", None);
4482		producer.append_group().unwrap(); // seq 0, but we miss on seq 5
4483		let consumer = producer.consume();
4484		assert!(matches!(consumer.fetch_group(5, None).await, Err(Error::NotFound)));
4485	}
4486
4487	#[tokio::test]
4488	async fn fetch_past_final_not_found() {
4489		let mut producer = track_producer("test", None);
4490		producer.append_group().unwrap(); // seq 0
4491		producer.finish().unwrap(); // final_sequence = 1
4492
4493		// A group at or past the final sequence can never exist, even with a handler,
4494		// so it resolves to NotFound.
4495		let dynamic = producer.dynamic();
4496		let consumer = producer.consume();
4497		assert!(matches!(consumer.fetch_group(5, None).await, Err(Error::NotFound)));
4498
4499		// And it doesn't signal the dynamic handler.
4500		assert!(dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending());
4501	}
4502
4503	/// Mint a track whose groups charge into a bounded [`cache::Pool`].
4504	fn pooled_producer(capacity: u64) -> (Producer, cache::Pool) {
4505		let pool = cache::Pool::new(capacity);
4506		let broadcast = broadcast::Info {
4507			origin: crate::origin::Info::default().with_pool(pool.clone()),
4508			..Default::default()
4509		};
4510		let producer = Producer::new(Arc::new(broadcast), "test", None);
4511		(producer, pool)
4512	}
4513
4514	fn finished_group(producer: &mut Producer, size: usize) -> u64 {
4515		let mut group = producer.append_group().unwrap();
4516		group
4517			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; size]))
4518			.unwrap();
4519		group.finish().unwrap();
4520		group.sequence
4521	}
4522
4523	/// While the pool is over capacity, every append accrues debt and pays it by
4524	/// evicting this track's own oldest groups, so the newest content survives.
4525	#[tokio::test]
4526	async fn debt_evicts_oldest_group() {
4527		tokio::time::pause();
4528
4529		// Fits one 10k group; each additional group pushes the pool over budget.
4530		let (mut producer, pool) = pooled_producer(10_000);
4531
4532		finished_group(&mut producer, 10_000); // seq 0
4533		finished_group(&mut producer, 10_000); // seq 1: over budget, debt starts accruing
4534		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
4535
4536		let consumer = producer.consume();
4537		assert!(consumer.peek_group(0).is_none(), "oldest group is evicted");
4538		assert!(consumer.peek_group(2).is_some(), "latest group survives");
4539		// Steady state carries the protected live edge plus the just-demoted group
4540		// (debt is charged before the demotion, so eviction lags one append).
4541		assert!(pool.used() <= 21_000, "usage hovers near capacity: {}", pool.used());
4542
4543		// A fresh subscriber skips the evicted groups entirely.
4544		let mut subscriber = producer.subscribe(None);
4545		assert!(subscriber.assert_group().sequence > 0, "evicted group is not delivered");
4546	}
4547
4548	/// The latest group is never in the eviction order, so it survives any budget.
4549	#[tokio::test]
4550	async fn latest_group_never_evicted() {
4551		tokio::time::pause();
4552
4553		// Far too small for even one group: the latest survives anyway.
4554		let (mut producer, pool) = pooled_producer(100);
4555		finished_group(&mut producer, 1000); // seq 0
4556		assert!(pool.used() > 100, "the latest may exceed the budget");
4557
4558		// Later writes evict the demoted seq 0; each new latest is untouchable in turn.
4559		finished_group(&mut producer, 1000); // seq 1: demotes seq 0
4560		finished_group(&mut producer, 1000); // seq 2: pays by evicting seq 0
4561
4562		let consumer = producer.consume();
4563		assert!(consumer.peek_group(0).is_none());
4564		let mut group = consumer.peek_group(2).expect("latest survives");
4565		assert_eq!(group.read_frame().await.unwrap().unwrap().payload.len(), 1000);
4566	}
4567
4568	/// A FETCH cache hit refreshes the group's access time: anything accessed more
4569	/// recently than the pool-wide average is protected, so the eviction walk skips
4570	/// it and evicts a never-read group instead, even one that arrived later.
4571	#[tokio::test]
4572	async fn fetch_refresh_survives_eviction() {
4573		tokio::time::pause();
4574
4575		let (mut producer, _pool) = pooled_producer(10_000);
4576		let consumer = producer.consume();
4577
4578		finished_group(&mut producer, 3_000); // seq 0
4579		tokio::time::advance(Duration::from_secs(1)).await;
4580		finished_group(&mut producer, 3_000); // seq 1
4581		tokio::time::advance(Duration::from_secs(1)).await;
4582		finished_group(&mut producer, 3_000); // seq 2
4583		tokio::time::advance(Duration::from_millis(500)).await;
4584
4585		// FETCH seq 0: the cache hit lifts its access time above the average.
4586		let mut fetched = consumer.fetch_group(0, None).await.unwrap();
4587		assert_eq!(fetched.read_frame().await.unwrap().unwrap().payload.len(), 3_000);
4588		tokio::time::advance(Duration::from_millis(500)).await;
4589
4590		// Pressure: seq 0 is first in eviction order but freshly accessed, so it
4591		// rotates to the back and the never-read seq 1 dies instead.
4592		finished_group(&mut producer, 3_000); // seq 3
4593		tokio::time::advance(Duration::from_secs(1)).await;
4594		finished_group(&mut producer, 3_000); // seq 4
4595
4596		assert!(consumer.peek_group(0).is_some(), "refreshed group survives");
4597		assert!(consumer.peek_group(1).is_none(), "unread group is evicted instead");
4598	}
4599
4600	/// A consumer holding an evicted group surfaces the eviction, not a hang or a
4601	/// truncated clean end.
4602	#[tokio::test]
4603	async fn eviction_aborts_readers() {
4604		tokio::time::pause();
4605
4606		let (mut producer, _pool) = pooled_producer(10_000);
4607		let mut subscriber = producer.subscribe(None);
4608
4609		finished_group(&mut producer, 10_000); // seq 0
4610		let mut group0 = subscriber.assert_group();
4611
4612		finished_group(&mut producer, 10_000); // seq 1: demotes seq 0
4613		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
4614
4615		let read = group0.read_frame().await;
4616		assert!(matches!(read, Err(Error::Evicted)), "expected Evicted, got {read:?}");
4617	}
4618
4619	/// A write smaller than the next victim carries debt instead of evicting: a
4620	/// large group dies only once enough debt accumulates, never to pay off a
4621	/// far smaller write.
4622	#[tokio::test]
4623	async fn small_writes_carry_debt() {
4624		tokio::time::pause();
4625
4626		let (mut producer, pool) = pooled_producer(22_000);
4627		let consumer = producer.consume();
4628
4629		finished_group(&mut producer, 20_000); // seq 0, the large victim-to-be
4630
4631		// The first few small writes owe far less than seq 0's size: the debt
4632		// carries over instead of evicting it.
4633		for _ in 0..3 {
4634			finished_group(&mut producer, 1_000);
4635		}
4636		assert!(consumer.peek_group(0).is_some(), "debt smaller than the victim carries");
4637
4638		// Enough small writes accumulate the debt to finally evict it.
4639		for _ in 0..20 {
4640			finished_group(&mut producer, 1_000);
4641		}
4642		assert!(
4643			consumer.peek_group(0).is_none(),
4644			"accumulated debt evicts the large group"
4645		);
4646		// Steady state hovers within about one group of capacity: a victim smaller
4647		// than the outstanding debt is never evicted, so the excess stays bounded.
4648		assert!(pool.used() <= 24_000, "usage hovers near capacity: {}", pool.used());
4649	}
4650
4651	/// One write pays at most twice what it produced, so a capacity shrink (or one
4652	/// track's burst) drains gradually instead of one writer dumping its whole
4653	/// backlog in a single call.
4654	#[tokio::test]
4655	async fn payment_capped_per_write() {
4656		tokio::time::pause();
4657
4658		let (mut producer, pool) = pooled_producer(1 << 40);
4659		for _ in 0..10 {
4660			finished_group(&mut producer, 1_000);
4661		}
4662
4663		// The governor slashes the target; nothing is reclaimed synchronously.
4664		pool.resize(100);
4665		let before = pool.used();
4666
4667		// One 1k write may evict at most ~2k of backlog, not all ten groups.
4668		finished_group(&mut producer, 1_000);
4669
4670		let consumer = producer.consume();
4671		assert!(consumer.peek_group(0).is_none(), "the oldest groups are evicted");
4672		assert!(consumer.peek_group(1).is_none());
4673		assert!(consumer.peek_group(2).is_some(), "the backlog drains gradually");
4674		assert!(pool.used() > before - 4_000, "one write must not dump the backlog");
4675	}
4676
4677	/// Accepting a track after pre-accept backfill must keep the same write
4678	/// counter: the counter is owned by the track state, so replacing the info
4679	/// can't strand the bytes already-created groups keep charging.
4680	#[tokio::test]
4681	async fn accept_preserves_write_accounting() {
4682		tokio::time::pause();
4683
4684		let pool = cache::Pool::new(12_000);
4685		let broadcast = broadcast::Info {
4686			origin: crate::origin::Info::default().with_pool(pool.clone()),
4687			..Default::default()
4688		};
4689		let request = Request::new(Arc::new(broadcast), "test");
4690		let dynamic = request.dynamic();
4691		let consumer = request.consume();
4692
4693		// Serve a backfill before the track is accepted, then grow it.
4694		let pending = consumer.fetch_group(0, None);
4695		let req = dynamic
4696			.requested_group()
4697			.now_or_never()
4698			.expect("should not block")
4699			.unwrap();
4700		let mut backfill = req.accept(None).unwrap();
4701		pending.await.unwrap();
4702		backfill
4703			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 30_000]))
4704			.unwrap();
4705
4706		// Accept with a fresh Info: the pre-accept group's writes must still be
4707		// drained by this track's future charges.
4708		let mut producer = request.accept(None);
4709		producer.append_group().unwrap().finish().unwrap();
4710		producer.append_group().unwrap().finish().unwrap();
4711
4712		assert!(
4713			producer.consume().peek_group(0).is_none(),
4714			"pre-accept backfill growth is reclaimed after accept"
4715		);
4716		assert!(pool.used() <= 13_000, "usage converges: {}", pool.used());
4717	}
4718
4719	/// Re-serving a sequence many times must not accumulate eviction hints: stale
4720	/// hints die on stamp mismatch and compaction reclaims them.
4721	#[tokio::test]
4722	async fn recreated_sequence_bounds_eviction_hints() {
4723		let (mut producer, _pool) = pooled_producer(1 << 40);
4724		producer.create_group(5u64.into()).unwrap().finish().unwrap();
4725
4726		for _ in 0..200 {
4727			let group = producer.create_group(1u64.into()).unwrap();
4728			group.abort(Error::Cancel).unwrap();
4729		}
4730
4731		let state = producer.state.read();
4732		assert!(
4733			state.evict.len() <= 2 * state.lookup.len() + EVICT_SLACK,
4734			"stale hints are compacted: {} entries for {} slots",
4735			state.evict.len(),
4736			state.lookup.len()
4737		);
4738	}
4739
4740	/// A frame write within the same coarse tick still outranks merely-inserted
4741	/// content, so the freshly-written group survives and the empty one pays.
4742	#[tokio::test]
4743	async fn same_tick_write_outranks_inserted() {
4744		tokio::time::pause();
4745
4746		// No time advances: every stamp lands in the same tick.
4747		let (mut producer, _pool) = pooled_producer(10_000);
4748
4749		producer.append_group().unwrap().finish().unwrap(); // seq 0: empty
4750		finished_group(&mut producer, 3_000); // seq 1: written
4751		finished_group(&mut producer, 3_000); // seq 2
4752		finished_group(&mut producer, 3_000); // seq 3
4753		finished_group(&mut producer, 3_000); // seq 4: over budget, pays
4754
4755		let consumer = producer.consume();
4756		assert!(consumer.peek_group(0).is_none(), "insert-only content pays first");
4757		assert!(consumer.peek_group(1).is_some(), "same-tick written content survives");
4758	}
4759
4760	/// A track that only appends frames to an open group, never inserting another
4761	/// group, still settles its eviction debt once enough bytes accumulate.
4762	#[tokio::test]
4763	async fn frame_only_writer_pays() {
4764		tokio::time::pause();
4765
4766		let (mut producer, pool) = pooled_producer(2_000);
4767		let mut demoted = producer.append_group().unwrap(); // seq 0
4768		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
4769
4770		// One large frame crosses the charge threshold: the write itself pays,
4771		// with no further group insert on this track.
4772		demoted
4773			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 300_000]))
4774			.unwrap();
4775
4776		assert!(
4777			pool.used() <= 5_000,
4778			"the frame write settled the debt: {}",
4779			pool.used()
4780		);
4781		assert!(matches!(demoted.finish(), Err(Error::Evicted)));
4782	}
4783
4784	/// One `Info` describing several tracks must not join their eviction accounting:
4785	/// each track opens its own account against the pool.
4786	#[tokio::test]
4787	async fn each_track_owns_its_account() {
4788		let broadcast = Arc::new(broadcast::Info::default());
4789		let info = Info::default();
4790		let a = Producer::new(broadcast.clone(), "a", info.clone());
4791		let b = Producer::new(broadcast, "b", info);
4792
4793		let a = a.state.read().cache.clone();
4794		let b = b.state.read().cache.clone();
4795		assert!(!Arc::ptr_eq(&a, &b), "each track owns its account");
4796	}
4797
4798	/// A `Dynamic` still serving fetches keeps the track alive, so the publisher
4799	/// letting go isn't an abrupt teardown: the handler can still serve the cache.
4800	#[tokio::test]
4801	async fn a_dynamic_defers_teardown() {
4802		let (mut producer, pool) = pooled_producer(1 << 40);
4803		let dynamic = producer.dynamic();
4804		finished_group(&mut producer, 100);
4805
4806		drop(producer);
4807		assert!(pool.used() > 0, "the handler still serves the cache");
4808
4809		drop(dynamic);
4810		assert_eq!(pool.used(), 0, "the last handle tears it down");
4811	}
4812
4813	/// A finished track releases everything once every handle is gone.
4814	///
4815	/// Its groups hold the cache account, and the account links back here, so that link
4816	/// has to be weak: anything stronger makes the state (and every cached frame in it)
4817	/// immortal, even with no producer or consumer left.
4818	#[tokio::test]
4819	async fn finished_track_frees_its_cache() {
4820		let (mut producer, pool) = pooled_producer(1 << 40);
4821		finished_group(&mut producer, 100);
4822		producer.finish().unwrap();
4823
4824		let state = producer.state.downgrade();
4825		drop(producer);
4826
4827		assert!(state.upgrade().is_none(), "the track state is freed");
4828		assert_eq!(pool.used(), 0, "so are its cached bytes");
4829	}
4830
4831	/// A group settling its eviction debt upgrades the account's weak handle, which
4832	/// counts as a producer on the track state. Teardown must not mistake that for a
4833	/// surviving publisher, or an abrupt drop silently behaves like a clean finish.
4834	#[tokio::test]
4835	async fn teardown_ignores_a_settling_group() {
4836		let (mut producer, pool) = pooled_producer(1 << 40);
4837		finished_group(&mut producer, 100);
4838
4839		// Stand in for a concurrent `cache::Track::settle`, mid-upgrade.
4840		let settling = producer.state.downgrade().upgrade().expect("open");
4841		drop(producer);
4842
4843		assert_eq!(pool.used(), 0, "the abrupt teardown still released the cache");
4844		drop(settling);
4845	}
4846
4847	/// A subscriber holding one cached group must not pin the whole track: a group
4848	/// carries the track's properties by value, not a handle back to its state.
4849	#[tokio::test]
4850	async fn cached_group_outlives_its_track() {
4851		let (mut producer, pool) = pooled_producer(1 << 40);
4852		let sequence = finished_group(&mut producer, 100);
4853		let group = producer.consume().peek_group(sequence).expect("cached");
4854		producer.finish().unwrap();
4855
4856		let state = producer.state.downgrade();
4857		drop(producer);
4858		assert!(state.upgrade().is_none(), "the track state is freed");
4859		assert!(pool.used() > 0, "the retained group keeps its own bytes");
4860
4861		drop(group);
4862		assert_eq!(pool.used(), 0, "which it releases when dropped");
4863	}
4864
4865	/// A backfill served before the track was accepted settles its own debt: the
4866	/// account exists from the moment the state does, so acceptance replacing the
4867	/// `Info` can't leave already-created groups writing for free.
4868	#[tokio::test]
4869	async fn pre_accept_backfill_settles_late_writes() {
4870		tokio::time::pause();
4871
4872		let pool = cache::Pool::new(2_000);
4873		let broadcast = broadcast::Info {
4874			origin: crate::origin::Info::default().with_pool(pool.clone()),
4875			..Default::default()
4876		};
4877		let request = Request::new(Arc::new(broadcast), "test");
4878		let dynamic = request.dynamic();
4879		let consumer = request.consume();
4880
4881		// Serve backfill seq 0 before the track is accepted.
4882		let pending = consumer.fetch_group(0, None);
4883		let req = dynamic
4884			.requested_group()
4885			.now_or_never()
4886			.expect("should not block")
4887			.unwrap();
4888		let mut backfill = req.accept(None).unwrap();
4889		pending.await.unwrap();
4890
4891		// Accept, then demote the backfill with a live group.
4892		let mut producer = request.accept(None);
4893		producer.append_group().unwrap().finish().unwrap();
4894
4895		// No further insert: the late write into the demoted backfill is the only
4896		// thing that can pay the debt it just took on.
4897		backfill
4898			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 300_000]))
4899			.unwrap();
4900
4901		assert!(
4902			pool.used() <= 5_000,
4903			"the frame write settled the debt: {}",
4904			pool.used()
4905		);
4906	}
4907
4908	/// A late frame write restarts the retention clock (retention is documented as
4909	/// time since last written or fetched), so an actively-growing group is not
4910	/// expired as old mid-write.
4911	#[tokio::test]
4912	async fn write_restarts_retention_clock() {
4913		tokio::time::pause();
4914
4915		let (mut producer, _pool) = pooled_producer(1 << 40);
4916		let mut straggler = producer.append_group().unwrap(); // seq 0
4917		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
4918
4919		// Idle past the window, then the straggler receives a late frame.
4920		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
4921		straggler
4922			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
4923			.unwrap();
4924		producer.append_group().unwrap().finish().unwrap(); // seq 2 runs expiry
4925
4926		let consumer = producer.consume();
4927		assert!(consumer.peek_group(0).is_some(), "the write restarted the clock");
4928
4929		// Once the writes stop, the group ages out normally.
4930		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
4931		producer.append_group().unwrap().finish().unwrap(); // seq 3 runs expiry
4932		assert!(consumer.peek_group(0).is_none(), "idle content still expires");
4933	}
4934
4935	/// Continuously refreshed entries at the front of the eviction order must not
4936	/// starve expiry of entries behind them: the scan cursor rotates.
4937	#[tokio::test]
4938	async fn refreshed_front_does_not_starve_expiry() {
4939		tokio::time::pause();
4940
4941		let (mut producer, _pool) = pooled_producer(1 << 40);
4942		let dynamic = producer.dynamic();
4943		let consumer = producer.consume();
4944
4945		producer.create_group(10u64.into()).unwrap().finish().unwrap();
4946		for sequence in 1..=5u64 {
4947			let pending = consumer.fetch_group(sequence, None);
4948			let req = dynamic
4949				.requested_group()
4950				.now_or_never()
4951				.expect("should not block")
4952				.unwrap();
4953			let mut group = req.accept(None).unwrap();
4954			group
4955				.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
4956				.unwrap();
4957			group.finish().unwrap();
4958			pending.await.unwrap();
4959		}
4960
4961		// Age everything out, then refresh the first four backfills so they sit
4962		// fresh at the front of the eviction order, hiding the expired fifth.
4963		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
4964		for sequence in 1..=4u64 {
4965			consumer.fetch_group(sequence, None).await.unwrap();
4966		}
4967
4968		// The rotating cursor reaches the fifth entry within a few writes.
4969		for _ in 0..3 {
4970			producer.append_group().unwrap().finish().unwrap();
4971		}
4972		assert!(consumer.peek_group(5).is_none(), "expired backfill is reclaimed");
4973		assert!(consumer.peek_group(1).is_some(), "refreshed backfill survives");
4974	}
4975
4976	/// A publisher re-creating an aborted sequence is delivered exactly once, at
4977	/// its actual arrival position: the historical arrival entry is dead.
4978	#[tokio::test]
4979	async fn recreated_sequence_delivered_once() {
4980		let (mut producer, _pool) = pooled_producer(1 << 40);
4981
4982		producer.create_group(0u64.into()).unwrap().finish().unwrap();
4983		let aborted = producer.create_group(1u64.into()).unwrap();
4984		aborted.abort(Error::Cancel).unwrap();
4985		producer.create_group(2u64.into()).unwrap().finish().unwrap();
4986		producer.create_group(1u64.into()).unwrap().finish().unwrap();
4987
4988		let mut subscriber = producer.subscribe(None);
4989		assert_eq!(subscriber.assert_group().sequence, 0);
4990		assert_eq!(subscriber.assert_group().sequence, 2);
4991		assert_eq!(
4992			subscriber.assert_group().sequence,
4993			1,
4994			"replacement arrives at its own position"
4995		);
4996		subscriber.assert_no_group();
4997	}
4998
4999	/// Datagrams share `max_sequence` but must not break group demotion: the live
5000	/// edge is tracked per group, so interleaving datagrams can't strand groups
5001	/// outside the eviction order and bypass the budget.
5002	#[tokio::test]
5003	async fn datagrams_do_not_block_eviction() {
5004		tokio::time::pause();
5005
5006		let (mut producer, pool) = pooled_producer(1_000);
5007		for _ in 0..10 {
5008			finished_group(&mut producer, 1_000);
5009			producer.append_datagram(Timestamp::ZERO, &b"beat"[..]).unwrap();
5010		}
5011
5012		let consumer = producer.consume();
5013		assert!(consumer.peek_group(0).is_none(), "old groups still evict");
5014		assert!(
5015			pool.used() < 4 * 1_256,
5016			"interleaved datagrams must not bypass the budget: {}",
5017			pool.used()
5018		);
5019	}
5020
5021	/// An aborted group releases its access sample along with its bytes, from any
5022	/// handle: ghost samples must not linger in the pool mean where they'd hold it
5023	/// in the past and over-protect every live group.
5024	#[tokio::test]
5025	async fn aborted_group_leaves_no_ghost_sample() {
5026		tokio::time::pause();
5027
5028		let (mut producer, pool) = pooled_producer(1 << 40);
5029		let group0 = producer.append_group().unwrap();
5030		producer.append_group().unwrap(); // demotes seq 0 into the mean
5031
5032		assert!(pool.average().is_some(), "demoted group is sampled");
5033		group0.abort(Error::Cancel).unwrap();
5034		assert_eq!(pool.average(), None, "the abort must remove the sample");
5035	}
5036
5037	/// Empty groups still carry fixed overhead; they must repay the budget when
5038	/// evicted rather than being unevictable freeloaders.
5039	#[tokio::test]
5040	async fn empty_groups_repay_overhead() {
5041		tokio::time::pause();
5042
5043		let (mut producer, pool) = pooled_producer(1_000);
5044		for _ in 0..100 {
5045			let mut group = producer.append_group().unwrap();
5046			group.finish().unwrap();
5047		}
5048
5049		assert!(
5050			pool.used() <= 3_000,
5051			"empty-group overhead must stay near the budget: {}",
5052			pool.used()
5053		);
5054	}
5055
5056	/// Late growth on an already-demoted group is billed: the gross-write counter
5057	/// feeds debt on the next append, so a straggler can't grow unbounded.
5058	#[tokio::test]
5059	async fn growth_on_demoted_group_is_billed() {
5060		tokio::time::pause();
5061
5062		let (mut producer, pool) = pooled_producer(2_000);
5063		let mut straggler = producer.append_group().unwrap(); // seq 0
5064		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
5065
5066		// The demoted group balloons: no eviction yet (nothing ran), but billed.
5067		straggler
5068			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 10_000]))
5069			.unwrap();
5070
5071		// The next append observes the growth and evicts the straggler.
5072		producer.append_group().unwrap().finish().unwrap(); // seq 2
5073
5074		let consumer = producer.consume();
5075		assert!(consumer.peek_group(0).is_none(), "the ballooned group is evicted");
5076		assert!(pool.used() <= 3_000, "growth is reclaimed: {}", pool.used());
5077	}
5078
5079	/// A stale arrival entry whose sequence was later re-served by fetched backfill
5080	/// must not leak the replacement into arrival-order subscriptions.
5081	#[tokio::test]
5082	async fn refilled_sequence_stays_out_of_subscriptions() {
5083		let (mut producer, _pool) = pooled_producer(1 << 40);
5084		let dynamic = producer.dynamic();
5085		let consumer = producer.consume();
5086
5087		producer.create_group(0u64.into()).unwrap().finish().unwrap();
5088		let aborted = producer.create_group(1u64.into()).unwrap();
5089		aborted.abort(Error::Cancel).unwrap();
5090		producer.create_group(2u64.into()).unwrap().finish().unwrap();
5091
5092		// Re-serve seq 1 as backfill; its slot replaces the aborted one, and the
5093		// old arrival entry for seq 1 now resolves to it.
5094		let pending = consumer.fetch_group(1, None);
5095		let req = dynamic
5096			.requested_group()
5097			.now_or_never()
5098			.expect("should not block")
5099			.unwrap();
5100		let mut group = req.accept(None).unwrap();
5101		group
5102			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill"))
5103			.unwrap();
5104		group.finish().unwrap();
5105		pending.await.unwrap();
5106
5107		// The backfill serves by sequence, but never in arrival order.
5108		assert!(consumer.peek_group(1).is_some());
5109		let mut subscriber = producer.subscribe(None);
5110		assert_eq!(subscriber.assert_group().sequence, 0);
5111		assert_eq!(subscriber.assert_group().sequence, 2);
5112		subscriber.assert_no_group();
5113	}
5114
5115	/// An expired backfill can't hide behind a refreshed one: the eviction-order
5116	/// expiry scans a bounded prefix instead of stopping at the first fresh entry.
5117	#[tokio::test]
5118	async fn expired_backfill_behind_refreshed_reclaimed() {
5119		tokio::time::pause();
5120
5121		let (mut producer, _pool) = pooled_producer(1 << 40);
5122		let dynamic = producer.dynamic();
5123		let consumer = producer.consume();
5124
5125		producer.create_group(5u64.into()).unwrap().finish().unwrap();
5126		for sequence in [2u64, 3u64] {
5127			let pending = consumer.fetch_group(sequence, None);
5128			let req = dynamic
5129				.requested_group()
5130				.now_or_never()
5131				.expect("should not block")
5132				.unwrap();
5133			let mut group = req.accept(None).unwrap();
5134			group
5135				.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
5136				.unwrap();
5137			group.finish().unwrap();
5138			pending.await.unwrap();
5139		}
5140
5141		// Keep seq 2 fresh while seq 3 (behind it in eviction order) expires.
5142		tokio::time::advance(Duration::from_secs(4)).await;
5143		consumer.fetch_group(2, None).await.unwrap();
5144		tokio::time::advance(DEFAULT_LATENCY_MAX - Duration::from_secs(2)).await;
5145		producer.create_group(6u64.into()).unwrap().finish().unwrap();
5146
5147		let consumer = producer.consume();
5148		assert!(consumer.peek_group(2).is_some(), "refreshed backfill survives");
5149		assert!(consumer.peek_group(3).is_none(), "expired backfill is reclaimed");
5150	}
5151
5152	/// A FETCH hit within the same coarse clock tick still protects the group: the
5153	/// refresh stamps one tick ahead, so it reads strictly newer than the mean.
5154	#[tokio::test]
5155	async fn same_tick_fetch_protects() {
5156		tokio::time::pause();
5157
5158		// No time advances at all: every timestamp lands in the same tick.
5159		let (mut producer, _pool) = pooled_producer(10_000);
5160		let consumer = producer.consume();
5161
5162		finished_group(&mut producer, 3_000); // seq 0
5163		finished_group(&mut producer, 3_000); // seq 1
5164		finished_group(&mut producer, 3_000); // seq 2
5165
5166		consumer.fetch_group(0, None).await.unwrap();
5167
5168		finished_group(&mut producer, 3_000); // seq 3
5169		finished_group(&mut producer, 3_000); // seq 4
5170
5171		assert!(consumer.peek_group(0).is_some(), "same-tick refresh protects");
5172		assert!(consumer.peek_group(1).is_none(), "the unread group dies instead");
5173	}
5174
5175	/// A refetched group that reclaims max_sequence is the live edge again: it must
5176	/// not re-enter the eviction order, or memory pressure could evict the newest
5177	/// content.
5178	#[tokio::test]
5179	async fn refetched_latest_stays_protected() {
5180		tokio::time::pause();
5181
5182		let (mut producer, _pool) = pooled_producer(10_000);
5183		let dynamic = producer.dynamic();
5184		let consumer = producer.consume();
5185
5186		let straggler = producer.append_group().unwrap(); // seq 0
5187
5188		// The publisher aborts its own latest group; the sequence stays at the live edge.
5189		let latest = producer.append_group().unwrap(); // seq 1
5190		latest.abort(Error::Cancel).unwrap();
5191
5192		// Re-fetch it: the replacement takes over max_sequence.
5193		let pending = consumer.fetch_group(1, None);
5194		let req = dynamic
5195			.requested_group()
5196			.now_or_never()
5197			.expect("should not block")
5198			.unwrap();
5199		let mut group = req.accept(None).unwrap();
5200		group
5201			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000]))
5202			.unwrap();
5203		group.finish().unwrap();
5204		pending.await.unwrap();
5205
5206		// The refetched latest is protected by omission: it has no entry in the
5207		// eviction order, so no amount of debt can select it.
5208		{
5209			let state = producer.state.read();
5210			assert!(state.lookup.contains_key(&1), "refetched group is cached");
5211			assert!(
5212				state.evict.iter().all(|(sequence, _)| *sequence != 1),
5213				"the live edge must not be an eviction candidate"
5214			);
5215		}
5216		drop(straggler);
5217	}
5218
5219	/// An evicted group is a cache miss, so a fetch re-fetches it and the accepted
5220	/// replacement serves the sequence again (not `Error::Duplicate`).
5221	#[tokio::test]
5222	async fn eviction_allows_refetch() {
5223		tokio::time::pause();
5224
5225		let (mut producer, _pool) = pooled_producer(10_000);
5226		let dynamic = producer.dynamic();
5227
5228		finished_group(&mut producer, 10_000); // seq 0
5229		finished_group(&mut producer, 10_000); // seq 1: demotes seq 0
5230		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
5231
5232		let consumer = producer.consume();
5233		assert!(consumer.peek_group(0).is_none());
5234		let pending = consumer.fetch_group(0, None);
5235
5236		let req = dynamic
5237			.requested_group()
5238			.now_or_never()
5239			.expect("should not block")
5240			.unwrap();
5241		assert_eq!(req.sequence(), 0);
5242
5243		let mut group = req.accept(None).unwrap();
5244		group
5245			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"refetched"))
5246			.unwrap();
5247		group.finish().unwrap();
5248
5249		let mut group = pending.await.unwrap();
5250		assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"refetched");
5251	}
5252
5253	/// A fetched (backfill) group is served by sequence but never replayed to
5254	/// arrival-order subscribers.
5255	#[tokio::test]
5256	async fn fetched_backfill_not_subscribed() {
5257		let (mut producer, _pool) = pooled_producer(1 << 40);
5258		let dynamic = producer.dynamic();
5259		let consumer = producer.consume();
5260
5261		// The publisher starts at seq 5; earlier groups exist only upstream.
5262		producer.create_group(5u64.into()).unwrap().finish().unwrap();
5263		producer.create_group(6u64.into()).unwrap().finish().unwrap();
5264
5265		// Fetch the gap: it lands in the cache and resolves the fetch...
5266		let pending = consumer.fetch_group(2, None);
5267		let req = dynamic
5268			.requested_group()
5269			.now_or_never()
5270			.expect("should not block")
5271			.unwrap();
5272		let mut group = req.accept(None).unwrap();
5273		group
5274			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill"))
5275			.unwrap();
5276		group.finish().unwrap();
5277		let mut fetched = pending.await.unwrap();
5278		assert_eq!(&fetched.read_frame().await.unwrap().unwrap().payload[..], b"backfill");
5279		assert!(consumer.peek_group(2).is_some(), "backfill is cached for later fetches");
5280
5281		// ...but an arrival-order subscriber only sees the live groups.
5282		let mut subscriber = producer.subscribe(None);
5283		assert_eq!(subscriber.assert_group().sequence, 5);
5284		assert_eq!(subscriber.assert_group().sequence, 6);
5285		subscriber.assert_no_group();
5286	}
5287
5288	/// Fetched backfill isn't in arrival order, so it ages out through the eviction
5289	/// order instead of lingering until the track closes.
5290	#[tokio::test]
5291	async fn expired_backfill_reclaimed() {
5292		tokio::time::pause();
5293
5294		let (mut producer, pool) = pooled_producer(1 << 40);
5295		let dynamic = producer.dynamic();
5296		let consumer = producer.consume();
5297
5298		producer.create_group(5u64.into()).unwrap().finish().unwrap();
5299
5300		// Serve a backfill fetch for an old sequence.
5301		let pending = consumer.fetch_group(2, None);
5302		let req = dynamic
5303			.requested_group()
5304			.now_or_never()
5305			.expect("should not block")
5306			.unwrap();
5307		let mut group = req.accept(None).unwrap();
5308		group
5309			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000]))
5310			.unwrap();
5311		group.finish().unwrap();
5312		pending.await.unwrap();
5313		let used = pool.used();
5314
5315		// Age past the track window; the next write reclaims the backfill.
5316		tokio::time::advance(DEFAULT_LATENCY_MAX + Duration::from_secs(1)).await;
5317		producer.create_group(6u64.into()).unwrap().finish().unwrap();
5318
5319		assert!(consumer.peek_group(2).is_none(), "expired backfill is reclaimed");
5320		assert!(pool.used() < used, "its bytes are released");
5321	}
5322
5323	#[tokio::test]
5324	async fn fetch_aborts_with_track() {
5325		let producer = track_producer("test", None);
5326		let dynamic = producer.dynamic();
5327		let consumer = producer.consume();
5328
5329		let pending = consumer.fetch_group(3, None);
5330		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
5331
5332		producer.abort(Error::Cancel).unwrap();
5333		assert!(pending.await.is_err());
5334		drop(dynamic);
5335	}
5336}