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, group, stats};
18
19use super::{Datagram, Requests};
20
21use super::Cap;
22pub use super::subscription::{Position, Subscription};
23
24use std::{
25	collections::{BTreeMap, HashSet, VecDeque},
26	ops::{Bound, RangeBounds},
27	sync::Arc,
28	sync::OnceLock,
29	sync::atomic::{AtomicBool, Ordering},
30	task::{Poll, ready},
31	time::Duration,
32};
33
34/// Default [`Info::max_age`] when the publisher doesn't set one.
35pub const DEFAULT_MAX_AGE: Duration = Duration::from_secs(5);
36
37/// Maximum number of datagrams retained in the per-track send buffer.
38///
39/// Datagrams are a best-effort send buffer, not a replay cache (unlike groups): only the last
40/// 64 datagrams are kept, so a stalled consumer cannot retain an unbounded backlog.
41/// The payload size limit also bounds the buffer's memory use.
42const MAX_DATAGRAMS: usize = 64;
43
44/// Slack before the eviction order is rebuilt, so a track holding just a few groups
45/// doesn't rebuild on every write.
46const EVICT_SLACK: usize = 64;
47
48/// How many live eviction candidates one debt payment examines (Redis-style
49/// bounded sampling): enough to step over a few protected (recently accessed)
50/// groups, small enough that a write never scans a long queue.
51const EVICT_SCAN: usize = 4;
52
53/// One pass over the eviction order at a fixed cache time.
54#[derive(Clone, Copy)]
55pub(super) struct ExpiryScan {
56	start: usize,
57	// Ceiling on how many entries this pass examines. `EVICT_SCAN` from the write
58	// path, the whole queue from the pool's cleanup pass.
59	width: usize,
60	now: u64,
61	max_ticks: u64,
62	// Cleanup dates pending activity; write-driven scans only check dated entries.
63	gc: bool,
64}
65
66/// Publisher-side properties of a track.
67///
68/// These are fixed by the publisher when the track is created and don't change
69/// while the track is alive. A subscriber learns them via
70/// [`broadcast::Consumer::track`](broadcast::Consumer::track),
71/// which returns the publisher's [`Info`] once the subscription is accepted.
72//
73// Deliberately not `Copy`, even though it's now a plain value: adding `Copy` turns
74// every existing `info.clone()` in a consumer's code into a `clippy::clone_on_copy`
75// error under `-D warnings`.
76#[derive(Clone, Debug)]
77#[non_exhaustive]
78pub struct Info {
79	/// Units per second for per-frame timestamps on this track.
80	///
81	/// Every track is timed; this defaults to [`Timescale::MILLI`]. On Lite05+ it is
82	/// reported in TRACK_INFO and the publisher zigzag-delta encodes per-frame
83	/// timestamps at this scale on the wire. Protocols whose wire can't carry it
84	/// (pre-Lite05 moq-lite, IETF moq-transport) fall back to local monotonic milliseconds.
85	pub timescale: Timescale,
86	/// How far behind the live edge a group may fall, in media timestamps, before it
87	/// is stale. The newest group is always retained.
88	///
89	/// A retention bound rather than a delivery one, the inverse of an HTTP
90	/// `Cache-Control: max-age`. [`Subscription::max_age`] is clamped to this, since a
91	/// group can't be waited for longer than it's kept around. Reported in TRACK_INFO so
92	/// relays re-serve with the same window. Defaults to [`DEFAULT_MAX_AGE`].
93	///
94	/// Measured against timestamps rather than the wall clock, so a congestion stall
95	/// (timestamps stop advancing) can't age content out. Wall-clock reclamation of
96	/// content nobody is accessing belongs to the cache pool's own
97	/// [`expiry`](crate::cache::Pool::expiry) window, not to this budget.
98	///
99	/// This is the `Publisher Max Age` on the wire, the publisher-side half of the
100	/// budget [`Subscription::max_age`] sets for a subscriber.
101	///
102	/// Encoded as milliseconds in a QUIC varint, so a duration of `2^62` milliseconds
103	/// or more cannot be put on the wire. Sub-millisecond precision is truncated
104	/// (`Duration::as_millis`) at encode time.
105	pub max_age: Duration,
106	/// The publisher's priority for this track, used only to break ties between
107	/// subscriptions of equal subscriber priority. Reported in TRACK_INFO (Lite05+).
108	pub priority: u8,
109}
110
111impl Default for Info {
112	fn default() -> Self {
113		Self {
114			timescale: Timescale::default(),
115			max_age: DEFAULT_MAX_AGE,
116			priority: 0,
117		}
118	}
119}
120
121impl Info {
122	/// Set the per-frame timestamp scale, returning `self` for chaining.
123	///
124	/// Defaults to [`Timescale::MILLI`]. On Lite05+ this scale is reported in TRACK_INFO
125	/// and used to encode per-frame timestamps on the wire.
126	pub fn with_timescale(mut self, timescale: Timescale) -> Self {
127		self.timescale = timescale;
128		self
129	}
130
131	/// Set how old a non-latest group may get before eviction, returning `self` for chaining.
132	pub fn with_max_age(mut self, max_age: Duration) -> Self {
133		self.max_age = max_age;
134		self
135	}
136
137	/// Set the publisher's tie-break priority, returning `self` for chaining.
138	pub fn with_priority(mut self, priority: u8) -> Self {
139		self.priority = priority;
140		self
141	}
142}
143
144#[derive(Default)]
145pub(crate) struct TrackState {
146	// The publisher's properties, once known; always Some for Subscriber/Producer.
147	// Copied by value into each group it creates.
148	info: Option<Info>,
149	// Whether a live Producer was minted. A reverse fetch may install `info`
150	// before acceptance, so the two states are deliberately separate.
151	published: bool,
152
153	// The broadcast this track belongs to. Supplies the cache pool its groups charge
154	// into and the `cache_duration` ceiling clamping `Info::max_age`.
155	broadcast: Arc<broadcast::Info>,
156
157	// This track's account against the shared cache pool, shared with every group it
158	// creates (see `cache::Track`). Holds the gross-write counter `charge_debt` drains,
159	// and the weak link a frame write follows back here to settle its own debt.
160	cache: Arc<cache::Track>,
161
162	// Cached groups by sequence: the single source of truth for what is cached. The
163	// two orderings below hold bare sequences and validate against this map, so a
164	// removed or replaced group turns their entries into discarded-on-pop hints.
165	// Ordered so an in-range subscriber can seek to its cursor instead of scanning
166	// every cached group for each delivery.
167	lookup: BTreeMap<u64, Slot>,
168
169	// Publisher-produced groups in arrival order as (sequence, stamp), walked by
170	// subscriptions; an entry only resolves while its stamp matches the slot's.
171	// Fetched backfill (`insert_group_request`) is deliberately absent: it is
172	// served by sequence, never replayed to arrival-order subscribers.
173	arrival: VecDeque<(u64, u32)>,
174
175	// Eviction order under memory pressure as (sequence, stamp): every cached
176	// group except the protected latest. `pay_debt` scans victims from the front;
177	// groups accessed more recently than the pool-wide average rotate to the back
178	// instead of dying, decoupling eviction order from arrival order. Entries are
179	// hints that only resolve while their stamp matches the slot's, so a re-served
180	// sequence can't accumulate duplicate hints that alias its replacement.
181	// Eviction is deliberately approximate: a bounded scan per write.
182	evict: VecDeque<(u64, u32)>,
183
184	// Outstanding eviction debt in bytes, accrued by writes while the shared pool
185	// is over capacity (see `cache::Pool::accrue`) and paid by aborting this
186	// track's own oldest groups. Per track, so eviction lands proportionally to
187	// what each track writes and never touches another track's cache.
188	debt: u64,
189
190	// Datagrams in arrival order, bounded by `MAX_DATAGRAMS`. Shares the group
191	// sequence namespace but is otherwise independent.
192	datagrams: VecDeque<Datagram>,
193
194	// Number of datagrams dropped off the front (over capacity), mapping a subscriber's absolute
195	// cursor to an index into `datagrams` (mirrors `offset` for groups).
196	datagram_offset: usize,
197
198	// We've popped the front of `arrival` this many times, mapping a subscriber's
199	// absolute cursor to an index.
200	offset: usize,
201
202	// The highest sequence number successfully appended to the track. Shared with
203	// datagrams, so it can run ahead of any cached group.
204	max_sequence: Option<u64>,
205
206	// The sequence of the newest cached group: the live edge, protected from
207	// eviction by never entering the eviction order. Tracked separately from
208	// `max_sequence` because datagrams advance that shared counter, and the live
209	// edge must still demote correctly when the next group lands past one.
210	latest_group: Option<u64>,
211
212	// Incarnation counter for `Slot::stamp`.
213	next_stamp: u32,
214
215	// The sequence number at which the track was finalized.
216	final_sequence: Option<u64>,
217
218	// The first sequence the live feed serves, once the publisher declared one
219	// (the wire's SUBSCRIBE_START). Lower groups never arrive on their own; a
220	// fetch can still create them.
221	start_sequence: Option<u64>,
222
223	// Where production stopped, snapshotted when the cached groups are released (an
224	// abort, or the last producer dropping). Computed live from the cache otherwise;
225	// see [`Self::resume_position`].
226	resume: Option<Position>,
227
228	// The error that caused the track to be aborted, if any.
229	abort: Option<Error>,
230
231	// Active subscriptions, in their own [`kio::Shared`] so a read-only `Consumer`
232	// registers under that lock instead of writing back into the track state.
233	// Kept here (rather than threaded through every handle) so any holder reaches it.
234	subscriptions: kio::Shared<Subscriptions>,
235
236	// The reverse fetch queue (see [`FetchState`]), same reasoning: cache-miss
237	// `fetch_group` calls enqueue here and a `Dynamic` drains.
238	fetch: kio::Shared<FetchState>,
239}
240
241/// A cached group plus its bookkeeping in the track's `lookup` map.
242///
243/// Access times and the evictable-population sample live in the group's own
244/// `cache::Charge`, so they share the group's lifecycle exactly: an abort from any
245/// handle releases the bytes and the sample together.
246struct Slot {
247	group: group::Producer,
248
249	// Incarnation stamp, echoed by this slot's arrival entry (if any). A re-served
250	// sequence (an aborted group re-created by the publisher or re-fetched as
251	// backfill) gets a fresh stamp, so a historical arrival entry can't resolve to
252	// the replacement and deliver it twice or at the wrong position.
253	stamp: u32,
254
255	// Whether this incarnation came from the live publisher and can replace older
256	// subscription content. Fetch-only backfill stays cached but never anchors drift.
257	visible: bool,
258}
259
260/// Heap the track keeps per cached group, excluding the group itself
261/// ([`group::CACHE_OVERHEAD`]).
262///
263/// One [`Slot`] under its sequence in `lookup`, plus a hint in each of `arrival` and
264/// `evict`. Doubled because both containers run half empty in the worst case: a
265/// `BTreeMap` node sits between half and fully packed, and a `VecDeque` holds up to
266/// twice the entries in it. Half of [`cache::ENTRY_OVERHEAD`]; see it for why this is
267/// derived rather than measured.
268pub(crate) const CACHE_OVERHEAD: u64 = 2 * (size_of::<u64>() + size_of::<Slot>() + 2 * size_of::<(u64, u32)>()) as u64;
269
270/// The registered subscriptions, aggregated by the producer.
271type Subscriptions = Vec<kio::Consumer<Subscription>>;
272
273/// Reverse state for [`Consumer::fetch_group`], beside the track state in its own
274/// [`kio::Shared`]: consumers enqueue (coalescing per sequence, so a relay opens one
275/// upstream FETCH per group) and [`Dynamic`] handlers drain under one lock, without
276/// write access to the track itself.
277pub(crate) type FetchState = Requests<u64, PendingFetch>;
278
279/// One fetch attempt for a sequence, shared by every [`Fetching`] that joined it.
280pub(crate) struct PendingFetch {
281	// The most demanding delivery priority across the joined fetches.
282	priority: u8,
283
284	// The lowest start across the joined fetches, so serving the attempt once satisfies
285	// every one of them. Only widened while the request is still queued: once a handler
286	// has taken it, its range is already on the wire.
287	frame_start: u64,
288
289	// Result channel back to the joined fetches. Written only on rejection; a
290	// successful accept resolves them through the track cache instead. Dropping
291	// every producer without writing (a vanished handler) closes the channel,
292	// which a [`Fetching`] reads as [`Error::NotFound`].
293	result: kio::Producer<FetchOutcome>,
294}
295
296/// The result of a fetch attempt. Stays empty on success (the group lands in the
297/// track cache); a handler writes `rejected` to fail every joined fetch.
298#[derive(Default)]
299pub(crate) struct FetchOutcome {
300	pub(crate) rejected: Option<Error>,
301}
302
303impl TrackState {
304	fn normalize_info(broadcast: &broadcast::Info, mut info: Info) -> Info {
305		info.max_age = info.max_age.min(broadcast.cache_duration);
306		info
307	}
308
309	fn accept(&mut self, info: Info) {
310		self.published = true;
311		self.install(info);
312	}
313
314	fn poll_info(&self) -> Poll<Result<Info>> {
315		if let Some(info) = &self.info {
316			Poll::Ready(Ok(info.clone()))
317		} else if let Some(err) = &self.abort {
318			// Aborted before anyone served it, so the info can never arrive: fail the
319			// waiting subscribes instead of parking them on a track nobody will fill.
320			Poll::Ready(Err(err.clone()))
321		} else {
322			Poll::Pending
323		}
324	}
325
326	/// Find the next live group at or after `index` in arrival order.
327	///
328	/// Returns the group and its absolute index so the consumer can advance past it.
329	/// The cached producer rather than a consumer: `consume` reads the clock and can
330	/// take the group's own lock, which the caller does once the track guard is gone.
331	fn poll_recv_group(&self, index: usize, min_sequence: u64) -> Poll<Result<Option<(group::Producer, usize)>>> {
332		let start = index.saturating_sub(self.offset);
333		for (i, (sequence, stamp)) in self.arrival.iter().enumerate().skip(start) {
334			if *sequence >= min_sequence
335				&& let Some(slot) = self.lookup.get(sequence)
336				&& slot.stamp == *stamp
337				&& !slot.group.is_aborted()
338			{
339				return Poll::Ready(Ok(Some((slot.group.clone(), self.offset + i))));
340			}
341		}
342
343		// TODO once we have drop notifications, check if index == final_sequence.
344		if self.is_complete() {
345			Poll::Ready(Ok(None))
346		} else if let Some(err) = &self.abort {
347			Poll::Ready(Err(err.clone()))
348		} else {
349			Poll::Pending
350		}
351	}
352
353	/// Find the next datagram at or after the subscriber's absolute `index`.
354	///
355	/// Returns the datagram and its absolute index so the consumer can advance past it. A
356	/// consumer whose `index` has fallen behind `datagram_offset` (older datagrams dropped)
357	/// resumes at the oldest still-buffered datagram, skipping the lost ones.
358	fn poll_recv_datagram(&self, index: usize) -> Poll<Result<Option<(Datagram, usize)>>> {
359		let start = index.saturating_sub(self.datagram_offset);
360		if let Some(datagram) = self.datagrams.get(start) {
361			return Poll::Ready(Ok(Some((datagram.clone(), self.datagram_offset + start))));
362		}
363
364		// Nothing buffered at the cursor: the track ending terminates the datagram stream too.
365		if self.is_complete() {
366			Poll::Ready(Ok(None))
367		} else if let Some(err) = &self.abort {
368			Poll::Ready(Err(err.clone()))
369		} else {
370			Poll::Pending
371		}
372	}
373
374	/// Push a datagram, dropping the oldest when the send buffer is full.
375	fn push_datagram(&mut self, datagram: Datagram) {
376		if self.datagrams.len() == MAX_DATAGRAMS {
377			self.datagrams.pop_front();
378			self.datagram_offset += 1;
379		}
380		self.datagrams.push_back(datagram);
381	}
382
383	/// Find the smallest-sequence cached group satisfying
384	/// `next_sequence <= seq < end_sequence (if set)`. Used by
385	/// [`Subscriber::next_group`] so the range can be widened (or unset)
386	/// after the fact and previously-skipped cached groups become available
387	/// without scanning past them in arrival order.
388	///
389	/// Returns `Poll::Pending` when no in-range group is currently cached but
390	/// future groups could still arrive in range; returns `Ok(None)` only when
391	/// the track is finalized and no further in-range group is possible.
392	fn poll_next_in_range(
393		&self,
394		next_sequence: u64,
395		end_sequence: Option<u64>,
396	) -> Poll<Result<Option<group::Producer>>> {
397		// If the exclusive end is already at or below where we'd resume, no
398		// group can ever satisfy this call until the cap rises. Pending (not
399		// None) so the consumer is parked rather than told the stream is over.
400		// An empty range (`end == 0`) parks even at the first sequence.
401		if let Some(end) = end_sequence
402			&& end <= next_sequence
403		{
404			if let Some(err) = &self.abort {
405				return Poll::Ready(Err(err.clone()));
406			}
407			return Poll::Pending;
408		}
409
410		let best = self
411			.lookup
412			.range(next_sequence..)
413			.map(|(_, slot)| &slot.group)
414			.take_while(|group| super::subscription::before_end(group.sequence, end_sequence))
415			.find(|group| !group.is_aborted());
416
417		if let Some(group) = best {
418			// Deliberately no cache refresh here: this is a pure seek, and the spliced
419			// merge consults candidates it may not deliver. The deliverer stamps the
420			// winner; a loser re-seeked every poll must not be shielded from eviction.
421			// The caller consumes it with the track guard released, like `poll_recv_group`.
422			return Poll::Ready(Ok(Some(group.clone())));
423		}
424
425		// No in-range group is cached. Decide whether more could ever arrive.
426		if let Some(err) = &self.abort {
427			return Poll::Ready(Err(err.clone()));
428		}
429		// `final_sequence` is one past the last possible sequence. If our
430		// floor is already at/past it, nothing else can land in range.
431		if let Some(fin) = self.final_sequence
432			&& next_sequence >= fin
433		{
434			return Poll::Ready(Ok(None));
435		}
436		Poll::Pending
437	}
438
439	/// The cached group for `sequence`, but only when it holds every frame from
440	/// `frame_start` onward.
441	///
442	/// A group cached from a frame-bounded subscription starts partway in, and serving
443	/// it to someone who asked for the whole group would silently hand back a tail. It
444	/// is a miss instead, so the fetch goes upstream for the frames that are missing.
445	fn covering_group(&self, sequence: u64, frame_start: u64) -> Option<&group::Producer> {
446		let slot = self.lookup.get(&sequence)?;
447		let first = slot.group.live_first_frame()?;
448		(first as u64 <= frame_start).then_some(&slot.group)
449	}
450
451	/// The publisher's max age window, or `None` while the info is unknown (an
452	/// unaccepted [`Request`]). Bounds the aggregate subscription; see [`clamp_combined`].
453	fn max_age_bound(&self) -> Option<Duration> {
454		self.info.as_ref().map(|info| info.max_age)
455	}
456
457	/// The live edge a subscription bounded at `cap` measures drift against: the
458	/// highest-sequence group that has presented a frame, or `None` while nothing
459	/// stamped is servable (an unstamped track drives no expiry at all; the cache's
460	/// own wall-clock policy is what bounds it).
461	///
462	/// One scan answers a whole poll. The highest-sequence anchor in range is above
463	/// every candidate below it and above none at or past it. Recomputing per candidate
464	/// would make walking a backlog of N groups off in one poll cost O(N^2).
465	///
466	/// Capped because a group can only be late relative to data that would actually be
467	/// served in its place: a subscription ending at a takeover boundary can't jump past
468	/// it, so the groups it still wants aren't stale just because the route ran on.
469	/// Fetched backfill is absent from `arrival`, so it cannot age subscription content
470	/// as though it were a live replacement.
471	fn live_edge(&self, cap: Option<u64>) -> Option<Edge> {
472		let presentation = self
473			.lookup
474			.range(..)
475			.rev()
476			.filter(|(seq, _)| super::subscription::before_end(**seq, cap))
477			.find_map(|(_, slot)| {
478				if !slot.visible || slot.group.is_aborted() {
479					return None;
480				}
481				// The map is ordered by sequence, so the first stamped group from the
482				// back is the newest content that exists.
483				let timestamp = slot.group.timestamp()?;
484				Some(PresentationEdge {
485					sequence: slot.group.sequence,
486					stamp: slot.stamp,
487					timestamp: slot.group.latest().unwrap_or(timestamp),
488				})
489			});
490
491		presentation.map(|presentation| Edge { presentation, cap })
492	}
493
494	/// The furthest presentation time the group at `sequence` could still reach: where
495	/// its immediate successor begins, or `None` when nothing proves where it stops.
496	///
497	/// An upper bound, deliberately. A frame's duration is not on the wire, so a group's
498	/// own last timestamp says where it *starts* presenting, not where it ends; only its
499	/// successor's start proves it cannot run past it. And only the *immediate* servable
500	/// successor counts: timestamps need not rise with sequence (a rewind reorders them),
501	/// so a later stamped group proves nothing about where an unstamped successor will
502	/// begin, and shrinking the bound is the unsafe direction. An unstamped successor
503	/// therefore leaves the reach unbounded until it presents its first frame.
504	fn reach(&self, sequence: u64, cap: Option<u64>) -> Option<Timestamp> {
505		let successor = self
506			.lookup
507			.range(sequence.saturating_add(1)..)
508			.map(|(_, slot)| slot)
509			.take_while(|slot| super::subscription::before_end(slot.group.sequence, cap))
510			.find(|slot| slot.visible && !slot.group.is_aborted())?;
511		successor.group.timestamp()
512	}
513
514	/// Whether the group at `sequence` has drifted further behind `edge` than `budget`
515	/// tolerates, so a subscriber should skip it rather than hand it over.
516	///
517	/// Presentation time measures a group by how far it could still *reach*, not by how far
518	/// behind it started. Being behind is survivable: priority transmits newer groups
519	/// first, so a backlog bursts at whatever rate is left over and closes the gap faster
520	/// than the live edge advances. What is not survivable is having nothing left worth
521	/// delivering, so a group is abandoned only once everything it could still present
522	/// falls outside the budget.
523	///
524	/// A group's reach is bounded by its immediate successor (see [`Self::reach`]): it
525	/// cannot present past where the next group begins. The candidate itself needs no
526	/// timestamp: an empty group is bounded by its stamped successor the same way. Only
527	/// timestamps drive expiry; wall-clock reclamation of idle content is the cache's
528	/// own policy, not the budget's.
529	///
530	/// The bound is exclusive, so the comparison is `>=` rather than `>`: the freshest frame
531	/// a group could still hold sits just *below* its reach, so an age equal to the budget
532	/// already puts every frame in it strictly past the budget. That also makes a zero
533	/// budget fall out for free instead of needing a special case.
534	///
535	/// The edge must sit strictly above the candidate. The live edge is never late
536	/// against itself, and backfill or the tail of a rewound timeline can carry a high
537	/// timestamp on a low sequence without being an edge at all.
538	fn is_stale(&self, sequence: u64, edge: Option<&Edge>, budget: Duration) -> bool {
539		let Some(edge) = edge else {
540			return false;
541		};
542		if !self.lookup.contains_key(&sequence) {
543			return false;
544		}
545
546		// The anchor was resolved under an earlier lock, so confirm it still names
547		// the same servable incarnation before it convicts a candidate. Failing safe
548		// (delivering) is right, since the next poll resolves fresh anchors.
549		let live_edge = &edge.presentation;
550		let reach = self.reach(sequence, edge.cap);
551		live_edge.sequence > sequence
552			&& self
553				.lookup
554				.get(&live_edge.sequence)
555				.is_some_and(|live| live.stamp == live_edge.stamp && !live.group.is_aborted())
556			&& reach.is_some_and(
557				|reach| matches!(live_edge.timestamp.checked_sub(reach), Ok(age) if Duration::from(age) >= budget),
558			)
559	}
560
561	/// Resolve a one-shot fetch from the track side: the cached group, or an [`Error`]
562	/// once it can never be served. A missing group is a failure ([`Error::NotFound`]), not an
563	/// end-of-stream. The handler side (a rejection, or no [`Dynamic`] at all) lives
564	/// in [`FetchState`]; [`Fetching`] polls both.
565	fn poll_fetch_cached(&self, sequence: u64, frame_start: u64) -> Poll<Result<group::Consumer>> {
566		if let Some(group) = self.covering_group(sequence, frame_start) {
567			// A cache hit refreshes the group: it resets both its age (expiry keys
568			// off the last access) and its standing against the pool-wide average,
569			// so the eviction walk keeps it over never-read groups.
570			group.cache_refresh();
571			return Poll::Ready(Ok(group.consume()));
572		}
573
574		if let Some(err) = &self.abort {
575			return Poll::Ready(Err(err.clone()));
576		}
577
578		// Past the final sequence: the group can never exist.
579		if self.final_sequence.is_some_and(|fin| sequence >= fin) {
580			return Poll::Ready(Err(Error::NotFound));
581		}
582
583		Poll::Pending
584	}
585
586	/// Expire groups idle past the pool's wall-clock LRU window, never the latest.
587	///
588	/// The window is the pool's [`expiry`](cache::Pool::expiry), not the track's
589	/// [`max_age`](Info::max_age): retention is a media-timestamp promise, while this
590	/// is the cache's own guard against unread content pinning RAM, shared by every
591	/// track in the pool.
592	///
593	/// One bounded, rotating scan over the eviction order, which holds every cached
594	/// group except the protected latest. The cursor persists in the cache account, so
595	/// entries beyond one scan window can't be starved by fresh (recently read,
596	/// fetched, or written) entries in front of them: every position is revisited
597	/// within a few writes. Expiry throughput is therefore EVICT_SCAN groups per write; the
598	/// byte budget reclaims the remainder under memory pressure, and the pool's sweep
599	/// ([`Self::expiry_scan_drain`]) covers a track that stopped writing entirely.
600	pub(super) fn evict_expired(&mut self) {
601		let scan = self.expiry_scan();
602		self.evict_expired_scan(scan);
603	}
604
605	/// Describe the next bounded expiry scan without mutating observable track state.
606	pub(super) fn expiry_scan(&self) -> ExpiryScan {
607		ExpiryScan {
608			start: self.cache.next_expiry_scan(EVICT_SCAN),
609			width: EVICT_SCAN,
610			now: self.cache.pool().now(),
611			max_ticks: self.cache.pool().expiry_ticks(),
612			gc: false,
613		}
614	}
615
616	/// Scan every cached candidate so undated accesses and old entries cannot hide
617	/// behind fresh entries at the front of the eviction order.
618	pub(super) fn expiry_scan_drain(&self) -> ExpiryScan {
619		ExpiryScan {
620			start: 0,
621			width: self.evict.len(),
622			now: self.cache.pool().now(),
623			max_ticks: self.cache.pool().expiry_ticks(),
624			gc: true,
625		}
626	}
627
628	#[cfg(test)]
629	pub(super) fn date_cache_accesses(&self, now: u64) {
630		for slot in self.lookup.values() {
631			slot.group.cache_accessed_tick(Some(now));
632		}
633	}
634
635	/// Whether an expiry scan would change observable track state.
636	///
637	/// Mirrors [`Self::evict_expired_scan`]'s walk exactly, stop rule included: a memo
638	/// that scanned further would report work the scan itself will not do.
639	pub(super) fn expiry_mutation_due(&self, scan: ExpiryScan) -> bool {
640		let len = self.evict.len();
641		if len > 0 {
642			let start = scan.start % len;
643			let mut retained = 0;
644			for step in 0..len.min(scan.width) {
645				let (sequence, stamp) = self.evict[(start + step) % len];
646				let Some(slot) = self.lookup.get(&sequence) else {
647					continue;
648				};
649				if slot.stamp != stamp {
650					continue;
651				}
652				if slot.group.is_aborted()
653					|| (Some(sequence) != self.latest_group
654						&& slot
655							.group
656							.cache_accessed_tick(scan.gc.then_some(scan.now))
657							.is_some_and(|tick| scan.now.saturating_sub(tick) > scan.max_ticks))
658				{
659					return true;
660				}
661				retained += 1;
662				if !scan.gc && retained >= EVICT_SCAN {
663					break;
664				}
665			}
666		}
667
668		self.arrival
669			.front()
670			.is_some_and(|(sequence, stamp)| !self.is_current(*sequence, *stamp))
671			|| self
672				.evict
673				.front()
674				.is_some_and(|(sequence, stamp)| !self.is_current(*sequence, *stamp))
675			|| self.evict.len() > 2 * self.lookup.len() + EVICT_SLACK
676	}
677
678	/// Apply a scan previously selected by [`Self::expiry_scan`] or
679	/// [`Self::expiry_scan_drain`].
680	pub(super) fn evict_expired_scan(&mut self, scan: ExpiryScan) {
681		let len = self.evict.len();
682		if len > 0 {
683			let start = scan.start % len;
684			let mut retained = 0;
685			for step in 0..len.min(scan.width) {
686				let (sequence, stamp) = self.evict[(start + step) % len];
687				let Some(slot) = self.lookup.get(&sequence) else {
688					continue;
689				};
690				if slot.stamp != stamp {
691					// A historical hint; the live entry is elsewhere in the queue.
692					continue;
693				}
694				// Already aborted: the frames are gone, reclaim the slot so a
695				// later fetch can serve the sequence again.
696				if slot.group.is_aborted() {
697					self.lookup.remove(&sequence);
698					continue;
699				}
700				if Some(sequence) == self.latest_group
701					|| slot
702						.group
703						.cache_accessed_tick(scan.gc.then_some(scan.now))
704						.is_none_or(|tick| scan.now.saturating_sub(tick) <= scan.max_ticks)
705				{
706					// Writes keep their scan bounded. Cleanup visits the entire
707					// queue to date pending accesses and find idle entries behind them.
708					retained += 1;
709					if !scan.gc && retained >= EVICT_SCAN {
710						break;
711					}
712					continue;
713				}
714				// Take the group out of the cache and abort it, so any consumer
715				// still reading surfaces `Error::Old` instead of blocking forever
716				// on a frame that will never arrive.
717				let slot = self.lookup.remove(&sequence).unwrap();
718				let _ = slot.group.abort(Error::Old);
719			}
720		}
721
722		// Trim dead leading arrival entries to advance the subscriber offset. An
723		// entry is dead once its slot is gone or re-stamped by a newer incarnation.
724		while let Some((sequence, stamp)) = self.arrival.front() {
725			if self.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp) {
726				break;
727			}
728			self.arrival.pop_front();
729			self.offset += 1;
730		}
731
732		// Drop dead leading eviction entries so scans stay over live candidates.
733		while let Some((sequence, stamp)) = self.evict.front() {
734			if self.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp) {
735				break;
736			}
737			self.evict.pop_front();
738		}
739
740		// Dead entries behind a live front can linger; rebuild once they clearly
741		// outnumber the live slots.
742		if self.evict.len() > 2 * self.lookup.len() + EVICT_SLACK {
743			let lookup = &self.lookup;
744			self.evict
745				.retain(|(sequence, stamp)| lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp));
746		}
747	}
748
749	/// Whether `(sequence, stamp)` names the currently cached incarnation.
750	fn is_current(&self, sequence: u64, stamp: u32) -> bool {
751		self.lookup.get(&sequence).is_some_and(|slot| slot.stamp == stamp)
752	}
753
754	/// Drop every cached group and reset the eviction bookkeeping. Each group's
755	/// access sample lives in its own charge, released when the group itself dies.
756	fn clear_cache(&mut self) {
757		self.lookup.clear();
758		self.arrival.clear();
759		self.evict.clear();
760		self.latest_group = None;
761		self.debt = 0;
762	}
763
764	/// Attach `info` to this track, clamping the publisher's window down to the
765	/// origin's [`cache_duration`](crate::origin::Config::cache_duration) ceiling so a
766	/// group is never retained longer than the origin allows. Every path that binds an
767	/// info to a track funnels through here, covering local publishers and relayed
768	/// (lite / IETF) tracks alike.
769	fn install(&mut self, info: Info) {
770		let info = Self::normalize_info(&self.broadcast, info);
771		self.info = Some(info);
772	}
773
774	/// Create the shared state for a track under `broadcast`, along with the cache
775	/// account it and its groups charge into.
776	///
777	/// The account holds a [`kio::Weak`] back to this state: a group must be able to
778	/// settle the track's eviction debt as it writes, but the track owns its cached
779	/// groups, so anything stronger would make the pair immortal.
780	fn spawn(broadcast: Arc<broadcast::Info>) -> kio::Producer<Self> {
781		let state = kio::Producer::new(Self {
782			broadcast: broadcast.clone(),
783			..Default::default()
784		});
785		let cache = cache::Track::new(broadcast.pool.clone(), state.downgrade());
786		state.write().ok().expect("a new track is open").cache = cache;
787		state
788	}
789
790	/// Reject a sequence that is still cached; a dead (aborted or evicted)
791	/// incarnation is removed so a fresh group can serve the sequence again.
792	///
793	/// Best effort: nothing remembers a sequence whose slot is already gone, so a
794	/// publisher re-sending a long-evicted sequence is accepted as new.
795	/// A group that starts above `frame_start` is also replaceable: it cannot answer a
796	/// request from there, so the wider producer takes the slot. Readers already
797	/// draining the old one keep their own handle.
798	fn claim_sequence(&mut self, sequence: u64, frame_start: u64) -> Result<()> {
799		if let Some(slot) = self.lookup.get(&sequence) {
800			// The same question `covering_group` asks: can this slot still answer from
801			// `frame_start`? If it can, the sequence is taken; if it can't, it is dead
802			// and the caller gets to replace it.
803			if slot
804				.group
805				.live_first_frame()
806				.is_some_and(|first| first as u64 <= frame_start)
807			{
808				return Err(Error::Duplicate);
809			}
810			self.lookup.remove(&sequence);
811		}
812		Ok(())
813	}
814
815	/// Insert a freshly-created group into the cache.
816	///
817	/// Updates the live edge, demoting the previous latest into the eviction order;
818	/// the current latest is never enqueued, which is what protects it from
819	/// eviction. `visible` controls arrival-order delivery: publisher-produced
820	/// groups reach subscribers, fetched backfill is served by sequence only.
821	fn insert_group(&mut self, group: &group::Producer, visible: bool) {
822		let sequence = group.sequence;
823		self.next_stamp = self.next_stamp.wrapping_add(1);
824		let stamp = self.next_stamp;
825
826		// The live edge is tracked separately from `max_sequence`, which datagrams
827		// share and can push past any cached group: demotion must still fire when
828		// the next group lands beyond a datagram-advanced counter.
829		if self.latest_group.is_none_or(|latest| sequence >= latest) {
830			// Demote the previous latest: it joins the eviction order (and the
831			// pool's access average) like any other cached group.
832			if let Some(latest) = self.latest_group
833				&& sequence > latest
834				&& let Some(prev) = self.lookup.get(&latest)
835			{
836				prev.group.cache_demote();
837				self.evict.push_back((latest, prev.stamp));
838			}
839			self.latest_group = Some(sequence);
840		} else {
841			group.cache_demote();
842			self.evict.push_back((sequence, stamp));
843		}
844
845		self.max_sequence = Some(self.max_sequence.map_or(sequence, |max| max.max(sequence)));
846		self.lookup.insert(
847			sequence,
848			Slot {
849				group: group.clone(),
850				stamp,
851				visible,
852			},
853		);
854		if visible {
855			self.arrival.push_back((sequence, stamp));
856		}
857	}
858
859	/// Admit a freshly-created group: settle eviction debt first (so the newcomer
860	/// can never be a victim of the very write that created it), insert it, then
861	/// expire idle groups.
862	fn commit_group(&mut self, group: &group::Producer, visible: bool) {
863		self.charge_debt();
864		self.insert_group(group, visible);
865		self.evict_expired();
866	}
867
868	/// Accrue and pay eviction debt for everything written since the last charge:
869	/// this track's account, which the groups' charges feed on every frame (so
870	/// growth on already-demoted groups and backfill is billed too).
871	///
872	/// Runs BEFORE the new group is inserted, so a brand-new entry is never a
873	/// victim of the very write that created it. A track whose oldest content is
874	/// staler than the pool-wide average access time accrues at double rate, so
875	/// stale-heavy tracks drain first.
876	///
877	/// Also runs from the frame-write path via [`cache::Track::settle`], which is
878	/// why it's reachable from the account, so a track that only appends frames to
879	/// open groups still pays.
880	pub(super) fn charge_debt(&mut self) {
881		let written = self.cache.take_written();
882		let pool = self.cache.pool().clone();
883		match pool.accrue(written) {
884			Some(mut accrued) => {
885				if self.oldest_is_stale(&pool) {
886					accrued = accrued.saturating_mul(2);
887				}
888				// `used` bounds what eviction could ever free, keeping a track that
889				// can't pay (everything protected) from hoarding a stale schedule.
890				self.debt = self.debt.saturating_add(accrued).min(pool.used());
891				// Cap each payment at twice what was written so one write never dumps
892				// a deep backlog at once; the remainder carries to the next write.
893				self.pay_debt(&pool, written.saturating_mul(2));
894			}
895			// Under capacity there is nothing to work off, and stale debt would
896			// cause a spurious eviction burst at the next pressure spike.
897			None => self.debt = 0,
898		}
899	}
900
901	/// Whether this track's oldest evictable group was accessed at or before the
902	/// pool-wide average, doubling the debt it accrues. A dead entry at the front
903	/// just reads as not-stale until the next payment or expiry cleans it up.
904	fn oldest_is_stale(&self, pool: &cache::Pool) -> bool {
905		let Some(average) = pool.average() else {
906			return false;
907		};
908		let Some((sequence, stamp)) = self.evict.front() else {
909			return false;
910		};
911		let Some(slot) = self.lookup.get(sequence) else {
912			return false;
913		};
914		slot.stamp == *stamp && !slot.group.is_aborted() && slot.group.cache_accessed() <= average
915	}
916
917	/// Abort this track's stalest groups until the outstanding debt is paid, or
918	/// `cap` bytes have been freed by this call.
919	///
920	/// Deliberately approximate, Redis-style: at most a handful of live candidates
921	/// are examined per call, from the front of the eviction order. A group
922	/// accessed more recently than the pool-wide average is protected and rotates
923	/// to the back, so fresh content in this track never dies while staler content
924	/// survives elsewhere; the unfreed bytes keep the pool over budget, shifting
925	/// the debt onto the tracks holding that staler content. When the next victim
926	/// is larger than the remaining debt it is left in place and the debt carries
927	/// over, so a small write never evicts a huge group (once the debt does cover
928	/// it, that one victim may overshoot `cap`).
929	fn pay_debt(&mut self, pool: &cache::Pool, cap: u64) {
930		let average = pool.average().unwrap_or(0);
931		let mut paid = 0u64;
932		let mut scanned = 0usize;
933		for _ in 0..self.evict.len() {
934			if self.debt == 0 || paid >= cap || scanned >= EVICT_SCAN {
935				return;
936			}
937			let Some((sequence, stamp)) = self.evict.pop_front() else {
938				return;
939			};
940			let Some(slot) = self.lookup.get(&sequence) else {
941				// Evicted or expired; discard the dead entry.
942				continue;
943			};
944			if slot.stamp != stamp {
945				// A historical hint; the live entry is elsewhere in the queue.
946				continue;
947			}
948			if slot.group.is_aborted() {
949				// Aborted upstream: the frames are already gone, reclaim the slot.
950				self.lookup.remove(&sequence);
951				continue;
952			}
953			if Some(sequence) == self.latest_group {
954				// The live edge is never enqueued, but tolerate finding it anyway.
955				self.evict.push_back((sequence, stamp));
956				continue;
957			}
958
959			scanned += 1;
960			// Protected: accessed more recently than the average (a fresh insert,
961			// an active reader, or a FETCH hit, which also covers a backfill still
962			// being filled). Rotate to the back.
963			if slot.group.cache_accessed() > average {
964				self.evict.push_back((sequence, stamp));
965				continue;
966			}
967			// The full footprint including overhead, so even empty groups repay
968			// their share of the budget when evicted.
969			let size = slot.group.cache_size();
970			if size > self.debt {
971				self.evict.push_front((sequence, stamp));
972				return;
973			}
974
975			self.debt -= size;
976			paid = paid.saturating_add(size);
977			let slot = self.lookup.remove(&sequence).unwrap();
978			let _ = slot.group.abort(Error::Evicted);
979		}
980	}
981
982	/// Record the declared first sequence of the live feed, replacing any earlier
983	/// declaration: the signal is scoped to the current subscription's demand,
984	/// which may legitimately move in either direction. `None` clears it (the
985	/// demand dropped to the live edge, whose floor is unknown until declared).
986	fn set_start(&mut self, start_sequence: Option<u64>) {
987		self.start_sequence = start_sequence;
988	}
989
990	/// Record the exclusive final sequence, rejecting a re-finish or a boundary that
991	/// would orphan already-produced groups.
992	fn set_final(&mut self, final_sequence: u64) -> Result<()> {
993		if self.final_sequence.is_some() {
994			return Err(Error::Closed);
995		}
996		if let Some(max) = self.max_sequence
997			&& final_sequence <= max
998		{
999			return Err(Error::ProtocolViolation);
1000		}
1001		self.final_sequence = Some(final_sequence);
1002		Ok(())
1003	}
1004
1005	/// Whether the track has reached its end: the final boundary is set and the live
1006	/// edge has caught up to it, so no further group can arrive. A future boundary
1007	/// (declared via [`Producer::finish_at`] ahead of the live edge) stays incomplete
1008	/// until the remaining groups are produced. Drives the end-of-stream signal from
1009	/// the read methods (`recv_group` / `next_group` / `read_frame` return `None`).
1010	fn is_complete(&self) -> bool {
1011		self.final_sequence
1012			.is_some_and(|fin| self.max_sequence.map_or(0, |max| max.saturating_add(1)) >= fin)
1013	}
1014
1015	/// Where a replacement route should pick this track up: one past the last frame
1016	/// produced, rolling to the start of the next group once the latest group is
1017	/// complete (nothing more can be appended to it).
1018	///
1019	/// `None` while the track has produced nothing, which is an unbounded takeover.
1020	fn resume_position(&self) -> Option<Position> {
1021		// A snapshot taken when the cache was released wins; the groups it was derived
1022		// from are gone.
1023		if self.resume.is_some() {
1024			return self.resume;
1025		}
1026
1027		let max = self.latest_group?;
1028		match self.lookup.get(&max).and_then(|slot| slot.group.resume_frame()) {
1029			// Still open *and* carrying frames, so the replacement continues it
1030			// frame-by-frame.
1031			Some(frame) => Some(Position {
1032				group: max,
1033				frame: frame as u64,
1034			}),
1035			// A copy that wrote nothing has no frames to splice onto, and the reader
1036			// already holds its (empty) handle. Pointing a replacement at frame 0 would
1037			// hand the same sequence out twice, so roll to the next group exactly as a
1038			// finished one does.
1039			None => Some(Position::group(max.saturating_add(1))),
1040		}
1041	}
1042
1043	fn poll_finished(&self) -> Poll<Result<u64>> {
1044		if let Some(fin) = self.final_sequence {
1045			Poll::Ready(Ok(fin))
1046		} else if let Some(err) = &self.abort {
1047			Poll::Ready(Err(err.clone()))
1048		} else {
1049			Poll::Pending
1050		}
1051	}
1052
1053	fn modify(producer: &kio::Producer<Self>) -> Result<kio::Mut<'_, Self>> {
1054		producer.write().map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
1055	}
1056
1057	/// Insert a group fetched for a [`group::Request`], setting the track's [`Info`]
1058	/// if it isn't accepted yet. The group's timescale comes from that info, so a
1059	/// fetch can serve an as-yet-unaccepted track (e.g. a relay with no live
1060	/// subscription). The group lands in the cache so a waiting
1061	/// [`Fetching`] resolves via [`Self::poll_fetch`].
1062	pub(crate) fn insert_group_request(
1063		&mut self,
1064		sequence: u64,
1065		frame_start: u64,
1066		info: Option<Info>,
1067	) -> Result<group::Producer> {
1068		if let Some(err) = &self.abort {
1069			return Err(err.clone());
1070		}
1071		if let Some(fin) = self.final_sequence
1072			&& sequence >= fin
1073		{
1074			return Err(Error::Closed);
1075		}
1076
1077		// Adopt the supplied info only if the track hasn't been accepted yet. Groups
1078		// created here charge the same account as any other, so backfill written
1079		// before the track is accepted settles its debt like the rest.
1080		if self.info.is_none() {
1081			self.install(info.unwrap_or_default());
1082		}
1083		let info = self.info.clone().unwrap();
1084
1085		// An evicted sequence can be re-fetched; a live one is a duplicate.
1086		self.claim_sequence(sequence, frame_start)?;
1087
1088		let group = group::Producer::new(group::Info { sequence }, info, self.cache.clone());
1089		// A backfill exists because someone is fetching it right now: stamp that
1090		// access so the eviction walk can't kill it before the fetch resolves.
1091		// It is also invisible to arrival-order subscribers: fetched on demand,
1092		// not produced live by the publisher.
1093		group.cache_refresh();
1094		self.commit_group(&group, false);
1095		Ok(group)
1096	}
1097}
1098
1099/// Record `err` and close the track: the shared tail of [`Producer::abort`] and
1100/// [`Producer::abort_unused`].
1101fn commit_abort(mut state: kio::Mut<'_, TrackState>, err: Error) {
1102	// Snapshot the frame boundary before the cache it's derived from goes away: an
1103	// abort is exactly when a replacement route asks where to resume.
1104	state.resume = state.resume_position();
1105	state.abort = Some(err);
1106	state.clear_cache();
1107	state.datagrams.clear();
1108	state.close();
1109}
1110
1111/// A producer for a track, used to create new groups.
1112#[derive(Clone)]
1113pub struct Producer {
1114	name: Arc<str>,
1115	info: Info,
1116	// The parent broadcast's info, inherited from [`broadcast::Producer::create_track`].
1117	// Top link of the ownership chain; carried for identity and future inheritance.
1118	broadcast: Arc<broadcast::Info>,
1119	state: kio::Producer<TrackState>,
1120	prev_subscription: Option<Subscription>,
1121	// Shared with every clone and every `Dynamic`: its `Drop` is the teardown.
1122	alive: Arc<Alive>,
1123	// Ingress stats scope, inherited from a tagged [`broadcast::Producer`]. Bumped as
1124	// one subscription on tag and closed when the last producer clone drops. Empty
1125	// (no-op) for an untagged broadcast.
1126	stats: stats::Scope,
1127}
1128
1129impl Producer {
1130	/// Build a producer for the given track metadata.
1131	///
1132	/// Crate-private: tracks are born from their broadcast via
1133	/// [`broadcast::Producer::create_track`] (or served on demand through a
1134	/// [`Request`]), which threads the broadcast's `Arc<broadcast::Info>` down. The
1135	/// track opens its cache account against that broadcast's origin pool, and every
1136	/// group it creates charges into it.
1137	pub(crate) fn new(
1138		broadcast: Arc<broadcast::Info>,
1139		name: impl Into<Arc<str>>,
1140		info: impl Into<Option<Info>>,
1141	) -> Self {
1142		let name = name.into();
1143		let info = TrackState::normalize_info(&broadcast, info.into().unwrap_or_default());
1144		let state = TrackState::spawn(broadcast.clone());
1145		state.write().ok().expect("a new track is open").accept(info.clone());
1146		let alive = Alive::new(name.clone(), state.clone());
1147		alive.publish(None);
1148		Self {
1149			name,
1150			info,
1151			state,
1152			broadcast,
1153			prev_subscription: None,
1154			alive,
1155			stats: stats::Scope::default(),
1156		}
1157	}
1158
1159	/// Attach the parent broadcast's ingress stats scope, counting this track as one
1160	/// ingress subscription (closed when the last producer clone drops). Called by a
1161	/// tagged [`broadcast::Producer`] when it creates the track.
1162	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
1163		self.alive.publish(Some(&scope));
1164		self.stats = scope;
1165		self
1166	}
1167
1168	/// The track's name, unique within its broadcast.
1169	pub fn name(&self) -> &str {
1170		&self.name
1171	}
1172
1173	/// The parent broadcast this track belongs to.
1174	pub fn broadcast(&self) -> &broadcast::Info {
1175		&self.broadcast
1176	}
1177
1178	/// Cache an already-produced group without rewriting its frames.
1179	///
1180	/// Used by an origin front to keep a warm copy of groups it already delivered
1181	/// after dropping the source track that produced them.
1182	pub(crate) fn adopt_group(&mut self, group: group::Producer, visible: bool) -> Result<()> {
1183		let mut state = self.modify()?;
1184		if let Some(fin) = state.final_sequence
1185			&& group.sequence >= fin
1186		{
1187			return Err(Error::Closed);
1188		}
1189		if state.lookup.contains_key(&group.sequence) {
1190			return Err(Error::Duplicate);
1191		}
1192		state.insert_group(&group, visible);
1193		Ok(())
1194	}
1195
1196	/// Create a new group with the given sequence number.
1197	pub fn create_group(&self, group: group::Info) -> Result<group::Producer> {
1198		let mut state = self.modify()?;
1199		if let Some(fin) = state.final_sequence
1200			&& group.sequence >= fin
1201		{
1202			return Err(Error::Closed);
1203		}
1204		let track = state.info.clone().unwrap();
1205
1206		// An evicted sequence can be re-created; a live one is a duplicate.
1207		state.claim_sequence(group.sequence, 0)?;
1208
1209		let group = group::Producer::new(group, track, state.cache.clone()).with_meter(self.stats.meter());
1210		state.commit_group(&group, true);
1211
1212		Ok(group)
1213	}
1214
1215	/// Create a new group with the next sequence number.
1216	pub fn append_group(&self) -> Result<group::Producer> {
1217		let mut state = self.modify()?;
1218		let sequence = match state.max_sequence {
1219			Some(s) => s.checked_add(1).ok_or(coding::BoundsExceeded)?,
1220			None => 0,
1221		};
1222		if let Some(fin) = state.final_sequence
1223			&& sequence >= fin
1224		{
1225			return Err(Error::Closed);
1226		}
1227
1228		let track = state.info.clone().unwrap();
1229
1230		let group =
1231			group::Producer::new(group::Info { sequence }, track, state.cache.clone()).with_meter(self.stats.meter());
1232		state.commit_group(&group, true);
1233
1234		Ok(group)
1235	}
1236
1237	/// Append a datagram with the next sequence number, returning the assigned sequence.
1238	///
1239	/// A datagram is delivered best-effort over a single QUIC datagram, parallel to the
1240	/// track's groups but drawing from the same sequence namespace (so interleaving with
1241	/// [`Self::append_group`] never reuses a number). There is no group fallback: each
1242	/// session drops (with a debug log) any datagram whose encoded body exceeds the
1243	/// transport's datagram size, and sessions that can't carry datagrams at all (IETF
1244	/// moq-transport, moq-lite before 05, or stream-only transports like WebSocket) never
1245	/// deliver them. Keep payloads well under the 1200-byte minimum path MTU. An origin
1246	/// publisher uses this; a relay preserving upstream numbering uses
1247	/// [`Self::insert_datagram`].
1248	pub fn append_datagram<B: crate::IntoBytes>(&mut self, timestamp: Timestamp, payload: B) -> Result<u64> {
1249		let payload = payload.into_bytes();
1250		if payload.len() > super::datagram::MAX_DATAGRAM_PAYLOAD {
1251			return Err(Error::FrameTooLarge);
1252		}
1253		// Resolved before the state guard borrows `self`.
1254		let meter = self.stats.meter();
1255		let mut state = self.modify()?;
1256		// Normalize into the track's timescale, like frames (see `group::Producer::create_frame`).
1257		let timescale = state.info.as_ref().unwrap().timescale;
1258		let timestamp = timestamp.convert(timescale).map_err(|_| Error::TimestampMismatch)?;
1259		let sequence = match state.max_sequence {
1260			Some(s) => s.checked_add(1).ok_or(coding::BoundsExceeded)?,
1261			None => 0,
1262		};
1263		if let Some(fin) = state.final_sequence
1264			&& sequence >= fin
1265		{
1266			return Err(Error::Closed);
1267		}
1268		state.max_sequence = Some(sequence);
1269		meter.datagram(payload.len() as u64);
1270		state.push_datagram(Datagram {
1271			sequence,
1272			timestamp,
1273			payload,
1274		});
1275		let cache = state.cache.clone();
1276		drop(state);
1277		cache.settle(None);
1278		Ok(sequence)
1279	}
1280
1281	/// Insert a datagram with an explicit sequence number.
1282	///
1283	/// Preserves the supplied sequence (bumping the shared `max_sequence` if needed), so a
1284	/// relay can forward a datagram without renumbering it. Most origin publishers want
1285	/// [`Self::append_datagram`] instead.
1286	pub fn insert_datagram<B: crate::IntoBytes>(
1287		&mut self,
1288		sequence: u64,
1289		timestamp: Timestamp,
1290		payload: B,
1291	) -> Result<()> {
1292		let payload = payload.into_bytes();
1293		if payload.len() > super::datagram::MAX_DATAGRAM_PAYLOAD {
1294			return Err(Error::FrameTooLarge);
1295		}
1296		// Resolved before the state guard borrows `self`.
1297		let meter = self.stats.meter();
1298		let mut state = self.modify()?;
1299		// Normalize into the track's timescale, like frames (see `group::Producer::create_frame`).
1300		let timescale = state.info.as_ref().unwrap().timescale;
1301		let timestamp = timestamp.convert(timescale).map_err(|_| Error::TimestampMismatch)?;
1302		if let Some(fin) = state.final_sequence
1303			&& sequence >= fin
1304		{
1305			return Err(Error::Closed);
1306		}
1307		state.max_sequence = Some(state.max_sequence.unwrap_or(0).max(sequence));
1308		meter.datagram(payload.len() as u64);
1309		state.push_datagram(Datagram {
1310			sequence,
1311			timestamp,
1312			payload,
1313		});
1314		let cache = state.cache.clone();
1315		drop(state);
1316		cache.settle(None);
1317		Ok(())
1318	}
1319
1320	/// Create a group with a single frame, at the given presentation timestamp.
1321	///
1322	/// The timestamp is converted into the track's timescale. For data without
1323	/// a presentation time, pass [`Timestamp::now`] explicitly.
1324	pub fn write_frame<B: crate::IntoBytes>(&mut self, timestamp: Timestamp, frame: B) -> Result<()> {
1325		let frame = crate::IntoBytes::into_bytes(frame);
1326		if frame.len() as u64 > group::MAX_CACHE_BYTES {
1327			return Err(Error::FrameTooLarge);
1328		}
1329		let mut group = self.append_group()?;
1330		group.write_frame(timestamp, frame)?;
1331		group.finish()?;
1332		Ok(())
1333	}
1334
1335	/// Mark the track as finished after the last appended group.
1336	///
1337	/// Sets the final sequence to one past the current max_sequence.
1338	/// No new groups at or above this sequence can be appended.
1339	/// NOTE: Old groups with lower sequence numbers can still arrive.
1340	pub fn finish(&self) -> Result<()> {
1341		let mut state = self.modify()?;
1342		let final_sequence = match state.max_sequence {
1343			Some(max) => max.checked_add(1).ok_or(coding::BoundsExceeded)?,
1344			None => 0,
1345		};
1346		state.set_final(final_sequence)
1347	}
1348
1349	/// Declare the track's exclusive final sequence, possibly ahead of the live edge.
1350	///
1351	/// `final_sequence` is the first sequence that will never be produced, so a track
1352	/// whose last group is 89 finishes at `90`. Passing a boundary beyond the current
1353	/// max_sequence records a known ending before the remaining groups arrive (e.g.
1354	/// learning a track ends at group 89 while only 87 has been received). The boundary
1355	/// must be strictly greater than the highest produced group, otherwise it would
1356	/// orphan groups that already exist ([`Error::ProtocolViolation`]).
1357	///
1358	/// Groups below `final_sequence` may still be created afterwards; groups at or above
1359	/// it are rejected. Consumers only see end-of-stream once the live edge reaches the
1360	/// boundary. Use [`Self::finish`] to finish exactly at the live edge.
1361	pub fn finish_at(&mut self, final_sequence: u64) -> Result<()> {
1362		self.modify()?.set_final(final_sequence)
1363	}
1364
1365	/// Declare the first group the live feed serves (the wire's SUBSCRIBE_START,
1366	/// or the start the subscription itself requested): groups below `sequence`
1367	/// will never arrive on their own, so a reader waiting for one fails over
1368	/// instead of stalling. A fetch can still retrieve them.
1369	///
1370	/// Scoped to the current subscription's demand, so a later declaration
1371	/// replaces this one in either direction: a re-subscription may start
1372	/// earlier, and a narrowed subscription skips groups an earlier declaration
1373	/// still promised. Pass `None` to clear it, for demand at the live edge:
1374	/// its floor is unknown until the feed declares one.
1375	pub fn start_at(&mut self, sequence: impl Into<Option<u64>>) -> Result<()> {
1376		self.modify()?.set_start(sequence.into());
1377		Ok(())
1378	}
1379
1380	/// The declared first sequence of the live feed, once [`Self::start_at`]
1381	/// declared one. `None` while nothing was declared.
1382	#[cfg(test)]
1383	pub(crate) fn start_sequence(&self) -> Option<u64> {
1384		self.state.read().start_sequence
1385	}
1386
1387	/// The exclusive final sequence, once [`Self::finish`] or [`Self::finish_at`] declared one.
1388	///
1389	/// `None` while the track is still open ended. Both methods reject a second boundary, so
1390	/// callers that may have already declared one check here first.
1391	pub fn final_sequence(&self) -> Option<u64> {
1392		self.state.read().final_sequence
1393	}
1394
1395	/// Abort the track with the given error.
1396	///
1397	/// Consumes the handle, since nothing can be written to an aborted track. Drops the
1398	/// cached groups so a stale [`Consumer`] can't pin them (and their frame buffers) in
1399	/// memory forever. Consumers that haven't drained yet surface the abort error instead
1400	/// of the leftover cache. Child groups are independent: a consumer that already pulled
1401	/// a [`group::Consumer`] keeps its own handle and can finish reading it.
1402	///
1403	/// [`finish`](Self::finish) is deliberately not terminal: it declares the final
1404	/// sequence, and lower-numbered groups may still be written afterwards.
1405	pub fn abort(self, err: Error) -> Result<()> {
1406		commit_abort(self.modify()?, err);
1407		Ok(())
1408	}
1409
1410	/// Abort an unused track, returning the producer unchanged if consumers remain.
1411	///
1412	/// Consumer creation and the unused check share a lock, so demand returning
1413	/// after [`poll_unused`](Self::poll_unused) prevents the abort. `Ok(())` means
1414	/// the track is closed, including when it was already closed; existing handles
1415	/// may still observe its final state. `Err(producer)` leaves the track unchanged
1416	/// so the caller can continue serving and wait for the next unused wake.
1417	#[expect(
1418		clippy::result_large_err,
1419		reason = "return the owned producer without allocating on an idle check"
1420	)]
1421	pub fn abort_unused(self, err: Error) -> std::result::Result<(), Self> {
1422		match self.state.write_unused() {
1423			kio::Unused::Idle(guard) => {
1424				commit_abort(guard, err);
1425				return Ok(());
1426			}
1427			kio::Unused::Closed => return Ok(()),
1428			kio::Unused::Used => {}
1429		}
1430		Err(self)
1431	}
1432
1433	/// Whether the track is open and anyone is consuming it right now.
1434	///
1435	/// A point-in-time snapshot for gating work on demand (on-demand capture,
1436	/// dropping cached state nobody is watching). Acting on it to *end* the track
1437	/// is the race [`abort_unused`](Self::abort_unused) exists for; use
1438	/// [`unused`](Self::unused) to wait for the edge.
1439	pub fn is_used(&self) -> bool {
1440		!self.is_closed() && self.state.is_used()
1441	}
1442
1443	/// Block until there are no active consumers.
1444	pub async fn unused(&self) -> Result<()> {
1445		self.state.unused().await.map_err(|_| self.abort_reason())
1446	}
1447
1448	/// Block until there is at least one active consumer.
1449	pub async fn used(&self) -> Result<()> {
1450		self.state.used().await.map_err(|_| self.abort_reason())
1451	}
1452
1453	/// Block until the track is closed or aborted, returning the cause.
1454	pub async fn closed(&self) -> Error {
1455		kio::wait(|waiter| self.poll_closed(waiter)).await
1456	}
1457
1458	/// Poll until the track is closed or aborted; ready with the cause.
1459	pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<Error> {
1460		self.state.poll_closed(waiter).map(|()| self.abort_reason())
1461	}
1462
1463	/// The recorded abort reason, or [`Error::Dropped`] if the track closed without one.
1464	fn abort_reason(&self) -> Error {
1465		self.state.read().abort.clone().unwrap_or(Error::Dropped)
1466	}
1467
1468	/// Return true if the track has been closed.
1469	pub fn is_closed(&self) -> bool {
1470		self.state.read().is_closed()
1471	}
1472
1473	/// Return the latest sequence number successfully appended to the track.
1474	pub fn latest(&self) -> Option<u64> {
1475		self.state.read().max_sequence
1476	}
1477
1478	/// Return true if this is the same track.
1479	pub fn is_clone(&self, other: &Self) -> bool {
1480		self.state.same_channel(&other.state)
1481	}
1482
1483	/// Create a weak reference that doesn't prevent auto-close.
1484	pub(crate) fn weak(&self) -> TrackWeak {
1485		TrackWeak {
1486			name: self.name.clone(),
1487			state: self.state.weak(),
1488		}
1489	}
1490
1491	/// Create a [`Demand`]: a cloneable, watch-only handle to this track's
1492	/// subscriber demand.
1493	///
1494	/// Lets a publisher gate work (e.g. on-demand capture) on whether anyone is
1495	/// subscribed, without the ability to publish frames or close the track. The
1496	/// handle is weak, so holding one neither keeps the track alive nor pins its
1497	/// cached groups.
1498	pub fn demand(&self) -> Demand {
1499		Demand {
1500			name: self.name.clone(),
1501			state: self.state.weak(),
1502		}
1503	}
1504
1505	/// Get a consumer handle for this in-process track.
1506	///
1507	/// Unlike a wire subscription, the info is already known, so a subscription
1508	/// opened from this handle resolves immediately.
1509	pub fn consume(&self) -> Consumer {
1510		Consumer::plain(self.name.clone(), self.state.consume())
1511	}
1512
1513	/// Subscribing to this in-process track, resolving synchronously.
1514	///
1515	/// The info is fixed at creation, so there's nothing to wait for (no
1516	/// SUBSCRIBE_OK round trip). Pass `None` for [`Subscription::default`].
1517	///
1518	/// The read cursor starts at the group the subscription named (its floor), or 0.
1519	/// [`Subscription::max_age`] is what asks for data: delivery skips everything above
1520	/// the floor that the budget convicts, so the default budget of zero delivers only
1521	/// the latest group and a larger one reaches back over what it can still use.
1522	pub fn subscribe(&self, subscription: impl Into<Option<Subscription>>) -> Subscriber {
1523		let preferences = subscription.into().unwrap_or_default();
1524
1525		// Info is fixed at creation and survives a close/abort, so read it without
1526		// requiring a live producer state. If the track already ended, the returned
1527		// subscriber surfaces the close/abort on its first read; the preferences are
1528		// simply never registered (nothing aggregates them anymore).
1529		let info = self.info.clone();
1530		let min_sequence = floor_of(&preferences);
1531		let subscription = kio::Producer::new(preferences);
1532		register_subscription(self.state.read(), &subscription);
1533		let drift_cap = kio::Producer::new(None);
1534
1535		// Hoisted: an inline `read()` guard would live to the end of the struct literal,
1536		// deadlocking against the `consume()` below.
1537		let broadcast = self.state.read().broadcast.clone();
1538		Subscriber {
1539			name: self.name.clone(),
1540			broadcast,
1541			info,
1542			inner: SubscriberKind::Plain(PlainSubscriber {
1543				state: self.state.consume(),
1544				subscription,
1545				min_sequence,
1546				index: 0,
1547				datagram_index: 0,
1548				next_sequence: 0,
1549				end_sequence: None,
1550				parked: BTreeMap::new(),
1551				stale_cap: None,
1552				drift_cap,
1553				stale: stats::Content::default(),
1554				seek_pending: BTreeMap::new(),
1555			}),
1556			// A producer-side (in-process) subscribe is not egress: stay untagged.
1557			stats: stats::Scope::default(),
1558			_stats_sub: stats::Subscription::default(),
1559		}
1560	}
1561
1562	/// Block until the aggregate subscription changes, then return the new value.
1563	///
1564	/// Yields the most demanding request across all live subscribers, or `None`
1565	/// once the last one drops. Used by relays to forward downstream demand
1566	/// upstream (e.g. SUBSCRIBE_UPDATE).
1567	pub async fn subscription_changed(&mut self) -> Result<Option<Subscription>> {
1568		kio::wait(|waiter| self.poll_subscription_changed(waiter)).await
1569	}
1570
1571	/// A non-blocking snapshot of the current aggregate subscription, or `None`
1572	/// when there are no live subscribers. Unlike [`Self::subscription`], this
1573	/// doesn't wait for a change or advance the change cursor.
1574	///
1575	/// The aggregate's [`Subscription::max_age`] is clamped to this track's
1576	/// [`Info::max_age`]: no subscriber can wait for a late group longer than the
1577	/// publisher keeps it.
1578	pub fn subscription(&self) -> Option<Subscription> {
1579		let state = self.state.read();
1580		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
1581		drop(state);
1582		snapshot_subscription(&subs, bound)
1583	}
1584
1585	/// Poll counterpart to [`subscription_changed`](Self::subscription_changed): the
1586	/// aggregate subscription whenever it changes, or `None` once nobody is subscribed.
1587	/// Errors once the track is aborted.
1588	pub fn poll_subscription_changed(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Subscription>>> {
1589		// Surface an abort as the stream ending. `poll_closed` parks on the closed
1590		// waiters, so per-group churn on the track state never wakes this poll.
1591		if self.state.poll_closed(waiter).is_ready() {
1592			let abort = self.state.read().abort.clone();
1593			return Poll::Ready(Err(abort.unwrap_or(Error::Dropped)));
1594		}
1595
1596		// Read the bound before locking `subs`, so the aggregation never nests the two locks.
1597		let state = self.state.read();
1598		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
1599		drop(state);
1600
1601		let prev = &self.prev_subscription;
1602		let mut combined = None;
1603		let mut guard = ready!(subs.poll(waiter, |subs| {
1604			let next = combined_subscription(subs, bound, waiter);
1605			if &next == prev {
1606				Poll::Pending
1607			} else {
1608				combined = next;
1609				Poll::Ready(())
1610			}
1611		}));
1612		// The aggregate changed: prune any closed subscribers now that we hold the lock.
1613		guard.retain(|sub| !sub.is_closed());
1614		drop(guard);
1615		self.prev_subscription = combined.clone();
1616		Poll::Ready(Ok(combined))
1617	}
1618
1619	/// Poll for the producer becoming unused (every consumer dropped).
1620	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
1621		self.state.poll_unused(waiter).map(|used| match used {
1622			Some(()) => Ok(()),
1623			None => Err(self.abort_reason()),
1624		})
1625	}
1626
1627	/// Create a [`Dynamic`] handle that serves on-demand fetches of uncached
1628	/// (old) groups. Most producers never need this; a relay creates one to fetch
1629	/// past groups from upstream.
1630	pub fn dynamic(&self) -> Dynamic {
1631		Dynamic::new(self.name.clone(), self.state.clone(), self.alive.clone())
1632	}
1633
1634	fn modify(&self) -> Result<kio::Mut<'_, TrackState>> {
1635		TrackState::modify(&self.state)
1636	}
1637}
1638
1639/// Pop the next queued group fetch off the fetch queue and wrap it in a
1640/// [`group::Request`] bound to a fresh producer handle. Shared by every
1641/// [`Dynamic`] handle on the track.
1642fn poll_requested_group(
1643	state: &kio::Producer<TrackState>,
1644	fetch: &kio::Shared<FetchState>,
1645	waiter: &kio::Waiter,
1646) -> Poll<Result<group::Request>> {
1647	// Prefer serving a queued fetch, even if the track has since aborted.
1648	if let Poll::Ready(mut guard) = fetch.poll(waiter, |fetch| {
1649		if fetch.has_queued() {
1650			Poll::Ready(())
1651		} else {
1652			Poll::Pending
1653		}
1654	}) {
1655		let sequence = guard.pop().expect("predicate guaranteed a request");
1656		// The popped attempt stays pending, so a fetch in the window between hand-off
1657		// and accept joins it instead of queueing a duplicate.
1658		// `group::Request::{accept, reject, drop}` removes the entry.
1659		let pending = guard.get(&sequence).expect("popped key must be pending");
1660		let priority = pending.priority;
1661		let frame_start = pending.frame_start;
1662		let result = pending.result.clone();
1663		drop(guard);
1664		return Poll::Ready(Ok(group::Request {
1665			state: state.clone(),
1666			fetch: fetch.clone(),
1667			sequence,
1668			priority,
1669			frame_start,
1670			result,
1671			done: false,
1672		}));
1673	}
1674
1675	// No fetch queued: surface a track abort so the handler loop can exit.
1676	match state.poll_ref(waiter, |state| match &state.abort {
1677		Some(err) => Poll::Ready(err.clone()),
1678		None => Poll::Pending,
1679	}) {
1680		Poll::Ready(Ok(err)) => Poll::Ready(Err(err)),
1681		Poll::Ready(Err(closed)) => Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped))),
1682		Poll::Pending => Poll::Pending,
1683	}
1684}
1685
1686/// Serves on-demand fetches of uncached (old) groups for a track, the group-level
1687/// analogue of [`broadcast::Dynamic`].
1688///
1689/// Most tracks never serve old content, so this capability lives on a dedicated
1690/// handle rather than [`Producer`]: a relay creates one (via
1691/// [`Producer::dynamic`] or [`Request::dynamic`]) to pull past groups
1692/// from upstream. While at least one is alive the track will block a cache-miss
1693/// [`Consumer::fetch_group`] waiting to be served; with none, an accepted track's
1694/// miss fails fast with [`Error::NotFound`].
1695pub struct Dynamic {
1696	name: Arc<str>,
1697	// Kept to insert served groups into the cache and observe track abort.
1698	state: kio::Producer<TrackState>,
1699	// The fetch queue this handle drains; its `dynamic` count gates `fetch_group`.
1700	fetch: kio::Shared<FetchState>,
1701	// Shared with the track's producers: a handler still serving fetches keeps the
1702	// track alive, like a producer clone does.
1703	alive: Arc<Alive>,
1704}
1705
1706impl Dynamic {
1707	fn new(name: Arc<str>, state: kio::Producer<TrackState>, alive: Arc<Alive>) -> Self {
1708		let fetch = state.read().fetch.clone();
1709		fetch.lock().add_handler();
1710		Self {
1711			name,
1712			state,
1713			fetch,
1714			alive,
1715		}
1716	}
1717
1718	/// The track's name, unique within its broadcast.
1719	pub fn name(&self) -> &str {
1720		&self.name
1721	}
1722
1723	/// Block until a consumer fetches a group that isn't cached, returning a
1724	/// [`group::Request`] to serve via [`group::Request::accept`].
1725	///
1726	/// A relay issues a wire FETCH first; an origin already has the group cached, so
1727	/// the fetch resolves without ever reaching here. Errors once the track is aborted.
1728	pub async fn requested_group(&self) -> Result<group::Request> {
1729		kio::wait(|waiter| self.poll_requested_group(waiter)).await
1730	}
1731
1732	/// Poll counterpart to [`requested_group`](Self::requested_group).
1733	pub fn poll_requested_group(&self, waiter: &kio::Waiter) -> Poll<Result<group::Request>> {
1734		poll_requested_group(&self.state, &self.fetch, waiter)
1735	}
1736
1737	/// Poll for the track becoming unused (every consumer dropped).
1738	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
1739		self.state.poll_unused(waiter).map(|_| ())
1740	}
1741}
1742
1743impl Clone for Dynamic {
1744	fn clone(&self) -> Self {
1745		// Count each live handle (mirrors `broadcast::Dynamic`).
1746		self.fetch.lock().add_handler();
1747		Self {
1748			name: self.name.clone(),
1749			state: self.state.clone(),
1750			fetch: self.fetch.clone(),
1751			alive: self.alive.clone(),
1752		}
1753	}
1754}
1755
1756impl Drop for Dynamic {
1757	fn drop(&mut self) {
1758		// Unlike `broadcast::Dynamic`, dropping the last handle doesn't abort the track:
1759		// a live `Producer` may still be serving the subscription. It just stops fetch
1760		// serving. Queued attempts no handler will ever pop are dropped, closing their
1761		// result channels so every joined `Fetching` resolves NotFound; an attempt
1762		// already handed to a handler stays, resolved by its `group::Request` instead.
1763		let mut fetch = self.fetch.lock();
1764		if fetch.remove_handler() {
1765			fetch.drain_queued();
1766		}
1767	}
1768}
1769
1770/// Ends the track when the last [`Producer`] or [`Dynamic`] drops.
1771///
1772/// A refcount rather than a "am I the last one?" check inside `Drop`: that answer is a
1773/// snapshot, and acting on it is exactly what invalidates it. The track state's own
1774/// producer count can't answer it either, since a group settling its eviction debt
1775/// upgrades the account's weak handle and counts there for the duration (see
1776/// [`cache::Track::settle`]). Holding a producer of its own also keeps the state
1777/// writable until the teardown has run, whatever order the last owner's fields drop in.
1778struct Alive {
1779	name: Arc<str>,
1780	state: kio::Producer<TrackState>,
1781
1782	// Set when a `Producer` is first minted, so a `Request` nobody accepted (its
1783	// `Dynamic` holds this guard too) isn't reported as an abandoned publisher.
1784	published: AtomicBool,
1785
1786	// Ingress subscription for this track, opened by the tagged producer that claimed
1787	// it and closed when this guard drops.
1788	stats: OnceLock<stats::Subscription>,
1789}
1790
1791impl Alive {
1792	fn new(name: Arc<str>, state: kio::Producer<TrackState>) -> Arc<Self> {
1793		Arc::new(Self {
1794			name,
1795			state,
1796			published: Default::default(),
1797			stats: Default::default(),
1798		})
1799	}
1800
1801	/// Note that a [`Producer`] was minted from this track, optionally under a tagged
1802	/// broadcast's ingress scope (counted as one subscription for as long as the track
1803	/// has a publisher).
1804	fn publish(&self, stats: Option<&stats::Scope>) {
1805		self.published.store(true, Ordering::Relaxed);
1806		if let Some(scope) = stats {
1807			// At most one scope ever arrives: a track is minted either through
1808			// `Producer::new` (+ `with_stats`) or through `Request::accept`, never both.
1809			let _ = self.stats.set(scope.subscribe());
1810		}
1811	}
1812}
1813
1814impl Drop for Alive {
1815	fn drop(&mut self) {
1816		// A request nobody accepted was never publishing; there's nothing to tear down.
1817		if !self.published.load(Ordering::Relaxed) {
1818			return;
1819		}
1820		// The last producer going away without finishing is an abrupt teardown:
1821		// release the cached groups so a stale consumer can't pin them (and their
1822		// frame buffers) forever, the same as an explicit abort. A cleanly
1823		// finished track keeps its cache so consumers can still drain it.
1824		// `abort()` closes the channel, so `write()` returns `Err(Ref)`. `finish()`
1825		// leaves it open with `final_sequence` set, so inspect both outcomes.
1826		match self.state.write() {
1827			Ok(mut state) => {
1828				if state.final_sequence.is_some() || state.abort.is_some() {
1829					return;
1830				}
1831				tracing::warn!(
1832					track = %self.name,
1833					"track::Producer dropped without finish() or abort()"
1834				);
1835				// See `abort`: keep the frame boundary once its groups go away.
1836				state.resume = state.resume_position();
1837				state.clear_cache();
1838				state.datagrams.clear();
1839			}
1840			Err(state) => {
1841				if state.final_sequence.is_some() || state.abort.is_some() {
1842					return;
1843				}
1844				tracing::warn!(
1845					track = %self.name,
1846					"track::Producer dropped without finish() or abort()"
1847				);
1848			}
1849		}
1850	}
1851}
1852
1853/// Aggregate every live subscriber's preferences into the most demanding request.
1854///
1855/// Read-only: iterates the subscriptions immutably and registers `waiter` on each, so a
1856/// preference update (or a subscriber dropping) wakes the caller's poll. Callers decide
1857/// readiness from the returned value, then prune closed subscribers through the `Mut`.
1858fn combined_subscription(subs: &Subscriptions, bound: Option<Duration>, waiter: &kio::Waiter) -> Option<Subscription> {
1859	let mut combined = None;
1860	for sub in subs.iter() {
1861		// A closed consumer means the subscriber dropped: it holds no live demand.
1862		// `Consumer::poll` evaluates the closure before the closed flag, so it would
1863		// still replay the final value into the aggregate; skip it explicitly so a
1864		// departed subscriber can't keep the aggregate pinned to its last request.
1865		if sub.is_closed() {
1866			continue;
1867		}
1868		// Arm both waiters explicitly. `poll` registers on the value channel only
1869		// when it returns Pending, and a subscriber that contributes demand (always
1870		// the case for the first one) folds as Ready, so nothing would watch for its
1871		// departure or its next update: the last one leaving would never wake this
1872		// poll, and a reader lifting the cap it had set would leave the publisher's
1873		// upstream parked at that cap for good.
1874		let _ = sub.poll_closed(waiter);
1875		let _ = sub.poll(waiter, |_| Poll::<()>::Pending);
1876		if let Poll::Ready(merged) = sub.read().poll_combined(&combined) {
1877			combined = Some(merged);
1878		}
1879	}
1880	clamp_combined(combined, bound)
1881}
1882
1883/// A non-blocking aggregate of the current subscriptions, without arming any waiter.
1884fn snapshot_subscription(subs: &kio::Shared<Subscriptions>, bound: Option<Duration>) -> Option<Subscription> {
1885	let mut combined: Option<Subscription> = None;
1886	for sub in subs.read().iter() {
1887		// Skip dropped subscribers, matching `combined_subscription`.
1888		if sub.is_closed() {
1889			continue;
1890		}
1891		if let Poll::Ready(merged) = sub.read().poll_combined(&combined) {
1892			combined = Some(merged);
1893		}
1894	}
1895	clamp_combined(combined, bound)
1896}
1897
1898/// The highest sequence this subscriber could actually be handed: its read cursor's cap
1899/// ([`Subscriber::set_groups`]) and any cap imposed from outside, whichever is lower.
1900///
1901/// Deliberately not [`Subscription::end`]. That is a *request to the publisher*, folded
1902/// in with every other subscriber's, and it does not filter this handle (see
1903/// [`Subscriber`]): another unbounded subscriber widens the aggregate and the groups
1904/// arrive here anyway. Capping the drift anchor with it would pin the live edge at the
1905/// requested end while delivery ran past it, and everything above would then have nothing
1906/// newer to be late against.
1907///
1908/// `outer` is what a reader wrapping this cursor imposes. A spliced segment is
1909/// deliberately not given an inner cursor cap (it would park boundary-crossing groups out
1910/// of sight), so its reader passes down its own cap and the segment boundary that way.
1911/// Without it, a segment would anchor drift on groups it can never hand over.
1912fn servable_cap(cursor: Option<u64>, outer: Option<u64>) -> Option<u64> {
1913	super::subscription::min_some(cursor, outer)
1914}
1915
1916/// The read cursor's floor: the group the subscription named, or 0 (no floor).
1917///
1918/// A floor is the only thing a start contributes; [`Subscription::max_age`] is what asks
1919/// for data. Delivery walks everything at or above the floor and skips what the budget
1920/// convicts, so a zero budget (the default) delivers only the live edge, a larger one
1921/// reaches back over what it can still use, and a floor above the live edge simply waits
1922/// there (a resumed subscription naming where it left off). One bound decides both what
1923/// is sent and what is expired, so the two cannot disagree.
1924fn floor_of(subscription: &Subscription) -> u64 {
1925	subscription.start.map(|start| start.group).unwrap_or(0)
1926}
1927
1928/// Clamp a drift budget to the publisher's retention window: nobody can wait for a late
1929/// group longer than the publisher keeps it around.
1930///
1931/// The single clamp point. Subscribers hold their preferences verbatim, so what they asked
1932/// for stays readable, and it is applied here on both sides of the aggregate: to the
1933/// combined request the publisher sees ([`clamp_combined`]) and to one subscriber's own
1934/// budget when it decides a group is stale ([`TrackState::is_stale`]). Those agree because
1935/// `min` distributes over the `max` that combines them. `bound` is `None` on a track whose
1936/// info isn't known yet (an unaccepted [`Request`]), which imposes no window.
1937fn clamp_max_age(mut max_age: Duration, bound: Option<Duration>) -> Duration {
1938	if let Some(bound) = bound {
1939		max_age = max_age.min(bound);
1940	}
1941	max_age
1942}
1943
1944/// Clamp the aggregate's max age budget to the publisher's window; see [`clamp_max_age`].
1945fn clamp_combined(combined: Option<Subscription>, bound: Option<Duration>) -> Option<Subscription> {
1946	let mut combined = combined?;
1947	combined.max_age = clamp_max_age(combined.max_age, bound);
1948	Some(combined)
1949}
1950
1951/// Register a subscription if the track is live: clone the shared list out of the
1952/// state, release the track lock, then push under the list's own lock. A closed
1953/// track skips the push; nothing aggregates the preferences anymore.
1954fn register_subscription(state: kio::Ref<'_, TrackState>, subscription: &kio::Producer<Subscription>) {
1955	if state.is_closed() {
1956		return;
1957	}
1958	let subs = state.subscriptions.clone();
1959	drop(state);
1960	subs.lock().push(subscription.consume());
1961}
1962
1963/// A weak reference to a track that doesn't prevent auto-close.
1964#[derive(Clone)]
1965pub(crate) struct TrackWeak {
1966	name: Arc<str>,
1967	state: kio::ProducerWeak<TrackState>,
1968}
1969
1970impl TrackWeak {
1971	/// A [`Consumer`] for the cached track, or `None` once it has closed.
1972	///
1973	/// The count moves under the same lock the close takes, which is the other half
1974	/// of [`Producer::abort_unused`]: a lookup either gets a consumer in time to
1975	/// decline the teardown, or gets nothing and re-requests the track. It is never
1976	/// handed a track that is already on its way out.
1977	pub fn try_consume(&self) -> Option<Consumer> {
1978		Some(Consumer::plain(self.name.clone(), self.state.try_consume()?))
1979	}
1980
1981	/// The shared name handle, for use as a broadcast lookup key (clone is a
1982	/// refcount bump, and the same `Arc` is shared with the track's handles).
1983	pub(crate) fn name(&self) -> &Arc<str> {
1984		&self.name
1985	}
1986
1987	/// Reject a track nothing ever served, resolving its pending subscribes with `err`.
1988	///
1989	/// A track whose [`Producer`] was minted is left alone and this returns false;
1990	/// so is one that already carries an abort reason. Fetched backfill can install
1991	/// [`Info`] before acceptance, so metadata alone does not prove a publisher exists.
1992	///
1993	/// Closes the state like [`Producer::abort`], so a [`Request`] still held by the
1994	/// publisher can't `accept` its way back to life afterwards.
1995	pub(crate) fn reject(&self, err: Error) -> bool {
1996		let Some(producer) = self.state.produce() else {
1997			return false;
1998		};
1999		let Ok(mut state) = producer.write() else {
2000			return false;
2001		};
2002		if state.published || state.abort.is_some() {
2003			return false;
2004		}
2005		state.abort = Some(err);
2006		state.close();
2007		true
2008	}
2009
2010	/// Whether anyone is consuming the track right now. A closed track doesn't
2011	/// count even if consumers linger to drain its cache: no new work is owed.
2012	pub(crate) fn is_used(&self) -> bool {
2013		!self.state.is_closed() && self.state.is_used()
2014	}
2015
2016	/// Park `waiter` for the next consumer appearing; a no-op once one exists.
2017	/// Feeds [`crate::broadcast::Demand`], which recomputes on wake.
2018	pub(crate) fn poll_used(&self, waiter: &kio::Waiter) {
2019		let _ = self.state.poll_used(waiter);
2020	}
2021
2022	/// Park `waiter` for the last consumer (or the track) going away; a no-op
2023	/// once none remain. Feeds [`crate::broadcast::Demand`].
2024	pub(crate) fn poll_unused(&self, waiter: &kio::Waiter) {
2025		let _ = self.state.poll_unused(waiter);
2026	}
2027}
2028
2029impl super::WeakEntry for TrackWeak {
2030	fn is_closed(&self) -> bool {
2031		self.state.is_closed()
2032	}
2033
2034	fn same_channel(&self, other: &Self) -> bool {
2035		self.state.same_channel(&other.state)
2036	}
2037}
2038
2039/// A cloneable, watch-only handle to a track's subscriber demand.
2040///
2041/// Obtained from [`Producer::demand`]. A publisher uses it to react to
2042/// whether anyone is subscribed (on-demand capture / encoding) without being able
2043/// to publish frames or close the track. It's a weak handle, so it neither keeps
2044/// the track alive nor pins its cached groups; once the owning [`Producer`]
2045/// goes away, [`used`](Self::used) / [`unused`](Self::unused) report the track's
2046/// closure.
2047#[derive(Clone)]
2048pub struct Demand {
2049	name: Arc<str>,
2050	state: kio::ProducerWeak<TrackState>,
2051}
2052
2053impl Demand {
2054	/// The track name this handle is bound to.
2055	pub fn name(&self) -> &str {
2056		&self.name
2057	}
2058
2059	/// Block until there is at least one active consumer.
2060	pub async fn used(&self) -> Result<()> {
2061		self.state.used().await.map_err(|_| self.abort_reason())
2062	}
2063
2064	/// Block until there are no active consumers.
2065	pub async fn unused(&self) -> Result<()> {
2066		self.state.unused().await.map_err(|_| self.abort_reason())
2067	}
2068
2069	/// Block until the track is closed or aborted, returning the cause.
2070	pub async fn closed(&self) -> Error {
2071		self.state.closed().await;
2072		self.abort_reason()
2073	}
2074
2075	/// The publisher's tie-break priority, as set in [`Info::priority`].
2076	pub(crate) fn priority(&self) -> u8 {
2077		// Always Some once the track exists; a closed one reads its last value.
2078		self.state.read().info.as_ref().map_or(0, |info| info.priority)
2079	}
2080
2081	/// Whether anyone is subscribed right now, without waiting.
2082	pub fn is_used(&self) -> bool {
2083		self.state.is_used()
2084	}
2085
2086	/// Poll-based variant of [`Self::used`].
2087	pub fn poll_used(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
2088		self.state.poll_used(waiter).map(|used| match used {
2089			Some(()) => Ok(()),
2090			None => Err(self.abort_reason()),
2091		})
2092	}
2093
2094	/// Poll-based variant of [`Self::unused`].
2095	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
2096		self.state.poll_unused(waiter).map(|used| match used {
2097			Some(()) => Ok(()),
2098			None => Err(self.abort_reason()),
2099		})
2100	}
2101
2102	/// Whether the track is gone, without waiting.
2103	pub(crate) fn is_closed(&self) -> bool {
2104		self.state.is_closed()
2105	}
2106
2107	/// Read the current demand and arm `waiter` for the next transition.
2108	///
2109	/// Level-triggered, unlike [`used`](Self::used) / [`unused`](Self::unused): it
2110	/// answers what the demand is *now* and wakes on whichever way it can move
2111	/// next, so a poll-driven caller watching several tracks at once doesn't have
2112	/// to track which edge it's waiting for.
2113	pub(crate) fn poll_state(&self, waiter: &kio::Waiter) -> DemandState {
2114		loop {
2115			match self.state.poll_used(waiter) {
2116				Poll::Ready(None) => return DemandState::Closed,
2117				// Not used, and armed for it becoming used.
2118				Poll::Pending => return DemandState::Idle,
2119				// Used, so arm for the reverse.
2120				Poll::Ready(Some(())) => match self.state.poll_unused(waiter) {
2121					Poll::Ready(None) => return DemandState::Closed,
2122					Poll::Pending => return DemandState::Active,
2123					// Went idle between the two reads, so neither poll armed anything.
2124					// Start over rather than returning a state with no waker behind it.
2125					Poll::Ready(Some(())) => continue,
2126				},
2127			}
2128		}
2129	}
2130
2131	/// The recorded abort reason, or [`Error::Dropped`] if the track closed without one.
2132	fn abort_reason(&self) -> Error {
2133		self.state.read().abort.clone().unwrap_or(Error::Dropped)
2134	}
2135}
2136
2137/// What [`Demand::poll_state`] found.
2138#[derive(Copy, Clone, Debug, Eq, PartialEq)]
2139pub(crate) enum DemandState {
2140	/// At least one subscriber.
2141	Active,
2142	/// No subscribers, but the track is still open.
2143	Idle,
2144	/// The track is gone; it will never be demanded again.
2145	Closed,
2146}
2147
2148/// A handle to a single track within a broadcast.
2149///
2150/// Obtained from [`broadcast::Consumer::track`]. Holding it sends nothing
2151/// to the publisher; it just names a track you can [`subscribe`](Self::subscribe)
2152/// to (a live, ongoing stream of groups) later. The same handle can be subscribed
2153/// to multiple times, and clones are cheap.
2154///
2155/// A track reached through a route-fed broadcast is *spliced*: it is backed by one
2156/// or more per-session tracks joined at group boundaries, and this handle reads
2157/// across them transparently.
2158#[derive(Clone)]
2159pub struct Consumer {
2160	name: Arc<str>,
2161	// The broadcast this track belongs to, so a catalog track can name the path its
2162	// relative references resolve against. Rebound by `broadcast::Consumer::track` to
2163	// that handle's view, which may name the broadcast differently than its producer did.
2164	broadcast: Arc<broadcast::Info>,
2165	inner: ConsumerKind,
2166	// Egress stats scope, set by a tagged [`broadcast::Consumer`] via
2167	// [`Self::with_stats`]. Empty (no-op) for an untagged track.
2168	stats: stats::Scope,
2169}
2170
2171#[derive(Clone)]
2172enum ConsumerKind {
2173	Plain(kio::Consumer<TrackState>),
2174	Spliced(super::resume::Consumer),
2175}
2176
2177impl Consumer {
2178	fn plain(name: Arc<str>, state: kio::Consumer<TrackState>) -> Self {
2179		let broadcast = state.read().broadcast.clone();
2180		Self {
2181			name,
2182			broadcast,
2183			inner: ConsumerKind::Plain(state),
2184			stats: stats::Scope::default(),
2185		}
2186	}
2187
2188	/// A consumer over a spliced logical track (a route-fed broadcast's track).
2189	pub(crate) fn spliced(name: Arc<str>, broadcast: Arc<broadcast::Info>, resume: super::resume::Consumer) -> Self {
2190		Self {
2191			name,
2192			broadcast,
2193			inner: ConsumerKind::Spliced(resume),
2194			stats: stats::Scope::default(),
2195		}
2196	}
2197
2198	/// Attach an egress stats scope, inherited by the subscriptions, fetches, and
2199	/// groups derived from this handle. Called by a tagged [`broadcast::Consumer`].
2200	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
2201		self.stats = scope;
2202		self
2203	}
2204
2205	/// Rebind the track to the broadcast handle it was reached through, so
2206	/// [`broadcast`](Self::broadcast) reports the path that handle was handed out at (see
2207	/// [`broadcast::Info::path`]). Called by [`broadcast::Consumer::track`].
2208	pub(crate) fn with_broadcast(mut self, broadcast: Arc<broadcast::Info>) -> Self {
2209		self.broadcast = broadcast;
2210		self
2211	}
2212
2213	/// Groups this copy still holds, so an origin can keep them after dropping the source.
2214	///
2215	/// Publisher-produced groups come back in arrival order, matching what
2216	/// `recv_group` would deliver; fetched backfill absent from arrival follows
2217	/// in sequence order for sequence fetches.
2218	pub(crate) fn cached_groups(&self) -> Vec<(group::Producer, bool)> {
2219		match &self.inner {
2220			ConsumerKind::Plain(state) => {
2221				let state = state.read();
2222				let mut out = Vec::with_capacity(state.lookup.len());
2223				for (sequence, stamp) in state.arrival.iter() {
2224					if let Some(slot) = state.lookup.get(sequence)
2225						&& slot.stamp == *stamp
2226						&& !slot.group.is_aborted()
2227					{
2228						out.push((slot.group.clone(), slot.visible));
2229					}
2230				}
2231				// Fetched backfill never enters arrival; keep it for sequence fetches.
2232				let mut copied: HashSet<u64> = out.iter().map(|(group, _)| group.sequence).collect();
2233				for (sequence, slot) in state.lookup.iter() {
2234					if !slot.group.is_aborted() && copied.insert(*sequence) {
2235						out.push((slot.group.clone(), slot.visible));
2236					}
2237				}
2238				out
2239			}
2240			ConsumerKind::Spliced(resume) => resume.cached_groups(),
2241		}
2242	}
2243
2244	/// A cached group that contains every frame from `frame_start` onward.
2245	///
2246	/// Unlike [`Self::peek_group`], this is suitable for satisfying a FETCH: a
2247	/// partial cached copy is a miss so the caller can ask upstream for its head.
2248	pub(crate) fn cached_group(&self, sequence: u64, frame_start: u64) -> Option<group::Consumer> {
2249		match &self.inner {
2250			ConsumerKind::Plain(state) => {
2251				let state = state.read();
2252				let group = state.covering_group(sequence, frame_start)?;
2253				group.cache_refresh();
2254				Some(group.consume())
2255			}
2256			ConsumerKind::Spliced(resume) => resume.cached_group(sequence, frame_start),
2257		}
2258	}
2259
2260	/// Publisher properties already resolved on this copy, if any.
2261	pub(crate) fn cached_info(&self) -> Option<Info> {
2262		match &self.inner {
2263			ConsumerKind::Plain(state) => state.read().info.clone(),
2264			ConsumerKind::Spliced(resume) => resume.cached_info(),
2265		}
2266	}
2267
2268	/// The track name this handle is bound to.
2269	pub fn name(&self) -> &str {
2270		&self.name
2271	}
2272
2273	/// The broadcast this track belongs to, as reached through the handle it came from.
2274	/// Its [`path`](broadcast::Info::path) is what a catalog's relative `broadcast`
2275	/// references resolve against.
2276	pub fn broadcast(&self) -> &broadcast::Info {
2277		&self.broadcast
2278	}
2279
2280	/// Open a live subscription.
2281	///
2282	/// Registers the subscription on the track and returns a [`kio::Pending`] that resolves to the
2283	/// [`Subscriber`] once the track info is available, or the track's abort error (or
2284	/// [`Error::Dropped`]) if it is already closed.
2285	///
2286	/// The read cursor starts at the group the subscription named (its floor), or 0.
2287	/// [`Subscription::max_age`] is what asks for data: delivery skips everything above
2288	/// the floor that the budget convicts, so the default budget of zero delivers only
2289	/// the latest group and a larger one reaches back over what it can still use.
2290	pub fn subscribe(&self, subscription: impl Into<Option<Subscription>>) -> kio::Pending<Subscribing> {
2291		let subscription = kio::Producer::new(subscription.into().unwrap_or_default());
2292
2293		let inner = match &self.inner {
2294			ConsumerKind::Plain(state) => {
2295				// Register the subscription if the track is live. If it is already closed, the
2296				// returned future resolves to the abort error via `Subscribing::poll_ok`.
2297				register_subscription(state.read(), &subscription);
2298				SubscribingKind::Plain(state.clone())
2299			}
2300			// A spliced subscription registers per segment once the subscriber polls.
2301			ConsumerKind::Spliced(resume) => SubscribingKind::Spliced(resume.clone()),
2302		};
2303
2304		kio::Pending::new(Subscribing {
2305			name: self.name.clone(),
2306			broadcast: self.broadcast.clone(),
2307			inner,
2308			subscription,
2309			stats: self.stats.clone(),
2310		})
2311	}
2312
2313	/// The newest group, when it is already cached: resolved synchronously, without
2314	/// counting as a fetch or a delivery. The IETF publisher snapshots its frame count to
2315	/// resolve Largest Object; a group that is not immediately available reads as no edge.
2316	pub(crate) fn peek_latest(&self) -> Option<group::Consumer> {
2317		match &self.inner {
2318			ConsumerKind::Plain(state) => {
2319				let sequence = state.read().max_sequence?;
2320				self.peek_group(sequence)
2321			}
2322			ConsumerKind::Spliced(resume) => resume.peek_latest(),
2323		}
2324	}
2325
2326	/// The nearest cached group below `sequence`, under the same terms as
2327	/// [`Self::peek_group`]. Walks the cache's own order, so gaps in the group numbering
2328	/// are crossed and aborted (evicted) entries are skipped.
2329	pub(crate) fn peek_before(&self, sequence: u64) -> Option<group::Consumer> {
2330		match &self.inner {
2331			ConsumerKind::Plain(state) => {
2332				let state = state.read();
2333				state
2334					.lookup
2335					.range(..sequence)
2336					.rev()
2337					.map(|(_, slot)| &slot.group)
2338					.find(|group| !group.is_aborted())
2339					.map(|group| group.consume())
2340			}
2341			ConsumerKind::Spliced(resume) => resume.peek_before(sequence),
2342		}
2343	}
2344
2345	/// A cached group by sequence, under the same terms as [`Self::peek_latest`]. Unlike a
2346	/// fetch, a peek does not refresh the group's cache standing, so it never keeps a
2347	/// group alive over one a subscriber actually read; an aborted (evicted) group is a
2348	/// miss.
2349	pub(crate) fn peek_group(&self, sequence: u64) -> Option<group::Consumer> {
2350		match &self.inner {
2351			ConsumerKind::Plain(state) => {
2352				let state = state.read();
2353				let slot = state.lookup.get(&sequence)?;
2354				if slot.group.is_aborted() {
2355					return None;
2356				}
2357				Some(slot.group.consume())
2358			}
2359			ConsumerKind::Spliced(resume) => resume.peek_group(sequence),
2360		}
2361	}
2362
2363	/// Attach a subscription's drift policy to a cached group resolved outside its cursor.
2364	pub(crate) fn guard_group(
2365		&self,
2366		group: group::Consumer,
2367		subscription: kio::Consumer<Subscription>,
2368		cap: kio::Consumer<Option<u64>>,
2369		bound: Option<u64>,
2370	) -> group::Consumer {
2371		let ConsumerKind::Plain(state) = &self.inner else {
2372			return group;
2373		};
2374		let sequence = group.sequence;
2375		group.with_expiry(Arc::new(GroupExpiry {
2376			state: state.weak(),
2377			subscription,
2378			cap,
2379			bound,
2380			sequence,
2381		}))
2382	}
2383
2384	/// Poll for a cached group by sequence, parking `waiter` until it lands.
2385	///
2386	/// `Ready(None)` once it can never arrive: the track ended below the sequence, or
2387	/// closed. Unlike [`Self::fetch_group`] this never asks anyone to produce the group,
2388	/// so it only resolves for one a live subscription is already pulling. That is what
2389	/// the spliced reader in [`super::resume`] wants: it waits for the route serving a
2390	/// group to deliver it, rather than opening a redundant fetch alongside.
2391	pub(crate) fn poll_peek_group(&self, sequence: u64, waiter: &kio::Waiter) -> Poll<Option<group::Consumer>> {
2392		let ConsumerKind::Plain(state) = &self.inner else {
2393			// A segment's track is never itself spliced.
2394			return Poll::Pending;
2395		};
2396
2397		let res = state.poll(waiter, |state| {
2398			match state.lookup.get(&sequence) {
2399				Some(slot) if !slot.group.is_aborted() => Poll::Ready(Some(slot.group.consume())),
2400				// An aborted slot is a hole this route can no longer fill.
2401				Some(_) => Poll::Ready(None),
2402				// Past the declared end, so it can never exist.
2403				None if state.final_sequence.is_some_and(|fin| sequence >= fin) => Poll::Ready(None),
2404				// Below the declared start, so the live feed skipped it for good.
2405				None if state.start_sequence.is_some_and(|start| sequence < start) => Poll::Ready(None),
2406				None => Poll::Pending,
2407			}
2408		});
2409
2410		match res {
2411			Poll::Ready(Ok(res)) => Poll::Ready(res),
2412			// The track died; whatever it cached went with it.
2413			Poll::Ready(Err(_)) => Poll::Ready(None),
2414			Poll::Pending => Poll::Pending,
2415		}
2416	}
2417
2418	/// Poll for a live cached copy of `sequence` that can serve frame `index`,
2419	/// parking until one exists.
2420	///
2421	/// Unlike [`Self::poll_peek_group`] this never gives a verdict: a missing
2422	/// group parks (registered for its arrival) even below the declared start,
2423	/// since demand may move backward and revive it. The spliced reader uses it
2424	/// to reconsider a route it gave up on, so it must be exact about what
2425	/// "available" means: a copy that cannot start at `index` (its head is gone)
2426	/// leaves the route buried rather than reviving it into a peek that would
2427	/// bury it again. That exactness is load-bearing, since the reader consults
2428	/// this ahead of its terminal checks: relaxing it to "the group exists" makes
2429	/// revive and re-bury alternate forever inside one poll
2430	/// (`resume::test::misaligned_copy_is_lost_without_spinning`, where the
2431	/// regression surfaces as a hang).
2432	pub(crate) fn poll_serving_group(&self, sequence: u64, index: u64, waiter: &kio::Waiter) -> Poll<()> {
2433		let ConsumerKind::Plain(state) = &self.inner else {
2434			// A segment's track is never itself spliced.
2435			return Poll::Pending;
2436		};
2437		let res = state.poll(waiter, |state| match state.lookup.get(&sequence) {
2438			Some(slot) if !slot.group.is_aborted() => {
2439				// `start_at` clamps up, so landing higher means the copy no longer
2440				// holds this position.
2441				let mut group = slot.group.consume();
2442				group.start_at(index);
2443				match group.index() == index {
2444					true => Poll::Ready(()),
2445					false => Poll::Pending,
2446				}
2447			}
2448			_ => Poll::Pending,
2449		});
2450		match res {
2451			Poll::Ready(Ok(())) => Poll::Ready(()),
2452			// The track died; whatever would arrive never will, and the caller's
2453			// terminal checks settle the wait.
2454			Poll::Ready(Err(_)) | Poll::Pending => Poll::Pending,
2455		}
2456	}
2457
2458	/// Fetching a single past group, without holding a live subscription.
2459	///
2460	/// Returns a [`kio::Pending`] that resolves to the [`group::Consumer`]:
2461	/// immediately if the group is cached, otherwise once a [`Dynamic`] serves
2462	/// the request (a wire FETCH for a relay). `options` accepts `None`, a [`group::Fetch`],
2463	/// or `group::Fetch::default()`.
2464	///
2465	/// The returned future resolves to [`Error::NotFound`] when the group can never be served
2466	/// (past the final sequence, or no [`Dynamic`] on the track), or the track's abort error
2467	/// if it's already closed. Concurrent fetches for the same sequence coalesce onto one
2468	/// handler request.
2469	pub fn fetch_group(&self, sequence: u64, options: impl Into<Option<group::Fetch>>) -> kio::Pending<Fetching> {
2470		let options = options.into().unwrap_or_default();
2471
2472		// One fetch per calling context, counted here (coalesced upstream work is
2473		// still one request served). Independent of `subscriptions` and the viewer
2474		// refcount.
2475		self.stats.fetch();
2476
2477		let state = match &self.inner {
2478			ConsumerKind::Plain(state) => state,
2479			// Spliced: routed to the newest segment's (plain) track, waiting for a
2480			// segment to exist if no route has served the track yet.
2481			ConsumerKind::Spliced(resume) => {
2482				return kio::Pending::new(Fetching {
2483					inner: FetchingKind::Spliced(resume.fetch_group(sequence, options)),
2484					stats: self.stats.clone(),
2485				});
2486			}
2487		};
2488
2489		let mut result = None;
2490
2491		// Queue a request only when the group isn't already resolvable from the track
2492		// (cached, aborted, or past-final all resolve through `Fetching::poll` without
2493		// a queue entry).
2494		let (fetch, unresolved) = {
2495			let state = state.read();
2496			(
2497				state.fetch.clone(),
2498				state.poll_fetch_cached(sequence, options.frame_start).is_pending(),
2499			)
2500		};
2501
2502		if unresolved {
2503			let mut fetch = fetch.lock();
2504			if let Some(pending) = fetch.join(&sequence) {
2505				// Join the in-flight attempt for this sequence (queued or already being
2506				// served): share its result channel, raising its priority if ours is higher
2507				// and widening its range if ours starts earlier.
2508				//
2509				// Widening only reaches the handler while the attempt is still queued;
2510				// once popped, the `group::Request` holds an immutable copy and its range is
2511				// already on the wire. What protects the late caller either way is the
2512				// coverage check above: it refuses a group starting above what it asked
2513				// for, so it fails cleanly and its retry queues a fresh attempt.
2514				pending.priority = pending.priority.max(options.priority);
2515				pending.frame_start = pending.frame_start.min(options.frame_start);
2516				result = Some(pending.result.consume());
2517			} else {
2518				// Queue a new attempt. The handler gate is atomic with a handler
2519				// dropping (no fetch stranded on a queue nobody drains); with no
2520				// handler, `Fetching::poll` fails fast instead.
2521				let producer = kio::Producer::<FetchOutcome>::default();
2522				let consumer = producer.consume();
2523				let attempt = PendingFetch {
2524					priority: options.priority,
2525					frame_start: options.frame_start,
2526					result: producer,
2527				};
2528				if fetch.insert(sequence, attempt).is_ok() {
2529					result = Some(consumer);
2530				}
2531			}
2532		}
2533
2534		kio::Pending::new(Fetching {
2535			inner: FetchingKind::Plain {
2536				state: state.clone(),
2537				fetch,
2538				sequence,
2539				frame_start: options.frame_start,
2540				result,
2541			},
2542			stats: self.stats.clone(),
2543		})
2544	}
2545
2546	/// Resolve the track's [`Info`] without subscribing.
2547	///
2548	/// A [`Consumer`] is a lazy handle, so the info may not be known yet: this waits
2549	/// for the producer to [`Request::accept`] the track (a wire TRACK_INFO round-trip
2550	/// for a relay), and errors with the track's abort error if it closes first.
2551	/// [`Subscriber::info`] is the already-resolved counterpart.
2552	pub fn query(&self) -> kio::Pending<Querying> {
2553		kio::Pending::new(Querying {
2554			inner: match &self.inner {
2555				ConsumerKind::Plain(state) => QueryingKind::Plain(state.clone()),
2556				ConsumerKind::Spliced(resume) => QueryingKind::Spliced(resume.clone()),
2557			},
2558		})
2559	}
2560
2561	/// Return the latest group sequence in the track, or `None` before any group.
2562	pub fn latest(&self) -> Option<u64> {
2563		match &self.inner {
2564			ConsumerKind::Plain(state) => state.read().max_sequence,
2565			ConsumerKind::Spliced(resume) => resume.latest(),
2566		}
2567	}
2568
2569	/// The frame-precise point a replacement route should resume from: one past the
2570	/// last frame this copy produced. `None` if it produced nothing.
2571	///
2572	/// Survives the track aborting, which is when a route change asks.
2573	pub(crate) fn resume_position(&self) -> Option<Position> {
2574		match &self.inner {
2575			ConsumerKind::Plain(state) => state.read().resume_position(),
2576			ConsumerKind::Spliced(resume) => resume.resume_position(),
2577		}
2578	}
2579
2580	/// Poll for the track reaching a terminal state: `Ok(())` once it is complete
2581	/// (the final group was produced), `Err` once it closed or aborted before
2582	/// completing. The origin's dispatcher uses this to tell a track that truly
2583	/// ended from one whose serving route died mid-stream.
2584	pub(crate) fn poll_complete(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
2585		let ConsumerKind::Plain(state) = &self.inner else {
2586			// Spliced tracks are compositions; the dispatcher never monitors one.
2587			return Poll::Pending;
2588		};
2589		match ready!(state.poll(waiter, |state| {
2590			if state.is_complete() {
2591				Poll::Ready(())
2592			} else {
2593				Poll::Pending
2594			}
2595		})) {
2596			Ok(_) => Poll::Ready(Ok(())),
2597			// Closed before completing. Read through the returned guard: it holds
2598			// the lock, so re-locking the channel here would deadlock.
2599			Err(closed) => Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped))),
2600		}
2601	}
2602}
2603
2604/// The pollable state of a [`Consumer::subscribe`]; awaited via the
2605/// [`kio::Pending`] wrapper, whose `DerefMut` exposes [`Self::update`].
2606pub struct Subscribing {
2607	name: Arc<str>,
2608	broadcast: Arc<broadcast::Info>,
2609	inner: SubscribingKind,
2610	subscription: kio::Producer<Subscription>,
2611	stats: stats::Scope,
2612}
2613
2614enum SubscribingKind {
2615	Plain(kio::Consumer<TrackState>),
2616	Spliced(super::resume::Consumer),
2617}
2618
2619impl Subscribing {
2620	/// Poll until the peer confirms the subscription, yielding the [`Subscriber`].
2621	/// Errors if the track is aborted or not found.
2622	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<Subscriber>> {
2623		match &self.inner {
2624			SubscribingKind::Plain(state) => {
2625				// Wait until the track info is available
2626				let info = ready!(state.poll(waiter, |state| state.poll_info()))
2627					.map_err(|e| e.abort.clone().unwrap_or(Error::Dropped))??;
2628
2629				let drift_cap = kio::Producer::new(None);
2630				let min_sequence = floor_of(&self.subscription.read());
2631				Poll::Ready(Ok(Subscriber {
2632					name: self.name.clone(),
2633					broadcast: self.broadcast.clone(),
2634					info,
2635					inner: SubscriberKind::Plain(PlainSubscriber {
2636						state: state.clone(),
2637						subscription: self.subscription.clone(),
2638						min_sequence,
2639						index: 0,
2640						datagram_index: 0,
2641						next_sequence: 0,
2642						end_sequence: None,
2643						parked: BTreeMap::new(),
2644						stale_cap: None,
2645						drift_cap,
2646						stale: stats::Content::default(),
2647						seek_pending: BTreeMap::new(),
2648					}),
2649					stats: self.stats.clone(),
2650					_stats_sub: self.stats.subscribe(),
2651				}))
2652			}
2653			SubscribingKind::Spliced(resume) => {
2654				// Resolved from the first segment's track. The publisher's max age
2655				// window is applied to each per-session aggregate, not here.
2656				let info = ready!(resume.poll_info(waiter))?;
2657
2658				Poll::Ready(Ok(Subscriber {
2659					name: self.name.clone(),
2660					broadcast: self.broadcast.clone(),
2661					info,
2662					inner: SubscriberKind::Spliced(Box::new(resume.subscribe_shared(self.subscription.clone()))),
2663					stats: self.stats.clone(),
2664					_stats_sub: self.stats.subscribe(),
2665				}))
2666			}
2667		}
2668	}
2669
2670	/// Change the subscription preferences before (or after) it resolves.
2671	///
2672	/// Returns [`Error::Closed`] if the track already ended; the update is
2673	/// meaningless at that point and can usually be ignored.
2674	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
2675		let mut state = self.subscription.write().map_err(|_| Error::Closed)?;
2676		*state = subscription;
2677		Ok(())
2678	}
2679}
2680
2681impl kio::Pollable for Subscribing {
2682	type Output = Result<Subscriber>;
2683
2684	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
2685		self.poll_ok(waiter)
2686	}
2687}
2688
2689/// The pollable state of a [`Consumer::query`]; awaited via the
2690/// [`kio::Pending`] wrapper.
2691pub struct Querying {
2692	inner: QueryingKind,
2693}
2694
2695enum QueryingKind {
2696	Plain(kio::Consumer<TrackState>),
2697	Spliced(super::resume::Consumer),
2698}
2699
2700impl Querying {
2701	/// Poll until the track's [`Info`] is known, without subscribing to its groups.
2702	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<Info>> {
2703		match &self.inner {
2704			QueryingKind::Plain(state) => {
2705				// Wait until the track info is available
2706				let info = ready!(state.poll(waiter, |state| state.poll_info()))
2707					.map_err(|e| e.abort.clone().unwrap_or(Error::Dropped))??;
2708				Poll::Ready(Ok(info))
2709			}
2710			QueryingKind::Spliced(resume) => resume.poll_info(waiter),
2711		}
2712	}
2713}
2714
2715impl kio::Pollable for Querying {
2716	type Output = Result<Info>;
2717
2718	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
2719		self.poll_ok(waiter)
2720	}
2721}
2722
2723impl group::Request {
2724	/// The group sequence the consumer wants.
2725	pub fn sequence(&self) -> u64 {
2726		self.sequence
2727	}
2728
2729	/// The delivery priority the consumer requested for this group.
2730	pub fn priority(&self) -> u8 {
2731		self.priority
2732	}
2733
2734	/// The first frame of the group the consumer wants; 0 is the whole group.
2735	///
2736	/// A handler serving this must [`start_at`](group::Producer::start_at) it, so the frames
2737	/// it writes carry the indices they have in the group rather than restarting at 0.
2738	///
2739	/// There is no end: the handler fetches through the end of the group so the result
2740	/// is cacheable for anyone (see [`group::Fetch::frame_start`]).
2741	pub fn frame_start(&self) -> u64 {
2742		self.frame_start
2743	}
2744
2745	/// Insert the fetched group into the track cache, resolving the waiting
2746	/// [`Consumer::fetch_group`], and return a [`group::Producer`] to fill.
2747	///
2748	/// The group's timescale comes from the track's [`Info`]. `info` sets that
2749	/// info if the track hasn't been accepted yet (a fetch with no live subscription),
2750	/// and is ignored once accepted. Returns [`Error::Duplicate`] if the group is
2751	/// already present, or the track's abort error if it closed while pending.
2752	pub fn accept(mut self, info: impl Into<Option<Info>>) -> Result<group::Producer> {
2753		self.done = true;
2754		// Cache the group before removing the attempt: the joined fetches resolve
2755		// through the cache, and removal closes their result channel (which alone
2756		// would read as NotFound).
2757		let res = TrackState::modify(&self.state)
2758			.and_then(|mut state| state.insert_group_request(self.sequence, self.frame_start, info.into()));
2759		self.remove();
2760		res
2761	}
2762
2763	/// Reject the fetch, resolving every joined [`Consumer::fetch_group`] with `err`.
2764	pub fn reject(mut self, err: Error) {
2765		self.done = true;
2766		// Remove before writing, so a fetch arriving now starts a fresh attempt
2767		// instead of joining a rejected one.
2768		self.remove();
2769		if let Ok(mut outcome) = self.result.write() {
2770			outcome.rejected = Some(err);
2771		}
2772	}
2773
2774	/// Remove this attempt from the fetch state, unless a newer attempt for the same
2775	/// sequence has already replaced it.
2776	fn remove(&self) {
2777		self.fetch
2778			.lock()
2779			.remove_if(&self.sequence, |pending| pending.result.same_channel(&self.result));
2780	}
2781}
2782
2783impl Drop for group::Request {
2784	fn drop(&mut self) {
2785		if self.done {
2786			return;
2787		}
2788		self.remove();
2789		if let Ok(mut outcome) = self.result.write() {
2790			outcome.rejected = Some(Error::Dropped);
2791		}
2792	}
2793}
2794
2795/// The pollable state of a [`Consumer::fetch_group`].
2796///
2797/// Awaited via the [`kio::Pending`] wrapper; resolves to the
2798/// [`group::Consumer`] once the group lands in the track's cache (already present,
2799/// or produced after a wire FETCH), or [`Error::NotFound`] if it can never exist.
2800pub struct Fetching {
2801	inner: FetchingKind,
2802	// Egress stats scope, so the resolved group carries a payload meter (and counts
2803	// as one delivered group). Empty (no-op) for an untagged track.
2804	stats: stats::Scope,
2805}
2806
2807enum FetchingKind {
2808	Plain {
2809		state: kio::Consumer<TrackState>,
2810		fetch: kio::Shared<FetchState>,
2811		sequence: u64,
2812		// This caller's own start, so a cached group that begins above it is a miss
2813		// rather than a short answer.
2814		frame_start: u64,
2815		// The joined attempt's result channel; `None` when no handler existed to queue on.
2816		result: Option<kio::Consumer<FetchOutcome>>,
2817	},
2818	/// A spliced track's fetch: waits for a segment, then fetches from it.
2819	Spliced(kio::Pending<super::resume::Fetching>),
2820}
2821
2822impl kio::Pollable for Fetching {
2823	type Output = Result<group::Consumer>;
2824
2825	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
2826		let (state, fetch, sequence, frame_start, result) = match &self.inner {
2827			FetchingKind::Plain {
2828				state,
2829				fetch,
2830				sequence,
2831				frame_start,
2832				result,
2833			} => (state, fetch, *sequence, *frame_start, result.as_ref()),
2834			FetchingKind::Spliced(spliced) => {
2835				// A fetched group is metered here (once), at the tagged handle: the
2836				// spliced source track it comes from is the origin's own, untagged.
2837				return kio::Pollable::poll(&**spliced, waiter)
2838					.map(|res| res.map(|group| group.with_meter(self.stats.meter())));
2839			}
2840		};
2841
2842		// Track side: the cached group, the abort error, or past-final. The outer
2843		// error is the channel closing without any of those.
2844		match state.poll(waiter, |state| state.poll_fetch_cached(sequence, frame_start)) {
2845			Poll::Ready(Ok(res)) => {
2846				return Poll::Ready(res.map(|mut group| {
2847					// Hand back a consumer sitting where the caller asked rather than at
2848					// the group's own start. Coverage was checked above, so this can only
2849					// skip frames they excluded.
2850					group.start_at(frame_start);
2851					group.with_meter(self.stats.meter())
2852				}));
2853			}
2854			Poll::Ready(Err(closed)) => {
2855				return Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped)));
2856			}
2857			Poll::Pending => {}
2858		}
2859
2860		// Handler side.
2861		let Some(result) = result else {
2862			// Never queued: no handler existed when the fetch was made. Fail fast while
2863			// that's still true; a handler that appeared since may yet fill the cache.
2864			return match fetch.poll(waiter, |fetch| match fetch.has_handlers() {
2865				false => Poll::Ready(()),
2866				true => Poll::Pending,
2867			}) {
2868				Poll::Ready(_guard) => Poll::Ready(Err(Error::NotFound)),
2869				Poll::Pending => Poll::Pending,
2870			};
2871		};
2872
2873		// A written rejection fails every joined fetch. The channel closing without
2874		// one means the attempt was dropped unserved (its handlers went away).
2875		match result.poll(waiter, |outcome| match &outcome.rejected {
2876			Some(err) => Poll::Ready(err.clone()),
2877			None => Poll::Pending,
2878		}) {
2879			Poll::Ready(Ok(err)) => Poll::Ready(Err(err)),
2880			Poll::Ready(Err(_closed)) => Poll::Ready(Err(Error::NotFound)),
2881			Poll::Pending => Poll::Pending,
2882		}
2883	}
2884}
2885
2886/// A live subscription to a track, used to read its groups.
2887///
2888/// Created via [`Consumer::subscribe`](Consumer::subscribe), or
2889/// directly from a [`Producer`] for an in-process track. Carries this
2890/// subscriber's [`Subscription`] preferences, which feed the producer's aggregate.
2891///
2892/// # Local cursor vs wire preference
2893///
2894/// Group bounds exist at two levels, and setting one does not imply the other:
2895///
2896/// - [`Self::set_groups`] limits **this subscriber's reads**, filtering exactly what
2897///   this handle returns without changing the publisher's demand.
2898/// - [`Subscription::start`] / [`Subscription::end`], set via [`Self::update`],
2899///   are a **request to the publisher**. They're aggregated across every live subscriber
2900///   (earliest start, widest end), so they say what the publisher should send, not what
2901///   this subscriber sees.
2902///
2903/// They stay separate because their scopes differ: a subscriber can't filter by the
2904/// aggregate, since another subscriber can widen it, and the publisher can't honor a
2905/// cursor it's never told about. So setting only the cursor still transfers the skipped
2906/// groups, and setting only the preference still returns groups another subscriber asked
2907/// for. Set both to skip them *and* avoid the transfer.
2908///
2909/// The one place they meet is where the cursor comes from. A new subscriber's cursor is
2910/// floored at the group its own subscription named (or 0), and its
2911/// [`Subscription::max_age`] decides what above the floor is worth delivering. Every later
2912/// move is the caller's.
2913pub struct Subscriber {
2914	name: Arc<str>,
2915	// The broadcast this track belongs to; see [`Self::broadcast`].
2916	broadcast: Arc<broadcast::Info>,
2917	info: Info,
2918	inner: SubscriberKind,
2919	// Egress stats scope, used to meter the groups this subscriber reads. Empty
2920	// (no-op) for an untagged track.
2921	stats: stats::Scope,
2922	// The subscription guard: bumps `subscriptions` (and the egress viewer refcount)
2923	// while held, closing them on drop. Empty (no-op) for an untagged track.
2924	_stats_sub: stats::Subscription,
2925}
2926
2927enum SubscriberKind {
2928	Plain(PlainSubscriber),
2929	// Boxed: the spliced cursor set dwarfs the plain cursor.
2930	Spliced(Box<super::resume::Subscriber>),
2931}
2932
2933/// One poll's view of how far this subscription may drift: the clamped budget and the
2934/// live edge to measure a candidate group against. Resolved once, then applied to every
2935/// group that poll considers.
2936#[derive(Clone)]
2937struct Drift {
2938	budget: Duration,
2939	edge: Option<Edge>,
2940}
2941
2942/// Keeps one handed-out group tied to the subscription whose cursor selected it.
2943struct GroupExpiry {
2944	/// Weak so a handed-out group never joins the track's consumer count, which is what
2945	/// [`crate::broadcast::Demand`] reads to decide the track still has readers. A group
2946	/// outlives the cursor that produced it (a relay serves one for the life of its
2947	/// stream), and pinning demand on that would hold an upstream subscription open past
2948	/// the last real subscriber.
2949	state: kio::ConsumerWeak<TrackState>,
2950	subscription: kio::Consumer<Subscription>,
2951	cap: kio::Consumer<Option<u64>>,
2952	bound: Option<u64>,
2953	sequence: u64,
2954}
2955
2956impl group::Expiry for GroupExpiry {
2957	fn is_expired(&self, waiter: &kio::Waiter) -> bool {
2958		let mut max_age = Duration::default();
2959		let _ = self.subscription.poll(waiter, |subscription| {
2960			max_age = subscription.max_age;
2961			Poll::<()>::Pending
2962		});
2963
2964		let mut cap = None;
2965		let _ = self.cap.poll(waiter, |current| {
2966			cap = **current;
2967			Poll::<()>::Pending
2968		});
2969		let cap = super::subscription::min_some(cap, self.bound);
2970
2971		let mut expired = false;
2972		let _ = self.state.poll(waiter, |state| {
2973			let budget = clamp_max_age(max_age, state.max_age_bound());
2974			loop {
2975				let edge = state.live_edge(cap);
2976				expired = state.is_stale(self.sequence, edge.as_ref(), budget);
2977				if expired {
2978					break;
2979				}
2980
2981				// A first timestamp can change the verdict without mutating the track:
2982				// on a group past the edge (a new edge), or on the candidate's
2983				// unstamped immediate successor (a reach where there was none).
2984				// Register on the candidate and every unstamped servable group above
2985				// it. If one raced this scan, resolve the edge again before Pending.
2986				let mut timestamp_raced = false;
2987				if let Some(slot) = state.lookup.get(&self.sequence) {
2988					let group = &slot.group;
2989					if group.timestamp().is_none()
2990						&& group.poll_timestamp(waiter).is_ready()
2991						&& group.timestamp().is_some()
2992					{
2993						timestamp_raced = true;
2994					}
2995				}
2996				for (_, slot) in state
2997					.lookup
2998					.range((std::ops::Bound::Excluded(self.sequence), std::ops::Bound::Unbounded))
2999				{
3000					let group = &slot.group;
3001					if !super::subscription::before_end(group.sequence, cap) {
3002						break;
3003					}
3004					if slot.visible
3005						&& !group.is_aborted()
3006						&& group.timestamp().is_none()
3007						&& group.poll_timestamp(waiter).is_ready()
3008						&& group.timestamp().is_some()
3009					{
3010						timestamp_raced = true;
3011						break;
3012					}
3013				}
3014				if !timestamp_raced {
3015					break;
3016				}
3017			}
3018
3019			// Register on track changes even though the current answer is known: a
3020			// newer group can move either live edge while this group read is pending.
3021			Poll::<()>::Pending
3022		});
3023
3024		expired
3025	}
3026}
3027
3028/// The group a poll's drift is measured against, identified well enough to tell it apart
3029/// from whatever may occupy its sequence by the time a candidate is judged.
3030#[derive(Clone)]
3031struct Edge {
3032	presentation: PresentationEdge,
3033	/// The cap the edge was resolved under, so per-candidate reach lookups measure
3034	/// against the same servable window.
3035	cap: Option<u64>,
3036}
3037
3038/// The newest servable group that has presented at least one frame.
3039#[derive(Clone, Copy)]
3040struct PresentationEdge {
3041	sequence: u64,
3042	/// The slot incarnation this timestamp was read from, so an eviction or a re-served
3043	/// sequence between resolving the anchor and using it is detectable.
3044	stamp: u32,
3045	/// The newest frame this group has presented, not its first. The candidate side of the
3046	/// comparison is an upper bound on what a group could still reach, so the edge side has
3047	/// to be the newest content that actually exists, or the two meet and nothing is ever
3048	/// convicted. Only ever grows as the group fills.
3049	timestamp: Timestamp,
3050}
3051
3052/// The cursor state for a subscription over a single (per-session) track.
3053struct PlainSubscriber {
3054	state: kio::Consumer<TrackState>,
3055
3056	subscription: kio::Producer<Subscription>,
3057	/// Arrival-order cursor used by `recv_group`.
3058	index: usize,
3059	/// Arrival-order cursor used by `recv_datagram`, independent of groups.
3060	datagram_index: usize,
3061	/// Minimum sequence to return from any `recv` method. Set by `start_at`.
3062	min_sequence: u64,
3063	/// One past the highest sequence returned by `next_group`.
3064	/// Used only by that method to skip late arrivals; does not affect `recv_group`.
3065	next_sequence: u64,
3066	/// Exclusive upper sequence bound for `next_group` and `recv_group`, in the form
3067	/// [`Cap::exclusive`] produces. `None` means no cap. Set by `end_at`;
3068	/// can be raised, lowered, or unset at any time. Groups at or past the cap stay in
3069	/// the producer's cache and become eligible again when the cap rises (or is removed).
3070	/// `Some(0)` is the empty range.
3071	end_sequence: Option<u64>,
3072	/// Groups received beyond the [`Self::end_sequence`] cap, held for `recv_group`
3073	/// until the cap rises (arrival-order reads consume the shared cursor, so they
3074	/// are parked here instead of dropped). Keyed by sequence so the lowest is
3075	/// re-offered first.
3076	parked: BTreeMap<u64, group::Consumer>,
3077	/// A cap imposed by a reader wrapping this cursor (a [`super::resume::Subscriber`]
3078	/// segment), folded into the drift anchor only. Delivery is still bounded by
3079	/// `end_sequence`, which stays unset on a segment so its completion is visible.
3080	stale_cap: Option<u64>,
3081	/// Shared effective cap used by groups after this cursor hands them out.
3082	drift_cap: kio::Producer<Option<u64>>,
3083	/// Groups the drift budget skipped since the count was last drained. Accumulated
3084	/// here rather than metered in place because the handle that owns the stats scope
3085	/// is the outer [`Subscriber`], which may be reading this cursor through a
3086	/// [`super::resume::Subscriber`] segment (untagged, so the outer wrapper is the
3087	/// only place attribution happens once).
3088	stale: stats::Content,
3089	/// Groups the seek path has convicted but whose sequences no caller has committed
3090	/// past yet, keyed by sequence; see [`Self::commit_seek_stale`].
3091	seek_pending: BTreeMap<u64, stats::Content>,
3092}
3093
3094impl PlainSubscriber {
3095	fn update_drift_cap(&mut self) {
3096		if let Ok(mut cap) = self.drift_cap.write() {
3097			*cap = servable_cap(self.end_sequence, self.stale_cap);
3098		}
3099	}
3100
3101	// A helper to automatically apply Dropped if the state is closed without an error.
3102	fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
3103	where
3104		F: Fn(&kio::Ref<'_, TrackState>) -> Poll<Result<R>>,
3105	{
3106		Poll::Ready(match ready!(self.state.poll(waiter, f)) {
3107			Ok(res) => res,
3108			// We try to clone abort just in case the function forgot to check for terminal state.
3109			Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
3110		})
3111	}
3112
3113	/// Take the groups skipped since the last call, for the owner to meter.
3114	fn take_stale(&mut self) -> stats::Content {
3115		std::mem::take(&mut self.stale)
3116	}
3117
3118	/// Note a group an outer reader skipped on this cursor's behalf, so it lands in the
3119	/// same counter as the ones skipped here.
3120	fn note_stale(&mut self, group: &group::Consumer) {
3121		self.stale.add(group.content());
3122	}
3123
3124	/// This subscriber's clamped drift budget and the live edge to measure against,
3125	/// resolved once per poll.
3126	///
3127	/// `cap` bounds the anchor: the caller passes the same window it reads from, so a
3128	/// group is only ever judged against content that could actually be served in its
3129	/// place. Read fresh each poll, so a mid-stream [`Control::update`] applies
3130	/// to the very next group, and shared across every candidate that poll walks off, so
3131	/// discarding a backlog of N groups costs one scan rather than N. Only ever
3132	/// [`Poll::Ready`]; the track ending surfaces as the error the caller was going to
3133	/// get anyway.
3134	fn poll_drift(&self, cap: Option<u64>, waiter: &kio::Waiter) -> Poll<Result<Drift>> {
3135		let mut max_age = Duration::default();
3136		let _ = self.subscription.poll(waiter, |subscription| {
3137			max_age = subscription.max_age;
3138			Poll::<()>::Pending
3139		});
3140		self.poll(waiter, move |state| {
3141			Poll::Ready(Ok(Drift {
3142				budget: clamp_max_age(max_age, state.max_age_bound()),
3143				edge: state.live_edge(cap),
3144			}))
3145		})
3146	}
3147
3148	/// Whether the drift budget says to skip `group`, against a [`Drift`] already resolved
3149	/// for this poll.
3150	fn poll_stale(&self, group: &group::Consumer, drift: &Drift, waiter: &kio::Waiter) -> Poll<Result<bool>> {
3151		self.poll(waiter, move |state| {
3152			Poll::Ready(Ok(state.is_stale(group.sequence, drift.edge.as_ref(), drift.budget)))
3153		})
3154	}
3155
3156	fn with_expiry(&self, group: group::Consumer) -> group::Consumer {
3157		let sequence = group.sequence;
3158		group.with_expiry(Arc::new(GroupExpiry {
3159			state: self.state.weak(),
3160			subscription: self.subscription.consume(),
3161			cap: self.drift_cap.consume(),
3162			bound: None,
3163			sequence,
3164		}))
3165	}
3166
3167	fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
3168		// An eviction aborts a parked group without touching any cursor this
3169		// subscriber polls, so each entry needs a waiter or this poll would never
3170		// rerun. `poll_closed` observes-or-registers under one lock: `Pending`
3171		// parks the waiter while the group is open (an open group cannot be
3172		// aborted), and `Ready` means closed, where only an abort invalidates the
3173		// entry. Checking `is_aborted` separately from the registration would leave
3174		// a window where an abort lands between the two and wakes nobody.
3175		let watch = |group: &group::Consumer| match group.poll_closed(waiter) {
3176			Poll::Pending => true,
3177			Poll::Ready(()) => !group.is_aborted(),
3178		};
3179
3180		// A raised `start_at` drops parked groups it overtook, and eviction/expiry
3181		// (which aborts a cached group) drops its parked entry. The latter is what
3182		// bounds parking: a subscription capped indefinitely holds only what the
3183		// track's cache policy still retains, not every group it ever observed.
3184		let min_sequence = self.min_sequence;
3185		self.parked
3186			.retain(|sequence, group| *sequence >= min_sequence && watch(group));
3187
3188		// One scan for the whole poll, so walking a backlog off stays linear in its size.
3189		let drift = ready!(self.poll_drift(servable_cap(self.end_sequence, self.stale_cap), waiter))?;
3190
3191		loop {
3192			// Re-offer the lowest parked group back inside the cap once it rises,
3193			// ahead of the arrival cursor: it is the oldest thing still owed.
3194			let consumer = match self.parked.keys().next().copied() {
3195				Some(sequence) if super::subscription::before_end(sequence, self.end_sequence) => {
3196					let group = self.parked.remove(&sequence).expect("just looked it up");
3197					// A re-offer is a delivery: stamp it like a fresh hand-out.
3198					group.cache_refresh();
3199					group
3200				}
3201				_ => {
3202					let Some((producer, found_index)) =
3203						ready!(self.poll(waiter, |state| state.poll_recv_group(self.index, self.min_sequence))?)
3204					else {
3205						// Parked groups survive a finished track: they become deliverable
3206						// again if the cap rises, so the stream isn't over while any are held.
3207						if self.parked.is_empty() {
3208							return Poll::Ready(Ok(None));
3209						}
3210						return Poll::Pending;
3211					};
3212					let consumer = producer.consume();
3213					// Stamp with the track guard released, so delivery never nests the
3214					// group's state lock under the track's.
3215					consumer.cache_refresh();
3216					self.index = found_index + 1;
3217
3218					// Park a group beyond the cap instead of dropping it, and keep scanning
3219					// so an in-range group that arrived behind it still flows.
3220					if !super::subscription::before_end(consumer.sequence, self.end_sequence) {
3221						// Watch it from the moment it parks: the retain pass above already
3222						// ran, so an entry admitted here would otherwise sit unwatched for
3223						// the rest of this poll, and an abort could wake nobody.
3224						if watch(&consumer) {
3225							self.parked.insert(consumer.sequence, consumer);
3226						}
3227						continue;
3228					}
3229					consumer
3230				}
3231			};
3232
3233			// Drop a group the drift budget has given up on and keep scanning, so one
3234			// poll walks a whole backlog off rather than handing it out group by group.
3235			if ready!(self.poll_stale(&consumer, &drift, waiter))? {
3236				self.stale.add(consumer.content());
3237				continue;
3238			}
3239			return Poll::Ready(Ok(Some(self.with_expiry(consumer))));
3240		}
3241	}
3242
3243	fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
3244		let Some((datagram, found_index)) =
3245			ready!(self.poll(waiter, |state| state.poll_recv_datagram(self.datagram_index))?)
3246		else {
3247			return Poll::Ready(Ok(None));
3248		};
3249
3250		self.datagram_index = found_index + 1;
3251		Poll::Ready(Ok(Some(datagram)))
3252	}
3253
3254	fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
3255		let floor = self.next_sequence.max(self.min_sequence);
3256		let Some(group) = ready!(self.poll_seek_group(floor, self.end_sequence, waiter))? else {
3257			return Poll::Ready(Ok(None));
3258		};
3259		self.next_sequence = group.sequence.saturating_add(1);
3260		// The delivery commits everything the seek stepped over to reach this group.
3261		self.commit_seek_stale(self.next_sequence);
3262		// Delivery is a cache access, same as the arrival-order path.
3263		group.cache_refresh();
3264		Poll::Ready(Ok(Some(group)))
3265	}
3266
3267	/// Seek the lowest servable group in `floor..end` without advancing any cursor,
3268	/// walking off everything the drift budget has convicted on the way.
3269	///
3270	/// The drift anchor is built from the same window the seek reads (`end` folded into
3271	/// the cap), so a candidate is only judged against content that could be served in
3272	/// its place. A nested splice depends on this: its outer boundary arrives only as
3273	/// this call's `end`, and an anchor past it would convict groups the caller still
3274	/// owes its reader.
3275	///
3276	/// Repeated polls return the same group until the caller's own floor passes it,
3277	/// which is the point: the spliced sequence path picks the lowest candidate across
3278	/// segments and must not commit a segment past a group it has not delivered.
3279	fn poll_seek_group(
3280		&mut self,
3281		floor: u64,
3282		end: Option<u64>,
3283		waiter: &kio::Waiter,
3284	) -> Poll<Result<Option<group::Consumer>>> {
3285		let mut floor = floor.max(self.min_sequence);
3286		let end = super::subscription::min_some(end, self.end_sequence);
3287		// One scan for the whole poll, so walking a backlog off stays linear in its size.
3288		let drift = ready!(self.poll_drift(servable_cap(end, self.stale_cap), waiter))?;
3289
3290		loop {
3291			let Some(producer) = ready!(self.poll(waiter, |state| state.poll_next_in_range(floor, end))?) else {
3292				// Deliberately no flush of `seek_pending` here: only a delivery commit
3293				// may count a conviction. This `None` can be an artifact of a floor
3294				// that will lower again, and a mid-group boundary can serve a
3295				// convicted sequence through another segment even after this cursor
3296				// retires. A conviction never committed is dropped uncounted with the
3297				// cursor, the same tail bound a retired segment already has.
3298				return Poll::Ready(Ok(None));
3299			};
3300			let group = producer.consume();
3301
3302			// Skip a group the budget has given up on and keep scanning, so one poll
3303			// walks a whole backlog off rather than handing it out group by group.
3304			if ready!(self.poll_stale(&group, &drift, waiter))? {
3305				// Not counted yet: a seek advances no cursor, and a conviction is not
3306				// permanent (the budget can widen, the edge can be evicted), so the
3307				// group may still be delivered. Re-snapshot on every re-examination so
3308				// the eventual count reflects the group's latest observed content.
3309				self.seek_pending.insert(group.sequence, group.content());
3310				floor = group.sequence.saturating_add(1);
3311				continue;
3312			}
3313
3314			// A conviction the budget walked back: the group is handed over after all,
3315			// so it must never reach the stale count.
3316			self.seek_pending.remove(&group.sequence);
3317			return Poll::Ready(Ok(Some(self.with_expiry(group))));
3318		}
3319	}
3320
3321	/// Count the seek path's convictions below `committed` into [`Self::stale`],
3322	/// exactly once each.
3323	///
3324	/// A seek is speculative: the spliced path seeks every segment and delivers from
3325	/// one, so a conviction only becomes a real skip once a delivery commits past it.
3326	/// Until then the entry waits in [`Self::seek_pending`], where a delivered group
3327	/// removes itself (see [`Self::poll_seek_group`]). `committed` must be the
3328	/// deliverer's sequence watermark (one past its last delivery), which only rises.
3329	/// The seek's floor is NOT that: it includes `start_at`, which can be lowered
3330	/// again, and a conviction it flushed could then be delivered after all.
3331	fn commit_seek_stale(&mut self, committed: u64) {
3332		let keep = self.seek_pending.split_off(&committed);
3333		for (_, content) in std::mem::replace(&mut self.seek_pending, keep) {
3334			self.stale.add(content);
3335		}
3336	}
3337
3338	/// Drop a conviction for `sequence` without counting it: the sequence was
3339	/// delivered by another cursor over the same content (a splice boundary inside
3340	/// the group leaves a copy in two segments), so its content is not stale.
3341	fn discard_seek_conviction(&mut self, sequence: u64) {
3342		self.seek_pending.remove(&sequence);
3343	}
3344}
3345
3346/// A cloneable handle to a subscriber's delivery preferences.
3347///
3348/// This updates the same subscription as the owning [`Subscriber`] without
3349/// borrowing its read cursor, so callers can change delivery priority, the max age
3350/// budget, or group bounds while another task is waiting for groups.
3351#[derive(Clone)]
3352pub struct Control {
3353	subscription: kio::Producer<Subscription>,
3354}
3355
3356impl Control {
3357	/// This subscriber's current preferences.
3358	pub fn subscription(&self) -> Subscription {
3359		self.subscription.read().clone()
3360	}
3361
3362	/// Replace this subscriber's preferences, updating the producer's aggregate.
3363	///
3364	/// Returns [`Error::Closed`] if the track already ended; the update is
3365	/// meaningless at that point and can usually be ignored.
3366	pub fn update(&self, subscription: Subscription) -> Result<()> {
3367		let mut state = self.subscription.write().map_err(|_| Error::Closed)?;
3368		*state = subscription;
3369		Ok(())
3370	}
3371}
3372
3373impl Subscriber {
3374	/// The track's [`Info`], resolved when the subscription was established.
3375	///
3376	/// Free, unlike [`Consumer::query`]: subscribing already waited for the info
3377	/// (SUBSCRIBE_OK on the wire), so a subscriber always has it.
3378	pub fn info(&self) -> &Info {
3379		&self.info
3380	}
3381
3382	/// The track's name, unique within its broadcast.
3383	pub fn name(&self) -> &str {
3384		&self.name
3385	}
3386
3387	/// The broadcast this track belongs to, as reached through the handle it came from.
3388	/// Its [`path`](broadcast::Info::path) is what a catalog's relative `broadcast`
3389	/// references resolve against.
3390	pub fn broadcast(&self) -> &broadcast::Info {
3391		&self.broadcast
3392	}
3393
3394	/// Attribute the groups the drift budget skipped since the last read.
3395	///
3396	/// Drained rather than metered where the skip happens, for the same reason a
3397	/// delivered group is metered here: a spliced subscriber reads through untagged
3398	/// per-segment cursors, so this wrapper is the one place that counts exactly once.
3399	fn count_stale(&mut self, meter: &stats::Meter) {
3400		// An untagged subscriber leaves the count where it is. A spliced reader polls
3401		// its segments through this same method, and those segments are untagged, so
3402		// draining here would throw the count away before the tagged handle wrapping
3403		// them ever sees it.
3404		if meter.is_tracked() {
3405			meter.stale(self.take_stale());
3406		}
3407	}
3408
3409	/// Take the groups the drift budget skipped since the last call, so a nesting
3410	/// handle (a spliced subscriber reading this one as a segment) can attribute them.
3411	pub(crate) fn take_stale(&mut self) -> stats::Content {
3412		match &mut self.inner {
3413			SubscriberKind::Plain(plain) => plain.take_stale(),
3414			SubscriberKind::Spliced(spliced) => spliced.take_stale(),
3415		}
3416	}
3417
3418	/// Bound the drift anchor from outside, for a reader that caps this subscriber
3419	/// without capping its cursor.
3420	///
3421	/// A [`super::resume::Subscriber`] segment is deliberately left uncapped
3422	/// ([`Self::set_groups`] would park boundary-crossing groups where its completion can't
3423	/// be seen), so its own cap has to reach the anchor this way or the segment measures
3424	/// drift against groups its reader will never be served. A spliced segment folds the
3425	/// cap into what it pushes onto its own segments, so the bound reaches the plain
3426	/// cursors at the leaves however deep the splices nest.
3427	pub(crate) fn set_stale_cap(&mut self, cap: Option<u64>) {
3428		match &mut self.inner {
3429			SubscriberKind::Plain(plain) => {
3430				plain.stale_cap = cap;
3431				plain.update_drift_cap();
3432			}
3433			SubscriberKind::Spliced(spliced) => spliced.set_stale_cap(cap),
3434		}
3435	}
3436
3437	/// Count the seek path's convictions below the deliverer's `committed` watermark
3438	/// as stale, exactly once each; see [`PlainSubscriber::commit_seek_stale`].
3439	///
3440	/// The spliced seek calls this on every segment before it delivers, so a losing
3441	/// segment's convictions are counted once the winning delivery moves the cursor
3442	/// past them, and never before.
3443	pub(crate) fn commit_seek_stale(&mut self, committed: u64) {
3444		match &mut self.inner {
3445			SubscriberKind::Plain(plain) => plain.commit_seek_stale(committed),
3446			SubscriberKind::Spliced(spliced) => spliced.commit_seek_stale(committed),
3447		}
3448	}
3449
3450	/// Drop any conviction for `sequence` without counting it; see
3451	/// [`PlainSubscriber::discard_seek_conviction`]. The spliced deliverer calls this
3452	/// for the sequence it just handed out, since a boundary inside that group leaves
3453	/// a convictable copy in the next segment that the delivery splices into.
3454	pub(crate) fn discard_seek_conviction(&mut self, sequence: u64) {
3455		match &mut self.inner {
3456			SubscriberKind::Plain(plain) => plain.discard_seek_conviction(sequence),
3457			SubscriberKind::Spliced(spliced) => spliced.discard_seek_conviction(sequence),
3458		}
3459	}
3460
3461	/// Whether the drift budget says to skip `group`, for a reader holding a group this
3462	/// subscriber handed it earlier (a parked one, re-offered once a cap rose). Counts
3463	/// the skip here so it reaches the stats with the rest. A spliced subscriber asks
3464	/// the segment whose window covers the group, so a nested park is re-checked
3465	/// against the same anchor a fresh read would use.
3466	pub(crate) fn poll_stale(&mut self, group: &group::Consumer, waiter: &kio::Waiter) -> Poll<Result<bool>> {
3467		let plain = match &mut self.inner {
3468			SubscriberKind::Plain(plain) => plain,
3469			SubscriberKind::Spliced(spliced) => return spliced.poll_stale(group, waiter),
3470		};
3471		let drift = ready!(plain.poll_drift(servable_cap(plain.end_sequence, plain.stale_cap), waiter))?;
3472		let stale = ready!(plain.poll_stale(group, &drift, waiter))?;
3473		if stale {
3474			plain.note_stale(group);
3475		}
3476		Poll::Ready(Ok(stale))
3477	}
3478
3479	/// Create a handle for updating this subscriber's delivery preferences.
3480	pub fn control(&self) -> Control {
3481		Control {
3482			subscription: match &self.inner {
3483				SubscriberKind::Plain(plain) => plain.subscription.clone(),
3484				SubscriberKind::Spliced(spliced) => spliced.prefs(),
3485			},
3486		}
3487	}
3488
3489	/// Poll for the next group in arrival order, without blocking.
3490	///
3491	/// Returns each group it delivers exactly once, in the order it landed on the wire,
3492	/// which may be out of sequence due to network reordering or loss. Use
3493	/// [`Self::ordered`] if you only want groups whose sequence number is higher than any
3494	/// previously returned.
3495	///
3496	/// Groups are semi-reliable, and the [`Subscription::max_age`] budget is the other
3497	/// thing (alongside eviction and a moving start) that decides which of them arrive:
3498	/// one that has drifted further behind the live edge than the budget tolerates is
3499	/// skipped rather than handed over, so a single poll walks off a whole backlog. The
3500	/// default is [`Duration::ZERO`], which takes the live
3501	/// edge and writes the rest off; raise it to read history. [`Self::set_groups`] and
3502	/// [`Subscription::start`] are filters, not exemptions: backfill needs a budget that
3503	/// covers it. [`Consumer::fetch_group`] is the way to ask for one old group outright.
3504	/// The budget remains attached to a returned group: if it stalls while newer data
3505	/// advances, its pending frame read ends with [`Error::Old`].
3506	///
3507	/// Honors the group range set by [`Self::set_groups`]:
3508	/// a group beyond the cap is parked (not dropped) and re-offered once the cap rises
3509	/// (lowest sequence first), without blocking in-range groups that arrive behind it.
3510	/// A parked group that the producer evicts or expires in the meantime is dropped,
3511	/// so parking never outlives the track's cache policy.
3512	///
3513	/// Returns `Poll::Ready(Ok(Some(group)))` when a group is available,
3514	/// `Poll::Ready(Ok(None))` when the track is finished,
3515	/// `Poll::Ready(Err(e))` when the track has been aborted, or
3516	/// `Poll::Pending` when no group is available yet.
3517	pub fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
3518		let meter = self.stats.meter();
3519		let res = match &mut self.inner {
3520			SubscriberKind::Plain(plain) => plain.poll_recv_group(waiter),
3521			SubscriberKind::Spliced(spliced) => spliced.poll_recv_group(waiter),
3522		};
3523		self.count_stale(&meter);
3524		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
3525	}
3526
3527	/// Receive the next group in arrival order.
3528	///
3529	/// Every group is returned exactly once, in the order it landed on the wire, which may
3530	/// be out of sequence due to network reordering or loss. Use [`Self::ordered`] if you
3531	/// only want groups whose sequence number is higher than any previously returned.
3532	/// See [`Self::poll_recv_group`] for how [`Self::set_groups`] applies.
3533	pub async fn recv_group(&mut self) -> Result<Option<group::Consumer>> {
3534		kio::wait(|waiter| self.poll_recv_group(waiter)).await
3535	}
3536
3537	/// Poll for the next datagram in arrival order, without blocking.
3538	///
3539	/// Datagrams are a separate best-effort channel from groups (see
3540	/// [`Producer::append_datagram`]); they share only the sequence namespace, and
3541	/// neither cursor moves the other. A consumer that falls too far behind silently
3542	/// loses the oldest datagrams.
3543	///
3544	/// Returns `Poll::Ready(Ok(Some(datagram)))` when one is available,
3545	/// `Poll::Ready(Ok(None))` when the track is finished, `Poll::Ready(Err(e))` when the track
3546	/// is aborted, or `Poll::Pending` when none is buffered yet.
3547	pub fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
3548		let meter = self.stats.meter();
3549		let res = match &mut self.inner {
3550			SubscriberKind::Plain(plain) => plain.poll_recv_datagram(waiter),
3551			SubscriberKind::Spliced(spliced) => spliced.poll_recv_datagram(waiter),
3552		};
3553		// Unlike a group (metered lazily as its frames are read), a datagram is
3554		// delivered whole here, so count it as the single-frame group it stands in for.
3555		if let Poll::Ready(Ok(Some(datagram))) = &res {
3556			meter.datagram(datagram.payload.len() as u64);
3557		}
3558		res
3559	}
3560
3561	/// Receive the next datagram in arrival order.
3562	///
3563	/// A best-effort channel parallel to [`Self::recv_group`]; the two share only the sequence
3564	/// namespace. To receive both concurrently from one subscriber, poll
3565	/// [`Self::poll_recv_group`] and [`Self::poll_recv_datagram`] together in a single `poll`
3566	/// closure (sequential `&mut` borrows), rather than awaiting the two `recv` futures at once.
3567	pub async fn recv_datagram(&mut self) -> Result<Option<Datagram>> {
3568		kio::wait(|waiter| self.poll_recv_datagram(waiter)).await
3569	}
3570
3571	/// The sequence cursor behind [`Ordered`], which owns the only public door to it.
3572	fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
3573		let meter = self.stats.meter();
3574		let res = match &mut self.inner {
3575			SubscriberKind::Plain(plain) => plain.poll_next_group(waiter),
3576			SubscriberKind::Spliced(spliced) => spliced.poll_next_group(waiter),
3577		};
3578		self.count_stale(&meter);
3579		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
3580	}
3581
3582	/// Seek the lowest cached group in `floor..end` without advancing any cursor.
3583	/// Crate-visible for [`super::resume::Subscriber`], whose sequence path picks the
3584	/// lowest candidate across its segments and advances only its own floor; see
3585	/// [`PlainSubscriber::poll_seek_group`].
3586	pub(crate) fn poll_seek_group(
3587		&mut self,
3588		floor: u64,
3589		end: Option<u64>,
3590		waiter: &kio::Waiter,
3591	) -> Poll<Result<Option<group::Consumer>>> {
3592		let meter = self.stats.meter();
3593		let res = match &mut self.inner {
3594			SubscriberKind::Plain(plain) => plain.poll_seek_group(floor, end, waiter),
3595			SubscriberKind::Spliced(spliced) => spliced.poll_seek_group(floor, end, waiter),
3596		};
3597		self.count_stale(&meter);
3598		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
3599	}
3600
3601	/// Read this track's groups in sequence order instead of arrival order.
3602	///
3603	/// Consumes the subscriber, so one handle carries exactly one cursor: an
3604	/// arrival-order [`Subscriber`] or a sequence-order [`Ordered`], never both at once.
3605	/// The two advance independently, and interleaving them produces a group stream that
3606	/// is neither, which is why the choice is a handle rather than a method.
3607	///
3608	/// Datagrams come along: they are a separate cursor either way, so the choice of group
3609	/// order says nothing about them.
3610	pub fn ordered(self) -> Ordered {
3611		Ordered { inner: self }
3612	}
3613
3614	/// Whether `other` was cloned from this subscriber (shares the same underlying state).
3615	pub fn is_clone(&self, other: &Self) -> bool {
3616		match (&self.inner, &other.inner) {
3617			(SubscriberKind::Plain(a), SubscriberKind::Plain(b)) => a.state.same_channel(&b.state),
3618			(SubscriberKind::Spliced(a), SubscriberKind::Spliced(b)) => a.is_clone(b),
3619			_ => false,
3620		}
3621	}
3622
3623	/// Poll for the track's declared final sequence, without blocking.
3624	pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
3625		match &mut self.inner {
3626			SubscriberKind::Plain(plain) => plain.poll(waiter, |state| state.poll_finished()),
3627			SubscriberKind::Spliced(spliced) => spliced.poll_finished(waiter),
3628		}
3629	}
3630
3631	/// Block until the track declares its end, returning the exclusive final sequence
3632	/// (also the total group count), or the cause on an abort.
3633	///
3634	/// Resolves as soon as the boundary is known, which may be ahead of the live edge
3635	/// when the producer finished via [`Producer::finish_at`]. This reports the declared
3636	/// end, not that every group has arrived: drive [`Self::recv_group`] (or
3637	/// [`Ordered::next_group`]) until it yields `None` to observe the track fully drained.
3638	pub async fn finished(&mut self) -> Result<u64> {
3639		kio::wait(|waiter| self.poll_finished(waiter)).await
3640	}
3641
3642	/// Limit subsequent reads to these group sequences without rewinding read progress.
3643	///
3644	/// `2..=5` includes groups 2 through 5; `2..5` excludes group 5. An omitted
3645	/// start preserves the current floor, and an omitted end removes the cap.
3646	/// Groups above the end remain buffered and can be read after raising the cap.
3647	/// This changes only local delivery; use [`Subscription::with_groups`] and
3648	/// [`Self::update`] to change the requested groups.
3649	pub fn set_groups(&mut self, groups: impl RangeBounds<u64>) {
3650		let (start, end) = super::subscription::sequence_bounds(groups);
3651		self.raise_start_to(start);
3652		self.end_at(end.map_or(Bound::Unbounded, Bound::Excluded));
3653	}
3654
3655	/// Start this subscriber's read cursor at the given sequence.
3656	///
3657	/// A local filter, not a request: it doesn't tell the publisher anything, so the
3658	/// skipped groups are still delivered and simply not returned. To ask the publisher
3659	/// to start there instead, set [`Subscription::start`] via [`Self::update`].
3660	/// See [Local cursor vs wire preference](Self#local-cursor-vs-wire-preference).
3661	pub(crate) fn start_at(&mut self, sequence: u64) {
3662		match &mut self.inner {
3663			SubscriberKind::Plain(plain) => plain.min_sequence = sequence,
3664			SubscriberKind::Spliced(spliced) => spliced.start_at(sequence),
3665		}
3666	}
3667
3668	/// Raise the read cursor's floor to `sequence`, keeping any higher floor already set.
3669	///
3670	/// The spliced layer positions a segment's inner cursor with this instead of
3671	/// [`Self::set_groups`]: the inner subscription already resolved a start from its own
3672	/// budget and floor, and an assignment would discard it.
3673	pub(crate) fn raise_start_to(&mut self, sequence: u64) {
3674		match &mut self.inner {
3675			SubscriberKind::Plain(plain) => plain.min_sequence = plain.min_sequence.max(sequence),
3676			SubscriberKind::Spliced(spliced) => spliced.raise_start_to(sequence),
3677		}
3678	}
3679
3680	/// Cap this subscriber's read cursor at `end`, or remove the cap with `..`.
3681	///
3682	/// The range says whether its sequence is delivered: `..=5` serves through group 5,
3683	/// `..5` stops before it. `..0` is the empty range: no group is delivered.
3684	/// [`Position::group_end`] translates a [`Subscription::end`].
3685	///
3686	/// A local filter, not a request; [`Subscription::end`] is the wire-level
3687	/// counterpart. See [Local cursor vs wire preference](Self#local-cursor-vs-wire-preference).
3688	///
3689	/// Groups beyond the cap are held rather than skipped past, so a later call to
3690	/// [`Self::set_groups`] with a higher bound (or unbounded) makes them available again.
3691	/// Lowering the cap below the consumer's current cursor parks the consumer until the
3692	/// cap is raised.
3693	pub(crate) fn end_at(&mut self, end: impl Into<Cap>) {
3694		let end = end.into();
3695		match &mut self.inner {
3696			SubscriberKind::Plain(plain) => {
3697				plain.end_sequence = end.exclusive();
3698				plain.update_drift_cap();
3699			}
3700			SubscriberKind::Spliced(spliced) => spliced.end_at(end),
3701		}
3702	}
3703
3704	/// This subscriber's current preferences.
3705	pub fn subscription(&self) -> Subscription {
3706		self.control().subscription()
3707	}
3708
3709	/// Replace this subscriber's delivery preferences.
3710	///
3711	/// Stored verbatim; the publisher's max age window is applied to the aggregate, not
3712	/// here (see [`Producer::subscription`]). Returns [`Error::Closed`] if the track
3713	/// already ended; the update is meaningless at that point and can usually be ignored.
3714	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
3715		match &mut self.inner {
3716			SubscriberKind::Plain(plain) => {
3717				let mut state = plain.subscription.write().map_err(|_| Error::Closed)?;
3718				*state = subscription;
3719			}
3720			SubscriberKind::Spliced(spliced) => spliced.update(subscription),
3721		}
3722		Ok(())
3723	}
3724
3725	/// Return the latest sequence number in the track.
3726	pub fn latest(&self) -> Option<u64> {
3727		match &self.inner {
3728			SubscriberKind::Plain(plain) => plain.state.read().max_sequence,
3729			SubscriberKind::Spliced(spliced) => spliced.latest(),
3730		}
3731	}
3732}
3733
3734/// A [`Subscriber`] that reads groups in sequence order.
3735///
3736/// Created by [`Subscriber::ordered`], which consumes the subscriber, so a track is read
3737/// one way or the other and never both. Every group it returns has a higher sequence
3738/// than the last, so a late arrival (network reordering, or a gap the cache filled after
3739/// the fact) is skipped rather than delivered out of turn.
3740///
3741/// # Age and skipping
3742///
3743/// [`Subscription::max_age`] applies as this cursor reads, exactly as it does on the
3744/// arrival cursor: a group is skipped once its *reach*, where its immediate successor
3745/// begins, is that far behind the newest frame on the track. Nothing weaker convicts it,
3746/// because the reach is the only proof that *every* frame it could still hold is past the
3747/// budget: a group's own timestamps say where it starts presenting, not where it stops,
3748/// and an unstamped successor leaves it unbounded and therefore kept. A backlog inside
3749/// the budget is still delivered whole, as a burst in order, which is what a decoder
3750/// reading a gap-free sequence needs.
3751///
3752/// The same budget follows a group already handed out: once newer content pulls that far
3753/// ahead, a read still waiting on it ends with [`Error::Old`] and the cursor moves on. A
3754/// gap costs nothing either way, since the cursor seeks to the lowest cached group in
3755/// range rather than waiting for the sequence it would have read next.
3756///
3757/// The budget is updatable mid-stream through [`Self::control`]. [`Duration::ZERO`] (the
3758/// default) keeps only what nothing newer has superseded, so a consumer that stalls and
3759/// resumes rejoins the live edge instead of replaying at 1x.
3760pub struct Ordered {
3761	inner: Subscriber,
3762}
3763
3764impl Ordered {
3765	/// The track's [`Info`], resolved when the subscription was established.
3766	pub fn info(&self) -> &Info {
3767		self.inner.info()
3768	}
3769
3770	/// The track's name, unique within its broadcast.
3771	pub fn name(&self) -> &str {
3772		self.inner.name()
3773	}
3774
3775	/// The broadcast this track belongs to, as reached through the handle it came from.
3776	pub fn broadcast(&self) -> &broadcast::Info {
3777		self.inner.broadcast()
3778	}
3779
3780	/// Poll for the next group with a higher sequence number than any previously
3781	/// returned, without blocking.
3782	///
3783	/// Honors the group range set by [`Self::set_groups`]:
3784	/// a group past the cap stays in the producer's cache and becomes eligible again if
3785	/// the cap rises or is removed.
3786	///
3787	/// Returns `Poll::Ready(Ok(Some(group)))` when a group is available,
3788	/// `Poll::Ready(Ok(None))` when the track is finished,
3789	/// `Poll::Ready(Err(e))` when the track has been aborted, or
3790	/// `Poll::Pending` when no group is available yet.
3791	pub fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
3792		self.inner.poll_next_group(waiter)
3793	}
3794
3795	/// Return the next group with a higher sequence number than any previously returned.
3796	pub async fn next_group(&mut self) -> Result<Option<group::Consumer>> {
3797		kio::wait(|waiter| self.poll_next_group(waiter)).await
3798	}
3799
3800	/// Poll for the next datagram in arrival order, without blocking.
3801	///
3802	/// Datagrams are a separate best-effort channel from groups (see
3803	/// [`Producer::append_datagram`]); they share only the sequence namespace, and neither
3804	/// cursor moves the other. Unordered by construction, so this behaves identically on
3805	/// either handle; it is here so a track carrying both channels needs one subscription
3806	/// rather than two.
3807	pub fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
3808		self.inner.poll_recv_datagram(waiter)
3809	}
3810
3811	/// Receive the next datagram in arrival order.
3812	///
3813	/// To read groups and datagrams concurrently from one handle, poll
3814	/// [`Self::poll_next_group`] and [`Self::poll_recv_datagram`] together in a single
3815	/// `poll` closure (sequential `&mut` borrows), rather than awaiting the two `recv`
3816	/// futures at once.
3817	pub async fn recv_datagram(&mut self) -> Result<Option<Datagram>> {
3818		kio::wait(|waiter| self.poll_recv_datagram(waiter)).await
3819	}
3820
3821	/// Limit subsequent reads to these group sequences without rewinding read progress.
3822	///
3823	/// See [`Subscriber::set_groups`] for inclusive, exclusive, and omitted bounds.
3824	pub fn set_groups(&mut self, groups: impl RangeBounds<u64>) {
3825		self.inner.set_groups(groups);
3826	}
3827
3828	/// Create a handle for updating this subscriber's delivery preferences without
3829	/// borrowing the read cursor.
3830	pub fn control(&self) -> Control {
3831		self.inner.control()
3832	}
3833
3834	/// This subscriber's current preferences.
3835	pub fn subscription(&self) -> Subscription {
3836		self.inner.subscription()
3837	}
3838
3839	/// Replace this subscriber's delivery preferences.
3840	///
3841	/// Returns [`Error::Closed`] if the track already ended.
3842	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
3843		self.inner.update(subscription)
3844	}
3845
3846	/// Poll for the track's declared final sequence, without blocking.
3847	pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
3848		self.inner.poll_finished(waiter)
3849	}
3850
3851	/// Block until the track declares its end, returning the exclusive final sequence.
3852	///
3853	/// See [`Subscriber::finished`]: this reports the declared end, not that every group
3854	/// has arrived.
3855	pub async fn finished(&mut self) -> Result<u64> {
3856		kio::wait(|waiter| self.poll_finished(waiter)).await
3857	}
3858
3859	/// The latest sequence number in the track.
3860	pub fn latest(&self) -> Option<u64> {
3861		self.inner.latest()
3862	}
3863
3864	/// Whether `other` reads the same underlying track state.
3865	pub fn is_clone(&self, other: &Self) -> bool {
3866		self.inner.is_clone(&other.inner)
3867	}
3868}
3869
3870/// A subscriber asked for a track this broadcast doesn't have yet.
3871///
3872/// Yielded by [`broadcast::Dynamic::requested_track`](crate::broadcast::Dynamic::requested_track),
3873/// or created up front with [`broadcast::Producer::reserve_track`](crate::broadcast::Producer::reserve_track).
3874/// Subscribers block until the request is
3875/// resolved: call [`accept`](Self::accept) to serve it with a [`Producer`], or
3876/// [`reject`](Self::reject) to fail them. Dropping it without either rejects with
3877/// [`Error::Dropped`].
3878///
3879/// Concurrent requests for one name are coalesced, so exactly one of these exists per
3880/// name at a time.
3881pub struct Request {
3882	name: Arc<str>,
3883	// The parent broadcast's info, threaded into the [`Producer`] on accept.
3884	broadcast: Arc<broadcast::Info>,
3885	state: kio::Producer<TrackState>,
3886
3887	// The previous subscription that was combined, used to detect changes.
3888	prev_subscription: Option<Subscription>,
3889
3890	// Shared with the accepted [`Producer`] and every [`Dynamic`]: its `Drop` is the
3891	// teardown, and it stays inert until a producer is minted.
3892	alive: Arc<Alive>,
3893
3894	// A requested track is served on demand, so it counts as fetch-capable from
3895	// birth: a consumer's cache-miss `fetch_group` waits to be served instead of
3896	// racing the producer (e.g. a relay) into creating its own handler. Released
3897	// when the request is accepted or dropped; by then the relay holds its own.
3898	_dynamic: Dynamic,
3899
3900	// Ingress stats scope, threaded into the accepted [`Producer`]. Empty (no-op)
3901	// unless this request was reserved on a tagged broadcast.
3902	stats: stats::Scope,
3903}
3904
3905impl Request {
3906	pub(crate) fn new(broadcast: Arc<broadcast::Info>, name: impl Into<Arc<str>>) -> Self {
3907		let name = name.into();
3908		let state = TrackState::spawn(broadcast.clone());
3909		let alive = Alive::new(name.clone(), state.clone());
3910		let dynamic = Dynamic::new(name.clone(), state.clone(), alive.clone());
3911		Self {
3912			name,
3913			broadcast,
3914			state,
3915			prev_subscription: None,
3916			alive,
3917			_dynamic: dynamic,
3918			stats: stats::Scope::default(),
3919		}
3920	}
3921
3922	/// Attach an ingress stats scope, applied to the [`Producer`] on accept. Set by
3923	/// a tagged [`broadcast::Producer::reserve_track`].
3924	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
3925		self.stats = scope;
3926		self
3927	}
3928
3929	/// The requested track name.
3930	pub fn name(&self) -> &str {
3931		&self.name
3932	}
3933
3934	/// A [`Consumer`] for the eventual track, usable before the request is accepted.
3935	pub fn consume(&self) -> Consumer {
3936		Consumer::plain(self.name.clone(), self.state.consume())
3937	}
3938
3939	/// Create a [`Dynamic`] handle that serves on-demand fetches of uncached
3940	/// groups, before [`Self::accept`] is even called. A relay creates one to fetch
3941	/// past groups from upstream while (or instead of) serving a live subscription.
3942	pub fn dynamic(&self) -> Dynamic {
3943		Dynamic::new(self.name.clone(), self.state.clone(), self.alive.clone())
3944	}
3945
3946	/// Poll for the request becoming unused (every consumer dropped), so a relay can
3947	/// stop serving and drop the request.
3948	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
3949		self.state.poll_unused(waiter).map(|_| ())
3950	}
3951
3952	/// Serve the request with the given track, resolving every waiting subscriber.
3953	///
3954	/// The name is taken from [`Self::name`]; `info` supplies the remaining knobs
3955	/// (`None` for the defaults). If the track was already aborted, the returned
3956	/// [`Producer`] is inert: writes fail with the abort error, as if it had been
3957	/// aborted immediately after accepting.
3958	pub fn accept(self, info: impl Into<Option<Info>>) -> Producer {
3959		let info = TrackState::normalize_info(&self.broadcast, info.into().unwrap_or_default());
3960		// A closed state means the track was aborted under us. Mirror `reject` and
3961		// tolerate it: the Producer we hand back simply can't write.
3962		if let Ok(mut state) = self.state.write() {
3963			state.accept(info.clone());
3964		}
3965		// Accepting the request creates the track producer: count it as one ingress
3966		// subscription (closed when the last handle drops). No-op when untagged.
3967		self.alive.publish(Some(&self.stats));
3968		Producer {
3969			name: self.name,
3970			info,
3971			broadcast: self.broadcast,
3972			state: self.state,
3973			prev_subscription: None,
3974			alive: self.alive,
3975			stats: self.stats,
3976		}
3977	}
3978
3979	/// Reject the request, waking all waiting subscribers with `err`.
3980	pub fn reject(self, err: Error) {
3981		if let Ok(mut state) = self.state.write() {
3982			state.abort = Some(err);
3983		}
3984	}
3985
3986	/// The delivery preferences aggregated across everyone waiting on this request,
3987	/// or `None` if nobody is waiting. Useful for sizing the track before accepting.
3988	pub fn subscription(&self) -> Option<Subscription> {
3989		let state = self.state.read();
3990		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
3991		drop(state);
3992		snapshot_subscription(&subs, bound)
3993	}
3994
3995	/// Block until the aggregate [`subscription`](Self::subscription) changes,
3996	/// yielding `None` once nobody is waiting.
3997	pub async fn subscription_changed(&mut self) -> Option<Subscription> {
3998		kio::wait(|waiter| self.poll_subscription_changed(waiter)).await
3999	}
4000
4001	/// Poll counterpart to [`subscription_changed`](Self::subscription_changed).
4002	pub fn poll_subscription_changed(&mut self, waiter: &kio::Waiter) -> Poll<Option<Subscription>> {
4003		let state = self.state.read();
4004		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
4005		drop(state);
4006
4007		let prev = &self.prev_subscription;
4008		let mut combined = None;
4009		let mut guard = ready!(subs.poll(waiter, |subs| {
4010			let next = combined_subscription(subs, bound, waiter);
4011			if &next == prev {
4012				Poll::Pending
4013			} else {
4014				combined = next;
4015				Poll::Ready(())
4016			}
4017		}));
4018		// The aggregate changed: prune any closed subscribers now that we hold the lock.
4019		guard.retain(|sub| !sub.is_closed());
4020		drop(guard);
4021		self.prev_subscription = combined.clone();
4022		Poll::Ready(combined)
4023	}
4024
4025	pub(super) fn weak(&self) -> TrackWeak {
4026		TrackWeak {
4027			name: self.name.clone(),
4028			state: self.state.weak(),
4029		}
4030	}
4031}
4032
4033#[cfg(test)]
4034use futures::FutureExt;
4035
4036#[cfg(test)]
4037#[allow(missing_docs)] // test-only assertion helpers
4038impl Subscriber {
4039	pub fn assert_group(&mut self) -> group::Consumer {
4040		self.recv_group()
4041			.now_or_never()
4042			.expect("group would have blocked")
4043			.expect("would have errored")
4044			.expect("track was closed")
4045	}
4046
4047	pub fn assert_no_group(&mut self) {
4048		assert!(
4049			self.recv_group().now_or_never().is_none(),
4050			"recv_group would not have blocked"
4051		);
4052	}
4053
4054	pub fn assert_not_closed(&mut self) {
4055		assert!(self.finished().now_or_never().is_none(), "should not be closed");
4056	}
4057
4058	pub fn assert_closed(&mut self) {
4059		assert!(self.finished().now_or_never().is_some(), "should be closed");
4060	}
4061
4062	// TODO assert specific errors after implementing PartialEq
4063	pub fn assert_error(&mut self) {
4064		assert!(
4065			self.finished().now_or_never().expect("should not block").is_err(),
4066			"should be error"
4067		);
4068	}
4069
4070	pub fn assert_is_clone(&self, other: &Self) {
4071		assert!(self.is_clone(other), "should be clone");
4072	}
4073
4074	pub fn assert_not_clone(&self, other: &Self) {
4075		assert!(!self.is_clone(other), "should not be clone");
4076	}
4077}
4078
4079#[cfg(test)]
4080mod test {
4081	use super::*;
4082	use crate::frame;
4083	use crate::model::test_tracing::count_drop_warnings;
4084	use std::time::Duration;
4085
4086	/// Mint a track for tests with a default parent broadcast, since tracks are
4087	/// normally born from a [`broadcast::Producer`].
4088	fn track_producer(name: impl Into<Arc<str>>, info: impl Into<Option<Info>>) -> Producer {
4089		Producer::new(Arc::new(broadcast::Info::default()), name, info)
4090	}
4091
4092	/// A bounded replay window for tests whose subject requires every buffered group.
4093	fn replay() -> Subscription {
4094		Subscription::default().with_max_age(Duration::from_secs(30))
4095	}
4096
4097	/// Helper: count live cached groups in state.
4098	fn live_groups(state: &TrackState) -> usize {
4099		state.lookup.len()
4100	}
4101
4102	/// Helper: get the sequence number of the first live group in arrival order.
4103	fn first_live_sequence(state: &TrackState) -> u64 {
4104		state
4105			.arrival
4106			.iter()
4107			.find(|(sequence, stamp)| state.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp))
4108			.map(|(sequence, _)| *sequence)
4109			.unwrap()
4110	}
4111
4112	/// Helper: non-blocking datagram receive that must be ready with a datagram.
4113	fn recv_datagram(dg: &mut Subscriber) -> Datagram {
4114		dg.recv_datagram()
4115			.now_or_never()
4116			.expect("datagram would have blocked")
4117			.expect("would have errored")
4118			.expect("track was closed")
4119	}
4120
4121	/// A declared start (SUBSCRIBE_START) resolves a peek below it as a permanent
4122	/// miss, while a group that is already cached below the start stays readable
4123	/// (a fetch can create one; the declaration only covers the live feed).
4124	#[tokio::test]
4125	async fn peek_resolves_below_the_declared_start() {
4126		let mut producer = track_producer("test", None);
4127		let consumer = producer.consume();
4128
4129		let mut cached = producer.create_group(group::Info { sequence: 1 }).unwrap();
4130		cached.write_frame(Timestamp::ZERO, b"backfill".to_vec()).unwrap();
4131		cached.finish().unwrap();
4132
4133		// Nothing declared yet: a missing group parks.
4134		let waiter = kio::Waiter::noop();
4135		assert!(consumer.poll_peek_group(0, &waiter).is_pending());
4136
4137		// The declaration turns the missing group into a permanent miss, but the
4138		// cached one below it still resolves.
4139		producer.start_at(3).unwrap();
4140		assert!(matches!(consumer.poll_peek_group(0, &waiter), Poll::Ready(None)));
4141		assert!(matches!(consumer.poll_peek_group(1, &waiter), Poll::Ready(Some(_))));
4142		assert!(consumer.poll_peek_group(3, &waiter).is_pending());
4143
4144		// The declaration follows the demand in either direction: forward retires
4145		// the skipped range, backward reopens it.
4146		producer.start_at(4).unwrap();
4147		assert!(matches!(consumer.poll_peek_group(3, &waiter), Poll::Ready(None)));
4148		producer.start_at(0).unwrap();
4149		assert!(consumer.poll_peek_group(0, &waiter).is_pending());
4150	}
4151
4152	#[tokio::test]
4153	async fn append_datagram_shares_group_sequence() {
4154		let mut producer = track_producer("test", None);
4155		let ts = Timestamp::from_millis(10).unwrap();
4156
4157		// Interleave groups and datagrams: they draw from one monotonic counter.
4158		assert_eq!(producer.append_group().unwrap().sequence, 0);
4159		assert_eq!(producer.append_datagram(ts, &b"a"[..]).unwrap(), 1);
4160		assert_eq!(producer.append_group().unwrap().sequence, 2);
4161		assert_eq!(producer.append_datagram(ts, &b"b"[..]).unwrap(), 3);
4162		assert_eq!(producer.latest(), Some(3));
4163	}
4164
4165	#[tokio::test]
4166	async fn append_datagram_roundtrip() {
4167		let mut producer = track_producer("test", None);
4168		let mut dg = producer.subscribe(None);
4169
4170		let ts = Timestamp::from_millis(42).unwrap();
4171		let seq = producer.append_datagram(ts, &b"hello"[..]).unwrap();
4172
4173		let got = recv_datagram(&mut dg);
4174		assert_eq!(got.sequence, seq);
4175		assert_eq!(got.timestamp, ts);
4176		assert_eq!(&got.payload[..], b"hello");
4177	}
4178
4179	#[tokio::test]
4180	async fn insert_datagram_preserves_sequence() {
4181		let mut producer = track_producer("test", None);
4182		let mut dg = producer.subscribe(None);
4183
4184		let ts = Timestamp::from_millis(5).unwrap();
4185		// A relay forwarding an upstream datagram keeps its sequence number.
4186		producer
4187			.insert_datagram(100, ts, bytes::Bytes::from_static(b"x"))
4188			.unwrap();
4189
4190		assert_eq!(recv_datagram(&mut dg).sequence, 100);
4191		// max_sequence advanced, so the next appended group/datagram continues past it.
4192		assert_eq!(producer.append_group().unwrap().sequence, 101);
4193	}
4194
4195	#[tokio::test]
4196	async fn insert_datagram_leaves_a_gap() {
4197		let mut producer = track_producer("test", None);
4198		let mut dg = producer.subscribe(None);
4199		let ts = Timestamp::from_millis(0).unwrap();
4200
4201		producer
4202			.insert_datagram(10, ts, bytes::Bytes::from_static(b"gap"))
4203			.unwrap();
4204		assert_eq!(recv_datagram(&mut dg).sequence, 10);
4205		assert_eq!(producer.append_datagram(ts, &b"next"[..]).unwrap(), 11);
4206		assert_eq!(producer.append_group().unwrap().sequence, 12);
4207	}
4208
4209	#[tokio::test]
4210	async fn insert_datagram_out_of_order_does_not_rewind() {
4211		let mut producer = track_producer("test", None);
4212		let mut dg = producer.subscribe(None);
4213		let ts = Timestamp::from_millis(0).unwrap();
4214
4215		producer
4216			.insert_datagram(10, ts, bytes::Bytes::from_static(b"high"))
4217			.unwrap();
4218		producer
4219			.insert_datagram(5, ts, bytes::Bytes::from_static(b"low"))
4220			.unwrap();
4221
4222		assert_eq!(recv_datagram(&mut dg).sequence, 10);
4223		assert_eq!(recv_datagram(&mut dg).sequence, 5);
4224		assert_eq!(producer.append_datagram(ts, &b"next"[..]).unwrap(), 11);
4225	}
4226
4227	#[tokio::test]
4228	async fn insert_datagram_duplicate_is_best_effort() {
4229		let mut producer = track_producer("test", None);
4230		let mut dg = producer.subscribe(None);
4231		let ts = Timestamp::from_millis(0).unwrap();
4232
4233		producer
4234			.insert_datagram(3, ts, bytes::Bytes::from_static(b"first"))
4235			.unwrap();
4236		producer
4237			.insert_datagram(3, ts, bytes::Bytes::from_static(b"again"))
4238			.unwrap();
4239
4240		assert_eq!(&recv_datagram(&mut dg).payload[..], b"first");
4241		assert_eq!(&recv_datagram(&mut dg).payload[..], b"again");
4242		assert_eq!(producer.append_datagram(ts, &b"next"[..]).unwrap(), 4);
4243	}
4244
4245	#[tokio::test]
4246	async fn insert_datagram_stale_does_not_rewind_after_append() {
4247		let mut producer = track_producer("test", None);
4248		let mut dg = producer.subscribe(None);
4249		let ts = Timestamp::from_millis(0).unwrap();
4250
4251		assert_eq!(producer.append_datagram(ts, &b"0"[..]).unwrap(), 0);
4252		assert_eq!(producer.append_datagram(ts, &b"1"[..]).unwrap(), 1);
4253		producer
4254			.insert_datagram(0, ts, bytes::Bytes::from_static(b"stale"))
4255			.unwrap();
4256
4257		assert_eq!(recv_datagram(&mut dg).sequence, 0);
4258		assert_eq!(recv_datagram(&mut dg).sequence, 1);
4259		assert_eq!(recv_datagram(&mut dg).sequence, 0);
4260		assert_eq!(producer.append_datagram(ts, &b"2"[..]).unwrap(), 2);
4261		assert_eq!(producer.append_group().unwrap().sequence, 3);
4262	}
4263
4264	#[tokio::test]
4265	async fn insert_datagram_cloned_producers_share_counter() {
4266		let mut producer = track_producer("test", None);
4267		let mut other = producer.clone();
4268		let mut dg = producer.subscribe(None);
4269		let ts = Timestamp::from_millis(0).unwrap();
4270
4271		producer
4272			.insert_datagram(4, ts, bytes::Bytes::from_static(b"a"))
4273			.unwrap();
4274		assert_eq!(other.append_datagram(ts, &b"b"[..]).unwrap(), 5);
4275		other.insert_datagram(8, ts, bytes::Bytes::from_static(b"c")).unwrap();
4276		assert_eq!(producer.append_group().unwrap().sequence, 9);
4277
4278		assert_eq!(recv_datagram(&mut dg).sequence, 4);
4279		assert_eq!(recv_datagram(&mut dg).sequence, 5);
4280		assert_eq!(recv_datagram(&mut dg).sequence, 8);
4281	}
4282
4283	#[test]
4284	fn insert_datagram_after_finish_is_closed() {
4285		let mut producer = track_producer("test", None);
4286		let ts = Timestamp::from_millis(0).unwrap();
4287		producer.finish().unwrap();
4288		assert!(matches!(
4289			producer.insert_datagram(0, ts, bytes::Bytes::from_static(b"x")),
4290			Err(Error::Closed)
4291		));
4292		assert!(matches!(producer.append_datagram(ts, &b"x"[..]), Err(Error::Closed)));
4293	}
4294
4295	#[test]
4296	fn insert_datagram_after_abort_fails() {
4297		let producer = track_producer("test", None);
4298		let mut other = producer.clone();
4299		let ts = Timestamp::from_millis(0).unwrap();
4300		producer.abort(Error::Cancel).unwrap();
4301		assert!(other.insert_datagram(0, ts, bytes::Bytes::from_static(b"x")).is_err());
4302	}
4303
4304	#[test]
4305	fn insert_datagram_respects_finish_at() {
4306		let mut producer = track_producer("test", None);
4307		let ts = Timestamp::from_millis(0).unwrap();
4308		producer.finish_at(10).unwrap();
4309		producer
4310			.insert_datagram(5, ts, bytes::Bytes::from_static(b"ok"))
4311			.unwrap();
4312		assert!(matches!(
4313			producer.insert_datagram(10, ts, bytes::Bytes::from_static(b"late")),
4314			Err(Error::Closed)
4315		));
4316		assert_eq!(producer.append_group().unwrap().sequence, 6);
4317	}
4318
4319	/// Datagram sequence advances do not move a route takeover past an open group.
4320	#[test]
4321	fn resume_position_uses_the_latest_group() {
4322		let mut datagram_only = track_producer("datagram-only", None);
4323		let datagram_only_consumer = datagram_only.consume();
4324		datagram_only
4325			.insert_datagram(8, Timestamp::ZERO, bytes::Bytes::from_static(b"x"))
4326			.unwrap();
4327		assert_eq!(
4328			datagram_only_consumer.resume_position(),
4329			None,
4330			"a datagram creates no group position to resume"
4331		);
4332
4333		let mut producer = track_producer("mixed", None);
4334		let consumer = producer.consume();
4335		let mut group = producer.create_group(group::Info { sequence: 3 }).unwrap();
4336		group
4337			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
4338			.unwrap();
4339		producer
4340			.insert_datagram(8, Timestamp::ZERO, bytes::Bytes::from_static(b"x"))
4341			.unwrap();
4342
4343		assert_eq!(
4344			consumer.resume_position(),
4345			Some(Position { group: 3, frame: 1 }),
4346			"the replacement must continue the open group"
4347		);
4348		group.abort(Error::Cancel).unwrap();
4349	}
4350
4351	/// Datagrams and groups are separate channels that share only a sequence namespace,
4352	/// so consuming one must not move the other's cursor.
4353	#[tokio::test]
4354	async fn recv_datagram_leaves_the_ordered_cursor_alone() {
4355		let mut producer = track_producer("test", None);
4356		let mut datagrams = producer.subscribe(None);
4357		let mut subscriber = producer.subscribe(None).ordered();
4358		let ts = Timestamp::from_millis(5).unwrap();
4359
4360		producer
4361			.insert_datagram(5, ts, bytes::Bytes::from_static(b"x"))
4362			.unwrap();
4363		assert_eq!(recv_datagram(&mut datagrams).sequence, 5);
4364
4365		producer.create_group(group::Info { sequence: 3 }).unwrap();
4366		producer.create_group(group::Info { sequence: 6 }).unwrap();
4367
4368		let mut next = || {
4369			subscriber
4370				.next_group()
4371				.now_or_never()
4372				.expect("group would have blocked")
4373				.expect("would have errored")
4374				.expect("track was closed")
4375				.sequence
4376		};
4377		assert_eq!(next(), 3, "the datagram at sequence 5 did not consume group 3");
4378		assert_eq!(next(), 6);
4379	}
4380
4381	#[tokio::test]
4382	async fn datagram_normalized_to_track_timescale() {
4383		let info = Info::default().with_timescale(Timescale::MICRO);
4384		let mut producer = track_producer("test", info);
4385		let mut dg = producer.subscribe(None);
4386
4387		// Supplied at millis; stored/emitted at the track's micro timescale.
4388		producer
4389			.append_datagram(Timestamp::from_millis(2).unwrap(), &b"z"[..])
4390			.unwrap();
4391		let got = recv_datagram(&mut dg);
4392		assert_eq!(got.timestamp.scale(), Timescale::MICRO);
4393		assert_eq!(got.timestamp.value(), 2_000);
4394	}
4395
4396	#[tokio::test]
4397	async fn datagram_rejects_oversized() {
4398		let mut producer = track_producer("test", None);
4399		let big = bytes::Bytes::from(vec![0u8; crate::model::datagram::MAX_DATAGRAM_PAYLOAD + 1]);
4400		let ts = Timestamp::from_millis(0).unwrap();
4401		assert!(matches!(
4402			producer.append_datagram(ts, big.clone()),
4403			Err(Error::FrameTooLarge)
4404		));
4405		assert!(matches!(
4406			producer.insert_datagram(0, ts, big),
4407			Err(Error::FrameTooLarge)
4408		));
4409	}
4410
4411	#[tokio::test]
4412	async fn datagram_fanout_to_subscribers() {
4413		let mut producer = track_producer("test", None);
4414		// Two independent subscribers, each with its own datagram cursor.
4415		let mut a = producer.subscribe(None);
4416		let mut b = producer.subscribe(None);
4417		let ts = Timestamp::from_millis(1).unwrap();
4418
4419		producer.append_datagram(ts, &b"first"[..]).unwrap();
4420		producer.append_datagram(ts, &b"second"[..]).unwrap();
4421
4422		// Both receive every datagram in order, independently.
4423		assert_eq!(&recv_datagram(&mut a).payload[..], b"first");
4424		assert_eq!(&recv_datagram(&mut a).payload[..], b"second");
4425		assert_eq!(&recv_datagram(&mut b).payload[..], b"first");
4426		assert_eq!(&recv_datagram(&mut b).payload[..], b"second");
4427	}
4428
4429	#[test]
4430	fn datagram_buffer_drops_oldest_at_capacity() {
4431		let mut producer = track_producer("test", None);
4432		let mut slow = producer.subscribe(None);
4433		let mut fast = producer.subscribe(None);
4434		let count = MAX_DATAGRAMS * 3;
4435		for sequence in 0..count {
4436			producer.append_datagram(Timestamp::ZERO, b"x".as_slice()).unwrap();
4437			assert_eq!(recv_datagram(&mut fast).sequence, sequence as u64);
4438		}
4439		assert_eq!(producer.state.read().datagrams.len(), MAX_DATAGRAMS);
4440		for sequence in count - MAX_DATAGRAMS..count {
4441			assert_eq!(recv_datagram(&mut slow).sequence, sequence as u64);
4442		}
4443		assert!(slow.poll_recv_datagram(&kio::Waiter::noop()).is_pending());
4444	}
4445
4446	#[tokio::test]
4447	async fn datagram_recv_pends_until_written() {
4448		let mut producer = track_producer("test", None);
4449		let mut dg = producer.subscribe(None);
4450
4451		assert!(
4452			dg.recv_datagram().now_or_never().is_none(),
4453			"should block with no datagrams"
4454		);
4455
4456		producer
4457			.append_datagram(Timestamp::from_millis(0).unwrap(), &b"go"[..])
4458			.unwrap();
4459		assert_eq!(&recv_datagram(&mut dg).payload[..], b"go");
4460	}
4461
4462	/// Exercises the full producer -> publisher-encode -> subscriber-decode -> producer seam
4463	/// (everything but the QUIC datagram send/recv), catching any field-order mismatch between
4464	/// the wire codec and the model.
4465	#[tokio::test]
4466	async fn datagram_wire_roundtrip_between_tracks() {
4467		use crate::coding::{Decode, Encode};
4468		use crate::lite;
4469
4470		let version = lite::Version::Lite05;
4471
4472		// Origin publishes a datagram; the publisher reads it and encodes the wire body.
4473		let mut origin = track_producer("test", None);
4474		let mut origin_dg = origin.subscribe(None);
4475		let ts = Timestamp::from_millis(7).unwrap();
4476		let seq = origin.append_datagram(ts, &b"payload"[..]).unwrap();
4477
4478		let d = recv_datagram(&mut origin_dg);
4479		let body = lite::Datagram {
4480			subscribe: 5,
4481			sequence: d.sequence,
4482			timestamp: d.timestamp.value(),
4483			payload: d.payload.clone(),
4484		}
4485		.encode_bytes(version)
4486		.unwrap();
4487
4488		// Subscriber decodes the body and writes it downstream, preserving the sequence.
4489		let mut slice = &body[..];
4490		let wire = lite::Datagram::decode(&mut slice, version).unwrap();
4491		let mut downstream = track_producer("test", None);
4492		let mut downstream_dg = downstream.subscribe(None);
4493		downstream
4494			.insert_datagram(
4495				wire.sequence,
4496				Timestamp::new(wire.timestamp, Timescale::MILLI).unwrap(),
4497				wire.payload,
4498			)
4499			.unwrap();
4500
4501		let got = recv_datagram(&mut downstream_dg);
4502		assert_eq!(got.sequence, seq);
4503		assert_eq!(got.timestamp, ts);
4504		assert_eq!(&got.payload[..], b"payload");
4505	}
4506
4507	#[tokio::test]
4508	async fn evict_expired_groups() {
4509		let producer = track_producer("test", None);
4510
4511		// Create 3 groups at time 0.
4512		producer.append_group().unwrap(); // seq 0
4513		producer.append_group().unwrap(); // seq 1
4514		producer.append_group().unwrap(); // seq 2
4515
4516		{
4517			let state = producer.state.read();
4518			assert_eq!(live_groups(&state), 3);
4519			assert_eq!(state.offset, 0);
4520		}
4521
4522		// Advance time past the pool's LRU window.
4523		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
4524
4525		// Append a new group to trigger eviction.
4526		producer.append_group().unwrap(); // seq 3
4527
4528		// Groups 0, 1, 2 are expired but seq 3 (the live edge) is kept. Their arrival
4529		// entries no longer resolve, so the leading ones are trimmed and the offset
4530		// advances past them.
4531		{
4532			let state = producer.state.read();
4533			assert_eq!(live_groups(&state), 1);
4534			assert_eq!(first_live_sequence(&state), 3);
4535			assert_eq!(state.offset, 3);
4536			assert!(!state.lookup.contains_key(&0));
4537			assert!(!state.lookup.contains_key(&1));
4538			assert!(!state.lookup.contains_key(&2));
4539			assert!(state.lookup.contains_key(&3));
4540		}
4541	}
4542
4543	/// A group whose frames outlive `max_age` is aged out when the next group starts, but
4544	/// a subscriber that already drained it must still see the clean end of group. Otherwise a
4545	/// track with long groups (a per-minute rollup, say) fails its readers at every boundary.
4546	#[tokio::test]
4547	async fn aging_out_a_finished_group_keeps_the_clean_end() {
4548		let producer = track_producer("test", None);
4549		let mut group = producer.create_group(group::Info { sequence: 0 }).unwrap();
4550		let mut consumer = group.consume();
4551
4552		group
4553			.write_frame(Timestamp::from_millis(0).unwrap(), b"hello".as_slice())
4554			.unwrap();
4555		assert_eq!(consumer.next_frame().await.unwrap().unwrap().size, 5);
4556
4557		// The group stays open well past the LRU window, then the next period starts.
4558		crate::model::clock::advance(cache::DEFAULT_EXPIRY * 2);
4559		group.finish().unwrap();
4560		let _next = producer.create_group(group::Info { sequence: 1 }).unwrap();
4561
4562		assert!(consumer.next_frame().await.unwrap().is_none());
4563	}
4564
4565	/// An actively-read group is not expired out from under its reader: every frame
4566	/// read restarts the retention clock. A group nobody reads still ages out on
4567	/// schedule, so reclamation stays intact.
4568	#[tokio::test]
4569	async fn active_reader_survives_expiry() {
4570		let producer = track_producer("test", None);
4571		let mut subscriber = producer.subscribe(None);
4572
4573		// A finished group with one frame per step of the read loop below.
4574		let mut group = producer.create_group(0u64.into()).unwrap();
4575		for _ in 0..10 {
4576			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
4577		}
4578		group.finish().unwrap();
4579		let mut reading = subscriber.assert_group();
4580
4581		// A sibling written at the same time that nobody ever reads.
4582		producer.create_group(1u64.into()).unwrap().finish().unwrap();
4583
4584		// Each step stays well inside the retention window, but the whole read
4585		// spans several windows. New groups keep the expiry scan running.
4586		for seq in 2..12u64 {
4587			crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2);
4588			let frame = reading.next_frame().await;
4589			assert!(
4590				matches!(frame, Ok(Some(_))),
4591				"an actively-read group must not expire mid-read (step {seq})"
4592			);
4593			producer.create_group(seq.into()).unwrap().finish().unwrap();
4594		}
4595
4596		let state = producer.state.read();
4597		assert!(state.lookup.contains_key(&0), "the read group survived");
4598		assert!(!state.lookup.contains_key(&1), "the unread group still expired");
4599	}
4600
4601	/// Whole-frame reads served from the prefetch batch must also keep the group
4602	/// alive: the batch is filled (and stamped) once per `Prefetch::CAP` frames,
4603	/// which bounds frames, not elapsed time, so a slow `read_frame` reader has to
4604	/// re-stamp on a time bound between refills.
4605	#[tokio::test]
4606	async fn slow_prefetch_reader_survives_expiry() {
4607		let producer = track_producer("test", None);
4608		let mut subscriber = producer.subscribe(None);
4609
4610		let mut group = producer.create_group(0u64.into()).unwrap();
4611		for _ in 0..20 {
4612			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
4613		}
4614		group.finish().unwrap();
4615		let mut reading = subscriber.assert_group();
4616
4617		// One whole-frame read per half-window: most are served straight from the
4618		// prefetch without locking. New groups keep the expiry scan running.
4619		for seq in 1..20u64 {
4620			crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2);
4621			let frame = reading.read_frame().await;
4622			assert!(
4623				matches!(frame, Ok(Some(_))),
4624				"a slow prefetch reader must not expire mid-read (step {seq})"
4625			);
4626			producer.create_group(seq.into()).unwrap().finish().unwrap();
4627		}
4628	}
4629
4630	/// Receiving a group is itself a cache access: a subscriber that takes
4631	/// delivery just before the group would age out still gets to read it a full
4632	/// window later.
4633	#[tokio::test]
4634	async fn delivery_restarts_the_expiry_clock() {
4635		let producer = track_producer("test", None);
4636		let mut subscriber = producer.subscribe(replay());
4637
4638		let mut group = producer.create_group(0u64.into()).unwrap();
4639		group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
4640		group.finish().unwrap();
4641		// A second group so seq 0 leaves the protected live edge.
4642		producer.create_group(1u64.into()).unwrap().finish().unwrap();
4643
4644		// Deliver just inside the window: the delivery stamps the group.
4645		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
4646		let mut reading = subscriber.assert_group();
4647
4648		// Almost another full window passes: far beyond the write, inside the
4649		// delivery stamp. The new group runs the expiry scan.
4650		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
4651		producer.create_group(2u64.into()).unwrap().finish().unwrap();
4652
4653		let frame = reading.read_frame().await.unwrap();
4654		assert!(frame.is_some(), "a just-delivered group must not expire unread");
4655	}
4656
4657	/// Streaming chunks into an in-flight frame is a write access: a straggler
4658	/// group (behind the live edge) trickling a large frame across several
4659	/// retention windows must not be expired mid-write.
4660	#[tokio::test]
4661	async fn streaming_frame_writes_keep_the_group_alive() {
4662		let producer = track_producer("test", None);
4663		let mut straggler = producer.create_group(0u64.into()).unwrap();
4664		// The live edge moves on, so the straggler is demoted and expirable.
4665		producer.create_group(1u64.into()).unwrap().finish().unwrap();
4666
4667		let mut frame = straggler
4668			.create_frame(frame::Info {
4669				size: 10,
4670				timestamp: Timestamp::ZERO,
4671			})
4672			.unwrap();
4673		// One chunk per half-window; the whole frame spans several windows. New
4674		// groups keep the expiry scan running.
4675		for seq in 2..12u64 {
4676			crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2);
4677			frame.write(bytes::Bytes::from_static(b"x")).unwrap();
4678			producer.create_group(seq.into()).unwrap().finish().unwrap();
4679		}
4680		frame.finish().unwrap();
4681		straggler.finish().unwrap();
4682
4683		let state = producer.state.read();
4684		assert!(
4685			state.lookup.contains_key(&0),
4686			"a group streaming a frame survives expiry"
4687		);
4688	}
4689
4690	/// The wire ingest coalesces its chunk wakes to the poll boundary, so a payload
4691	/// whose tail arrives all at once completes without a single `frame_notify`.
4692	/// Committing is itself a write access: a group that just finished a frame must
4693	/// not be expired by the next track write on its stale frame-open stamp.
4694	#[tokio::test]
4695	async fn coalesced_frame_completion_keeps_the_group_alive() {
4696		let producer = track_producer("test", None);
4697		let mut straggler = producer.create_group(0u64.into()).unwrap();
4698		// The live edge moves on, so the straggler is demoted and expirable.
4699		producer.create_group(1u64.into()).unwrap().finish().unwrap();
4700
4701		let mut frame = straggler
4702			.create_frame_owned(frame::Info {
4703				size: 3,
4704				timestamp: Timestamp::ZERO,
4705			})
4706			.unwrap();
4707
4708		// The sender stalls past the retention window, then the whole payload lands in
4709		// one poll turn: the loop never returns `Pending`, so `notify` is never reached
4710		// and `finish` is the only write the charge sees.
4711		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
4712		frame.write(bytes::Bytes::from_static(b"abc")).unwrap();
4713		frame.finish().unwrap();
4714		straggler.finish().unwrap();
4715
4716		// A new group runs the expiry scan.
4717		producer.create_group(2u64.into()).unwrap().finish().unwrap();
4718
4719		let state = producer.state.read();
4720		assert!(
4721			state.lookup.contains_key(&0),
4722			"a group whose frame just completed must not expire"
4723		);
4724	}
4725
4726	/// Re-offering a parked group (once the cap rises) is a delivery: it restarts
4727	/// the expiry clock so the subscriber gets to read what it was just handed.
4728	#[tokio::test]
4729	async fn parked_reoffer_restarts_the_expiry_clock() {
4730		let producer = track_producer("test", None);
4731		let mut subscriber = producer.subscribe(None);
4732		subscriber.set_groups(..1);
4733
4734		for seq in 0..2u64 {
4735			let mut group = producer.create_group(seq.into()).unwrap();
4736			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
4737			group.finish().unwrap();
4738		}
4739
4740		// Group 0 is in range; group 1 is beyond the cap and parks.
4741		assert_eq!(subscriber.assert_group().sequence, 0);
4742		subscriber.assert_no_group();
4743
4744		// Just inside the window, the cap rises and the re-offer stamps group 1.
4745		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
4746		subscriber.set_groups(..2);
4747		let mut reading = subscriber.assert_group();
4748		assert_eq!(reading.sequence, 1);
4749
4750		// Almost another full window passes: far beyond the write, inside the
4751		// re-offer stamp. The new group runs the expiry scan.
4752		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
4753		producer.create_group(2u64.into()).unwrap().finish().unwrap();
4754
4755		let frame = reading.read_frame().await.unwrap();
4756		assert!(frame.is_some(), "a just-re-offered group must not expire unread");
4757	}
4758
4759	#[tokio::test]
4760	async fn evict_keeps_max_sequence() {
4761		let producer = track_producer("test", None);
4762		producer.append_group().unwrap(); // seq 0
4763
4764		// Advance time past the LRU window.
4765		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
4766
4767		// Append another group; seq 0 is expired and evicted.
4768		producer.append_group().unwrap(); // seq 1
4769
4770		{
4771			let state = producer.state.read();
4772			assert_eq!(live_groups(&state), 1);
4773			assert_eq!(first_live_sequence(&state), 1);
4774			assert_eq!(state.offset, 1);
4775		}
4776	}
4777
4778	#[tokio::test]
4779	async fn no_eviction_when_fresh() {
4780		let producer = track_producer("test", None);
4781		producer.append_group().unwrap(); // seq 0
4782		producer.append_group().unwrap(); // seq 1
4783		producer.append_group().unwrap(); // seq 2
4784
4785		{
4786			let state = producer.state.read();
4787			assert_eq!(live_groups(&state), 3);
4788			assert_eq!(state.offset, 0);
4789		}
4790	}
4791
4792	#[tokio::test]
4793	async fn consumer_skips_evicted_groups() {
4794		let producer = track_producer("test", None);
4795		producer.append_group().unwrap(); // seq 0
4796
4797		let mut consumer = producer.subscribe(None);
4798
4799		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
4800		producer.append_group().unwrap(); // seq 1
4801
4802		// Group 0 was evicted. Consumer should get group 1.
4803		let group = consumer.assert_group();
4804		assert_eq!(group.sequence, 1);
4805	}
4806
4807	/// Mint a track under an origin whose pool has the given wall-clock LRU window.
4808	fn track_producer_expiring(name: impl Into<Arc<str>>, expiry: impl Into<Option<Duration>>) -> Producer {
4809		track_producer_pooled(name, cache::Pool::new(cache::Config::default().with_expiry(expiry)))
4810	}
4811
4812	/// Mint a track under an origin caching into `pool`.
4813	fn track_producer_pooled(name: impl Into<Arc<str>>, pool: cache::Pool) -> Producer {
4814		Producer::new(
4815			Arc::new(broadcast::Info {
4816				pool,
4817				..Default::default()
4818			}),
4819			name,
4820			None,
4821		)
4822	}
4823
4824	/// The write path is not the only thing that runs expiry: a pool sweep reclaims a
4825	/// track's idle groups even when the track never writes again, which is the only
4826	/// bound on a publisher that stalls with a group still open.
4827	#[tokio::test]
4828	async fn pool_sweep_expires_without_a_write() {
4829		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
4830		let producer = track_producer_pooled("test", pool.clone());
4831		let mut stalled = producer.append_group().unwrap(); // seq 0, left open
4832		stalled.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
4833		producer.append_group().unwrap(); // seq 1, the live edge
4834
4835		crate::model::clock::advance(Duration::from_secs(2));
4836		pool.sweep();
4837
4838		assert!(
4839			!producer.state.read().lookup.contains_key(&0),
4840			"the sweep reclaimed an idle open group with no write behind it"
4841		);
4842	}
4843
4844	#[test]
4845	fn cache_gc_dates_activity_before_expiring_it() {
4846		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
4847		let now = crate::model::clock::now();
4848		assert_eq!(pool.gc(now), Some(now + Duration::from_millis(500)));
4849		let producer = track_producer_pooled("test", pool.clone());
4850		let mut group = producer.append_group().unwrap();
4851		group.write_frame(Timestamp::ZERO, b"first".as_slice()).unwrap();
4852		producer.append_group().unwrap();
4853		// Activity written while cleanup was idle gets the new supplied time.
4854		let later = now + Duration::from_secs(60);
4855		pool.gc(later);
4856		assert!(producer.state.read().lookup.contains_key(&0));
4857		pool.gc(later + Duration::from_secs(2));
4858		assert!(!producer.state.read().lookup.contains_key(&0));
4859	}
4860
4861	#[test]
4862	fn cache_gc_reaches_old_entries_behind_a_fresh_front() {
4863		let expiry = Duration::from_secs(1);
4864		let pool = cache::Pool::new(cache::Config::default().with_expiry(expiry));
4865		let producer = track_producer_pooled("test", pool.clone());
4866		let now = crate::model::clock::now();
4867		let groups: Vec<_> = (0..EVICT_SCAN * 3).map(|_| producer.append_group().unwrap()).collect();
4868		producer.append_group().unwrap();
4869		pool.gc(now);
4870		// Keep more than a write scan's worth of leading entries fresh.
4871		for group in &groups[..EVICT_SCAN * 2] {
4872			group.cache_refresh();
4873		}
4874		pool.gc(now + expiry * 2);
4875		let state = producer.state.read();
4876		for sequence in 0..EVICT_SCAN * 2 {
4877			assert!(state.lookup.contains_key(&(sequence as u64)), "fresh front survives");
4878		}
4879		for sequence in EVICT_SCAN * 2..EVICT_SCAN * 3 {
4880			assert!(!state.lookup.contains_key(&(sequence as u64)), "old tail is reclaimed");
4881		}
4882	}
4883
4884	/// One sweep drains a whole idle backlog, not a rotating window of it: a quiet
4885	/// track has no writes left to revisit the rest of the queue with, so a bounded
4886	/// pass would leave the oldest groups parked for a backlog's length in windows.
4887	#[tokio::test]
4888	async fn pool_sweep_drains_a_deep_backlog() {
4889		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
4890		let producer = track_producer_pooled("test", pool.clone());
4891
4892		// Comfortably more than one write-driven scan window (EVICT_SCAN).
4893		let backlog = 4 * EVICT_SCAN;
4894		for _ in 0..backlog {
4895			let mut group = producer.append_group().unwrap();
4896			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
4897		}
4898		producer.append_group().unwrap(); // the live edge, always protected
4899
4900		crate::model::clock::advance(Duration::from_secs(2));
4901		pool.sweep();
4902
4903		let state = producer.state.read();
4904		let stale = (0..backlog as u64).filter(|seq| state.lookup.contains_key(seq)).count();
4905		assert_eq!(stale, 0, "one sweep reclaimed the whole idle backlog");
4906	}
4907
4908	#[tokio::test]
4909	async fn pool_expiry_controls_eviction() {
4910		// A shorter LRU window on the pool evicts sooner than the default.
4911		let producer = track_producer_expiring("test", Duration::from_secs(1));
4912		producer.append_group().unwrap(); // seq 0
4913
4914		// Past the pool's window but well within cache::DEFAULT_EXPIRY.
4915		crate::model::clock::advance(Duration::from_secs(2));
4916		producer.append_group().unwrap(); // seq 1
4917
4918		// Seq 0 is gone because the pool only keeps idle groups for 1s.
4919		let state = producer.state.read();
4920		assert_eq!(live_groups(&state), 1);
4921		assert_eq!(first_live_sequence(&state), 1);
4922	}
4923
4924	#[tokio::test]
4925	async fn small_frame_write_expires_idle_siblings() {
4926		let producer = track_producer_expiring("test", Duration::from_secs(1));
4927		producer.append_group().unwrap().finish().unwrap(); // seq 0
4928		let mut live = producer.append_group().unwrap(); // seq 1
4929
4930		crate::model::clock::advance(Duration::from_secs(2));
4931		live.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
4932
4933		let expired = !producer.state.read().lookup.contains_key(&0);
4934		assert!(expired, "a small frame write runs expiry");
4935	}
4936
4937	#[tokio::test]
4938	async fn fresh_expiry_scan_does_not_wake_track_consumers() {
4939		use std::sync::atomic::{AtomicBool, Ordering};
4940
4941		let producer = track_producer_expiring("test", cache::DEFAULT_EXPIRY);
4942		producer.append_group().unwrap().finish().unwrap();
4943		let mut live = producer.append_group().unwrap();
4944		let mut consumer = producer.subscribe(None);
4945		assert_eq!(consumer.assert_group().sequence, 0);
4946		assert_eq!(consumer.assert_group().sequence, 1);
4947
4948		let woken = Arc::new(AtomicBool::new(false));
4949		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
4950		assert!(consumer.poll_recv_group(&waiter).is_pending());
4951
4952		live.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
4953		assert!(
4954			!woken.load(Ordering::SeqCst),
4955			"a no-op expiry scan must not wake track consumers"
4956		);
4957	}
4958
4959	#[tokio::test]
4960	async fn streaming_frame_write_expires_idle_siblings() {
4961		let producer = track_producer_expiring("test", Duration::from_secs(1));
4962		producer.append_group().unwrap().finish().unwrap(); // seq 0
4963		let mut live = producer.append_group().unwrap(); // seq 1
4964		let mut frame = live
4965			.create_frame(frame::Info {
4966				size: 1,
4967				timestamp: Timestamp::ZERO,
4968			})
4969			.unwrap();
4970
4971		crate::model::clock::advance(Duration::from_secs(2));
4972		frame.write(b"x".as_slice()).unwrap();
4973
4974		let expired = !producer.state.read().lookup.contains_key(&0);
4975		assert!(expired, "a streamed chunk runs expiry");
4976	}
4977
4978	#[tokio::test]
4979	async fn appended_datagram_expires_idle_groups() {
4980		let mut producer = track_producer_expiring("test", Duration::from_secs(1));
4981		producer.append_group().unwrap().finish().unwrap(); // seq 0
4982		producer.append_group().unwrap().finish().unwrap(); // seq 1
4983
4984		crate::model::clock::advance(Duration::from_secs(2));
4985		producer.append_datagram(Timestamp::ZERO, b"x".as_slice()).unwrap();
4986
4987		let expired = !producer.state.read().lookup.contains_key(&0);
4988		assert!(expired, "an appended datagram runs expiry");
4989	}
4990
4991	#[tokio::test]
4992	async fn forwarded_datagram_expires_idle_groups() {
4993		let mut producer = track_producer_expiring("test", Duration::from_secs(1));
4994		producer.append_group().unwrap().finish().unwrap(); // seq 0
4995		producer.append_group().unwrap().finish().unwrap(); // seq 1
4996
4997		crate::model::clock::advance(Duration::from_secs(2));
4998		producer
4999			.insert_datagram(2, Timestamp::ZERO, bytes::Bytes::from_static(b"x"))
5000			.unwrap();
5001
5002		let expired = !producer.state.read().lookup.contains_key(&0);
5003		assert!(expired, "a forwarded datagram runs expiry");
5004	}
5005
5006	/// A track's `max_age` is a media-timestamp budget: it does not drive wall-clock
5007	/// eviction, so a stall (no accesses, no timestamp progress) shorter than the
5008	/// pool's LRU window can't age content out no matter how small the window is.
5009	#[tokio::test]
5010	async fn max_age_does_not_drive_wall_eviction() {
5011		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(1)));
5012		producer.append_group().unwrap(); // seq 0
5013
5014		// Far past max_age in wall time, but inside the pool's LRU window.
5015		crate::model::clock::advance(Duration::from_secs(10));
5016		producer.append_group().unwrap(); // seq 1
5017
5018		let state = producer.state.read();
5019		assert_eq!(live_groups(&state), 2, "max_age is media time, not a wall clock");
5020	}
5021
5022	/// Disabling the pool's expiry keeps idle groups until byte pressure reclaims them.
5023	#[tokio::test]
5024	async fn disabled_pool_expiry_never_reclaims() {
5025		let producer = track_producer_expiring("test", None);
5026		producer.append_group().unwrap(); // seq 0
5027
5028		crate::model::clock::advance(Duration::from_secs(3600));
5029		producer.append_group().unwrap(); // seq 1
5030
5031		let state = producer.state.read();
5032		assert_eq!(live_groups(&state), 2);
5033	}
5034
5035	#[test]
5036	fn max_age_clamped_to_cache() {
5037		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2)));
5038
5039		// A max age budget beyond the cache is capped in the aggregate; a group can't be
5040		// waited for longer than the publisher keeps it. The subscriber's own preference
5041		// is stored verbatim, so what it asked for stays readable.
5042		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
5043		assert_eq!(subscriber.subscription().max_age, Duration::from_secs(10));
5044		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5045
5046		// A budget within the cache is left alone, and ZERO (skip immediately) stays ZERO.
5047		subscriber
5048			.update(Subscription::default().with_max_age(Duration::from_millis(500)))
5049			.unwrap();
5050		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_millis(500));
5051
5052		subscriber
5053			.update(Subscription::default().with_max_age(Duration::ZERO))
5054			.unwrap();
5055		assert_eq!(producer.subscription().unwrap().max_age, Duration::ZERO);
5056	}
5057
5058	/// Mint a track under an origin whose retention ceiling is `cap`, so the
5059	/// track's own window is clamped down to it on bind.
5060	fn track_producer_capped(name: impl Into<Arc<str>>, info: Info, cap: Duration) -> Producer {
5061		Producer::new(
5062			Arc::new(broadcast::Info {
5063				cache_duration: cap,
5064				..Default::default()
5065			}),
5066			name,
5067			info,
5068		)
5069	}
5070
5071	#[test]
5072	fn origin_cache_duration_clamps_max_age() {
5073		// A publisher asking to keep groups for a minute is capped to the origin's 1s
5074		// ceiling; a publisher already below the ceiling is left alone (it's a min).
5075		let capped = track_producer_capped(
5076			"test",
5077			Info::default().with_max_age(Duration::from_secs(60)),
5078			Duration::from_secs(1),
5079		);
5080		assert_eq!(capped.state.read().max_age_bound(), Some(Duration::from_secs(1)));
5081		assert_eq!(capped.subscribe(None).info().max_age, Duration::from_secs(1));
5082
5083		let under = track_producer_capped(
5084			"test",
5085			Info::default().with_max_age(Duration::from_millis(500)),
5086			Duration::from_secs(1),
5087		);
5088		assert_eq!(under.state.read().max_age_bound(), Some(Duration::from_millis(500)));
5089	}
5090
5091	/// The origin ceiling clamps the media-timestamp budget only; wall-clock
5092	/// reclamation belongs to the pool's LRU window, not the ceiling.
5093	#[tokio::test]
5094	async fn origin_cache_duration_does_not_wall_evict() {
5095		let producer = track_producer_capped(
5096			"test",
5097			Info::default().with_max_age(Duration::from_secs(60)),
5098			Duration::from_secs(1),
5099		);
5100		producer.append_group().unwrap(); // seq 0
5101
5102		// Far past the ceiling in wall time, but inside the pool's LRU window.
5103		crate::model::clock::advance(Duration::from_secs(2));
5104		producer.append_group().unwrap(); // seq 1
5105
5106		let state = producer.state.read();
5107		assert_eq!(live_groups(&state), 2);
5108	}
5109
5110	#[test]
5111	fn max_age_clamped_via_every_update_path() {
5112		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2)));
5113		let over = Subscription::default().with_max_age(Duration::from_secs(10));
5114
5115		// The clamp lives in the aggregation, so it applies no matter which entry point
5116		// wrote the raw preference. Previously only `Subscriber::update` clamped.
5117		let mut subscriber = producer.subscribe(over.clone());
5118		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5119
5120		subscriber.control().update(over.clone()).unwrap();
5121		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5122
5123		subscriber.update(over).unwrap();
5124		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5125	}
5126
5127	#[test]
5128	fn max_age_aggregate_clamps_across_subscribers() {
5129		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2)));
5130
5131		// The aggregate takes the max, then clamps once. Equivalent to clamping each
5132		// subscriber first, since `min` distributes over `max`.
5133		let _a = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
5134		let _b = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
5135
5136		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5137	}
5138
5139	/// Append a finished group presenting at `millis`, so the track carries a media
5140	/// timeline for the drift budget to measure against.
5141	fn append_at(producer: &mut Producer, millis: u64) -> u64 {
5142		let mut group = producer.append_group().unwrap();
5143		group
5144			.write_frame(Timestamp::from_millis(millis).unwrap(), bytes::Bytes::from_static(b"x"))
5145			.unwrap();
5146		group.finish().unwrap();
5147		group.sequence
5148	}
5149
5150	/// Every group the subscriber can read right now, in delivery order.
5151	fn drain(subscriber: &mut Subscriber) -> Vec<u64> {
5152		let mut sequences = Vec::new();
5153		while let Some(Ok(Some(group))) = subscriber.recv_group().now_or_never() {
5154			sequences.push(group.sequence);
5155		}
5156		sequences
5157	}
5158
5159	#[test]
5160	fn real_time_skips_a_backlog_to_the_live_edge() {
5161		let mut producer = track_producer("test", None);
5162		for second in 0..5 {
5163			append_at(&mut producer, second * 1000);
5164		}
5165
5166		// The default budget is REAL_TIME: a subscriber joining a track that already
5167		// holds five seconds of history takes the live edge, not the history. This is
5168		// the ceiling a takeover backlog runs into.
5169		let mut subscriber = producer.subscribe(None);
5170		assert_eq!(drain(&mut subscriber), vec![4]);
5171
5172		// And it stays caught up: the next group is live when it lands.
5173		append_at(&mut producer, 5000);
5174		assert_eq!(drain(&mut subscriber), vec![5]);
5175	}
5176
5177	#[test]
5178	fn real_time_skips_a_backlog_after_catching_up() {
5179		let mut producer = track_producer("test", None);
5180		append_at(&mut producer, 0);
5181		let mut subscriber = producer.subscribe(None);
5182		assert_eq!(drain(&mut subscriber), vec![0]);
5183
5184		// Catch-up is a subscriber state, not a startup-only choice. A reader can pause
5185		// between groups and still needs to shed the backlog that accumulated meanwhile.
5186		for second in 1..6 {
5187			append_at(&mut producer, second * 1000);
5188		}
5189		assert_eq!(drain(&mut subscriber), vec![5]);
5190	}
5191
5192	#[test]
5193	fn a_newer_edge_changes_an_active_catch_up() {
5194		let mut producer = track_producer("test", None);
5195		for second in 0..5 {
5196			append_at(&mut producer, second * 1000);
5197		}
5198		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(1)));
5199		assert_eq!(
5200			subscriber
5201				.recv_group()
5202				.now_or_never()
5203				.unwrap()
5204				.unwrap()
5205				.unwrap()
5206				.sequence,
5207			3
5208		);
5209
5210		// Group 4 was the edge at the first delivery. Advancing twice puts it outside
5211		// the budget before the subscriber asks for another group.
5212		append_at(&mut producer, 5000);
5213		append_at(&mut producer, 10000);
5214		assert_eq!(drain(&mut subscriber), vec![5, 6]);
5215	}
5216
5217	#[test]
5218	fn a_growing_edge_changes_an_active_catch_up() {
5219		let mut producer = track_producer("test", None);
5220		append_at(&mut producer, 0);
5221		append_at(&mut producer, 1000);
5222		let mut edge = producer.append_group().unwrap();
5223		edge.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"a"))
5224			.unwrap();
5225
5226		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(2)));
5227		assert_eq!(
5228			subscriber
5229				.recv_group()
5230				.now_or_never()
5231				.unwrap()
5232				.unwrap()
5233				.unwrap()
5234				.sequence,
5235			0
5236		);
5237
5238		// The edge is still the same group, but its newest presentation moved far
5239		// enough that group 1 has fallen out of range.
5240		edge.write_frame(Timestamp::from_millis(5000).unwrap(), bytes::Bytes::from_static(b"b"))
5241			.unwrap();
5242		assert_eq!(drain(&mut subscriber), vec![2]);
5243	}
5244
5245	#[test]
5246	fn a_budget_admits_groups_within_it() {
5247		let mut producer = track_producer("test", None);
5248		for second in 0..5 {
5249			append_at(&mut producer, second * 1000);
5250		}
5251
5252		// Two seconds of tolerance keeps the groups presenting within 2s of the live
5253		// edge (2s, 3s, 4s) and drops the two below it. Group 1 reaches exactly 2s behind
5254		// the edge, and the reach bound is exclusive, so every frame it could hold is
5255		// already past the budget.
5256		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(2)));
5257		assert_eq!(drain(&mut subscriber), vec![2, 3, 4]);
5258	}
5259
5260	#[test]
5261	fn a_budget_reaches_back_over_the_cache() {
5262		let mut producer = track_producer("test", None);
5263		for second in 0..5 {
5264			append_at(&mut producer, second * 1000);
5265		}
5266
5267		// The default zero budget calls every non-latest group stale, so a subscription
5268		// that says nothing joins at the live edge.
5269		let mut live = producer.subscribe(None);
5270		assert_eq!(drain(&mut live), vec![4]);
5271
5272		// Two seconds of tolerance covers the groups presenting within 2s of the live
5273		// edge, so the same join is handed the head of what it can still use. One bound
5274		// decides both what is sent and what is expired, so a subscriber is never sent
5275		// history it would discard on arrival.
5276		let budget = Subscription::default().with_max_age(Duration::from_secs(2));
5277		let mut subscriber = producer.subscribe(budget);
5278		assert_eq!(drain(&mut subscriber), vec![2, 3, 4]);
5279	}
5280
5281	#[test]
5282	fn a_named_start_is_a_floor_not_a_request() {
5283		let mut producer = track_producer("test", None);
5284		for second in 0..5 {
5285			append_at(&mut producer, second * 1000);
5286		}
5287
5288		// The budget is the only thing that asks for data; a named start only bounds how
5289		// far back it may reach. Naming group 1 at real time still delivers the live edge
5290		// alone, since the zero budget calls everything older stale.
5291		let named = Subscription::default().with_start(Position::group(1));
5292		let mut subscriber = producer.subscribe(named);
5293		assert_eq!(drain(&mut subscriber), vec![4]);
5294
5295		// A budget reaching further back than the floor is cut off at it.
5296		let floored = Subscription::default()
5297			.with_start(Position::group(3))
5298			.with_max_age(Duration::from_secs(10));
5299		let mut subscriber = producer.subscribe(floored);
5300		assert_eq!(drain(&mut subscriber), vec![3, 4]);
5301
5302		// A floor below what the budget admits changes nothing.
5303		let slack = Subscription::default()
5304			.with_start(Position::group(1))
5305			.with_max_age(Duration::from_secs(2));
5306		let mut subscriber = producer.subscribe(slack);
5307		assert_eq!(drain(&mut subscriber), vec![2, 3, 4]);
5308	}
5309
5310	#[test]
5311	fn a_floor_above_the_live_edge_waits_there() {
5312		let mut producer = track_producer("test", None);
5313		for second in 0..3 {
5314			append_at(&mut producer, second * 1000);
5315		}
5316
5317		// A resumed subscription names where it left off, which may not exist yet. The
5318		// cursor sits at the floor rather than sliding back to what is cached.
5319		let resumed = Subscription::default()
5320			.with_start(Position::group(7))
5321			.with_max_age(Duration::from_secs(10));
5322		let mut subscriber = producer.subscribe(resumed);
5323		assert_eq!(drain(&mut subscriber), Vec::<u64>::new());
5324		append_at(&mut producer, 3000); // sequence 3: still below the floor
5325		assert_eq!(drain(&mut subscriber), Vec::<u64>::new());
5326		for second in 4..8 {
5327			append_at(&mut producer, second * 1000);
5328		}
5329		assert_eq!(drain(&mut subscriber), vec![7]);
5330	}
5331
5332	#[test]
5333	fn a_late_lower_group_within_the_budget_is_delivered() {
5334		let producer = track_producer("test", None);
5335		for (sequence, millis) in [(5, 0), (6, 1000), (7, 2000)] {
5336			let mut group = producer.create_group(group::Info { sequence }).unwrap();
5337			group
5338				.write_frame(Timestamp::from_millis(millis).unwrap(), bytes::Bytes::from_static(b"x"))
5339				.unwrap();
5340			group.finish().unwrap();
5341		}
5342
5343		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(5)));
5344		assert_eq!(drain(&mut subscriber), vec![5, 6, 7]);
5345
5346		// Arriving below everything already delivered is not what makes content stale:
5347		// the budget is the only gate, and this straggler's timestamp is within it. A
5348		// consumer that needs sequence order reorders (or drops) it itself.
5349		let mut late = producer.create_group(group::Info { sequence: 4 }).unwrap();
5350		late.write_frame(Timestamp::from_millis(500).unwrap(), bytes::Bytes::from_static(b"late"))
5351			.unwrap();
5352		late.finish().unwrap();
5353		assert_eq!(drain(&mut subscriber), vec![4]);
5354	}
5355
5356	#[test]
5357	fn drift_is_measured_in_presentation_time_not_arrival_time() {
5358		let mut producer = track_producer("test", None);
5359		// A relay ingesting a backlog creates every group at once, so arrival time says
5360		// they are all equally fresh. Their timestamps say otherwise, which is the whole
5361		// point of measuring in presentation time.
5362		for second in 0..4 {
5363			append_at(&mut producer, second * 1000);
5364		}
5365
5366		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(1500)));
5367		assert_eq!(drain(&mut subscriber), vec![1, 2, 3]);
5368	}
5369
5370	#[tokio::test]
5371	async fn a_stamped_successor_expires_an_unstamped_group() {
5372		let mut producer = track_producer("test", None);
5373		let mut subscriber = producer.subscribe(None);
5374		producer.append_group().unwrap(); // seq 0 stalls before its first frame
5375
5376		append_at(&mut producer, 1000); // seq 1 proves the live feed moved on
5377
5378		// The candidate needs no timestamp of its own: its reach is where its stamped
5379		// successor begins, which the zero budget already puts out of range.
5380		assert_eq!(drain(&mut subscriber), vec![1]);
5381	}
5382
5383	#[tokio::test]
5384	async fn a_handed_out_group_expires_while_its_first_frame_is_stalled() {
5385		let mut producer = track_producer("test", None);
5386		let mut subscriber = producer.subscribe(None);
5387		producer.append_group().unwrap();
5388
5389		let mut stalled = subscriber.recv_group().await.unwrap().expect("stalled group");
5390		let pending = tokio::spawn(async move { stalled.read_frame().await });
5391		tokio::task::yield_now().await;
5392		assert!(
5393			!pending.is_finished(),
5394			"the empty live edge still waits for its first frame"
5395		);
5396
5397		crate::model::clock::advance(Duration::from_secs(1));
5398		append_at(&mut producer, 1000);
5399
5400		// It ends rather than fails: the reader took every frame the group ever had
5401		// (none), so nothing was truncated. What it was waiting for was the producer,
5402		// and a group abandoned where its reader stands looks exactly like one that
5403		// ended there.
5404		let result = pending.await.unwrap();
5405		assert!(matches!(result, Ok(None)), "the held group ends: {result:?}");
5406	}
5407
5408	/// A first timestamp on a *newer* group can convict a held one, so the held reader has
5409	/// to be woken by it. The conviction needs a group beyond the held one's successor:
5410	/// a group is bounded by where its successor begins, so the successor itself never
5411	/// proves it stale.
5412	#[tokio::test]
5413	async fn a_handed_out_group_wakes_when_a_newer_group_gets_its_first_timestamp() {
5414		let mut producer = track_producer("test", None);
5415		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
5416		let mut old = producer.append_group().unwrap();
5417		old.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"old"))
5418			.unwrap();
5419
5420		// Group 1 bounds group 0's reach at 1s.
5421		append_at(&mut producer, 1000);
5422
5423		let mut held = subscriber.recv_group().await.unwrap().expect("old group");
5424		assert!(held.read_frame().await.unwrap().is_some());
5425
5426		let mut live = producer.append_group().unwrap();
5427		let pending = tokio::spawn(async move { held.read_frame().await });
5428		tokio::task::yield_now().await;
5429		assert!(!pending.is_finished(), "the newer group has no timestamp yet");
5430
5431		// 2s edge against group 0's 1s reach: a full second past the budget.
5432		live.write_frame(
5433			Timestamp::from_millis(2000).unwrap(),
5434			bytes::Bytes::from_static(b"live"),
5435		)
5436		.unwrap();
5437		tokio::task::yield_now().await;
5438
5439		assert!(pending.is_finished(), "the new presentation edge wakes the held reader");
5440		// Drained, so the budget ends the group rather than truncating it.
5441		let result = pending.await.unwrap();
5442		assert!(matches!(result, Ok(None)), "the held group ends: {result:?}");
5443	}
5444
5445	/// The ordinary live case, at the default real-time budget: 2s GOPs produced one at
5446	/// a time and read as they arrive. The budget must take the live edge without
5447	/// shortening the group the reader is already on, so every frame of every group is
5448	/// delivered and each group *ends* rather than fails at its boundary.
5449	///
5450	/// Two things would break this. Measuring drift from a group's first frame rather
5451	/// than its reader's position convicts every group the moment its successor opens,
5452	/// since a live reader is always a little behind the edge. And the reader is parked
5453	/// at its group's end when that happens, because the FIN and the next group's first
5454	/// frame are separate events and the wire does not order them.
5455	#[tokio::test]
5456	async fn real_time_reads_a_live_stream_without_truncating_it() {
5457		let producer = track_producer("test", None);
5458		let mut subscriber = producer.subscribe(None);
5459
5460		let gop = |n: u64| {
5461			[
5462				Timestamp::from_millis(n * 2000).unwrap(),
5463				Timestamp::from_millis(n * 2000 + 1900).unwrap(),
5464			]
5465		};
5466		let write = |group: &mut group::Producer, timestamp| {
5467			group.write_frame(timestamp, bytes::Bytes::from_static(b"x")).unwrap();
5468		};
5469
5470		let mut open = producer.append_group().unwrap();
5471		write(&mut open, gop(0)[0]);
5472		write(&mut open, gop(0)[1]);
5473		let mut reading = subscriber.recv_group().await.unwrap().expect("the live group");
5474
5475		let mut read = Vec::new();
5476		for n in 1..5u64 {
5477			let sequence = reading.sequence;
5478			let mut frames = 0;
5479			while let Some(res) = reading.read_frame().now_or_never() {
5480				match res.expect("no truncation while draining") {
5481					Some(_) => frames += 1,
5482					None => panic!("group {sequence} ended early"),
5483				}
5484			}
5485			read.push((sequence, frames));
5486
5487			let next = {
5488				// Parked at the end of the current group: every frame is read and no FIN
5489				// has landed.
5490				let mut end = std::pin::pin!(reading.read_frame());
5491				assert!(futures::poll!(end.as_mut()).is_pending(), "parked on the FIN");
5492
5493				// The next keyframe opens its group. The verdict is taken here, in the
5494				// window before the previous group's FIN arrives.
5495				let mut opened = producer.append_group().unwrap();
5496				write(&mut opened, gop(n)[0]);
5497				let verdict = futures::poll!(end.as_mut());
5498
5499				open.finish().unwrap();
5500				let res = match verdict {
5501					Poll::Ready(res) => res,
5502					Poll::Pending => end.await,
5503				};
5504				assert!(
5505					matches!(res, Ok(None)),
5506					"group {sequence} ends at the boundary rather than failing: {res:?}"
5507				);
5508				opened
5509			};
5510			let mut next = next;
5511			write(&mut next, gop(n)[1]);
5512
5513			reading = subscriber.recv_group().await.unwrap().expect("the next live group");
5514			open = next;
5515		}
5516
5517		assert_eq!(read, vec![(0, 2), (1, 2), (2, 2), (3, 2)], "every frame of every group");
5518	}
5519
5520	/// A budget is spent from where the reader stands, not from where its group opened.
5521	///
5522	/// A 2s GOP with a 1s budget: the reader has drained to 1900ms when the next group
5523	/// opens at 2000ms, so it is 100ms behind the live edge and well inside what it
5524	/// asked for. A straggling frame of the old group arriving after the new one opened
5525	/// (which is ordinary, the two are separate streams) must still reach it.
5526	///
5527	/// Measuring from the group's first frame instead makes the drift 2000ms, so a
5528	/// budget shorter than one GOP would drop the tail of every GOP.
5529	#[tokio::test]
5530	async fn a_budget_is_measured_from_the_readers_position() {
5531		let producer = track_producer("test", None);
5532		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(1)));
5533
5534		let mut open = producer.append_group().unwrap();
5535		open.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"key"))
5536			.unwrap();
5537		open.write_frame(
5538			Timestamp::from_millis(1900).unwrap(),
5539			bytes::Bytes::from_static(b"tail"),
5540		)
5541		.unwrap();
5542
5543		let mut reading = subscriber.recv_group().await.unwrap().expect("the live group");
5544		assert!(reading.read_frame().await.unwrap().is_some());
5545		assert!(reading.read_frame().await.unwrap().is_some());
5546
5547		let mut end = std::pin::pin!(reading.read_frame());
5548		assert!(futures::poll!(end.as_mut()).is_pending(), "parked at 1900ms");
5549
5550		// The next GOP opens. The reader is 100ms behind it, inside its 1s budget.
5551		let mut next = producer.append_group().unwrap();
5552		next.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"key"))
5553			.unwrap();
5554		assert!(
5555			futures::poll!(end.as_mut()).is_pending(),
5556			"a reader inside its budget is not expired by the next group opening"
5557		);
5558
5559		// A straggler from the old group, still within the budget.
5560		open.write_frame(
5561			Timestamp::from_millis(1950).unwrap(),
5562			bytes::Bytes::from_static(b"late"),
5563		)
5564		.unwrap();
5565		let late = end.await.expect("the straggler is not truncated");
5566		assert_eq!(
5567			late.map(|frame| frame.timestamp),
5568			Some(Timestamp::from_millis(1950).unwrap())
5569		);
5570	}
5571
5572	/// A group the budget ends rather than fails stays ended. `expired` alone would
5573	/// turn the clean answer into [`Error::Old`] on the next poll, and a caller is
5574	/// allowed to probe again past the end of a group.
5575	#[tokio::test]
5576	async fn an_ended_group_stays_ended_when_probed_again() {
5577		let mut producer = track_producer("test", None);
5578		let mut subscriber = producer.subscribe(None);
5579		let mut open = producer.append_group().unwrap();
5580		open.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"a"))
5581			.unwrap();
5582
5583		let mut group = subscriber.recv_group().await.unwrap().expect("group");
5584		assert!(group.read_frame().await.unwrap().is_some());
5585
5586		let probes = tokio::spawn(async move {
5587			let first = group.read_frame().await;
5588			let second = group.read_frame().await;
5589			let finished = group.finished().await;
5590			(first, second, finished)
5591		});
5592		tokio::task::yield_now().await;
5593
5594		append_at(&mut producer, 1000);
5595
5596		let (first, second, finished) = probes.await.unwrap();
5597		assert!(matches!(first, Ok(None)), "the group ends: {first:?}");
5598		assert!(matches!(second, Ok(None)), "and stays ended: {second:?}");
5599		assert!(matches!(finished, Ok(1)), "reporting what it delivered: {finished:?}");
5600	}
5601
5602	#[tokio::test]
5603	async fn a_drained_group_finishes_cleanly_after_the_live_edge_advances() {
5604		let mut producer = track_producer("test", None);
5605		let mut subscriber = producer.subscribe(None);
5606		append_at(&mut producer, 0);
5607
5608		let mut group = subscriber.recv_group().await.unwrap().expect("first group");
5609		assert!(group.read_frame().await.unwrap().is_some());
5610
5611		append_at(&mut producer, 1000);
5612
5613		assert!(group.read_frame().await.unwrap().is_none());
5614		assert!(!group.latency_expired());
5615	}
5616
5617	#[tokio::test]
5618	async fn a_handed_out_partial_frame_expires_while_its_payload_is_stalled() {
5619		let mut producer = track_producer("test", None);
5620		let mut subscriber = producer.subscribe(None);
5621		let mut source = producer.append_group().unwrap();
5622		let mut writing = source
5623			.create_frame(frame::Info {
5624				size: 6,
5625				timestamp: Timestamp::ZERO,
5626			})
5627			.unwrap();
5628		writing.write(bytes::Bytes::from_static(b"old")).unwrap();
5629
5630		let mut group = subscriber.recv_group().await.unwrap().expect("partial group");
5631		let mut frame = group.next_frame().await.unwrap().expect("partial frame");
5632		assert_eq!(
5633			frame.read_chunk().await.unwrap(),
5634			Some(bytes::Bytes::from_static(b"old"))
5635		);
5636		let pending = tokio::spawn(async move { frame.read_chunk().await });
5637		tokio::task::yield_now().await;
5638		assert!(!pending.is_finished(), "the partial payload is still stalled");
5639
5640		crate::model::clock::advance(Duration::from_secs(1));
5641		append_at(&mut producer, 1000);
5642
5643		let result = pending.await.unwrap();
5644		assert!(
5645			matches!(result, Err(Error::Old)),
5646			"the in-flight frame expires: {result:?}"
5647		);
5648		writing.abort(Error::Cancel).unwrap();
5649	}
5650
5651	#[test]
5652	fn max_age_bounds_the_budget() {
5653		// The publisher only keeps a group around for 500ms, so a subscriber asking to
5654		// wait ten seconds for one still gives up at 500ms: the same clamp the aggregate
5655		// applies, on the subscriber's own side of it.
5656		let mut producer = track_producer("test", Info::default().with_max_age(Duration::from_millis(500)));
5657		append_at(&mut producer, 0);
5658		append_at(&mut producer, 1000);
5659		append_at(&mut producer, 2000);
5660
5661		// Group 0 reaches 1s, a full second behind the 2s edge, so the clamped 500ms
5662		// budget drops it. An unclamped ten seconds would have kept it.
5663		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
5664		assert_eq!(drain(&mut subscriber), vec![1, 2]);
5665	}
5666
5667	#[test]
5668	fn an_explicit_start_gets_no_exemption_from_the_budget() {
5669		let mut producer = track_producer("test", None);
5670		for second in 0..4 {
5671			append_at(&mut producer, second * 1000);
5672		}
5673
5674		// Asking to start at the beginning is a filter, not a request for reliability.
5675		// Backfill needs a budget that covers it; without one the live edge still wins.
5676		let mut subscriber = producer.subscribe(Subscription::default().with_start(Position::group(0)));
5677		subscriber.start_at(0);
5678		assert_eq!(drain(&mut subscriber), vec![3]);
5679
5680		let mut patient = producer.subscribe(
5681			Subscription::default()
5682				.with_start(Position::group(0))
5683				.with_max_age(Duration::from_secs(10)),
5684		);
5685		patient.start_at(0);
5686		assert_eq!(drain(&mut patient), vec![0, 1, 2, 3]);
5687	}
5688
5689	/// A group's age is where its content *ends*, not where it began. A long group whose
5690	/// tail is level with the live edge still owes the reader every frame in it, so
5691	/// judging it by its first timestamp would discard exactly the group being filled.
5692	#[test]
5693	fn a_long_group_is_not_stale_while_its_tail_reaches_the_edge() {
5694		let mut producer = track_producer("test", None);
5695
5696		// Group 0 spans 0..2000ms; group 1 starts at 2000ms, where group 0 ends.
5697		let mut long = producer.append_group().unwrap();
5698		for ms in [0u64, 500, 1000, 1500, 2000] {
5699			long.write_frame(Timestamp::from_millis(ms).unwrap(), bytes::Bytes::from_static(b"x"))
5700				.unwrap();
5701		}
5702		long.finish().unwrap();
5703		append_at(&mut producer, 2000);
5704
5705		// A budget far shorter than the group's own span still keeps it.
5706		let mut sub = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
5707		assert_eq!(drain(&mut sub), vec![0, 1]);
5708	}
5709
5710	/// The same group, once its tail has fallen behind, is stale like any other.
5711	/// The same long group, once its *successor* has itself fallen behind. A frame's
5712	/// duration is not on the wire, so group 0's own last timestamp proves nothing about
5713	/// where it ends; only group 1's start bounds it. Group 0 is convicted once that bound
5714	/// is further behind the edge than the budget allows.
5715	#[test]
5716	fn a_long_group_is_stale_once_its_successor_falls_behind() {
5717		let mut producer = track_producer("test", None);
5718
5719		let mut long = producer.append_group().unwrap();
5720		for ms in [0u64, 500, 1000] {
5721			long.write_frame(Timestamp::from_millis(ms).unwrap(), bytes::Bytes::from_static(b"x"))
5722				.unwrap();
5723		}
5724		long.finish().unwrap();
5725		// Group 0 reaches at most 3s (where group 1 starts), a full second behind the 4s
5726		// edge and so past the budget. Group 1 reaches the edge itself and is kept.
5727		append_at(&mut producer, 3000);
5728		append_at(&mut producer, 4000);
5729
5730		let mut sub = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
5731		assert_eq!(drain(&mut sub), vec![1, 2]);
5732	}
5733
5734	/// An unstamped immediate successor leaves a group's reach unbounded: a later
5735	/// stamped group proves nothing about where the successor will begin, and
5736	/// shrinking the bound is the unsafe direction.
5737	#[tokio::test]
5738	async fn an_unstamped_immediate_successor_leaves_reach_unbounded() {
5739		let mut producer = track_producer("test", None);
5740		let mut subscriber = producer.subscribe(None);
5741
5742		append_at(&mut producer, 0); // seq 0
5743		producer.append_group().unwrap(); // seq 1 stalls before its first frame
5744		append_at(&mut producer, 10_000); // seq 2
5745
5746		// Group 1's reach is group 2's start, a full edge behind: stale at zero budget.
5747		// Group 0's reach is unknown until group 1 presents its first frame, so it is
5748		// kept rather than convicted on a bound that could shrink the wrong way.
5749		assert_eq!(drain(&mut subscriber), vec![0, 2]);
5750	}
5751
5752	/// Reach is the *immediate* successor's start, never a minimum across later groups.
5753	/// Timestamps need not rise with sequence: a rewind can put a much earlier timestamp on
5754	/// a much later group, and that group proves nothing about where the candidate's own
5755	/// successor begins. Taking the minimum would shrink the bound and discard content that
5756	/// is still well inside the budget.
5757	#[test]
5758	fn reach_follows_the_immediate_successor_not_a_later_rewind() {
5759		let mut producer = track_producer("test", None);
5760
5761		// Group 0 is bounded by group 1 at 10s. Groups 2 and 3 rewind to 1s and 2s.
5762		append_at(&mut producer, 0);
5763		append_at(&mut producer, 10_000);
5764		append_at(&mut producer, 1_000);
5765		append_at(&mut producer, 2_000);
5766
5767		// Group 0 reaches 10s, so nothing here proves it is past a 500ms budget: it could
5768		// hold frames through nearly 10s. A minimum over later groups would put its reach
5769		// at 1s and drop it.
5770		let mut sub = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
5771		assert!(
5772			drain(&mut sub).contains(&0),
5773			"group 0 is bounded by its successor at 10s, not by a later rewind"
5774		);
5775	}
5776
5777	/// Datagrams are unordered by construction, so the sequence cursor carries them too:
5778	/// a track using both channels needs one subscription, not two.
5779	#[tokio::test]
5780	async fn ordered_carries_datagrams() {
5781		let mut producer = track_producer("test", None);
5782		let mut sub = producer.subscribe(None).ordered();
5783
5784		producer
5785			.insert_datagram(5, Timestamp::from_millis(5).unwrap(), bytes::Bytes::from_static(b"x"))
5786			.unwrap();
5787		producer.create_group(group::Info { sequence: 3 }).unwrap();
5788
5789		let datagram = sub
5790			.recv_datagram()
5791			.now_or_never()
5792			.expect("datagram would have blocked")
5793			.expect("would have errored")
5794			.expect("track was closed");
5795		assert_eq!(datagram.sequence, 5);
5796
5797		// The datagram did not consume the group cursor.
5798		let group = sub
5799			.next_group()
5800			.now_or_never()
5801			.expect("group would have blocked")
5802			.expect("would have errored")
5803			.expect("track was closed");
5804		assert_eq!(group.sequence, 3);
5805	}
5806
5807	/// Every group the ordered cursor can read right now, in sequence order.
5808	fn drain_ordered(subscriber: &mut Ordered) -> Vec<u64> {
5809		let mut sequences = Vec::new();
5810		while let Some(Ok(Some(group))) = subscriber.next_group().now_or_never() {
5811			sequences.push(group.sequence);
5812		}
5813		sequences
5814	}
5815
5816	/// A cached backlog is not free to deliver: a consumer that stalls and resumes would
5817	/// otherwise replay it at 1x and stay behind forever, since nothing it reads ever
5818	/// blocks. The budget applies as the cursor reads, exactly as on the arrival cursor.
5819	#[test]
5820	fn next_group_sheds_a_stale_backlog() {
5821		let mut producer = track_producer("test", None);
5822		for second in 0..4 {
5823			append_at(&mut producer, second * 1000);
5824		}
5825
5826		let mut subscriber = producer.subscribe(None).ordered();
5827		assert_eq!(drain_ordered(&mut subscriber), vec![3]);
5828
5829		// The arrival cursor, which the relay forwarders use, sheds the same backlog.
5830		let mut arrival = producer.subscribe(None);
5831		assert_eq!(drain(&mut arrival), vec![3]);
5832	}
5833
5834	/// Only what is provably too old goes. A group is convicted by its *reach* (where its
5835	/// successor begins), so a budget spanning part of the backlog keeps every group that
5836	/// could still present something inside it, and the burst is delivered gap-free.
5837	#[test]
5838	fn next_group_keeps_a_backlog_inside_the_budget() {
5839		let mut producer = track_producer("test", None);
5840		for second in 0..4 {
5841			append_at(&mut producer, second * 1000);
5842		}
5843
5844		// Group 0 reaches 1s, a full 2s behind the 3s edge, so it is gone. Group 1
5845		// reaches 2s and could still present up to it: inside a 1.5s budget.
5846		let mut subscriber = producer
5847			.subscribe(Subscription::default().with_max_age(Duration::from_millis(1500)))
5848			.ordered();
5849		assert_eq!(drain_ordered(&mut subscriber), vec![1, 2, 3]);
5850
5851		// A budget covering the whole history still bursts it in full.
5852		let mut replay = producer.subscribe(replay()).ordered();
5853		assert_eq!(drain_ordered(&mut replay), vec![0, 1, 2, 3]);
5854	}
5855
5856	/// A group whose immediate successor has not presented a frame has no proven reach,
5857	/// so nothing says its frames are too old and the ordered cursor keeps it.
5858	#[test]
5859	fn next_group_keeps_a_group_with_no_proven_reach() {
5860		let mut producer = track_producer("test", None);
5861		append_at(&mut producer, 0); // seq 0
5862		producer.append_group().unwrap(); // seq 1 stalls before its first frame
5863		append_at(&mut producer, 10_000); // seq 2
5864
5865		// Group 1 reaches 10s, a full edge behind: convicted. Group 0 is bounded only by
5866		// group 1, which has yet to say where it begins.
5867		let mut subscriber = producer.subscribe(None).ordered();
5868		assert_eq!(drain_ordered(&mut subscriber), vec![0, 2]);
5869	}
5870
5871	#[tokio::test]
5872	async fn real_time_skips_older_sequences_with_equal_ages() {
5873		let mut producer = track_producer("test", None);
5874		append_at(&mut producer, 0);
5875		append_at(&mut producer, 0);
5876
5877		let mut subscriber = producer.subscribe(None);
5878		assert_eq!(drain(&mut subscriber), vec![1]);
5879	}
5880
5881	#[test]
5882	fn fetch_ignores_the_budget() {
5883		let mut producer = track_producer("test", None);
5884		for second in 0..4 {
5885			append_at(&mut producer, second * 1000);
5886		}
5887
5888		// A fetch names one old group explicitly, so there is no live edge to drift
5889		// from: the budget bounds a subscription, not a request for a specific group.
5890		let consumer = producer.consume();
5891		let group = consumer.fetch_group(0, None).now_or_never().unwrap().unwrap();
5892		assert_eq!(group.sequence, 0);
5893	}
5894
5895	/// A one-shot fetch populates the shared cache but never the live arrival cursor,
5896	/// so it cannot make content available to a subscription look stale.
5897	#[tokio::test]
5898	async fn fetched_group_is_not_a_live_drift_edge() {
5899		let mut producer = track_producer("test", None);
5900		let dynamic = producer.dynamic();
5901		let consumer = producer.consume();
5902		append_at(&mut producer, 0);
5903
5904		let pending = consumer.fetch_group(100, None);
5905		let req = dynamic
5906			.requested_group()
5907			.now_or_never()
5908			.expect("fetch request is ready")
5909			.unwrap();
5910		let mut fetched = req.accept(None).unwrap();
5911		fetched
5912			.write_frame(
5913				Timestamp::from_millis(100_000).unwrap(),
5914				bytes::Bytes::from_static(b"fetched"),
5915			)
5916			.unwrap();
5917		fetched.finish().unwrap();
5918		pending.await.unwrap();
5919
5920		let mut groups = producer.subscribe(None);
5921		assert_eq!(groups.assert_group().sequence, 0);
5922		groups.assert_no_group();
5923	}
5924
5925	/// The live edge is resolved once per poll and applied to every group that poll
5926	/// walks off, so it has to be revalidated before it convicts anything: an eviction
5927	/// in between would otherwise discard a group on the strength of content that is no
5928	/// longer there to jump to.
5929	#[test]
5930	fn an_evicted_live_edge_convicts_nothing() {
5931		let mut producer = track_producer("test", None);
5932		append_at(&mut producer, 0);
5933		let edge = append_at(&mut producer, 30_000);
5934
5935		let state = producer.state.read();
5936		let drift = Drift {
5937			budget: Duration::ZERO,
5938			edge: state.live_edge(None),
5939		};
5940		assert!(
5941			state.is_stale(0, drift.edge.as_ref(), drift.budget),
5942			"stale against a live edge"
5943		);
5944		drop(state);
5945
5946		// The edge dies between resolving it and judging the candidate.
5947		let slot = producer.modify().unwrap().lookup.remove(&edge).unwrap();
5948		let _ = slot.group.abort(Error::Evicted);
5949
5950		let state = producer.state.read();
5951		assert!(
5952			!state.is_stale(0, drift.edge.as_ref(), drift.budget),
5953			"a vanished edge is no reason to drop what is left"
5954		);
5955	}
5956
5957	#[tokio::test]
5958	async fn a_lower_sequence_is_never_the_live_edge() {
5959		let producer = track_producer("test", None);
5960		// A high timestamp on a lower sequence is not a live edge: backfill served on
5961		// demand sits there, and so does the tail of a timeline the publisher rewound.
5962		// Only groups above the candidate anchor the measure, so neither can convict
5963		// the group that follows it.
5964		let mut straggler = producer.create_group(0u64.into()).unwrap();
5965		straggler
5966			.write_frame(Timestamp::from_millis(60_000).unwrap(), bytes::Bytes::from_static(b"x"))
5967			.unwrap();
5968		straggler.finish().unwrap();
5969
5970		let mut rewound = producer.create_group(1u64.into()).unwrap();
5971		rewound
5972			.write_frame(Timestamp::from_millis(0).unwrap(), bytes::Bytes::from_static(b"x"))
5973			.unwrap();
5974		rewound.finish().unwrap();
5975
5976		let mut subscriber = producer.subscribe(replay());
5977		assert_eq!(drain(&mut subscriber), vec![0, 1]);
5978	}
5979
5980	#[test]
5981	fn a_requested_end_does_not_cap_the_live_edge() {
5982		let mut producer = track_producer("test", None);
5983		for second in 0..4 {
5984			append_at(&mut producer, second * 1000);
5985		}
5986
5987		// `Subscription::end` is a request to the publisher, folded in with every other
5988		// subscriber's, and it does not filter this handle: the groups above it arrive
5989		// anyway. Capping the live edge with it would pin the edge below them, leaving
5990		// everything past it with nothing newer to be late against.
5991		let mut subscriber = producer.subscribe(Subscription::default().with_end(Position::after_group(1)));
5992		assert_eq!(drain(&mut subscriber), vec![3]);
5993	}
5994
5995	#[test]
5996	fn a_capped_subscriber_measures_drift_against_its_cap() {
5997		let mut producer = track_producer("test", None);
5998		append_at(&mut producer, 0);
5999		append_at(&mut producer, 1000);
6000
6001		// Capped at group 0: the route running on past the cap is data this subscriber
6002		// can never be served, so it isn't a live edge to be late against. This is what
6003		// keeps a spliced segment from dropping the groups either side of a takeover
6004		// boundary.
6005		let mut subscriber = producer.subscribe(Subscription::default().with_end(Position::after_group(0)));
6006		subscriber.set_groups(..1);
6007		assert_eq!(drain(&mut subscriber), vec![0]);
6008
6009		// Raising the cap re-offers the parked group, now measured against the wider
6010		// edge it just admitted.
6011		subscriber.set_groups(..);
6012		assert_eq!(drain(&mut subscriber), vec![1]);
6013	}
6014
6015	#[test]
6016	fn subscriber_control_updates_while_read_future_is_pending() {
6017		let producer = track_producer("test", None);
6018		let mut subscriber = producer.subscribe(None);
6019		let control = subscriber.control();
6020
6021		let mut recv = Box::pin(subscriber.recv_group());
6022		assert!(recv.as_mut().now_or_never().is_none());
6023
6024		control.update(Subscription::default().with_priority(7)).unwrap();
6025
6026		let aggregate = producer.subscription().expect("expected an active subscription");
6027		assert_eq!(aggregate.priority, 7);
6028	}
6029
6030	#[test]
6031	fn dropped_subscriber_leaves_no_ghost_in_aggregate() {
6032		// Regression (#2351): a departed subscriber must not keep contributing its
6033		// last subscription to the aggregate. When it did, a relay's linger loop
6034		// never observed the track going idle, and an identical viewer reconnecting
6035		// within the linger window was reset when the stale timer fired.
6036		let mut producer = track_producer("test", None);
6037		let a = producer.subscribe(Subscription::default().with_priority(5));
6038
6039		// Prime the change cursor: the aggregate currently has one subscriber.
6040		let waiter = kio::Waiter::noop();
6041		assert!(
6042			matches!(producer.poll_subscription_changed(&waiter), Poll::Ready(Ok(Some(_)))),
6043			"one live subscriber should aggregate to Some",
6044		);
6045
6046		// The only subscriber leaves.
6047		drop(a);
6048
6049		// The aggregate must report the drop to None, not the ghost's last value.
6050		assert!(
6051			matches!(producer.poll_subscription_changed(&waiter), Poll::Ready(Ok(None))),
6052			"a dropped subscriber must not linger in the aggregate",
6053		);
6054
6055		// And the snapshot used by the linger loop must agree.
6056		assert!(
6057			producer.subscription().is_none(),
6058			"snapshot must exclude a dropped subscriber",
6059		);
6060	}
6061
6062	#[test]
6063	fn dropped_subscriber_wakes_the_aggregate() {
6064		// The value being right isn't enough: nothing re-polls the aggregate on its
6065		// own, so the drop has to wake the waiter. A subscriber contributing demand
6066		// takes `kio::Consumer::poll`'s Ready path, which registers no waiter, so
6067		// the departure needs the closed waiter armed explicitly. Without it a relay
6068		// never learns the last viewer left and holds the upstream subscription (and
6069		// the upstream's viewer count) open forever.
6070		use std::sync::atomic::{AtomicBool, Ordering};
6071
6072		let mut producer = track_producer("test", None);
6073		let a = producer.subscribe(Subscription::default().with_priority(5));
6074
6075		let woken = Arc::new(AtomicBool::new(false));
6076		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
6077
6078		// Prime the cursor, then confirm the next poll parks.
6079		assert!(matches!(
6080			producer.poll_subscription_changed(&waiter),
6081			Poll::Ready(Ok(Some(_)))
6082		));
6083		assert!(
6084			producer.poll_subscription_changed(&waiter).is_pending(),
6085			"the aggregate is unchanged, so this poll must park",
6086		);
6087		assert!(!woken.load(Ordering::SeqCst), "nothing happened yet");
6088
6089		drop(a);
6090		assert!(
6091			woken.load(Ordering::SeqCst),
6092			"the last subscriber leaving must wake the aggregate watcher",
6093		);
6094	}
6095
6096	#[test]
6097	fn widest_subscriber_update_wakes_the_aggregate() {
6098		// The value counterpart of the drop above. A subscriber that widens the
6099		// fold takes the Ready path too, so its next update registered no waiter.
6100		// A relay whose upstream cap came from a downstream reader then never
6101		// learned that the reader lifted it, and the groups parked upstream never
6102		// resumed: every session stayed up and nothing flowed.
6103		use std::sync::atomic::{AtomicBool, Ordering};
6104
6105		let mut producer = track_producer("test", None);
6106		let _narrow = producer.subscribe(Subscription::default().with_end(Position::after_group(3)));
6107		let mut wide = producer.subscribe(Subscription::default());
6108
6109		let woken = Arc::new(AtomicBool::new(false));
6110		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
6111
6112		assert!(matches!(
6113			producer.poll_subscription_changed(&waiter),
6114			Poll::Ready(Ok(Some(_)))
6115		));
6116		assert!(producer.poll_subscription_changed(&waiter).is_pending());
6117		assert!(!woken.load(Ordering::SeqCst), "nothing happened yet");
6118
6119		wide.update(Subscription::default().with_end(Position::after_group(5)))
6120			.unwrap();
6121		assert!(
6122			woken.load(Ordering::SeqCst),
6123			"the widest subscriber changing must wake the aggregate watcher",
6124		);
6125		match producer.poll_subscription_changed(&waiter) {
6126			Poll::Ready(Ok(Some(sub))) => assert_eq!(sub.end, Position::after_group(5)),
6127			other => panic!("expected the narrowed aggregate, got {other:?}"),
6128		}
6129	}
6130
6131	/// An [`ArcWake`] that just records that it was woken.
6132	struct FlagWake(Arc<std::sync::atomic::AtomicBool>);
6133
6134	impl futures::task::ArcWake for FlagWake {
6135		fn wake_by_ref(arc_self: &Arc<Self>) {
6136			arc_self.0.store(true, std::sync::atomic::Ordering::SeqCst);
6137		}
6138	}
6139
6140	#[tokio::test]
6141	async fn out_of_order_max_sequence_at_front() {
6142		let producer = track_producer("test", None);
6143
6144		// Arrive out of order: seq 5 first, then 3, then 4.
6145		producer.create_group(group::Info { sequence: 5 }).unwrap();
6146		producer.create_group(group::Info { sequence: 3 }).unwrap();
6147		producer.create_group(group::Info { sequence: 4 }).unwrap();
6148
6149		// max_sequence = 5, which is at the front of the VecDeque.
6150		{
6151			let state = producer.state.read();
6152			assert_eq!(state.max_sequence, Some(5));
6153		}
6154
6155		// Expire all three groups.
6156		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
6157
6158		// Append seq 6 (becomes new max_sequence).
6159		producer.append_group().unwrap(); // seq 6
6160
6161		// Seq 3, 4, 5 are all expired. Seq 5 was the old max_sequence but now 6 is.
6162		// All old groups are evicted.
6163		{
6164			let state = producer.state.read();
6165			assert_eq!(live_groups(&state), 1);
6166			assert_eq!(first_live_sequence(&state), 6);
6167			assert!(!state.lookup.contains_key(&3));
6168			assert!(!state.lookup.contains_key(&4));
6169			assert!(!state.lookup.contains_key(&5));
6170			assert!(state.lookup.contains_key(&6));
6171		}
6172	}
6173
6174	#[tokio::test]
6175	async fn max_sequence_at_front_blocks_trim() {
6176		let producer = track_producer("test", None);
6177
6178		// Arrive: seq 5, then seq 3.
6179		producer.create_group(group::Info { sequence: 5 }).unwrap();
6180
6181		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
6182
6183		// Seq 3 arrives late; max_sequence is still 5 (at front).
6184		producer.create_group(group::Info { sequence: 3 }).unwrap();
6185
6186		// Seq 5 is max_sequence (protected). Seq 3 is not expired (just created).
6187		// Nothing should be evicted.
6188		{
6189			let state = producer.state.read();
6190			assert_eq!(live_groups(&state), 2);
6191			assert_eq!(state.offset, 0);
6192		}
6193
6194		// Expire seq 3 as well.
6195		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
6196
6197		// Seq 2 arrives late, triggering eviction.
6198		producer.create_group(group::Info { sequence: 2 }).unwrap();
6199
6200		// Seq 5 is the live edge (protected) and still resolves at the front of
6201		// `arrival`, so nothing is trimmed and the offset stays. Seq 3 expired out of
6202		// `lookup`, leaving a hole its arrival entry no longer resolves; seq 2 is
6203		// fresh and kept.
6204		{
6205			let state = producer.state.read();
6206			assert_eq!(live_groups(&state), 2);
6207			assert_eq!(state.offset, 0);
6208			assert!(state.lookup.contains_key(&5));
6209			assert!(!state.lookup.contains_key(&3));
6210			assert!(state.lookup.contains_key(&2));
6211		}
6212
6213		// Consumer should still be able to read through the hole.
6214		let mut consumer = producer.subscribe(None);
6215		let group = consumer.assert_group();
6216		// consume() starts at index 0; the first arrival entry that still resolves is seq 5.
6217		assert_eq!(group.sequence, 5);
6218	}
6219
6220	#[tokio::test]
6221	async fn abort_clears_cached_groups() {
6222		let producer = track_producer("test", None);
6223		producer.append_group().unwrap();
6224		producer.append_group().unwrap();
6225
6226		// A stale consumer that never drains must not pin the cached groups.
6227		let mut consumer = producer.subscribe(None);
6228		assert_eq!(live_groups(&producer.state.read()), 2);
6229
6230		producer.clone().abort(Error::Cancel).unwrap();
6231
6232		{
6233			let state = producer.state.read();
6234			assert!(state.lookup.is_empty(), "cached groups should be dropped on abort");
6235			assert!(state.arrival.is_empty());
6236			assert!(state.evict.is_empty());
6237		}
6238
6239		// The consumer now surfaces the abort error rather than the leftover cache.
6240		let result = consumer.recv_group().now_or_never().expect("should not block");
6241		assert!(matches!(result, Err(Error::Cancel)));
6242	}
6243
6244	#[tokio::test]
6245	async fn drop_unfinished_clears_cached_groups() {
6246		let producer = track_producer("test", None);
6247		let writer = producer.clone();
6248		writer.append_group().unwrap();
6249
6250		// A stale consumer keeps the channel (and thus the cache) alive.
6251		let mut consumer = producer.subscribe(None);
6252		assert_eq!(live_groups(&producer.state.read()), 1);
6253
6254		// Drop every producer without finishing: the cache is released.
6255		drop(writer);
6256		drop(producer);
6257
6258		let result = consumer.recv_group().now_or_never().expect("should not block");
6259		assert!(matches!(result, Err(Error::Dropped)));
6260	}
6261
6262	#[tokio::test]
6263	async fn drop_after_abort_does_not_warn() {
6264		// abort() closes the channel after recording `abort`. Drop must treat the
6265		// read-only guard returned by write() as clean or it emits a false WARN.
6266		let warns = count_drop_warnings("track::Producer dropped without finish", || {
6267			let producer = track_producer("test", None);
6268			let keep = producer.clone();
6269			let writer = producer.clone();
6270			let group = writer.append_group().unwrap();
6271			group.finish().unwrap();
6272			let _consumer = producer.subscribe(None);
6273			writer.abort(Error::Cancel).unwrap();
6274			drop(keep);
6275		});
6276		assert_eq!(warns, 0, "abort-then-drop must not emit unfinished-producer WARN");
6277	}
6278
6279	#[tokio::test]
6280	async fn drop_unfinished_warns() {
6281		let warns = count_drop_warnings("track::Producer dropped without finish", || {
6282			let producer = track_producer("test", None);
6283			let writer = producer.clone();
6284			writer.append_group().unwrap();
6285			let _consumer = producer.subscribe(None);
6286			drop(writer);
6287			drop(producer);
6288		});
6289		assert!(warns >= 1, "unfinished drop must emit unfinished-producer WARN");
6290	}
6291
6292	#[tokio::test]
6293	async fn drop_finished_keeps_cached_groups() {
6294		let producer = track_producer("test", None);
6295		producer.append_group().unwrap();
6296		producer.finish().unwrap();
6297
6298		let mut consumer = producer.subscribe(None);
6299		drop(producer);
6300
6301		// A cleanly finished track keeps its cache so the consumer can still drain.
6302		assert_eq!(consumer.assert_group().sequence, 0);
6303		let done = consumer.recv_group().now_or_never().expect("should not block").unwrap();
6304		assert!(done.is_none(), "consumer should drain then see clean finish");
6305	}
6306
6307	#[tokio::test]
6308	async fn cached_groups_preserve_arrival_order() {
6309		let producer = track_producer("test", None);
6310		producer.create_group(group::Info { sequence: 5 }).unwrap();
6311		producer.create_group(group::Info { sequence: 3 }).unwrap();
6312
6313		let groups = producer.consume().cached_groups();
6314		let sequences: Vec<u64> = groups.iter().map(|(group, _)| group.sequence).collect();
6315		assert_eq!(
6316			sequences,
6317			vec![5, 3],
6318			"warm snapshot must follow arrival, not sequence order"
6319		);
6320	}
6321
6322	#[test]
6323	fn append_finish_cannot_be_rewritten() {
6324		let producer = track_producer("test", None);
6325
6326		// Finishing an empty track is valid (fin = 0, total groups = 0).
6327		assert!(producer.finish().is_ok());
6328		assert!(producer.finish().is_err());
6329		assert!(producer.append_group().is_err());
6330	}
6331
6332	#[test]
6333	fn finish_after_groups() {
6334		let producer = track_producer("test", None);
6335
6336		producer.append_group().unwrap();
6337		assert!(producer.finish().is_ok());
6338		assert!(producer.finish().is_err());
6339		assert!(producer.append_group().is_err());
6340	}
6341
6342	#[test]
6343	fn finish_at_rejects_a_boundary_at_or_below_the_live_edge() {
6344		let mut producer = track_producer("test", None);
6345		producer.create_group(group::Info { sequence: 5 }).unwrap();
6346
6347		// The boundary is exclusive, so it must be strictly above the highest produced
6348		// group. 5 or below would orphan groups that already exist.
6349		assert!(producer.finish_at(4).is_err());
6350		assert!(producer.finish_at(5).is_err());
6351		assert!(producer.finish_at(6).is_ok());
6352
6353		{
6354			let state = producer.state.read();
6355			assert_eq!(state.final_sequence, Some(6));
6356		}
6357
6358		// Re-finishing is rejected, and no group at or above the boundary can be created.
6359		assert!(producer.finish_at(6).is_err());
6360		assert!(producer.create_group(group::Info { sequence: 4 }).is_ok());
6361		assert!(producer.create_group(group::Info { sequence: 6 }).is_err());
6362	}
6363
6364	#[test]
6365	fn final_sequence_reports_the_declared_boundary() {
6366		let mut producer = track_producer("test", None);
6367		assert_eq!(producer.final_sequence(), None);
6368
6369		producer.create_group(group::Info { sequence: 5 }).unwrap();
6370		assert_eq!(producer.final_sequence(), None, "a group does not declare a boundary");
6371
6372		producer.finish_at(9).unwrap();
6373		assert_eq!(producer.final_sequence(), Some(9));
6374
6375		// finish() would try to declare a second boundary, so callers check first.
6376		assert!(producer.finish().is_err());
6377	}
6378
6379	#[test]
6380	fn final_sequence_reports_the_live_edge_after_finish() {
6381		let producer = track_producer("test", None);
6382		producer.create_group(group::Info { sequence: 5 }).unwrap();
6383		producer.finish().unwrap();
6384		assert_eq!(producer.final_sequence(), Some(6));
6385	}
6386
6387	#[tokio::test]
6388	async fn finish_at_declares_a_future_boundary() {
6389		let mut producer = track_producer("test", None);
6390		producer.create_group(group::Info { sequence: 5 }).unwrap();
6391
6392		// Learn the track ends at group 6 (exclusive 7) while the live edge is still 5.
6393		producer.finish_at(7).unwrap();
6394
6395		let mut consumer = producer.subscribe(None);
6396		assert_eq!(consumer.assert_group().sequence, 5);
6397
6398		// The boundary is known immediately, but the track isn't done: group 6 is still
6399		// outstanding, so the consumer parks rather than seeing end-of-stream.
6400		let boundary = consumer
6401			.finished()
6402			.now_or_never()
6403			.expect("boundary is known immediately")
6404			.expect("would have errored");
6405		assert_eq!(boundary, 7);
6406		assert!(
6407			consumer.recv_group().now_or_never().is_none(),
6408			"should wait for the outstanding group"
6409		);
6410
6411		// The trailing group arrives (below the boundary), then the track completes.
6412		producer.create_group(group::Info { sequence: 6 }).unwrap();
6413		assert_eq!(consumer.assert_group().sequence, 6);
6414		let done = consumer
6415			.recv_group()
6416			.now_or_never()
6417			.expect("should not block")
6418			.expect("would have errored");
6419		assert!(done.is_none(), "track completes once the boundary is reached");
6420	}
6421
6422	#[tokio::test]
6423	async fn recv_group_finishes_without_waiting_for_gaps() {
6424		let producer = track_producer("test", None);
6425		producer.create_group(group::Info { sequence: 1 }).unwrap();
6426		producer.finish().unwrap();
6427
6428		let mut consumer = producer.subscribe(None);
6429		assert_eq!(consumer.assert_group().sequence, 1);
6430
6431		let done = consumer
6432			.recv_group()
6433			.now_or_never()
6434			.expect("should not block")
6435			.expect("would have errored");
6436		assert!(done.is_none(), "track should finish without waiting for gaps");
6437	}
6438
6439	#[tokio::test]
6440	async fn next_group_skips_late_arrivals() {
6441		let producer = track_producer("test", None);
6442		let mut consumer = producer.subscribe(None).ordered();
6443
6444		// Seq 5 arrives first.
6445		producer.create_group(group::Info { sequence: 5 }).unwrap();
6446		let group = consumer
6447			.next_group()
6448			.now_or_never()
6449			.expect("should not block")
6450			.expect("would have errored")
6451			.expect("track should not be closed");
6452		assert_eq!(group.sequence, 5);
6453
6454		// Seq 3 arrives late, skipped because 3 <= 5.
6455		producer.create_group(group::Info { sequence: 3 }).unwrap();
6456		// Seq 4 arrives late and is also skipped.
6457		producer.create_group(group::Info { sequence: 4 }).unwrap();
6458		// Seq 7 arrives and is returned.
6459		producer.create_group(group::Info { sequence: 7 }).unwrap();
6460
6461		let group = consumer
6462			.next_group()
6463			.now_or_never()
6464			.expect("should not block")
6465			.expect("would have errored")
6466			.expect("track should not be closed");
6467		assert_eq!(group.sequence, 7);
6468
6469		// No more groups. This would block.
6470		assert!(
6471			consumer.next_group().now_or_never().is_none(),
6472			"should block waiting for a higher sequence"
6473		);
6474	}
6475
6476	#[tokio::test]
6477	async fn next_group_returns_arrivals_in_order() {
6478		let producer = track_producer("test", None);
6479		let mut consumer = producer.subscribe(replay()).ordered();
6480
6481		// Seq 3 arrives first, then seq 5. Both should be returned in arrival order.
6482		producer.create_group(group::Info { sequence: 3 }).unwrap();
6483		producer.create_group(group::Info { sequence: 5 }).unwrap();
6484
6485		let group = consumer
6486			.next_group()
6487			.now_or_never()
6488			.expect("should not block")
6489			.expect("would have errored")
6490			.expect("track should not be closed");
6491		assert_eq!(group.sequence, 3);
6492
6493		let group = consumer
6494			.next_group()
6495			.now_or_never()
6496			.expect("should not block")
6497			.expect("would have errored")
6498			.expect("track should not be closed");
6499		assert_eq!(group.sequence, 5);
6500	}
6501
6502	#[tokio::test]
6503	async fn ordered_and_arrival_cursors_are_independent() {
6504		let producer = track_producer("test", None);
6505		let mut ordered = producer.subscribe(replay()).ordered();
6506		let mut arrival = producer.subscribe(replay());
6507
6508		// Out-of-order arrivals: seq 5 first, then seq 3.
6509		producer.create_group(group::Info { sequence: 5 }).unwrap();
6510		producer.create_group(group::Info { sequence: 3 }).unwrap();
6511
6512		// The ordered handle returns the smallest sequence first, regardless of
6513		// arrival order.
6514		let group = ordered
6515			.next_group()
6516			.now_or_never()
6517			.expect("should not block")
6518			.expect("would have errored")
6519			.expect("track should not be closed");
6520		assert_eq!(group.sequence, 3);
6521
6522		// The plain handle walks arrivals, so it still starts at seq 5.
6523		assert_eq!(arrival.assert_group().sequence, 5);
6524	}
6525
6526	#[tokio::test]
6527	async fn end_at_caps_next_group() {
6528		let producer = track_producer("test", None);
6529		let mut consumer = producer.subscribe(replay()).ordered();
6530
6531		for s in 0..6 {
6532			producer.create_group(group::Info { sequence: s }).unwrap();
6533		}
6534
6535		consumer.set_groups(..3);
6536
6537		// Groups 0, 1, 2 are within the cap.
6538		assert_eq!(
6539			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6540			0
6541		);
6542		assert_eq!(
6543			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6544			1
6545		);
6546		assert_eq!(
6547			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6548			2
6549		);
6550
6551		// Group 3 is beyond the cap: next_group parks even though cached groups exist.
6552		assert!(
6553			consumer.next_group().now_or_never().is_none(),
6554			"capped consumer must block instead of returning out-of-range groups"
6555		);
6556	}
6557
6558	#[tokio::test]
6559	async fn end_at_release_drains_cached_groups() {
6560		let producer = track_producer("test", None);
6561		let mut consumer = producer.subscribe(replay()).ordered();
6562
6563		for s in 0..6 {
6564			producer.create_group(group::Info { sequence: s }).unwrap();
6565		}
6566
6567		consumer.set_groups(..2);
6568		assert_eq!(
6569			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6570			0
6571		);
6572		assert_eq!(
6573			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6574			1
6575		);
6576		assert!(consumer.next_group().now_or_never().is_none(), "capped at 2");
6577
6578		// Raise the cap; previously-blocked cached groups become available again.
6579		consumer.set_groups(..5);
6580		assert_eq!(
6581			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6582			2
6583		);
6584		assert_eq!(
6585			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6586			3
6587		);
6588		assert_eq!(
6589			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6590			4
6591		);
6592		assert!(consumer.next_group().now_or_never().is_none(), "capped at 5");
6593
6594		// Remove the cap; everything remaining flows.
6595		consumer.set_groups(..);
6596		assert_eq!(
6597			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6598			5
6599		);
6600		assert!(consumer.next_group().now_or_never().is_none(), "no more groups");
6601	}
6602
6603	#[tokio::test]
6604	async fn end_at_lower_than_cursor_parks_consumer() {
6605		let producer = track_producer("test", None);
6606		let mut consumer = producer.subscribe(replay()).ordered();
6607
6608		for s in 0..3 {
6609			producer.create_group(group::Info { sequence: s }).unwrap();
6610		}
6611
6612		// Drain everything with no cap.
6613		assert_eq!(
6614			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6615			0
6616		);
6617		assert_eq!(
6618			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6619			1
6620		);
6621		assert_eq!(
6622			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6623			2
6624		);
6625
6626		// Lower the cap below the cursor. New groups beyond the cap are blocked.
6627		consumer.set_groups(..2);
6628		producer.create_group(group::Info { sequence: 3 }).unwrap();
6629		producer.create_group(group::Info { sequence: 4 }).unwrap();
6630		assert!(
6631			consumer.next_group().now_or_never().is_none(),
6632			"cap is below cursor; nothing returnable until cap rises"
6633		);
6634
6635		// Restoring the cap to no-limit (or any value >= cursor) releases them.
6636		consumer.set_groups(..);
6637		assert_eq!(
6638			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6639			3
6640		);
6641		assert_eq!(
6642			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6643			4
6644		);
6645	}
6646
6647	#[tokio::test]
6648	async fn end_at_toggling_around_late_arrivals() {
6649		let producer = track_producer("test", None);
6650		let mut consumer = producer.subscribe(replay()).ordered();
6651
6652		consumer.set_groups(..6);
6653
6654		// Out-of-order arrivals all within the cap.
6655		producer.create_group(group::Info { sequence: 2 }).unwrap();
6656		producer.create_group(group::Info { sequence: 5 }).unwrap();
6657		producer.create_group(group::Info { sequence: 3 }).unwrap();
6658		// One beyond the cap; should be held even though it arrived in the middle.
6659		producer.create_group(group::Info { sequence: 8 }).unwrap();
6660		producer.create_group(group::Info { sequence: 4 }).unwrap();
6661
6662		// next_group walks in sequence order through everything <= cap.
6663		assert_eq!(
6664			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6665			2
6666		);
6667		assert_eq!(
6668			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6669			3
6670		);
6671		assert_eq!(
6672			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6673			4
6674		);
6675		assert_eq!(
6676			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6677			5
6678		);
6679		// Now blocked: 8 is still beyond the cap.
6680		assert!(consumer.next_group().now_or_never().is_none());
6681
6682		// Raise the cap; cached seq 8 is finally served.
6683		consumer.set_groups(..11);
6684		assert_eq!(
6685			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6686			8
6687		);
6688	}
6689
6690	/// `recv_group` (arrival order) honors the `end_at` cap by parking, like
6691	/// `next_group`: beyond-cap groups are held, not dropped, and a raised cap
6692	/// re-offers them, even after the track finishes.
6693	#[tokio::test]
6694	async fn end_at_parks_recv_group() {
6695		let producer = track_producer("test", None);
6696		let mut consumer = producer.subscribe(replay());
6697
6698		for s in 0..3 {
6699			producer.create_group(group::Info { sequence: s }).unwrap();
6700		}
6701
6702		consumer.set_groups(..2);
6703		assert_eq!(
6704			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6705			0
6706		);
6707		assert_eq!(
6708			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6709			1
6710		);
6711		assert!(consumer.recv_group().now_or_never().is_none(), "capped at 2");
6712
6713		// A finished track keeps the parked group claimable: the cap may rise.
6714		producer.finish().unwrap();
6715		assert!(
6716			consumer.recv_group().now_or_never().is_none(),
6717			"still parked after finish"
6718		);
6719
6720		consumer.set_groups(..);
6721		assert_eq!(
6722			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6723			2
6724		);
6725		assert!(
6726			matches!(consumer.recv_group().now_or_never(), Some(Ok(None))),
6727			"finished once the parked group drains"
6728		);
6729	}
6730
6731	/// A group beyond the cap must not block in-range groups that arrive behind
6732	/// it: a relay can ingest a burst micro-reordered (newest first).
6733	#[tokio::test]
6734	async fn recv_group_serves_arrivals_behind_the_cap() {
6735		let producer = track_producer("test", None);
6736		let mut consumer = producer.subscribe(replay());
6737
6738		consumer.set_groups(..2);
6739
6740		// Reordered burst: the beyond-cap group arrives first.
6741		producer.create_group(group::Info { sequence: 2 }).unwrap();
6742		producer.create_group(group::Info { sequence: 0 }).unwrap();
6743		producer.create_group(group::Info { sequence: 1 }).unwrap();
6744
6745		assert_eq!(
6746			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6747			0
6748		);
6749		assert_eq!(
6750			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6751			1
6752		);
6753		assert!(consumer.recv_group().now_or_never().is_none(), "capped at 2");
6754
6755		consumer.set_groups(..3);
6756		assert_eq!(
6757			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6758			2
6759		);
6760	}
6761
6762	#[tokio::test]
6763	async fn group_ranges_preserve_the_floor_when_the_cap_changes() {
6764		let producer = track_producer("test", None);
6765		let mut consumer = producer.subscribe(None);
6766		consumer.set_groups(2..=2);
6767		producer.create_group(group::Info { sequence: 2 }).unwrap();
6768		assert_eq!(consumer.recv_group().await.unwrap().unwrap().sequence, 2);
6769		consumer.set_groups(..4);
6770		producer.create_group(group::Info { sequence: 1 }).unwrap();
6771		producer.create_group(group::Info { sequence: 3 }).unwrap();
6772		assert_eq!(consumer.recv_group().await.unwrap().unwrap().sequence, 3);
6773		consumer.set_groups(0..=4);
6774		producer.create_group(group::Info { sequence: 0 }).unwrap();
6775		producer.create_group(group::Info { sequence: 4 }).unwrap();
6776		assert_eq!(consumer.recv_group().await.unwrap().unwrap().sequence, 4);
6777	}
6778
6779	/// A raised `start_at` drops parked groups it overtook instead of re-offering
6780	/// them once the cap rises.
6781	#[tokio::test]
6782	async fn start_at_drops_parked_recv_groups() {
6783		let producer = track_producer("test", None);
6784		let mut consumer = producer.subscribe(None);
6785
6786		consumer.set_groups(..1);
6787		producer.create_group(group::Info { sequence: 1 }).unwrap();
6788		assert!(
6789			consumer.recv_group().now_or_never().is_none(),
6790			"group 1 parked at the cap"
6791		);
6792
6793		consumer.start_at(2);
6794		consumer.set_groups(..);
6795		producer.create_group(group::Info { sequence: 2 }).unwrap();
6796		assert_eq!(
6797			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6798			2,
6799			"the overtaken parked group is dropped, not re-offered"
6800		);
6801	}
6802
6803	/// A parked group the producer aborts (eviction/expiry) is dropped: it is
6804	/// neither delivered once the cap rises nor allowed to hold the stream open
6805	/// after the track finishes. This is what bounds parking by the cache policy.
6806	#[tokio::test]
6807	async fn evicted_parked_recv_groups_are_dropped() {
6808		let producer = track_producer("test", None);
6809		let mut consumer = producer.subscribe(None);
6810
6811		producer.create_group(group::Info { sequence: 0 }).unwrap();
6812		assert_eq!(
6813			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6814			0
6815		);
6816
6817		consumer.set_groups(..1);
6818		let straggler = producer.create_group(group::Info { sequence: 1 }).unwrap();
6819		assert!(
6820			consumer.recv_group().now_or_never().is_none(),
6821			"group 1 parked at the cap"
6822		);
6823
6824		// The cache evicts the parked group (abort-as-tombstone), then the track ends.
6825		straggler.abort(Error::Old).unwrap();
6826		producer.finish().unwrap();
6827
6828		consumer.set_groups(..);
6829		assert!(
6830			matches!(consumer.recv_group().now_or_never(), Some(Ok(None))),
6831			"a dead parked group must not be delivered or hold the stream open"
6832		);
6833	}
6834
6835	/// Eviction aborts a parked group behind a sleeping subscriber's back. Nothing
6836	/// else will poll it (the track already finished), so the entry has to carry a
6837	/// waiter or the subscription sleeps forever holding its stream open.
6838	#[tokio::test]
6839	async fn evicted_parked_group_wakes_the_clean_end() {
6840		use std::sync::atomic::{AtomicUsize, Ordering};
6841		use std::task::{Context, Wake};
6842
6843		/// A waker that counts its wakes, for asserting a pending poll left a live
6844		/// registration behind.
6845		struct CountWaker(AtomicUsize);
6846		impl Wake for CountWaker {
6847			fn wake(self: std::sync::Arc<Self>) {
6848				self.0.fetch_add(1, Ordering::SeqCst);
6849			}
6850		}
6851
6852		let producer = track_producer("test", None);
6853		let mut consumer = producer.subscribe(None);
6854
6855		producer.create_group(group::Info { sequence: 0 }).unwrap();
6856		assert_eq!(
6857			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6858			0
6859		);
6860
6861		consumer.set_groups(..1);
6862		let straggler = producer.create_group(group::Info { sequence: 1 }).unwrap();
6863		assert!(consumer.recv_group().now_or_never().is_none(), "parked at the cap");
6864		producer.finish().unwrap();
6865
6866		let counter = std::sync::Arc::new(CountWaker(AtomicUsize::new(0)));
6867		let waker = std::task::Waker::from(counter.clone());
6868		let mut cx = Context::from_waker(&waker);
6869		let mut fut = std::pin::pin!(consumer.recv_group());
6870		assert!(
6871			fut.as_mut().poll(&mut cx).is_pending(),
6872			"the parked group holds it open"
6873		);
6874
6875		straggler.abort(Error::Old).unwrap();
6876		assert!(counter.0.load(Ordering::SeqCst) > 0, "the eviction wakeup was lost");
6877		assert!(matches!(fut.as_mut().poll(&mut cx), Poll::Ready(Ok(None))));
6878	}
6879
6880	/// An exclusive cap at 0 is the empty range: no group is delivered, even group 0.
6881	#[tokio::test]
6882	async fn end_at_zero_is_the_empty_range() {
6883		let producer = track_producer("test", None);
6884		let mut consumer = producer.subscribe(replay());
6885		producer.create_group(group::Info { sequence: 0 }).unwrap();
6886		producer.create_group(group::Info { sequence: 1 }).unwrap();
6887
6888		consumer.set_groups(..0);
6889		assert!(
6890			consumer.recv_group().now_or_never().is_none(),
6891			"empty cap delivers nothing"
6892		);
6893
6894		consumer.set_groups(..1);
6895		assert_eq!(
6896			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6897			0
6898		);
6899		assert!(consumer.recv_group().now_or_never().is_none(), "group 1 stays parked");
6900	}
6901
6902	/// A requested empty range plus a local empty cap still parks while another
6903	/// subscriber keeps aggregate demand unbounded.
6904	#[tokio::test]
6905	async fn empty_local_cap_holds_while_another_subscriber_requests_everything() {
6906		let producer = track_producer("test", None);
6907		let mut everything = producer.subscribe(replay());
6908		let mut empty = producer.subscribe(Subscription::default().with_end(Position::group(0)));
6909		empty.set_groups((Bound::Unbounded, Position::group(0).group_end()));
6910
6911		for s in 0..3 {
6912			producer.create_group(group::Info { sequence: s }).unwrap();
6913		}
6914
6915		assert_eq!(
6916			everything
6917				.recv_group()
6918				.now_or_never()
6919				.unwrap()
6920				.unwrap()
6921				.unwrap()
6922				.sequence,
6923			0
6924		);
6925		assert!(
6926			empty.recv_group().now_or_never().is_none(),
6927			"local empty cap must not ride the unbounded aggregate"
6928		);
6929
6930		empty.set_groups(..2);
6931		assert_eq!(empty.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence, 0);
6932		assert_eq!(empty.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence, 1);
6933		assert!(empty.recv_group().now_or_never().is_none(), "still capped at 2");
6934
6935		empty.set_groups(..);
6936		assert_eq!(empty.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence, 2);
6937	}
6938
6939	/// A frame-limited exclusive end includes the last group and stops before that frame.
6940	#[tokio::test]
6941	async fn end_at_frame_limited_last_group() {
6942		let producer = track_producer("test", None);
6943		let mut consumer = producer.subscribe(replay()).ordered();
6944		let end = Position::after(1, 1).unwrap();
6945		consumer.set_groups((Bound::Unbounded, end.group_end()));
6946
6947		for s in 0..3u64 {
6948			let mut group = producer.create_group(group::Info { sequence: s }).unwrap();
6949			for i in 0..3u8 {
6950				group.write_frame(Timestamp::ZERO, vec![i]).unwrap();
6951			}
6952			group.finish().unwrap();
6953		}
6954
6955		assert_eq!(
6956			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6957			0
6958		);
6959		let mut last = consumer.next_group().now_or_never().unwrap().unwrap().unwrap();
6960		assert_eq!(last.sequence, 1);
6961		last.set_frames(..end.frame);
6962		assert_eq!(
6963			last.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
6964			0
6965		);
6966		assert_eq!(
6967			last.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
6968			1
6969		);
6970		assert!(
6971			last.read_frame().now_or_never().unwrap().unwrap().is_none(),
6972			"frame cap is exclusive"
6973		);
6974		assert!(
6975			consumer.next_group().now_or_never().is_none(),
6976			"group 2 is past the exclusive group cap"
6977		);
6978	}
6979
6980	/// An inclusive bound at the last group withholds nothing.
6981	#[tokio::test]
6982	async fn end_at_maximum_group_is_unbounded() {
6983		let producer = track_producer("test", None);
6984		let mut consumer = producer.subscribe(replay()).ordered();
6985		consumer.set_groups(..=u64::MAX);
6986
6987		producer.create_group(group::Info { sequence: 0 }).unwrap();
6988		producer.create_group(group::Info { sequence: u64::MAX }).unwrap();
6989
6990		assert_eq!(
6991			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6992			0
6993		);
6994		assert_eq!(
6995			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
6996			u64::MAX
6997		);
6998	}
6999
7000	#[test]
7001	fn write_frame_rejects_an_oversized_frame_before_appending_its_group() {
7002		let mut producer = track_producer("test", None);
7003		let frame = bytes::Bytes::from(vec![0; group::MAX_CACHE_BYTES as usize + 1]);
7004
7005		assert!(matches!(
7006			producer.write_frame(Timestamp::ZERO, frame),
7007			Err(Error::FrameTooLarge)
7008		));
7009		assert_eq!(producer.latest(), None, "the rejected frame did not publish a group");
7010	}
7011
7012	#[test]
7013	fn append_group_returns_bounds_exceeded_on_sequence_overflow() {
7014		let producer = track_producer("test", None);
7015		{
7016			let mut state = producer.state.write().ok().unwrap();
7017			state.max_sequence = Some(u64::MAX);
7018		}
7019
7020		assert!(matches!(producer.append_group(), Err(Error::BoundsExceeded(_))));
7021	}
7022
7023	#[tokio::test]
7024	async fn fetch_cache_hit() {
7025		let producer = track_producer("test", None);
7026
7027		// Produce a cached group.
7028		let mut group = producer.append_group().unwrap(); // seq 0
7029		group
7030			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hello"))
7031			.unwrap();
7032		group.finish().unwrap();
7033
7034		// A cached group resolves immediately and never queues a request. `peek_group`
7035		// also returns it synchronously.
7036		let dynamic = producer.dynamic();
7037		let consumer = producer.consume();
7038		assert!(consumer.peek_group(0).is_some());
7039		let mut g = consumer.fetch_group(0, None).await.unwrap();
7040		assert_eq!(g.sequence, 0);
7041		assert_eq!(&g.read_frame().await.unwrap().unwrap().payload[..], b"hello");
7042
7043		// Nothing was queued for the dynamic handler to serve.
7044		assert!(dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending());
7045	}
7046
7047	#[tokio::test]
7048	async fn fetch_miss_signals_dynamic() {
7049		let producer = track_producer("test", None);
7050		let dynamic = producer.dynamic();
7051		let consumer = producer.consume();
7052
7053		// A cache miss isn't in `peek_group`, but a dynamic handler exists, so
7054		// `fetch_group` stays pending and queues a request. `*pending` derefs the
7055		// wrapper to the inner `Fetching` (a `kio::Pollable`).
7056		assert!(consumer.peek_group(5).is_none());
7057		let pending = consumer.fetch_group(5, group::Fetch::default().with_priority(7));
7058		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
7059
7060		let req = dynamic
7061			.requested_group()
7062			.now_or_never()
7063			.expect("should not block")
7064			.unwrap();
7065		assert_eq!(req.sequence(), 5);
7066		assert_eq!(req.priority(), 7);
7067
7068		// Serve it by accepting the request; the fetch then resolves.
7069		let mut group = req.accept(None).unwrap();
7070		group
7071			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi"))
7072			.unwrap();
7073		group.finish().unwrap();
7074
7075		let mut g = pending.await.unwrap();
7076		assert_eq!(g.sequence, 5);
7077		assert_eq!(&g.read_frame().await.unwrap().unwrap().payload[..], b"hi");
7078	}
7079
7080	#[tokio::test]
7081	async fn fetch_miss_rejects() {
7082		let producer = track_producer("test", None);
7083		let dynamic = producer.dynamic();
7084		let consumer = producer.consume();
7085
7086		let pending = consumer.fetch_group(5, None);
7087		let req = dynamic
7088			.requested_group()
7089			.now_or_never()
7090			.expect("should not block")
7091			.unwrap();
7092
7093		req.reject(Error::Cancel);
7094		assert!(matches!(pending.await, Err(Error::Cancel)));
7095		let fetch = producer.state.read().fetch.clone();
7096		assert!(fetch.read().is_empty());
7097	}
7098
7099	#[tokio::test]
7100	async fn fetch_miss_drop_rejects() {
7101		let producer = track_producer("test", None);
7102		let dynamic = producer.dynamic();
7103		let consumer = producer.consume();
7104
7105		let pending = consumer.fetch_group(5, None);
7106		let req = dynamic
7107			.requested_group()
7108			.now_or_never()
7109			.expect("should not block")
7110			.unwrap();
7111
7112		drop(req);
7113		assert!(matches!(pending.await, Err(Error::Dropped)));
7114	}
7115
7116	#[tokio::test]
7117	async fn fetch_reject_does_not_poison_retry() {
7118		let producer = track_producer("test", None);
7119		let dynamic = producer.dynamic();
7120		let consumer = producer.consume();
7121
7122		let pending = consumer.fetch_group(5, None);
7123		let req = dynamic
7124			.requested_group()
7125			.now_or_never()
7126			.expect("should not block")
7127			.unwrap();
7128		req.reject(Error::Cancel);
7129		assert!(matches!(pending.await, Err(Error::Cancel)));
7130
7131		let retry = consumer.fetch_group(5, None);
7132		let req = dynamic
7133			.requested_group()
7134			.now_or_never()
7135			.expect("should not block")
7136			.unwrap();
7137		let mut group = req.accept(None).unwrap();
7138		group
7139			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"retry"))
7140			.unwrap();
7141		group.finish().unwrap();
7142
7143		let mut group = retry.await.unwrap();
7144		assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"retry");
7145	}
7146
7147	/// A group cached from a frame-bounded subscription starts partway in. Serving it to
7148	/// someone who asked for the whole group would silently hand back a tail, so it is a
7149	/// miss and the fetch goes upstream instead.
7150	#[tokio::test]
7151	async fn fetch_ignores_a_group_that_starts_too_late() {
7152		let producer = track_producer("test", None);
7153		let dynamic = producer.dynamic();
7154		let consumer = producer.consume();
7155
7156		// The live subscription resumed mid-group, so only the tail is cached.
7157		let mut group = producer.create_group(group::Info { sequence: 0 }).unwrap();
7158		group.start_at(3).unwrap();
7159		group
7160			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"tail"))
7161			.unwrap();
7162		group.finish().unwrap();
7163
7164		// A fetch for the tail is covered and resolves from the cache.
7165		let fetch = consumer.fetch_group(0, group::Fetch::default().with_frame_start(3));
7166		let cached = fetch.now_or_never().expect("covered by the cache").unwrap();
7167		assert_eq!(cached.index(), 3);
7168
7169		// A fetch for the whole group is not, so it queues for a handler rather than
7170		// resolving to the tail.
7171		let mut fetch = std::pin::pin!(consumer.fetch_group(0, None));
7172		assert!(
7173			futures::poll!(fetch.as_mut()).is_pending(),
7174			"must not answer from the tail"
7175		);
7176
7177		let request = dynamic.requested_group().await.unwrap();
7178		assert_eq!((request.sequence(), request.frame_start()), (0, 0));
7179
7180		// Serving it replaces the too-narrow entry rather than colliding with it.
7181		let mut whole = request.accept(None).unwrap();
7182		whole
7183			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
7184			.unwrap();
7185		whole.finish().unwrap();
7186
7187		let mut served = fetch.await.unwrap();
7188		assert_eq!(served.index(), 0);
7189		assert_eq!(
7190			served.read_frame().await.unwrap().unwrap().payload,
7191			bytes::Bytes::from_static(b"head")
7192		);
7193	}
7194
7195	/// Joining a queued fetch widens its range, and a caller that arrives once the range
7196	/// is already on the wire fails cleanly rather than being handed the narrower group.
7197	#[tokio::test]
7198	async fn fetch_widens_or_fails_cleanly() {
7199		let producer = track_producer("test", None);
7200		let dynamic = producer.dynamic();
7201		let consumer = producer.consume();
7202
7203		let _narrow = consumer.fetch_group(0, group::Fetch::default().with_frame_start(5));
7204		let mut narrow = std::pin::pin!(_narrow);
7205		assert!(futures::poll!(narrow.as_mut()).is_pending());
7206
7207		// Still queued: widening is honored, because nothing has read the range yet.
7208		let _wider = consumer.fetch_group(0, group::Fetch::default().with_frame_start(2));
7209		let mut wider = std::pin::pin!(_wider);
7210		assert!(futures::poll!(wider.as_mut()).is_pending());
7211
7212		let request = dynamic.requested_group().await.unwrap();
7213		assert_eq!(request.frame_start(), 2, "widened while queued");
7214
7215		// Handed off now: the handler holds its own copy of the range, so a later wider
7216		// caller cannot move what is already being served.
7217		let _widest = consumer.fetch_group(0, group::Fetch::default().with_frame_start(0));
7218		let mut widest = std::pin::pin!(_widest);
7219		assert!(futures::poll!(widest.as_mut()).is_pending());
7220		assert_eq!(request.frame_start(), 2, "the in-flight range is already on the wire");
7221
7222		let mut group = request.accept(None).unwrap();
7223		// A handler numbers the frames from where it was asked to start, as `serve_fetch`
7224		// does; that offset is what makes the cached group too narrow for `widest`.
7225		group.start_at(2).unwrap();
7226		group
7227			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"from2"))
7228			.unwrap();
7229		group.finish().unwrap();
7230
7231		// The two callers it covers resolve; the one it doesn't fails cleanly rather
7232		// than being handed a group that starts above what it asked for.
7233		assert_eq!(narrow.await.unwrap().index(), 5);
7234		assert_eq!(wider.await.unwrap().index(), 2);
7235		assert!(matches!(widest.await, Err(Error::NotFound)));
7236	}
7237
7238	#[tokio::test]
7239	async fn fetch_coalesces_concurrent() {
7240		let producer = track_producer("test", None);
7241		let dynamic = producer.dynamic();
7242		let consumer = producer.consume();
7243
7244		// Two fetches for the same uncached group produce ONE handler request,
7245		// carrying the higher of the two priorities.
7246		let first = consumer.fetch_group(5, group::Fetch::default().with_priority(1));
7247		let second = consumer.fetch_group(5, group::Fetch::default().with_priority(7));
7248		assert!(kio::Pollable::poll(&*first, &kio::Waiter::noop()).is_pending());
7249
7250		let req = dynamic
7251			.requested_group()
7252			.now_or_never()
7253			.expect("should not block")
7254			.unwrap();
7255		assert_eq!(req.sequence(), 5);
7256		assert_eq!(req.priority(), 7);
7257		assert!(
7258			dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending(),
7259			"the second fetch queued a duplicate request"
7260		);
7261
7262		// A fetch arriving while the request is already in flight joins it too.
7263		let third = consumer.fetch_group(5, None);
7264
7265		// One accept resolves all of them.
7266		let mut group = req.accept(None).unwrap();
7267		group
7268			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi"))
7269			.unwrap();
7270		group.finish().unwrap();
7271
7272		assert_eq!(first.await.unwrap().sequence, 5);
7273		assert_eq!(second.await.unwrap().sequence, 5);
7274		assert_eq!(third.await.unwrap().sequence, 5);
7275	}
7276
7277	#[tokio::test]
7278	async fn fetch_coalesced_reject_fails_all() {
7279		let producer = track_producer("test", None);
7280		let dynamic = producer.dynamic();
7281		let consumer = producer.consume();
7282
7283		let first = consumer.fetch_group(5, None);
7284		let second = consumer.fetch_group(5, None);
7285		let req = dynamic
7286			.requested_group()
7287			.now_or_never()
7288			.expect("should not block")
7289			.unwrap();
7290		req.reject(Error::Cancel);
7291
7292		assert!(matches!(first.await, Err(Error::Cancel)));
7293		assert!(matches!(second.await, Err(Error::Cancel)));
7294
7295		// The rejected attempt is gone: a retry starts a fresh one.
7296		let retry = consumer.fetch_group(5, None);
7297		assert!(kio::Pollable::poll(&*retry, &kio::Waiter::noop()).is_pending());
7298		let req = dynamic
7299			.requested_group()
7300			.now_or_never()
7301			.expect("should not block")
7302			.unwrap();
7303		assert_eq!(req.sequence(), 5);
7304	}
7305
7306	#[tokio::test]
7307	async fn fetch_queued_fails_when_handlers_leave() {
7308		let producer = track_producer("test", None);
7309		let dynamic = producer.dynamic();
7310		let consumer = producer.consume();
7311
7312		// Queued but never popped: the last handler leaving fails it fast.
7313		let pending = consumer.fetch_group(5, None);
7314		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
7315		drop(dynamic);
7316		assert!(matches!(pending.await, Err(Error::NotFound)));
7317
7318		// And the attempt didn't leak.
7319		let fetch = producer.state.read().fetch.clone();
7320		assert!(fetch.read().is_empty());
7321	}
7322
7323	#[tokio::test]
7324	async fn fetch_miss_no_dynamic_not_found() {
7325		// A track with no `Dynamic` can't serve old content, so a cache miss
7326		// resolves to NotFound instead of blocking forever.
7327		let producer = track_producer("test", None);
7328		producer.append_group().unwrap(); // seq 0, but we miss on seq 5
7329		let consumer = producer.consume();
7330		assert!(matches!(consumer.fetch_group(5, None).await, Err(Error::NotFound)));
7331	}
7332
7333	#[tokio::test]
7334	async fn fetch_past_final_not_found() {
7335		let producer = track_producer("test", None);
7336		producer.append_group().unwrap(); // seq 0
7337		producer.finish().unwrap(); // final_sequence = 1
7338
7339		// A group at or past the final sequence can never exist, even with a handler,
7340		// so it resolves to NotFound.
7341		let dynamic = producer.dynamic();
7342		let consumer = producer.consume();
7343		assert!(matches!(consumer.fetch_group(5, None).await, Err(Error::NotFound)));
7344
7345		// And it doesn't signal the dynamic handler.
7346		assert!(dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending());
7347	}
7348
7349	/// Mint a track whose groups charge into a bounded [`cache::Pool`].
7350	fn pooled_producer(capacity: u64) -> (Producer, cache::Pool) {
7351		let config = cache::Config::default()
7352			.with_capacity(capacity)
7353			.with_expiry(cache::DEFAULT_EXPIRY);
7354		let pool = cache::Pool::new(config);
7355		let broadcast = broadcast::Info {
7356			pool: pool.clone(),
7357			..Default::default()
7358		};
7359		let producer = Producer::new(Arc::new(broadcast), "test", None);
7360		(producer, pool)
7361	}
7362
7363	fn finished_group(producer: &mut Producer, size: usize) -> u64 {
7364		let mut group = producer.append_group().unwrap();
7365		group
7366			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; size]))
7367			.unwrap();
7368		group.finish().unwrap();
7369		group.sequence
7370	}
7371
7372	/// While the pool is over capacity, every append accrues debt and pays it by
7373	/// evicting this track's own oldest groups, so the newest content survives.
7374	#[tokio::test]
7375	async fn debt_evicts_oldest_group() {
7376		// Fits one 10k group; each additional group pushes the pool over budget.
7377		let (mut producer, pool) = pooled_producer(10_000);
7378
7379		finished_group(&mut producer, 10_000); // seq 0
7380		finished_group(&mut producer, 10_000); // seq 1: over budget, debt starts accruing
7381		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
7382
7383		let consumer = producer.consume();
7384		assert!(consumer.peek_group(0).is_none(), "oldest group is evicted");
7385		assert!(consumer.peek_group(2).is_some(), "latest group survives");
7386		// Steady state carries the protected live edge plus the just-demoted group
7387		// (debt is charged before the demotion, so eviction lags one append).
7388		assert!(
7389			pool.used() <= 2 * (10_000 + cache::ENTRY_OVERHEAD),
7390			"usage hovers near capacity: {}",
7391			pool.used()
7392		);
7393
7394		// A fresh subscriber skips the evicted groups entirely.
7395		let mut subscriber = producer.subscribe(replay());
7396		assert!(subscriber.assert_group().sequence > 0, "evicted group is not delivered");
7397	}
7398
7399	/// The latest group is never in the eviction order, so it survives any budget.
7400	#[tokio::test]
7401	async fn latest_group_never_evicted() {
7402		// Far too small for even one group: the latest survives anyway.
7403		let (mut producer, pool) = pooled_producer(100);
7404		finished_group(&mut producer, 1000); // seq 0
7405		assert!(pool.used() > 100, "the latest may exceed the budget");
7406
7407		// Later writes evict the demoted seq 0; each new latest is untouchable in turn.
7408		finished_group(&mut producer, 1000); // seq 1: demotes seq 0
7409		finished_group(&mut producer, 1000); // seq 2: pays by evicting seq 0
7410
7411		let consumer = producer.consume();
7412		assert!(consumer.peek_group(0).is_none());
7413		let mut group = consumer.peek_group(2).expect("latest survives");
7414		assert_eq!(group.read_frame().await.unwrap().unwrap().payload.len(), 1000);
7415	}
7416
7417	/// A FETCH cache hit refreshes the group's access time: anything accessed more
7418	/// recently than the pool-wide average is protected, so the eviction walk skips
7419	/// it and evicts a never-read group instead, even one that arrived later.
7420	#[tokio::test]
7421	async fn fetch_refresh_survives_eviction() {
7422		let (mut producer, _pool) = pooled_producer(10_000);
7423		let consumer = producer.consume();
7424
7425		finished_group(&mut producer, 3_000); // seq 0
7426		crate::model::clock::advance(Duration::from_secs(1));
7427		finished_group(&mut producer, 3_000); // seq 1
7428		crate::model::clock::advance(Duration::from_secs(1));
7429		finished_group(&mut producer, 3_000); // seq 2
7430		crate::model::clock::advance(Duration::from_millis(500));
7431
7432		// FETCH seq 0: the cache hit lifts its access time above the average.
7433		let mut fetched = consumer.fetch_group(0, None).await.unwrap();
7434		assert_eq!(fetched.read_frame().await.unwrap().unwrap().payload.len(), 3_000);
7435		crate::model::clock::advance(Duration::from_millis(500));
7436
7437		// Pressure: seq 0 is first in eviction order but freshly accessed, so it
7438		// rotates to the back and the never-read seq 1 dies instead.
7439		finished_group(&mut producer, 3_000); // seq 3
7440		crate::model::clock::advance(Duration::from_secs(1));
7441		finished_group(&mut producer, 3_000); // seq 4
7442
7443		assert!(consumer.peek_group(0).is_some(), "refreshed group survives");
7444		assert!(consumer.peek_group(1).is_none(), "unread group is evicted instead");
7445	}
7446
7447	/// A consumer holding an evicted group surfaces the eviction, not a hang or a
7448	/// truncated clean end.
7449	#[tokio::test]
7450	async fn eviction_aborts_readers() {
7451		let (mut producer, _pool) = pooled_producer(10_000);
7452		let mut subscriber = producer.subscribe(None);
7453
7454		finished_group(&mut producer, 10_000); // seq 0
7455		let mut group0 = subscriber.assert_group();
7456
7457		finished_group(&mut producer, 10_000); // seq 1: demotes seq 0
7458		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
7459
7460		let read = group0.read_frame().await;
7461		assert!(matches!(read, Err(Error::Evicted)), "expected Evicted, got {read:?}");
7462	}
7463
7464	/// A write smaller than the next victim carries debt instead of evicting: a
7465	/// large group dies only once enough debt accumulates, never to pay off a
7466	/// far smaller write.
7467	#[tokio::test]
7468	async fn small_writes_carry_debt() {
7469		// Payloads dwarf the fixed per-group charge, so the budget arithmetic below is
7470		// about bytes written rather than bookkeeping.
7471		let unit = 100 * cache::ENTRY_OVERHEAD;
7472		let (mut producer, pool) = pooled_producer(22 * unit);
7473		let consumer = producer.consume();
7474
7475		finished_group(&mut producer, 20 * unit as usize); // seq 0, the large victim-to-be
7476
7477		// The first few small writes owe far less than seq 0's size: the debt
7478		// carries over instead of evicting it.
7479		for _ in 0..3 {
7480			finished_group(&mut producer, unit as usize);
7481		}
7482		assert!(consumer.peek_group(0).is_some(), "debt smaller than the victim carries");
7483
7484		// Enough small writes accumulate the debt to finally evict it.
7485		for _ in 0..20 {
7486			finished_group(&mut producer, unit as usize);
7487		}
7488		assert!(
7489			consumer.peek_group(0).is_none(),
7490			"accumulated debt evicts the large group"
7491		);
7492		// Steady state hovers within about one group of capacity: a victim smaller
7493		// than the outstanding debt is never evicted, so the excess stays bounded.
7494		assert!(pool.used() <= 24 * unit, "usage hovers near capacity: {}", pool.used());
7495	}
7496
7497	/// One write pays at most twice what it produced, so a capacity shrink (or one
7498	/// track's burst) drains gradually instead of one writer dumping its whole
7499	/// backlog in a single call.
7500	#[tokio::test]
7501	async fn payment_capped_per_write() {
7502		let (mut producer, pool) = pooled_producer(1 << 40);
7503		for _ in 0..10 {
7504			finished_group(&mut producer, 1_000);
7505		}
7506
7507		// The governor slashes the target; nothing is reclaimed synchronously.
7508		pool.resize(100);
7509		let before = pool.used();
7510
7511		// One 1k write may evict at most ~2k of backlog, not all ten groups.
7512		finished_group(&mut producer, 1_000);
7513
7514		let consumer = producer.consume();
7515		assert!(consumer.peek_group(0).is_none(), "the oldest groups are evicted");
7516		assert!(consumer.peek_group(1).is_none());
7517		assert!(consumer.peek_group(2).is_some(), "the backlog drains gradually");
7518		assert!(pool.used() > before - 4_000, "one write must not dump the backlog");
7519	}
7520
7521	/// Accepting a track after pre-accept backfill must keep the same write
7522	/// counter: the counter is owned by the track state, so replacing the info
7523	/// can't strand the bytes already-created groups keep charging.
7524	#[tokio::test]
7525	async fn accept_preserves_write_accounting() {
7526		let config = cache::Config::default()
7527			.with_capacity(12_000)
7528			.with_expiry(cache::DEFAULT_EXPIRY);
7529		let pool = cache::Pool::new(config);
7530		let broadcast = broadcast::Info {
7531			pool: pool.clone(),
7532			..Default::default()
7533		};
7534		let request = Request::new(Arc::new(broadcast), "test");
7535		let dynamic = request.dynamic();
7536		let consumer = request.consume();
7537
7538		// Serve a backfill before the track is accepted, then grow it.
7539		let pending = consumer.fetch_group(0, None);
7540		let req = dynamic
7541			.requested_group()
7542			.now_or_never()
7543			.expect("should not block")
7544			.unwrap();
7545		let mut backfill = req.accept(None).unwrap();
7546		pending.await.unwrap();
7547		backfill
7548			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 30_000]))
7549			.unwrap();
7550
7551		// Accept with a fresh Info: the pre-accept group's writes must still be
7552		// drained by this track's future charges.
7553		let producer = request.accept(None);
7554		producer.append_group().unwrap().finish().unwrap();
7555		producer.append_group().unwrap().finish().unwrap();
7556
7557		assert!(
7558			producer.consume().peek_group(0).is_none(),
7559			"pre-accept backfill growth is reclaimed after accept"
7560		);
7561		assert!(pool.used() <= 13_000, "usage converges: {}", pool.used());
7562	}
7563
7564	/// Re-serving a sequence many times must not accumulate eviction hints: stale
7565	/// hints die on stamp mismatch and compaction reclaims them.
7566	#[tokio::test]
7567	async fn recreated_sequence_bounds_eviction_hints() {
7568		let (producer, _pool) = pooled_producer(1 << 40);
7569		producer.create_group(5u64.into()).unwrap().finish().unwrap();
7570
7571		for _ in 0..200 {
7572			let group = producer.create_group(1u64.into()).unwrap();
7573			group.abort(Error::Cancel).unwrap();
7574		}
7575
7576		let state = producer.state.read();
7577		assert!(
7578			state.evict.len() <= 2 * state.lookup.len() + EVICT_SLACK,
7579			"stale hints are compacted: {} entries for {} slots",
7580			state.evict.len(),
7581			state.lookup.len()
7582		);
7583	}
7584
7585	/// A frame write within the same coarse tick still outranks merely-inserted
7586	/// content, so the freshly-written group survives and the empty one pays.
7587	#[tokio::test]
7588	async fn same_tick_write_outranks_inserted() {
7589		// Payloads dwarf the fixed per-group charge, so the budget arithmetic below is
7590		// about bytes written rather than bookkeeping.
7591		let unit = 100 * cache::ENTRY_OVERHEAD;
7592		// No time advances: every stamp lands in the same tick.
7593		let (mut producer, _pool) = pooled_producer(10 * unit);
7594
7595		producer.append_group().unwrap().finish().unwrap(); // seq 0: empty
7596		finished_group(&mut producer, 3 * unit as usize); // seq 1: written
7597		finished_group(&mut producer, 3 * unit as usize); // seq 2
7598		finished_group(&mut producer, 3 * unit as usize); // seq 3
7599		finished_group(&mut producer, 3 * unit as usize); // seq 4: over budget, pays
7600
7601		let consumer = producer.consume();
7602		assert!(consumer.peek_group(0).is_none(), "insert-only content pays first");
7603		assert!(consumer.peek_group(1).is_some(), "same-tick written content survives");
7604	}
7605
7606	/// A track that only appends frames to an open group, never inserting another
7607	/// group, still settles its eviction debt once enough bytes accumulate.
7608	#[tokio::test]
7609	async fn frame_only_writer_pays() {
7610		let (producer, pool) = pooled_producer(2_000);
7611		let mut demoted = producer.append_group().unwrap(); // seq 0
7612		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
7613
7614		// One large frame crosses the charge threshold: the write itself pays,
7615		// with no further group insert on this track.
7616		demoted
7617			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 300_000]))
7618			.unwrap();
7619
7620		assert!(
7621			pool.used() <= 5_000,
7622			"the frame write settled the debt: {}",
7623			pool.used()
7624		);
7625		assert!(matches!(demoted.finish(), Err(Error::Evicted)));
7626	}
7627
7628	/// One `Info` describing several tracks must not join their eviction accounting:
7629	/// each track opens its own account against the pool.
7630	#[tokio::test]
7631	async fn each_track_owns_its_account() {
7632		let broadcast = Arc::new(broadcast::Info::default());
7633		let info = Info::default();
7634		let a = Producer::new(broadcast.clone(), "a", info.clone());
7635		let b = Producer::new(broadcast, "b", info);
7636
7637		let a = a.state.read().cache.clone();
7638		let b = b.state.read().cache.clone();
7639		assert!(!Arc::ptr_eq(&a, &b), "each track owns its account");
7640	}
7641
7642	/// A `Dynamic` still serving fetches keeps the track alive, so the publisher
7643	/// letting go isn't an abrupt teardown: the handler can still serve the cache.
7644	#[tokio::test]
7645	async fn a_dynamic_defers_teardown() {
7646		let (mut producer, pool) = pooled_producer(1 << 40);
7647		let dynamic = producer.dynamic();
7648		finished_group(&mut producer, 100);
7649
7650		drop(producer);
7651		assert!(pool.used() > 0, "the handler still serves the cache");
7652
7653		drop(dynamic);
7654		assert_eq!(pool.used(), 0, "the last handle tears it down");
7655	}
7656
7657	/// A finished track releases everything once every handle is gone.
7658	///
7659	/// Its groups hold the cache account, and the account links back here, so that link
7660	/// has to be weak: anything stronger makes the state (and every cached frame in it)
7661	/// immortal, even with no producer or consumer left.
7662	#[tokio::test]
7663	async fn finished_track_frees_its_cache() {
7664		let (mut producer, pool) = pooled_producer(1 << 40);
7665		finished_group(&mut producer, 100);
7666		producer.finish().unwrap();
7667
7668		let state = producer.state.downgrade();
7669		drop(producer);
7670
7671		assert!(state.upgrade().is_none(), "the track state is freed");
7672		assert_eq!(pool.used(), 0, "so are its cached bytes");
7673	}
7674
7675	/// A group settling its eviction debt upgrades the account's weak handle, which
7676	/// counts as a producer on the track state. Teardown must not mistake that for a
7677	/// surviving publisher, or an abrupt drop silently behaves like a clean finish.
7678	#[tokio::test]
7679	async fn teardown_ignores_a_settling_group() {
7680		let (mut producer, pool) = pooled_producer(1 << 40);
7681		finished_group(&mut producer, 100);
7682
7683		// Stand in for a concurrent `cache::Track::settle`, mid-upgrade.
7684		let settling = producer.state.downgrade().upgrade().expect("open");
7685		drop(producer);
7686
7687		assert_eq!(pool.used(), 0, "the abrupt teardown still released the cache");
7688		drop(settling);
7689	}
7690
7691	/// A subscriber holding one cached group must not pin the whole track: a group
7692	/// carries the track's properties by value, not a handle back to its state.
7693	#[tokio::test]
7694	async fn cached_group_outlives_its_track() {
7695		let (mut producer, pool) = pooled_producer(1 << 40);
7696		let sequence = finished_group(&mut producer, 100);
7697		let group = producer.consume().peek_group(sequence).expect("cached");
7698		producer.finish().unwrap();
7699
7700		let state = producer.state.downgrade();
7701		drop(producer);
7702		assert!(state.upgrade().is_none(), "the track state is freed");
7703		assert!(pool.used() > 0, "the retained group keeps its own bytes");
7704
7705		drop(group);
7706		assert_eq!(pool.used(), 0, "which it releases when dropped");
7707	}
7708
7709	/// A backfill served before the track was accepted settles its own debt: the
7710	/// account exists from the moment the state does, so acceptance replacing the
7711	/// `Info` can't leave already-created groups writing for free.
7712	#[tokio::test]
7713	async fn pre_accept_backfill_settles_late_writes() {
7714		let config = cache::Config::default()
7715			.with_capacity(2_000)
7716			.with_expiry(cache::DEFAULT_EXPIRY);
7717		let pool = cache::Pool::new(config);
7718		let broadcast = broadcast::Info {
7719			pool: pool.clone(),
7720			..Default::default()
7721		};
7722		let request = Request::new(Arc::new(broadcast), "test");
7723		let dynamic = request.dynamic();
7724		let consumer = request.consume();
7725
7726		// Serve backfill seq 0 before the track is accepted.
7727		let pending = consumer.fetch_group(0, None);
7728		let req = dynamic
7729			.requested_group()
7730			.now_or_never()
7731			.expect("should not block")
7732			.unwrap();
7733		let mut backfill = req.accept(None).unwrap();
7734		pending.await.unwrap();
7735
7736		// Accept, then demote the backfill with a live group.
7737		let producer = request.accept(None);
7738		producer.append_group().unwrap().finish().unwrap();
7739
7740		// No further insert: the late write into the demoted backfill is the only
7741		// thing that can pay the debt it just took on.
7742		backfill
7743			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 300_000]))
7744			.unwrap();
7745
7746		assert!(
7747			pool.used() <= 5_000,
7748			"the frame write settled the debt: {}",
7749			pool.used()
7750		);
7751	}
7752
7753	/// A late frame write restarts the LRU clock (the window measures time since
7754	/// last written or fetched), so an actively-growing group is not expired as
7755	/// idle mid-write.
7756	#[tokio::test]
7757	async fn write_restarts_retention_clock() {
7758		let (producer, _pool) = pooled_producer(1 << 40);
7759		let mut straggler = producer.append_group().unwrap(); // seq 0
7760		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
7761
7762		// Idle past the window, then the straggler receives a late frame.
7763		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
7764		straggler
7765			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
7766			.unwrap();
7767		producer.append_group().unwrap().finish().unwrap(); // seq 2 runs expiry
7768
7769		let consumer = producer.consume();
7770		assert!(consumer.peek_group(0).is_some(), "the write restarted the clock");
7771
7772		// Once the writes stop, the group ages out normally.
7773		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
7774		producer.append_group().unwrap().finish().unwrap(); // seq 3 runs expiry
7775		assert!(consumer.peek_group(0).is_none(), "idle content still expires");
7776	}
7777
7778	/// Continuously refreshed entries at the front of the eviction order must not
7779	/// starve expiry of entries behind them: the scan cursor rotates.
7780	#[tokio::test]
7781	async fn refreshed_front_does_not_starve_expiry() {
7782		let (producer, _pool) = pooled_producer(1 << 40);
7783		let dynamic = producer.dynamic();
7784		let consumer = producer.consume();
7785
7786		producer.create_group(10u64.into()).unwrap().finish().unwrap();
7787		for sequence in 1..=5u64 {
7788			let pending = consumer.fetch_group(sequence, None);
7789			let req = dynamic
7790				.requested_group()
7791				.now_or_never()
7792				.expect("should not block")
7793				.unwrap();
7794			let mut group = req.accept(None).unwrap();
7795			group
7796				.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
7797				.unwrap();
7798			group.finish().unwrap();
7799			pending.await.unwrap();
7800		}
7801
7802		// Age everything out, then refresh the first four backfills so they sit
7803		// fresh at the front of the eviction order, hiding the expired fifth.
7804		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
7805		for sequence in 1..=4u64 {
7806			consumer.fetch_group(sequence, None).await.unwrap();
7807		}
7808
7809		// The rotating cursor reaches the fifth entry within a few writes.
7810		for _ in 0..3 {
7811			producer.append_group().unwrap().finish().unwrap();
7812		}
7813		assert!(consumer.peek_group(5).is_none(), "expired backfill is reclaimed");
7814		assert!(consumer.peek_group(1).is_some(), "refreshed backfill survives");
7815	}
7816
7817	/// A publisher re-creating an aborted sequence is delivered exactly once, at
7818	/// its actual arrival position: the historical arrival entry is dead.
7819	#[tokio::test]
7820	async fn recreated_sequence_delivered_once() {
7821		let (producer, _pool) = pooled_producer(1 << 40);
7822
7823		producer.create_group(0u64.into()).unwrap().finish().unwrap();
7824		let aborted = producer.create_group(1u64.into()).unwrap();
7825		aborted.abort(Error::Cancel).unwrap();
7826		producer.create_group(2u64.into()).unwrap().finish().unwrap();
7827		producer.create_group(1u64.into()).unwrap().finish().unwrap();
7828
7829		let mut subscriber = producer.subscribe(replay());
7830		assert_eq!(subscriber.assert_group().sequence, 0);
7831		assert_eq!(subscriber.assert_group().sequence, 2);
7832		assert_eq!(
7833			subscriber.assert_group().sequence,
7834			1,
7835			"replacement arrives at its own position"
7836		);
7837		subscriber.assert_no_group();
7838	}
7839
7840	/// Datagrams share `max_sequence` but must not break group demotion: the live
7841	/// edge is tracked per group, so interleaving datagrams can't strand groups
7842	/// outside the eviction order and bypass the budget.
7843	#[tokio::test]
7844	async fn datagrams_do_not_block_eviction() {
7845		let (mut producer, pool) = pooled_producer(1_000);
7846		for _ in 0..10 {
7847			finished_group(&mut producer, 1_000);
7848			producer.append_datagram(Timestamp::ZERO, &b"beat"[..]).unwrap();
7849		}
7850
7851		let consumer = producer.consume();
7852		assert!(consumer.peek_group(0).is_none(), "old groups still evict");
7853		assert!(
7854			pool.used() < 4 * 1_256,
7855			"interleaved datagrams must not bypass the budget: {}",
7856			pool.used()
7857		);
7858	}
7859
7860	/// An aborted group releases its access sample along with its bytes, from any
7861	/// handle: ghost samples must not linger in the pool mean where they'd hold it
7862	/// in the past and over-protect every live group.
7863	#[tokio::test]
7864	async fn aborted_group_leaves_no_ghost_sample() {
7865		let (producer, pool) = pooled_producer(1 << 40);
7866		let group0 = producer.append_group().unwrap();
7867		producer.append_group().unwrap(); // demotes seq 0 into the mean
7868
7869		assert!(pool.average().is_some(), "demoted group is sampled");
7870		group0.abort(Error::Cancel).unwrap();
7871		assert_eq!(pool.average(), None, "the abort must remove the sample");
7872	}
7873
7874	/// Empty groups still carry fixed overhead; they must repay the budget when
7875	/// evicted rather than being unevictable freeloaders.
7876	#[tokio::test]
7877	async fn empty_groups_repay_overhead() {
7878		let (producer, pool) = pooled_producer(1_000);
7879		for _ in 0..100 {
7880			let group = producer.append_group().unwrap();
7881			group.finish().unwrap();
7882		}
7883
7884		assert!(
7885			pool.used() <= 3_000,
7886			"empty-group overhead must stay near the budget: {}",
7887			pool.used()
7888		);
7889	}
7890
7891	/// Late growth on an already-demoted group is billed: the gross-write counter
7892	/// feeds debt on the next append, so a straggler can't grow unbounded.
7893	#[tokio::test]
7894	async fn growth_on_demoted_group_is_billed() {
7895		let (producer, pool) = pooled_producer(2_000);
7896		let mut straggler = producer.append_group().unwrap(); // seq 0
7897		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
7898
7899		// The demoted group balloons: no eviction yet (nothing ran), but billed.
7900		straggler
7901			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 10_000]))
7902			.unwrap();
7903
7904		// The next append observes the growth and evicts the straggler.
7905		producer.append_group().unwrap().finish().unwrap(); // seq 2
7906
7907		let consumer = producer.consume();
7908		assert!(consumer.peek_group(0).is_none(), "the ballooned group is evicted");
7909		assert!(pool.used() <= 3_000, "growth is reclaimed: {}", pool.used());
7910	}
7911
7912	/// A stale arrival entry whose sequence was later re-served by fetched backfill
7913	/// must not leak the replacement into arrival-order subscriptions.
7914	#[tokio::test]
7915	async fn refilled_sequence_stays_out_of_subscriptions() {
7916		let (producer, _pool) = pooled_producer(1 << 40);
7917		let dynamic = producer.dynamic();
7918		let consumer = producer.consume();
7919
7920		producer.create_group(0u64.into()).unwrap().finish().unwrap();
7921		let aborted = producer.create_group(1u64.into()).unwrap();
7922		aborted.abort(Error::Cancel).unwrap();
7923		producer.create_group(2u64.into()).unwrap().finish().unwrap();
7924
7925		// Re-serve seq 1 as backfill; its slot replaces the aborted one, and the
7926		// old arrival entry for seq 1 now resolves to it.
7927		let pending = consumer.fetch_group(1, None);
7928		let req = dynamic
7929			.requested_group()
7930			.now_or_never()
7931			.expect("should not block")
7932			.unwrap();
7933		let mut group = req.accept(None).unwrap();
7934		group
7935			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill"))
7936			.unwrap();
7937		group.finish().unwrap();
7938		pending.await.unwrap();
7939
7940		// The backfill serves by sequence, but never in arrival order.
7941		assert!(consumer.peek_group(1).is_some());
7942		let mut subscriber = producer.subscribe(replay());
7943		assert_eq!(subscriber.assert_group().sequence, 0);
7944		assert_eq!(subscriber.assert_group().sequence, 2);
7945		subscriber.assert_no_group();
7946	}
7947
7948	/// An expired backfill can't hide behind a refreshed one: the eviction-order
7949	/// expiry scans a bounded prefix instead of stopping at the first fresh entry.
7950	#[tokio::test]
7951	async fn expired_backfill_behind_refreshed_reclaimed() {
7952		let (producer, _pool) = pooled_producer(1 << 40);
7953		let dynamic = producer.dynamic();
7954		let consumer = producer.consume();
7955
7956		producer.create_group(5u64.into()).unwrap().finish().unwrap();
7957		for sequence in [2u64, 3u64] {
7958			let pending = consumer.fetch_group(sequence, None);
7959			let req = dynamic
7960				.requested_group()
7961				.now_or_never()
7962				.expect("should not block")
7963				.unwrap();
7964			let mut group = req.accept(None).unwrap();
7965			group
7966				.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
7967				.unwrap();
7968			group.finish().unwrap();
7969			pending.await.unwrap();
7970		}
7971
7972		// Keep seq 2 fresh while seq 3 (behind it in eviction order) expires.
7973		crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2 + Duration::from_secs(1));
7974		consumer.fetch_group(2, None).await.unwrap();
7975		crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2 + Duration::from_secs(1));
7976		producer.create_group(6u64.into()).unwrap().finish().unwrap();
7977
7978		let consumer = producer.consume();
7979		assert!(consumer.peek_group(2).is_some(), "refreshed backfill survives");
7980		assert!(consumer.peek_group(3).is_none(), "expired backfill is reclaimed");
7981	}
7982
7983	/// A FETCH hit within the same coarse clock tick still protects the group: the
7984	/// refresh stamps one tick ahead, so it reads strictly newer than the mean.
7985	#[tokio::test]
7986	async fn same_tick_fetch_protects() {
7987		// No time advances at all: every timestamp lands in the same tick.
7988		let (mut producer, _pool) = pooled_producer(10_000);
7989		let consumer = producer.consume();
7990
7991		finished_group(&mut producer, 3_000); // seq 0
7992		finished_group(&mut producer, 3_000); // seq 1
7993		finished_group(&mut producer, 3_000); // seq 2
7994
7995		consumer.fetch_group(0, None).await.unwrap();
7996
7997		finished_group(&mut producer, 3_000); // seq 3
7998		finished_group(&mut producer, 3_000); // seq 4
7999
8000		assert!(consumer.peek_group(0).is_some(), "same-tick refresh protects");
8001		assert!(consumer.peek_group(1).is_none(), "the unread group dies instead");
8002	}
8003
8004	/// A refetched group that reclaims max_sequence is the live edge again: it must
8005	/// not re-enter the eviction order, or memory pressure could evict the newest
8006	/// content.
8007	#[tokio::test]
8008	async fn refetched_latest_stays_protected() {
8009		let (producer, _pool) = pooled_producer(10_000);
8010		let dynamic = producer.dynamic();
8011		let consumer = producer.consume();
8012
8013		let straggler = producer.append_group().unwrap(); // seq 0
8014
8015		// The publisher aborts its own latest group; the sequence stays at the live edge.
8016		let latest = producer.append_group().unwrap(); // seq 1
8017		latest.abort(Error::Cancel).unwrap();
8018
8019		// Re-fetch it: the replacement takes over max_sequence.
8020		let pending = consumer.fetch_group(1, None);
8021		let req = dynamic
8022			.requested_group()
8023			.now_or_never()
8024			.expect("should not block")
8025			.unwrap();
8026		let mut group = req.accept(None).unwrap();
8027		group
8028			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000]))
8029			.unwrap();
8030		group.finish().unwrap();
8031		pending.await.unwrap();
8032
8033		// The refetched latest is protected by omission: it has no entry in the
8034		// eviction order, so no amount of debt can select it.
8035		{
8036			let state = producer.state.read();
8037			assert!(state.lookup.contains_key(&1), "refetched group is cached");
8038			assert!(
8039				state.evict.iter().all(|(sequence, _)| *sequence != 1),
8040				"the live edge must not be an eviction candidate"
8041			);
8042		}
8043		drop(straggler);
8044	}
8045
8046	/// An evicted group is a cache miss, so a fetch re-fetches it and the accepted
8047	/// replacement serves the sequence again (not `Error::Duplicate`).
8048	#[tokio::test]
8049	async fn eviction_allows_refetch() {
8050		let (mut producer, _pool) = pooled_producer(10_000);
8051		let dynamic = producer.dynamic();
8052
8053		finished_group(&mut producer, 10_000); // seq 0
8054		finished_group(&mut producer, 10_000); // seq 1: demotes seq 0
8055		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
8056
8057		let consumer = producer.consume();
8058		assert!(consumer.peek_group(0).is_none());
8059		let pending = consumer.fetch_group(0, None);
8060
8061		let req = dynamic
8062			.requested_group()
8063			.now_or_never()
8064			.expect("should not block")
8065			.unwrap();
8066		assert_eq!(req.sequence(), 0);
8067
8068		let mut group = req.accept(None).unwrap();
8069		group
8070			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"refetched"))
8071			.unwrap();
8072		group.finish().unwrap();
8073
8074		let mut group = pending.await.unwrap();
8075		assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"refetched");
8076	}
8077
8078	/// An aborted group is dead whatever frame it starts at: the sequence is claimable
8079	/// again, and a cache lookup misses rather than handing back a slot that can no
8080	/// longer serve it.
8081	///
8082	/// The abort and the group's first frame decide this together, so they are read
8083	/// under one guard. Read separately, the abort can land between them and the slot
8084	/// answers as a live duplicate on the strength of an offset it only still has
8085	/// because it died. That interleaving is what the single guard rules out; this
8086	/// pins the committed semantics it has to preserve.
8087	#[test]
8088	fn an_aborted_group_releases_its_sequence() {
8089		let producer = track_producer("test", None);
8090		let consumer = producer.consume();
8091
8092		let mut group = producer.create_group(group::Info { sequence: 3 }).unwrap();
8093		group
8094			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
8095			.unwrap();
8096
8097		// While it lives the slot answers, so the sequence is taken.
8098		assert!(matches!(
8099			producer.create_group(group::Info { sequence: 3 }),
8100			Err(Error::Duplicate)
8101		));
8102		assert!(consumer.peek_group(3).is_some());
8103
8104		group.abort(Error::Cancel).unwrap();
8105
8106		assert!(consumer.peek_group(3).is_none(), "an aborted slot is a cache miss");
8107		producer
8108			.create_group(group::Info { sequence: 3 })
8109			.expect("an aborted slot releases its sequence")
8110			.finish()
8111			.unwrap();
8112	}
8113
8114	/// A fetched (backfill) group is served by sequence but never replayed to
8115	/// arrival-order subscribers.
8116	#[tokio::test]
8117	async fn fetched_backfill_not_subscribed() {
8118		let (producer, _pool) = pooled_producer(1 << 40);
8119		let dynamic = producer.dynamic();
8120		let consumer = producer.consume();
8121
8122		// The publisher starts at seq 5; earlier groups exist only upstream.
8123		producer.create_group(5u64.into()).unwrap().finish().unwrap();
8124		producer.create_group(6u64.into()).unwrap().finish().unwrap();
8125
8126		// Fetch the gap: it lands in the cache and resolves the fetch...
8127		let pending = consumer.fetch_group(2, None);
8128		let req = dynamic
8129			.requested_group()
8130			.now_or_never()
8131			.expect("should not block")
8132			.unwrap();
8133		let mut group = req.accept(None).unwrap();
8134		group
8135			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill"))
8136			.unwrap();
8137		group.finish().unwrap();
8138		let mut fetched = pending.await.unwrap();
8139		assert_eq!(&fetched.read_frame().await.unwrap().unwrap().payload[..], b"backfill");
8140		assert!(consumer.peek_group(2).is_some(), "backfill is cached for later fetches");
8141
8142		// ...but an arrival-order subscriber only sees the live groups.
8143		let mut subscriber = producer.subscribe(replay());
8144		assert_eq!(subscriber.assert_group().sequence, 5);
8145		assert_eq!(subscriber.assert_group().sequence, 6);
8146		subscriber.assert_no_group();
8147	}
8148
8149	/// Fetched backfill isn't in arrival order, so it ages out through the eviction
8150	/// order instead of lingering until the track closes.
8151	#[tokio::test]
8152	async fn expired_backfill_reclaimed() {
8153		let (producer, pool) = pooled_producer(1 << 40);
8154		let dynamic = producer.dynamic();
8155		let consumer = producer.consume();
8156
8157		producer.create_group(5u64.into()).unwrap().finish().unwrap();
8158
8159		// Serve a backfill fetch for an old sequence.
8160		let pending = consumer.fetch_group(2, None);
8161		let req = dynamic
8162			.requested_group()
8163			.now_or_never()
8164			.expect("should not block")
8165			.unwrap();
8166		let mut group = req.accept(None).unwrap();
8167		group
8168			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000]))
8169			.unwrap();
8170		group.finish().unwrap();
8171		pending.await.unwrap();
8172		let used = pool.used();
8173
8174		// Age past the pool's LRU window; the next write reclaims the backfill.
8175		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
8176		producer.create_group(6u64.into()).unwrap().finish().unwrap();
8177
8178		assert!(consumer.peek_group(2).is_none(), "expired backfill is reclaimed");
8179		assert!(pool.used() < used, "its bytes are released");
8180	}
8181
8182	#[tokio::test]
8183	async fn fetch_aborts_with_track() {
8184		let producer = track_producer("test", None);
8185		let dynamic = producer.dynamic();
8186		let consumer = producer.consume();
8187
8188		let pending = consumer.fetch_group(3, None);
8189		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
8190
8191		producer.abort(Error::Cancel).unwrap();
8192		assert!(pending.await.is_err());
8193		drop(dynamic);
8194	}
8195}