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