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