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