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;
22use super::resume::Successor;
23pub use super::subscription::{Position, Subscription};
24
25use std::{
26	collections::{BTreeMap, HashSet, VecDeque},
27	ops::{Bound, RangeBounds},
28	sync::Arc,
29	sync::OnceLock,
30	sync::atomic::{AtomicBool, Ordering},
31	task::{Poll, ready},
32	time::Duration,
33};
34
35/// Default [`Info::max_age`] when the publisher doesn't set one.
36pub const DEFAULT_MAX_AGE: Duration = Duration::from_secs(5);
37
38// The higher-first midpoint. IETF flips priority (lower first), so this goes out as 128, the
39// draft's usual publisher priority, while moq-lite carries 127 as written: one urgency on both.
40const DEFAULT_PRIORITY: u8 = 127;
41
42/// Maximum number of datagrams retained in the per-track send buffer.
43///
44/// Datagrams are a best-effort send buffer, not a replay cache (unlike groups): only the last
45/// 64 datagrams are kept, so a stalled consumer cannot retain an unbounded backlog.
46/// The payload size limit also bounds the buffer's memory use.
47const MAX_DATAGRAMS: usize = 64;
48
49/// Slack before the eviction order is rebuilt, so a track holding just a few groups
50/// doesn't rebuild on every write.
51const EVICT_SLACK: usize = 64;
52
53/// How many live eviction candidates one debt payment examines (Redis-style
54/// bounded sampling): enough to step over a few protected (recently accessed)
55/// groups, small enough that a write never scans a long queue.
56const EVICT_SCAN: usize = 4;
57
58/// One pass over the eviction order at a fixed cache time.
59#[derive(Clone, Copy)]
60pub(super) struct ExpiryScan {
61	start: usize,
62	// Ceiling on how many entries this pass examines. `EVICT_SCAN` from the write
63	// path, the whole queue from the pool's cleanup pass.
64	width: usize,
65	now: u64,
66	max_ticks: u64,
67	// Cleanup dates pending activity; write-driven scans only check dated entries.
68	gc: bool,
69}
70
71/// Publisher-side properties of a track.
72///
73/// These are fixed by the publisher when the track is created and don't change
74/// while the track is alive. A subscriber learns them via
75/// [`broadcast::Consumer::track`](broadcast::Consumer::track),
76/// which returns the publisher's [`Info`] once the subscription is accepted.
77//
78// Deliberately not `Copy`, even though it's now a plain value: adding `Copy` turns
79// every existing `info.clone()` in a consumer's code into a `clippy::clone_on_copy`
80// error under `-D warnings`.
81#[derive(Clone, Debug)]
82#[non_exhaustive]
83pub struct Info {
84	/// Units per second for per-frame timestamps on this track.
85	///
86	/// Every track is timed; this defaults to [`Timescale::MILLI`]. On Lite05+ it is
87	/// reported in TRACK_INFO and the publisher zigzag-delta encodes per-frame
88	/// timestamps at this scale on the wire. Protocols whose wire can't carry it
89	/// (pre-Lite05 moq-lite, IETF moq-transport) fall back to local monotonic milliseconds.
90	pub timescale: Timescale,
91	/// How far behind the live edge a group may fall, in media timestamps, before it
92	/// is stale. The newest group is always retained.
93	///
94	/// A retention bound rather than a delivery one, the inverse of an HTTP
95	/// `Cache-Control: max-age`. [`Subscription::max_age`] is clamped to this, since a
96	/// group can't be waited for longer than it's kept around. Reported in TRACK_INFO so
97	/// relays re-serve with the same window. Defaults to [`DEFAULT_MAX_AGE`].
98	///
99	/// Measured against timestamps rather than the wall clock, so a congestion stall
100	/// (timestamps stop advancing) can't age content out. Wall-clock reclamation of
101	/// content nobody is accessing belongs to the cache pool's own
102	/// [`expiry`](crate::cache::Pool::expiry) window, not to this budget.
103	///
104	/// This is the `Publisher Max Age` on the wire, the publisher-side half of the
105	/// budget [`Subscription::max_age`] sets for a subscriber.
106	///
107	/// Encoded as milliseconds in a QUIC varint, so a duration of `2^62` milliseconds
108	/// or more cannot be put on the wire. Sub-millisecond precision is truncated
109	/// (`Duration::as_millis`) at encode time.
110	pub max_age: Duration,
111	/// The publisher's priority for this track, used only to break ties between
112	/// subscriptions of equal subscriber priority. Reported in TRACK_INFO (Lite05+).
113	/// Higher is more urgent. Defaults to 127, the midpoint.
114	pub priority: u8,
115}
116
117impl Default for Info {
118	fn default() -> Self {
119		Self {
120			timescale: Timescale::default(),
121			max_age: DEFAULT_MAX_AGE,
122			priority: DEFAULT_PRIORITY,
123		}
124	}
125}
126
127impl Info {
128	/// Set the per-frame timestamp scale, returning `self` for chaining.
129	///
130	/// Defaults to [`Timescale::MILLI`]. On Lite05+ this scale is reported in TRACK_INFO
131	/// and used to encode per-frame timestamps on the wire.
132	pub fn with_timescale(mut self, timescale: Timescale) -> Self {
133		self.timescale = timescale;
134		self
135	}
136
137	/// Set how old a non-latest group may get before eviction, returning `self` for chaining.
138	pub fn with_max_age(mut self, max_age: Duration) -> Self {
139		self.max_age = max_age;
140		self
141	}
142
143	/// Set the publisher's tie-break priority, returning `self` for chaining.
144	pub fn with_priority(mut self, priority: u8) -> Self {
145		self.priority = priority;
146		self
147	}
148}
149
150#[derive(Default)]
151pub(crate) struct TrackState {
152	// The publisher's properties, once known; always Some for Subscriber/Producer.
153	// Copied by value into each group it creates.
154	info: Option<Info>,
155	// Whether a live Producer was minted. A reverse fetch may install `info`
156	// before acceptance, so the two states are deliberately separate.
157	published: bool,
158	// Whether a dynamic handler took the request. Its answer is the handler's call,
159	// so ending the broadcast leaves it pending rather than rejecting it.
160	claimed: bool,
161
162	// The broadcast this track belongs to. Supplies the cache pool its groups charge
163	// into and the `cache_duration` ceiling clamping `Info::max_age`.
164	broadcast: Arc<broadcast::Info>,
165
166	// This track's account against the shared cache pool, shared with every group it
167	// creates (see `cache::Track`). Holds the gross-write counter `charge_debt` drains,
168	// and the weak link a frame write follows back here to settle its own debt.
169	cache: Arc<cache::Track>,
170
171	// Cached groups by sequence: the single source of truth for what is cached. The
172	// two orderings below hold bare sequences and validate against this map, so a
173	// removed or replaced group turns their entries into discarded-on-pop hints.
174	// Ordered so an in-range subscriber can seek to its cursor instead of scanning
175	// every cached group for each delivery.
176	lookup: BTreeMap<u64, Slot>,
177
178	// Publisher-produced groups in arrival order as (sequence, stamp), walked by
179	// subscriptions; an entry only resolves while its stamp matches the slot's.
180	// Fetched backfill (`insert_group_request`) is deliberately absent: it is
181	// served by sequence, never replayed to arrival-order subscribers.
182	arrival: VecDeque<(u64, u32)>,
183
184	// Eviction order under memory pressure as (sequence, stamp): every cached
185	// group except the protected latest. `pay_debt` scans victims from the front;
186	// groups accessed more recently than the pool-wide average rotate to the back
187	// instead of dying, decoupling eviction order from arrival order. Entries are
188	// hints that only resolve while their stamp matches the slot's, so a re-served
189	// sequence can't accumulate duplicate hints that alias its replacement.
190	// Eviction is deliberately approximate: a bounded scan per write.
191	evict: VecDeque<(u64, u32)>,
192
193	// Outstanding eviction debt in bytes, accrued by writes while the shared pool
194	// is over capacity (see `cache::Pool::accrue`) and paid by aborting this
195	// track's own oldest groups. Per track, so eviction lands proportionally to
196	// what each track writes and never touches another track's cache.
197	debt: u64,
198
199	// Datagrams in arrival order, bounded by `MAX_DATAGRAMS`. Shares the group
200	// sequence namespace but is otherwise independent.
201	datagrams: VecDeque<Datagram>,
202
203	// Number of datagrams dropped off the front (over capacity), mapping a subscriber's absolute
204	// cursor to an index into `datagrams` (mirrors `offset` for groups).
205	datagram_offset: usize,
206
207	// We've popped the front of `arrival` this many times, mapping a subscriber's
208	// absolute cursor to an index.
209	offset: usize,
210
211	// The highest sequence number successfully appended to the track. Shared with
212	// datagrams, so it can run ahead of any cached group.
213	max_sequence: Option<u64>,
214
215	// The sequence of the newest cached group: the live edge, protected from
216	// eviction by never entering the eviction order until the track is `closed`.
217	// Tracked separately from `max_sequence` because datagrams advance that shared
218	// counter, and the live edge must still demote correctly when the next group
219	// lands past one.
220	latest_group: Option<u64>,
221
222	// Incarnation counter for `Slot::stamp`.
223	next_stamp: u32,
224
225	// The sequence number at which the track was finalized.
226	final_sequence: Option<u64>,
227
228	// Nothing more can arrive: the last producer dropped after a declared boundary,
229	// or the receiving session closed locally without declaring an end.
230	sealed: bool,
231
232	// No producer remains (aborted, sealed, or dropped), so nothing protects the live
233	// edge any longer: the pool's idle expiry reclaims it like every other group, and a
234	// stale consumer cannot pin the cache.
235	closed: bool,
236
237	// The first sequence the live feed serves, once the publisher declared one
238	// (the wire's SUBSCRIBE_START). Lower groups are not waited for, though one
239	// may still arrive late or be fetched.
240	start_sequence: Option<u64>,
241
242	// Whether `start_sequence` is only the floor a subscription asked for, still
243	// waiting on the serving session to resolve where the live feed begins (a
244	// lite-06+ SUBSCRIBE_START). Readers that must know the resolved start (see
245	// [`Consumer::poll_start`]) wait on it; everything else treats the floor as usual.
246	start_pending: bool,
247
248	// Where production stopped, snapshotted when the open groups are released (an
249	// abort, or the last producer dropping). Computed live from the cache otherwise;
250	// see [`Self::resume_position`].
251	resume: Option<Position>,
252
253	// The error that caused the track to be aborted, if any.
254	abort: Option<Error>,
255
256	// Whether the declared end still stands after an abort: every group below it was
257	// produced and finished before the abort landed. See [`Self::is_complete`].
258	settled: bool,
259
260	// Active subscriptions, in their own [`kio::Shared`] so a read-only `Consumer`
261	// registers under that lock instead of writing back into the track state.
262	// Kept here (rather than threaded through every handle) so any holder reaches it.
263	subscriptions: kio::Shared<Subscriptions>,
264
265	// The reverse fetch queue (see [`FetchState`]), same reasoning: cache-miss
266	// `fetch_group` calls enqueue here and a `Dynamic` drains.
267	fetch: kio::Shared<FetchState>,
268}
269
270/// A cached group plus its bookkeeping in the track's `lookup` map.
271///
272/// Access times and the evictable-population sample live in the group's own
273/// `cache::Charge`, so they share the group's lifecycle exactly: an abort from any
274/// handle releases the bytes and the sample together.
275struct Slot {
276	group: group::Producer,
277
278	// Incarnation stamp, echoed by this slot's arrival entry (if any). A re-served
279	// sequence (an aborted group re-created by the publisher or re-fetched as
280	// backfill) gets a fresh stamp, so a historical arrival entry can't resolve to
281	// the replacement and deliver it twice or at the wrong position.
282	stamp: u32,
283
284	// Whether this incarnation came from the live publisher and can replace older
285	// subscription content. Fetch-only backfill stays cached but never anchors drift.
286	visible: bool,
287
288	// Held while this is the live edge (see `TrackState::protects`), so an ended track
289	// sharing the group leaves it alone.
290	protection: Option<cache::Protection>,
291}
292
293/// Heap the track keeps per cached group, excluding the group itself
294/// ([`group::CACHE_OVERHEAD`]).
295///
296/// One [`Slot`] under its sequence in `lookup`, plus a hint in each of `arrival` and
297/// `evict`. Doubled because both containers run half empty in the worst case: a
298/// `BTreeMap` node sits between half and fully packed, and a `VecDeque` holds up to
299/// twice the entries in it. Half of [`cache::ENTRY_OVERHEAD`]; see it for why this is
300/// derived rather than measured.
301pub(crate) const CACHE_OVERHEAD: u64 = 2 * (size_of::<u64>() + size_of::<Slot>() + 2 * size_of::<(u64, u32)>()) as u64;
302
303/// The registered subscriptions, aggregated by the producer.
304type Subscriptions = Vec<kio::Consumer<Subscription>>;
305
306/// Reverse state for [`Consumer::fetch_group`], beside the track state in its own
307/// [`kio::Shared`]: consumers enqueue (coalescing per sequence, so a relay opens one
308/// upstream FETCH per group) and [`Dynamic`] handlers drain under one lock, without
309/// write access to the track itself.
310pub(crate) type FetchState = Requests<u64, PendingFetch>;
311
312/// One fetch attempt for a sequence, shared by every [`Fetching`] that joined it.
313pub(crate) struct PendingFetch {
314	// The most demanding delivery priority across the joined fetches.
315	priority: u8,
316
317	// The lowest start across the joined fetches, so serving the attempt once satisfies
318	// every one of them. Only widened while the request is still queued: once a handler
319	// has taken it, its range is already on the wire.
320	frame_start: u64,
321
322	// Result channel back to the joined fetches. Written only on rejection; a
323	// successful accept resolves them through the track cache instead. Dropping
324	// every producer without writing (a vanished handler) closes the channel,
325	// which a [`Fetching`] reads as [`Error::NotFound`].
326	result: kio::Producer<FetchOutcome>,
327}
328
329/// The result of a fetch attempt. Stays empty on success (the group lands in the
330/// track cache); a handler writes `rejected` to fail every joined fetch.
331#[derive(Default)]
332pub(crate) struct FetchOutcome {
333	pub(crate) rejected: Option<Error>,
334}
335
336impl TrackState {
337	fn normalize_info(broadcast: &broadcast::Info, mut info: Info) -> Info {
338		info.max_age = info.max_age.min(broadcast.cache_duration);
339		info
340	}
341
342	fn accept(&mut self, info: Info) {
343		self.published = true;
344		self.install(info);
345	}
346
347	fn poll_info(&self) -> Poll<Result<Info>> {
348		if let Some(info) = &self.info {
349			Poll::Ready(Ok(info.clone()))
350		} else if let Some(err) = &self.abort {
351			// Aborted before anyone served it, so the info can never arrive: fail the
352			// waiting subscribes instead of parking them on a track nobody will fill.
353			Poll::Ready(Err(err.clone()))
354		} else {
355			Poll::Pending
356		}
357	}
358
359	/// Find the next live group at or after `index` in arrival order.
360	///
361	/// Returns the group and its absolute index so the consumer can advance past it.
362	/// The cached producer rather than a consumer: `consume` reads the clock and can
363	/// take the group's own lock, which the caller does once the track guard is gone.
364	fn poll_recv_group(&self, index: usize, min_sequence: u64) -> Poll<Result<Option<(group::Producer, usize)>>> {
365		let start = index.saturating_sub(self.offset);
366		for (i, (sequence, stamp)) in self.arrival.iter().enumerate().skip(start) {
367			if *sequence >= min_sequence
368				&& let Some(slot) = self.lookup.get(sequence)
369				&& slot.stamp == *stamp
370				&& !slot.group.is_aborted()
371			{
372				return Poll::Ready(Ok(Some((slot.group.clone(), self.offset + i))));
373			}
374		}
375
376		// TODO once we have drop notifications, check if index == final_sequence.
377		if self.is_complete() {
378			Poll::Ready(Ok(None))
379		} else if let Some(err) = &self.abort {
380			Poll::Ready(Err(err.clone()))
381		} else {
382			Poll::Pending
383		}
384	}
385
386	/// Find the next datagram at or after the subscriber's absolute `index`.
387	///
388	/// Returns the datagram and its absolute index so the consumer can advance past it. A
389	/// consumer whose `index` has fallen behind `datagram_offset` (older datagrams dropped)
390	/// resumes at the oldest still-buffered datagram, skipping the lost ones.
391	fn poll_recv_datagram(&self, index: usize) -> Poll<Result<Option<(Datagram, usize)>>> {
392		let start = index.saturating_sub(self.datagram_offset);
393		if let Some(datagram) = self.datagrams.get(start) {
394			return Poll::Ready(Ok(Some((datagram.clone(), self.datagram_offset + start))));
395		}
396
397		// Nothing buffered at the cursor: the track ending terminates the datagram stream too.
398		if self.is_complete() {
399			Poll::Ready(Ok(None))
400		} else if let Some(err) = &self.abort {
401			Poll::Ready(Err(err.clone()))
402		} else {
403			Poll::Pending
404		}
405	}
406
407	/// Push a datagram, dropping the oldest when the send buffer is full.
408	fn push_datagram(&mut self, datagram: Datagram) {
409		if self.datagrams.len() == MAX_DATAGRAMS {
410			self.datagrams.pop_front();
411			self.datagram_offset += 1;
412		}
413		self.datagrams.push_back(datagram);
414	}
415
416	/// Find the smallest-sequence cached group satisfying
417	/// `next_sequence <= seq < end_sequence (if set)`. Used by
418	/// [`Subscriber::next_group`] so the range can be widened (or unset)
419	/// after the fact and previously-skipped cached groups become available
420	/// without scanning past them in arrival order.
421	///
422	/// Returns `Poll::Pending` when no in-range group is currently cached but
423	/// future groups could still arrive in range; returns `Ok(None)` only when
424	/// the track is finalized and no further in-range group is possible.
425	fn poll_next_in_range(
426		&self,
427		next_sequence: u64,
428		end_sequence: Option<u64>,
429	) -> Poll<Result<Option<group::Producer>>> {
430		// Nothing more can arrive once the track was sealed (dropped with its end
431		// declared) or aborted. Either closed the channel, so parking is not an option:
432		// the consumer would be handed the abort, or `Dropped`, instead of how it ended.
433		let closed = self.sealed || self.abort.is_some();
434		// Once nothing more is in range: an abort before the end settled cut the track
435		// off. One after it is a clean end (see `is_complete`), as is reaching the end.
436		let end = || match &self.abort {
437			Some(err) if !self.settled => Err(err.clone()),
438			_ => Ok(None),
439		};
440
441		// If the exclusive end is already at or below where we'd resume, no
442		// group can ever satisfy this call until the cap rises. Pending (not
443		// None) so the consumer is parked rather than told the stream is over.
444		// An empty range (`end == 0`) parks even at the first sequence.
445		if let Some(cap) = end_sequence
446			&& cap <= next_sequence
447		{
448			if closed {
449				return Poll::Ready(end());
450			}
451			return Poll::Pending;
452		}
453
454		let best = self
455			.lookup
456			.range(next_sequence..)
457			.map(|(_, slot)| &slot.group)
458			.take_while(|group| super::subscription::before_end(group.sequence, end_sequence))
459			.find(|group| !group.is_aborted());
460
461		if let Some(group) = best {
462			// Deliberately no cache refresh here: this is a pure seek, and the spliced
463			// merge consults candidates it may not deliver. The deliverer stamps the
464			// winner; a loser re-seeked every poll must not be shielded from eviction.
465			// The caller consumes it with the track guard released, like `poll_recv_group`.
466			return Poll::Ready(Ok(Some(group.clone())));
467		}
468
469		// No in-range group is cached. Decide whether more could ever arrive.
470		// `final_sequence` is one past the last possible sequence. If our
471		// floor is already at/past it, nothing else can land in range.
472		// A closed track produces nothing more either, so a gap below its
473		// boundary is the end, not a wait. Cached in-range groups were
474		// returned above, so an aborted track drains what finished first.
475		// This is the cursor the ordered and spliced readers use.
476		if closed || self.final_sequence.is_some_and(|fin| next_sequence >= fin) {
477			return Poll::Ready(end());
478		}
479		Poll::Pending
480	}
481
482	/// The cached group for `sequence`, but only when it holds every frame from
483	/// `frame_start` onward.
484	///
485	/// A group cached from a frame-bounded subscription starts partway in, and serving
486	/// it to someone who asked for the whole group would silently hand back a tail. It
487	/// is a miss instead, so the fetch goes upstream for the frames that are missing.
488	fn covering_group(&self, sequence: u64, frame_start: u64) -> Option<&group::Producer> {
489		let slot = self.lookup.get(&sequence)?;
490		let first = slot.group.live_first_frame()?;
491		(first as u64 <= frame_start).then_some(&slot.group)
492	}
493
494	/// The publisher's max age window, or `None` while the info is unknown (an
495	/// unaccepted [`Request`]). Bounds the aggregate subscription; see [`clamp_combined`].
496	fn max_age_bound(&self) -> Option<Duration> {
497		self.info.as_ref().map(|info| info.max_age)
498	}
499
500	/// The live edge a subscription bounded at `cap` measures drift against: the
501	/// highest-sequence group that has presented a frame, or `None` while nothing
502	/// stamped is servable (an unstamped track drives no expiry at all; the cache's
503	/// own wall-clock policy is what bounds it).
504	///
505	/// One scan answers a whole poll. The highest-sequence anchor in range is above
506	/// every candidate below it and above none at or past it. Recomputing per candidate
507	/// would make walking a backlog of N groups off in one poll cost O(N^2).
508	///
509	/// Capped because a group can only be late relative to data that would actually be
510	/// served in its place: a subscription ending at a takeover boundary can't jump past
511	/// it, so the groups it still wants aren't stale just because the route ran on.
512	/// Fetched backfill is absent from `arrival`, so it cannot age subscription content
513	/// as though it were a live replacement.
514	fn live_edge(&self, cap: Option<u64>) -> Option<PresentationEdge> {
515		self.lookup
516			.range(..)
517			.rev()
518			.filter(|(seq, _)| super::subscription::before_end(**seq, cap))
519			.find_map(|(_, slot)| {
520				if !slot.visible || slot.group.is_aborted() {
521					return None;
522				}
523				// The map is ordered by sequence, so the first stamped group from the
524				// back is the newest content that exists.
525				let timestamp = slot.group.timestamp()?;
526				Some(PresentationEdge {
527					sequence: slot.group.sequence,
528					stamp: slot.stamp,
529					timestamp: slot.group.latest().unwrap_or(timestamp),
530				})
531			})
532	}
533
534	/// This track's own edge under the exclusive `cap`, for measuring drift. An outer
535	/// edge and a successor live on other tracks; the caller revalidates those before
536	/// taking this lock and passes them in, so the locks never nest.
537	fn drift_edge(&self, cap: Option<u64>, outer: Option<(u64, Timestamp)>, successor: Option<Timestamp>) -> Edge {
538		Edge {
539			presentation: self.live_edge(cap),
540			outer,
541			cap,
542			successor,
543		}
544	}
545
546	/// Whether `sequence` still holds the servable incarnation `stamp`.
547	fn holds(&self, sequence: u64, stamp: u32) -> bool {
548		self.lookup
549			.get(&sequence)
550			.is_some_and(|slot| slot.stamp == stamp && !slot.group.is_aborted())
551	}
552
553	/// The furthest presentation time the group at `sequence` could still reach: where
554	/// its immediate successor begins, or `None` when nothing proves where it stops.
555	///
556	/// An upper bound, deliberately. A frame's duration is not on the wire, so a group's
557	/// own last timestamp says where it *starts* presenting, not where it ends; only its
558	/// successor's start proves it cannot run past it. And only the *immediate* servable
559	/// successor counts: timestamps need not rise with sequence (a rewind reorders them),
560	/// so a later stamped group proves nothing about where an unstamped successor will
561	/// begin, and shrinking the bound is the unsafe direction. An unstamped successor
562	/// therefore leaves the reach unbounded until it presents its first frame.
563	///
564	/// With no servable successor below `cap`, the reader's next group is past the cap,
565	/// and `beyond` is where it starts when another track serves it (a splice's next
566	/// segment; see [`Anchor::successor`]).
567	fn reach(&self, sequence: u64, cap: Option<u64>, beyond: Option<Timestamp>) -> Option<Timestamp> {
568		match self.first_servable(sequence.saturating_add(1), cap) {
569			Some(group) => group.timestamp(),
570			None => beyond,
571		}
572	}
573
574	/// The first servable group in `from..cap`, stamped or not.
575	fn first_servable(&self, from: u64, cap: Option<u64>) -> Option<&group::Producer> {
576		self.lookup
577			.range(from..)
578			.map(|(_, slot)| slot)
579			.take_while(|slot| super::subscription::before_end(slot.group.sequence, cap))
580			.find(|slot| slot.visible && !slot.group.is_aborted())
581			.map(|slot| &slot.group)
582	}
583
584	/// Whether the group at `sequence` has drifted further behind `edge` than `budget`
585	/// tolerates, so a subscriber should skip it rather than hand it over.
586	///
587	/// Presentation time measures a group by how far it could still *reach*, not by how far
588	/// behind it started. Being behind is survivable: priority transmits newer groups
589	/// first, so a backlog bursts at whatever rate is left over and closes the gap faster
590	/// than the live edge advances. What is not survivable is having nothing left worth
591	/// delivering, so a group is abandoned only once everything it could still present
592	/// falls outside the budget.
593	///
594	/// A group's reach is bounded by its immediate successor (see [`Self::reach`]): it
595	/// cannot present past where the next group begins. The candidate itself needs no
596	/// timestamp: an empty group is bounded by its stamped successor the same way. Only
597	/// timestamps drive expiry; wall-clock reclamation of idle content is the cache's
598	/// own policy, not the budget's.
599	///
600	/// The bound is exclusive, so the comparison is `>=` rather than `>`: the freshest frame
601	/// a group could still hold sits just *below* its reach, so an age equal to the budget
602	/// already puts every frame in it strictly past the budget. That also makes a zero
603	/// budget fall out for free instead of needing a special case.
604	///
605	/// The edge must sit strictly above the candidate. The live edge is never late
606	/// against itself, and backfill or the tail of a rewound timeline can carry a high
607	/// timestamp on a low sequence without being an edge at all. Of the edges that
608	/// qualify, the highest sequence is the newest content, whichever track holds it:
609	/// this one's, or the `outer` edge a splice pushed from another segment.
610	fn is_stale(&self, sequence: u64, edge: &Edge, budget: Duration) -> bool {
611		if !self.lookup.contains_key(&sequence) {
612			return false;
613		}
614
615		// The anchor was resolved under an earlier lock, so confirm it still names
616		// the same servable incarnation before it convicts a candidate. Failing safe
617		// (delivering) is right, since the next poll resolves fresh anchors.
618		let local = edge
619			.presentation
620			.filter(|live| self.holds(live.sequence, live.stamp))
621			.map(|live| (live.sequence, live.timestamp));
622		// `outer` was revalidated and `successor` resolved on their own tracks before this
623		// lock was taken ([`LiveEdge::is_live`], [`Successor::poll_start`]). There is no
624		// slot for them here, and taking their locks here would nest.
625		let Some((_, timestamp)) = local
626			.into_iter()
627			.chain(edge.outer)
628			.filter(|(live, _)| *live > sequence)
629			.max_by_key(|(live, _)| *live)
630		else {
631			return false;
632		};
633		self.reach(sequence, edge.cap, edge.successor)
634			.is_some_and(|reach| matches!(timestamp.checked_sub(reach), Ok(age) if Duration::from(age) >= budget))
635	}
636
637	/// Resolve a one-shot fetch from the track side: the cached group, or an [`Error`]
638	/// once it can never be served. A missing group is a failure ([`Error::NotFound`]), not an
639	/// end-of-stream. The handler side (a rejection, or no [`Dynamic`] at all) lives
640	/// in [`FetchState`]; [`Fetching`] polls both.
641	fn poll_fetch_cached(&self, sequence: u64, frame_start: u64) -> Poll<Result<group::Consumer>> {
642		// A group aborted between the lookup and the consume (an abandoned fetch cut
643		// short) is a miss, not a hit that fails on its first read.
644		if let Some(group) = self.covering_group(sequence, frame_start)
645			&& let Some(consumer) = group.try_consume()
646		{
647			// A cache hit refreshes the group: it resets both its age (expiry keys
648			// off the last access) and its standing against the pool-wide average,
649			// so the eviction walk keeps it over never-read groups.
650			group.cache_refresh();
651			return Poll::Ready(Ok(consumer));
652		}
653
654		if let Some(err) = &self.abort {
655			return Poll::Ready(Err(err.clone()));
656		}
657
658		// Past the final sequence: the group can never exist.
659		if self.final_sequence.is_some_and(|fin| sequence >= fin) {
660			return Poll::Ready(Err(Error::NotFound));
661		}
662
663		Poll::Pending
664	}
665
666	/// Expire groups idle past the pool's wall-clock LRU window, never the latest.
667	///
668	/// The window is the pool's [`expiry`](cache::Pool::expiry), not the track's
669	/// [`max_age`](Info::max_age): retention is a media-timestamp promise, while this
670	/// is the cache's own guard against unread content pinning RAM, shared by every
671	/// track in the pool.
672	///
673	/// One bounded, rotating scan over the eviction order, which holds every cached
674	/// group except the protected latest. The cursor persists in the cache account, so
675	/// entries beyond one scan window can't be starved by fresh (recently read,
676	/// fetched, or written) entries in front of them: every position is revisited
677	/// within a few writes. Expiry throughput is therefore EVICT_SCAN groups per write; the
678	/// byte budget reclaims the remainder under memory pressure, and the pool's sweep
679	/// ([`Self::expiry_scan_drain`]) covers a track that stopped writing entirely.
680	pub(super) fn evict_expired(&mut self) {
681		let scan = self.expiry_scan();
682		self.evict_expired_scan(scan);
683	}
684
685	/// Describe the next bounded expiry scan without mutating observable track state.
686	pub(super) fn expiry_scan(&self) -> ExpiryScan {
687		ExpiryScan {
688			start: self.cache.next_expiry_scan(EVICT_SCAN),
689			width: EVICT_SCAN,
690			now: self.cache.pool().now(),
691			max_ticks: self.cache.pool().expiry_ticks(),
692			gc: false,
693		}
694	}
695
696	/// Scan every cached candidate so undated accesses and old entries cannot hide
697	/// behind fresh entries at the front of the eviction order.
698	pub(super) fn expiry_scan_drain(&self) -> ExpiryScan {
699		ExpiryScan {
700			start: 0,
701			width: self.evict.len(),
702			now: self.cache.pool().now(),
703			max_ticks: self.cache.pool().expiry_ticks(),
704			gc: true,
705		}
706	}
707
708	#[cfg(test)]
709	pub(super) fn date_cache_accesses(&self, now: u64) {
710		for slot in self.lookup.values() {
711			slot.group.cache_accessed_tick(Some(now));
712		}
713	}
714
715	/// Whether an expiry scan would change observable track state.
716	///
717	/// Mirrors [`Self::evict_expired_scan`]'s walk exactly, stop rule included: a memo
718	/// that scanned further would report work the scan itself will not do.
719	pub(super) fn expiry_mutation_due(&self, scan: ExpiryScan) -> bool {
720		let len = self.evict.len();
721		if len > 0 {
722			let start = scan.start % len;
723			let mut retained = 0;
724			for step in 0..len.min(scan.width) {
725				let (sequence, stamp) = self.evict[(start + step) % len];
726				let Some(slot) = self.lookup.get(&sequence) else {
727					continue;
728				};
729				if slot.stamp != stamp {
730					continue;
731				}
732				if slot.group.is_aborted()
733					|| (!self.protects(sequence)
734						&& slot
735							.group
736							.cache_accessed_tick(scan.gc.then_some(scan.now))
737							.is_some_and(|tick| scan.now.saturating_sub(tick) > scan.max_ticks))
738				{
739					return true;
740				}
741				retained += 1;
742				if !scan.gc && retained >= EVICT_SCAN {
743					break;
744				}
745			}
746		}
747
748		self.arrival
749			.front()
750			.is_some_and(|(sequence, stamp)| !self.is_current(*sequence, *stamp))
751			|| self
752				.evict
753				.front()
754				.is_some_and(|(sequence, stamp)| !self.is_current(*sequence, *stamp))
755			|| self.evict.len() > 2 * self.lookup.len() + EVICT_SLACK
756	}
757
758	/// Expire an ended track's idle groups, whose closed channel refuses the write
759	/// [`Self::evict_expired_scan`] takes: abort them in place, releasing their frames,
760	/// and leave the slots, which every read path already skips. A group another track
761	/// still holds as its live edge stays, since tracks can share a group (a warm copy
762	/// adopts the relay copy's).
763	pub(super) fn expire_closed(&self, scan: ExpiryScan) {
764		for (sequence, stamp) in &self.evict {
765			let Some(slot) = self.lookup.get(sequence) else {
766				continue;
767			};
768			if slot.stamp == *stamp
769				&& !slot.group.is_aborted()
770				&& !slot.group.cache_protected()
771				&& slot
772					.group
773					.cache_accessed_tick(scan.gc.then_some(scan.now))
774					.is_some_and(|tick| scan.now.saturating_sub(tick) > scan.max_ticks)
775			{
776				let _ = slot.group.clone().abort(Error::Old);
777			}
778		}
779	}
780
781	/// Apply a scan previously selected by [`Self::expiry_scan`] or
782	/// [`Self::expiry_scan_drain`].
783	pub(super) fn evict_expired_scan(&mut self, scan: ExpiryScan) {
784		let len = self.evict.len();
785		if len > 0 {
786			let start = scan.start % len;
787			let mut retained = 0;
788			for step in 0..len.min(scan.width) {
789				let (sequence, stamp) = self.evict[(start + step) % len];
790				let Some(slot) = self.lookup.get(&sequence) else {
791					continue;
792				};
793				if slot.stamp != stamp {
794					// A historical hint; the live entry is elsewhere in the queue.
795					continue;
796				}
797				// Already aborted: the frames are gone, reclaim the slot so a
798				// later fetch can serve the sequence again.
799				if slot.group.is_aborted() {
800					self.lookup.remove(&sequence);
801					continue;
802				}
803				if self.protects(sequence)
804					|| slot
805						.group
806						.cache_accessed_tick(scan.gc.then_some(scan.now))
807						.is_none_or(|tick| scan.now.saturating_sub(tick) <= scan.max_ticks)
808				{
809					// Writes keep their scan bounded. Cleanup visits the entire
810					// queue to date pending accesses and find idle entries behind them.
811					retained += 1;
812					if !scan.gc && retained >= EVICT_SCAN {
813						break;
814					}
815					continue;
816				}
817				// Take the group out of the cache and abort it, so any consumer
818				// still reading surfaces `Error::Old` instead of blocking forever
819				// on a frame that will never arrive.
820				let slot = self.lookup.remove(&sequence).unwrap();
821				let _ = slot.group.abort(Error::Old);
822			}
823		}
824
825		// Trim dead leading arrival entries to advance the subscriber offset. An
826		// entry is dead once its slot is gone or re-stamped by a newer incarnation.
827		while let Some((sequence, stamp)) = self.arrival.front() {
828			if self.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp) {
829				break;
830			}
831			self.arrival.pop_front();
832			self.offset += 1;
833		}
834
835		// Drop dead leading eviction entries so scans stay over live candidates.
836		while let Some((sequence, stamp)) = self.evict.front() {
837			if self.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp) {
838				break;
839			}
840			self.evict.pop_front();
841		}
842
843		// Dead entries behind a live front can linger; rebuild once they clearly
844		// outnumber the live slots.
845		if self.evict.len() > 2 * self.lookup.len() + EVICT_SLACK {
846			let lookup = &self.lookup;
847			self.evict
848				.retain(|(sequence, stamp)| lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp));
849		}
850	}
851
852	/// Whether `(sequence, stamp)` names the currently cached incarnation.
853	fn is_current(&self, sequence: u64, stamp: u32) -> bool {
854		self.lookup.get(&sequence).is_some_and(|slot| slot.stamp == stamp)
855	}
856
857	/// Whether `sequence` is the live edge, which eviction and expiry never take
858	/// while a producer remains.
859	fn protects(&self, sequence: u64) -> bool {
860		!self.closed && Some(sequence) == self.latest_group
861	}
862
863	/// No producer remains: stop protecting the live edge, so the pool's idle expiry
864	/// reclaims every group once readers stop touching it, and a stale consumer can't
865	/// pin the cache (and its frame buffers) forever.
866	fn close_cache(&mut self) {
867		if std::mem::replace(&mut self.closed, true) {
868			return;
869		}
870		if let Some(latest) = self.latest_group
871			&& let Some(slot) = self.lookup.get_mut(&latest)
872		{
873			slot.group.cache_demote();
874			slot.protection = None;
875			self.evict.push_back((latest, slot.stamp));
876		}
877	}
878
879	/// Drop the open groups nobody will finish now that the track ended abruptly, and
880	/// keep the finished ones for readers still draining. A consumer that already
881	/// pulled an open group keeps its own handle and ends with it.
882	fn drop_open_groups(&mut self) {
883		self.lookup.retain(|_, slot| slot.group.is_finished());
884	}
885
886	/// Attach `info` to this track, clamping the publisher's window down to the
887	/// origin's [`cache_duration`](crate::origin::Config::cache_duration) ceiling so a
888	/// group is never retained longer than the origin allows. Every path that binds an
889	/// info to a track funnels through here, covering local publishers and relayed
890	/// (lite / IETF) tracks alike.
891	fn install(&mut self, info: Info) {
892		let info = Self::normalize_info(&self.broadcast, info);
893		self.info = Some(info);
894	}
895
896	/// Create the shared state for a track under `broadcast`, along with the cache
897	/// account it and its groups charge into.
898	///
899	/// The account holds a [`kio::Weak`] back to this state: a group must be able to
900	/// settle the track's eviction debt as it writes, but the track owns its cached
901	/// groups, so anything stronger would make the pair immortal.
902	fn spawn(broadcast: Arc<broadcast::Info>) -> kio::Producer<Self> {
903		let state = kio::Producer::new(Self {
904			broadcast: broadcast.clone(),
905			..Default::default()
906		});
907		let cache = cache::Track::new(broadcast.pool.clone(), state.downgrade());
908		state.write().ok().expect("a new track is open").cache = cache;
909		state
910	}
911
912	/// Reject a sequence that is still cached; a dead (aborted or evicted)
913	/// incarnation is removed so a fresh group can serve the sequence again.
914	///
915	/// Best effort: nothing remembers a sequence whose slot is already gone, so a
916	/// publisher re-sending a long-evicted sequence is accepted as new.
917	/// A group that starts above `frame_start` is also replaceable: it cannot answer a
918	/// request from there, so the wider producer takes the slot. Readers already
919	/// draining the old one keep their own handle.
920	fn claim_sequence(&mut self, sequence: u64, frame_start: u64) -> Result<()> {
921		if let Some(slot) = self.lookup.get(&sequence) {
922			// The same question `covering_group` asks: can this slot still answer from
923			// `frame_start`? If it can, the sequence is taken; if it can't, it is dead
924			// and the caller gets to replace it.
925			if slot
926				.group
927				.live_first_frame()
928				.is_some_and(|first| first as u64 <= frame_start)
929			{
930				return Err(Error::Duplicate);
931			}
932			self.lookup.remove(&sequence);
933		}
934		Ok(())
935	}
936
937	/// Insert a freshly-created group into the cache.
938	///
939	/// Updates the live edge, demoting the previous latest into the eviction order;
940	/// the current latest is never enqueued, which is what protects it from
941	/// eviction. `visible` controls arrival-order delivery: publisher-produced
942	/// groups reach subscribers, fetched backfill is served by sequence only.
943	fn insert_group(&mut self, group: &group::Producer, visible: bool) {
944		let sequence = group.sequence;
945		self.next_stamp = self.next_stamp.wrapping_add(1);
946		let stamp = self.next_stamp;
947
948		// The live edge is tracked separately from `max_sequence`, which datagrams
949		// share and can push past any cached group: demotion must still fire when
950		// the next group lands beyond a datagram-advanced counter.
951		if self.latest_group.is_none_or(|latest| sequence >= latest) {
952			// Demote the previous latest: it joins the eviction order (and the
953			// pool's access average) like any other cached group.
954			if let Some(latest) = self.latest_group
955				&& sequence > latest
956				&& let Some(prev) = self.lookup.get_mut(&latest)
957			{
958				prev.group.cache_demote();
959				prev.protection = None;
960				self.evict.push_back((latest, prev.stamp));
961			}
962			self.latest_group = Some(sequence);
963		}
964		if !self.protects(sequence) {
965			group.cache_demote();
966			self.evict.push_back((sequence, stamp));
967		}
968
969		self.max_sequence = Some(self.max_sequence.map_or(sequence, |max| max.max(sequence)));
970		self.lookup.insert(
971			sequence,
972			Slot {
973				group: group.clone(),
974				stamp,
975				visible,
976				protection: self.protects(sequence).then(|| group.cache_protect()),
977			},
978		);
979		if visible {
980			self.arrival.push_back((sequence, stamp));
981		}
982	}
983
984	/// Admit a freshly-created group: settle eviction debt first (so the newcomer
985	/// can never be a victim of the very write that created it), insert it, then
986	/// expire idle groups.
987	fn commit_group(&mut self, group: &group::Producer, visible: bool) {
988		self.charge_debt();
989		self.insert_group(group, visible);
990		self.evict_expired();
991	}
992
993	/// Accrue and pay eviction debt for everything written since the last charge:
994	/// this track's account, which the groups' charges feed on every frame (so
995	/// growth on already-demoted groups and backfill is billed too).
996	///
997	/// Runs BEFORE the new group is inserted, so a brand-new entry is never a
998	/// victim of the very write that created it. A track whose oldest content is
999	/// staler than the pool-wide average access time accrues at double rate, so
1000	/// stale-heavy tracks drain first.
1001	///
1002	/// Also runs from the frame-write path via [`cache::Track::settle`], which is
1003	/// why it's reachable from the account, so a track that only appends frames to
1004	/// open groups still pays.
1005	pub(super) fn charge_debt(&mut self) {
1006		let written = self.cache.take_written();
1007		let pool = self.cache.pool().clone();
1008		match pool.accrue(written) {
1009			Some(mut accrued) => {
1010				if self.oldest_is_stale(&pool) {
1011					accrued = accrued.saturating_mul(2);
1012				}
1013				// `used` bounds what eviction could ever free, keeping a track that
1014				// can't pay (everything protected) from hoarding a stale schedule.
1015				self.debt = self.debt.saturating_add(accrued).min(pool.used());
1016				// Cap each payment at twice what was written so one write never dumps
1017				// a deep backlog at once; the remainder carries to the next write.
1018				self.pay_debt(&pool, written.saturating_mul(2));
1019			}
1020			// Under capacity there is nothing to work off, and stale debt would
1021			// cause a spurious eviction burst at the next pressure spike.
1022			None => self.debt = 0,
1023		}
1024	}
1025
1026	/// Whether this track's oldest evictable group was accessed at or before the
1027	/// pool-wide average, doubling the debt it accrues. A dead entry at the front
1028	/// just reads as not-stale until the next payment or expiry cleans it up.
1029	fn oldest_is_stale(&self, pool: &cache::Pool) -> bool {
1030		let Some(average) = pool.average() else {
1031			return false;
1032		};
1033		let Some((sequence, stamp)) = self.evict.front() else {
1034			return false;
1035		};
1036		let Some(slot) = self.lookup.get(sequence) else {
1037			return false;
1038		};
1039		slot.stamp == *stamp && !slot.group.is_aborted() && slot.group.cache_accessed() <= average
1040	}
1041
1042	/// Abort this track's stalest groups until the outstanding debt is paid, or
1043	/// `cap` bytes have been freed by this call.
1044	///
1045	/// Deliberately approximate, Redis-style: at most a handful of live candidates
1046	/// are examined per call, from the front of the eviction order. A group
1047	/// accessed more recently than the pool-wide average is protected and rotates
1048	/// to the back, so fresh content in this track never dies while staler content
1049	/// survives elsewhere; the unfreed bytes keep the pool over budget, shifting
1050	/// the debt onto the tracks holding that staler content. When the next victim
1051	/// is larger than the remaining debt it is left in place and the debt carries
1052	/// over, so a small write never evicts a huge group (once the debt does cover
1053	/// it, that one victim may overshoot `cap`).
1054	fn pay_debt(&mut self, pool: &cache::Pool, cap: u64) {
1055		let average = pool.average().unwrap_or(0);
1056		let mut paid = 0u64;
1057		let mut scanned = 0usize;
1058		for _ in 0..self.evict.len() {
1059			if self.debt == 0 || paid >= cap || scanned >= EVICT_SCAN {
1060				return;
1061			}
1062			let Some((sequence, stamp)) = self.evict.pop_front() else {
1063				return;
1064			};
1065			let Some(slot) = self.lookup.get(&sequence) else {
1066				// Evicted or expired; discard the dead entry.
1067				continue;
1068			};
1069			if slot.stamp != stamp {
1070				// A historical hint; the live entry is elsewhere in the queue.
1071				continue;
1072			}
1073			if slot.group.is_aborted() {
1074				// Aborted upstream: the frames are already gone, reclaim the slot.
1075				self.lookup.remove(&sequence);
1076				continue;
1077			}
1078			if self.protects(sequence) {
1079				// The live edge is never enqueued, but tolerate finding it anyway.
1080				self.evict.push_back((sequence, stamp));
1081				continue;
1082			}
1083
1084			scanned += 1;
1085			// Protected: accessed more recently than the average (a fresh insert,
1086			// an active reader, or a FETCH hit, which also covers a backfill still
1087			// being filled). Rotate to the back.
1088			if slot.group.cache_accessed() > average {
1089				self.evict.push_back((sequence, stamp));
1090				continue;
1091			}
1092			// The full footprint including overhead, so even empty groups repay
1093			// their share of the budget when evicted.
1094			let size = slot.group.cache_size();
1095			if size > self.debt {
1096				self.evict.push_front((sequence, stamp));
1097				return;
1098			}
1099
1100			self.debt -= size;
1101			paid = paid.saturating_add(size);
1102			let slot = self.lookup.remove(&sequence).unwrap();
1103			let _ = slot.group.abort(Error::Evicted);
1104		}
1105	}
1106
1107	/// Record the declared first sequence of the live feed, replacing any earlier
1108	/// declaration: the signal is scoped to the current subscription's demand,
1109	/// which may legitimately move in either direction. `None` clears it (the
1110	/// demand dropped to the live edge, whose floor is unknown until declared).
1111	fn set_start(&mut self, start_sequence: Option<u64>, pending: bool) {
1112		self.start_sequence = start_sequence;
1113		self.start_pending = pending;
1114	}
1115
1116	/// Record the exclusive final sequence, rejecting a re-finish or a boundary that
1117	/// would orphan already-produced groups.
1118	fn set_final(&mut self, final_sequence: u64) -> Result<()> {
1119		if self.final_sequence.is_some() {
1120			return Err(Error::Closed);
1121		}
1122		if let Some(max) = self.max_sequence
1123			&& final_sequence <= max
1124		{
1125			return Err(Error::ProtocolViolation);
1126		}
1127		self.final_sequence = Some(final_sequence);
1128		Ok(())
1129	}
1130
1131	/// Whether the track has reached its end: the final boundary is set and the live
1132	/// edge has caught up to it, so no further group can arrive. A future boundary
1133	/// (declared via [`Producer::finish_at`] ahead of the live edge) stays incomplete
1134	/// until the remaining groups are produced, or until the last producer drops without
1135	/// them. Drives the end-of-stream signal from
1136	/// the read methods (`recv_group` / `next_group` / `read_frame` return `None`).
1137	///
1138	/// An abort before the end settled wins over it: a group below the boundary was
1139	/// still open, so the track was cut off rather than ended.
1140	fn is_complete(&self) -> bool {
1141		// `sealed` also ends a locally closed receive track without a declared end.
1142		// An abort still wins unless that end had already settled: a group below it was still open.
1143		let reached = self.sealed
1144			|| self
1145				.final_sequence
1146				.is_some_and(|fin| self.max_sequence.map_or(0, |max| max.saturating_add(1)) >= fin);
1147		reached && (self.abort.is_none() || self.settled)
1148	}
1149
1150	/// Whether the declared end is reached and every cached group below it finished,
1151	/// so nothing the end promised is still in flight.
1152	fn is_settled(&self) -> bool {
1153		let Some(fin) = self.final_sequence else {
1154			return false;
1155		};
1156		self.is_complete() && self.lookup.range(..fin).all(|(_, slot)| slot.group.is_finished())
1157	}
1158
1159	/// Where a replacement route should pick this track up: one past the last frame
1160	/// produced, rolling to the start of the next group once the latest group is
1161	/// complete (nothing more can be appended to it).
1162	///
1163	/// `None` while the track has produced nothing, which is an unbounded takeover.
1164	fn resume_position(&self) -> Option<Position> {
1165		// A snapshot taken when the open groups were released wins; the group it was
1166		// derived from may be gone.
1167		if self.resume.is_some() {
1168			return self.resume;
1169		}
1170
1171		let max = self.latest_group?;
1172		match self.lookup.get(&max).and_then(|slot| slot.group.resume_frame()) {
1173			// Still open *and* carrying frames, so the replacement continues it
1174			// frame-by-frame.
1175			Some(frame) => Some(Position {
1176				group: max,
1177				frame: frame as u64,
1178			}),
1179			// A copy that wrote nothing has no frames to splice onto, and the reader
1180			// already holds its (empty) handle. Pointing a replacement at frame 0 would
1181			// hand the same sequence out twice, so roll to the next group exactly as a
1182			// finished one does.
1183			None => Some(Position::group(max.saturating_add(1))),
1184		}
1185	}
1186
1187	fn poll_finished(&self) -> Poll<Result<u64>> {
1188		if let Some(fin) = self.final_sequence {
1189			Poll::Ready(Ok(fin))
1190		} else if let Some(err) = &self.abort {
1191			Poll::Ready(Err(err.clone()))
1192		} else if self.sealed {
1193			Poll::Ready(Err(Error::Closed))
1194		} else {
1195			Poll::Pending
1196		}
1197	}
1198
1199	/// Distinguish a locally closed receive track from a declared content end.
1200	fn poll_end(&self) -> Poll<Result<Option<u64>>> {
1201		if self.sealed && self.final_sequence.is_none() && self.abort.is_none() {
1202			Poll::Ready(Ok(None))
1203		} else {
1204			self.poll_finished().map(|res| res.map(Some))
1205		}
1206	}
1207
1208	fn modify(producer: &kio::Producer<Self>) -> Result<kio::Mut<'_, Self>> {
1209		producer.write().map_err(|r| r.abort.clone().unwrap_or(Error::Dropped))
1210	}
1211
1212	/// Insert a group fetched for a [`group::Request`], setting the track's [`Info`]
1213	/// if it isn't accepted yet. The group's timescale comes from that info, so a
1214	/// fetch can serve an as-yet-unaccepted track (e.g. a relay with no live
1215	/// subscription). The group lands in the cache so a waiting
1216	/// [`Fetching`] resolves via [`Self::poll_fetch`].
1217	pub(crate) fn insert_group_request(
1218		&mut self,
1219		sequence: u64,
1220		frame_start: u64,
1221		info: Option<Info>,
1222	) -> Result<group::Producer> {
1223		if let Some(err) = &self.abort {
1224			return Err(err.clone());
1225		}
1226		if let Some(fin) = self.final_sequence
1227			&& sequence >= fin
1228		{
1229			return Err(Error::Closed);
1230		}
1231
1232		// Adopt the supplied info only if the track hasn't been accepted yet. Groups
1233		// created here charge the same account as any other, so backfill written
1234		// before the track is accepted settles its debt like the rest.
1235		if self.info.is_none() {
1236			self.install(info.unwrap_or_default());
1237		}
1238		let info = self.info.clone().unwrap();
1239
1240		// An evicted sequence can be re-fetched; a live one is a duplicate.
1241		self.claim_sequence(sequence, frame_start)?;
1242
1243		let mut group = group::Producer::new(group::Info { sequence }, info, self.cache.clone());
1244		// Start where the request did before the group is visible: a fetch looking it up
1245		// in between would otherwise see it begin at 0 and get a reader that later skips
1246		// the head it asked for.
1247		group.start_at(frame_start)?;
1248		// A backfill exists because someone is fetching it right now: stamp that
1249		// access so the eviction walk can't kill it before the fetch resolves.
1250		// It is also invisible to arrival-order subscribers: fetched on demand,
1251		// not produced live by the publisher.
1252		group.cache_refresh();
1253		self.commit_group(&group, false);
1254		Ok(group)
1255	}
1256}
1257
1258/// Record `err` and close the track: the shared tail of [`Producer::abort`] and
1259/// [`Producer::abort_unused`].
1260fn commit_abort(mut state: kio::Mut<'_, TrackState>, err: Error) {
1261	// Snapshot the frame boundary before the open group it may sit in goes away: an
1262	// abort is exactly when a replacement route asks where to resume.
1263	state.resume = state.resume_position();
1264	// Decided before the open groups go: the groups below the end are the evidence.
1265	state.settled = state.is_settled();
1266	state.abort = Some(err);
1267	// Keep what finished for consumers still draining: they get it, then the abort (or
1268	// the clean end, when the end settled). Clearing here would abort finished groups a
1269	// slower reader has not pulled yet. The pool's idle expiry bounds how long they stay.
1270	state.drop_open_groups();
1271	state.close_cache();
1272	state.close();
1273}
1274
1275/// A producer for a track, used to create new groups.
1276#[derive(Clone)]
1277pub struct Producer {
1278	name: Arc<str>,
1279	info: Info,
1280	// The parent broadcast's info, inherited from [`broadcast::Producer::create_track`].
1281	// Top link of the ownership chain; carried for identity and future inheritance.
1282	broadcast: Arc<broadcast::Info>,
1283	state: kio::Producer<TrackState>,
1284	prev_subscription: Option<Subscription>,
1285	// Shared with every clone and every `Dynamic`: its `Drop` is the teardown.
1286	alive: Arc<Alive>,
1287	// Ingress stats scope, inherited from a tagged [`broadcast::Producer`]. Bumped as
1288	// one subscription on tag and closed when the last producer clone drops. Empty
1289	// (no-op) for an untagged broadcast.
1290	stats: stats::Scope,
1291}
1292
1293impl Producer {
1294	/// The immutable publisher priority committed when this track was accepted.
1295	pub(crate) fn publisher_priority(&self) -> u8 {
1296		self.info.priority
1297	}
1298
1299	/// Build a producer for the given track metadata.
1300	///
1301	/// Crate-private: tracks are born from their broadcast via
1302	/// [`broadcast::Producer::create_track`] (or served on demand through a
1303	/// [`Request`]), which threads the broadcast's `Arc<broadcast::Info>` down. The
1304	/// track opens its cache account against that broadcast's origin pool, and every
1305	/// group it creates charges into it.
1306	pub(crate) fn new(
1307		broadcast: Arc<broadcast::Info>,
1308		name: impl Into<Arc<str>>,
1309		info: impl Into<Option<Info>>,
1310	) -> Self {
1311		let name = name.into();
1312		let info = TrackState::normalize_info(&broadcast, info.into().unwrap_or_default());
1313		let state = TrackState::spawn(broadcast.clone());
1314		state.write().ok().expect("a new track is open").accept(info.clone());
1315		let alive = Alive::new(name.clone(), state.clone());
1316		alive.publish(None);
1317		Self {
1318			name,
1319			info,
1320			state,
1321			broadcast,
1322			prev_subscription: None,
1323			alive,
1324			stats: stats::Scope::default(),
1325		}
1326	}
1327
1328	/// Attach the parent broadcast's ingress stats scope, counting this track as one
1329	/// ingress subscription (closed when the last producer clone drops). Called by a
1330	/// tagged [`broadcast::Producer`] when it creates the track.
1331	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
1332		self.alive.publish(Some(&scope));
1333		self.stats = scope;
1334		self
1335	}
1336
1337	/// The track's name, unique within its broadcast.
1338	pub fn name(&self) -> &str {
1339		&self.name
1340	}
1341
1342	/// The parent broadcast this track belongs to.
1343	pub fn broadcast(&self) -> &broadcast::Info {
1344		&self.broadcast
1345	}
1346
1347	/// Cache an already-produced group without rewriting its frames.
1348	///
1349	/// Used by an origin front to keep a warm copy of groups it already delivered
1350	/// after dropping the source track that produced them.
1351	pub(crate) fn adopt_group(&mut self, group: group::Producer, visible: bool) -> Result<()> {
1352		let mut state = self.modify()?;
1353		if let Some(fin) = state.final_sequence
1354			&& group.sequence >= fin
1355		{
1356			return Err(Error::Closed);
1357		}
1358		if state.lookup.contains_key(&group.sequence) {
1359			return Err(Error::Duplicate);
1360		}
1361		state.insert_group(&group, visible);
1362		Ok(())
1363	}
1364
1365	/// Create a new group with the given sequence number.
1366	pub fn create_group(&self, group: group::Info) -> Result<group::Producer> {
1367		let mut state = self.modify()?;
1368		if let Some(fin) = state.final_sequence
1369			&& group.sequence >= fin
1370		{
1371			return Err(Error::Closed);
1372		}
1373		let track = state.info.clone().unwrap();
1374
1375		// An evicted sequence can be re-created; a live one is a duplicate.
1376		state.claim_sequence(group.sequence, 0)?;
1377
1378		let group = group::Producer::new(group, track, state.cache.clone()).with_meter(self.stats.meter());
1379		state.commit_group(&group, true);
1380
1381		Ok(group)
1382	}
1383
1384	/// Create a new group with the next sequence number.
1385	pub fn append_group(&self) -> Result<group::Producer> {
1386		let mut state = self.modify()?;
1387		let sequence = match state.max_sequence {
1388			Some(s) => s.checked_add(1).ok_or(coding::BoundsExceeded)?,
1389			None => 0,
1390		};
1391		if let Some(fin) = state.final_sequence
1392			&& sequence >= fin
1393		{
1394			return Err(Error::Closed);
1395		}
1396
1397		let track = state.info.clone().unwrap();
1398
1399		let group =
1400			group::Producer::new(group::Info { sequence }, track, state.cache.clone()).with_meter(self.stats.meter());
1401		state.commit_group(&group, true);
1402
1403		Ok(group)
1404	}
1405
1406	/// Append a datagram with the next sequence number, returning the assigned sequence.
1407	///
1408	/// A datagram is delivered best-effort over a single QUIC datagram, parallel to the
1409	/// track's groups but drawing from the same sequence namespace (so interleaving with
1410	/// [`Self::append_group`] never reuses a number). There is no group fallback: each
1411	/// session drops (with a debug log) any datagram whose encoded body exceeds the
1412	/// transport's datagram size, and sessions that can't carry datagrams at all (moq-lite
1413	/// before 05, or stream-only transports like WebSocket) never deliver them. Keep payloads well under the 1200-byte minimum path MTU. An origin
1414	/// publisher uses this; a relay preserving upstream numbering uses
1415	/// [`Self::insert_datagram`].
1416	pub fn append_datagram<B: crate::IntoBytes>(&mut self, timestamp: Timestamp, payload: B) -> Result<u64> {
1417		let payload = payload.into_bytes();
1418		if payload.len() > super::datagram::MAX_DATAGRAM_PAYLOAD {
1419			return Err(Error::FrameTooLarge);
1420		}
1421		// Resolved before the state guard borrows `self`.
1422		let meter = self.stats.meter();
1423		let mut state = self.modify()?;
1424		// Normalize into the track's timescale, like frames (see `group::Producer::create_frame`).
1425		let timescale = state.info.as_ref().unwrap().timescale;
1426		let timestamp = timestamp.convert(timescale).map_err(|_| Error::TimestampMismatch)?;
1427		let sequence = match state.max_sequence {
1428			Some(s) => s.checked_add(1).ok_or(coding::BoundsExceeded)?,
1429			None => 0,
1430		};
1431		if let Some(fin) = state.final_sequence
1432			&& sequence >= fin
1433		{
1434			return Err(Error::Closed);
1435		}
1436		state.max_sequence = Some(sequence);
1437		meter.datagram(payload.len() as u64);
1438		state.push_datagram(Datagram {
1439			sequence,
1440			timestamp,
1441			payload,
1442		});
1443		let cache = state.cache.clone();
1444		drop(state);
1445		cache.settle(None);
1446		Ok(sequence)
1447	}
1448
1449	/// Insert a datagram with an explicit sequence number.
1450	///
1451	/// Preserves the supplied sequence (bumping the shared `max_sequence` if needed), so a
1452	/// relay can forward a datagram without renumbering it. Most origin publishers want
1453	/// [`Self::append_datagram`] instead.
1454	pub fn insert_datagram<B: crate::IntoBytes>(
1455		&mut self,
1456		sequence: u64,
1457		timestamp: Timestamp,
1458		payload: B,
1459	) -> Result<()> {
1460		let payload = payload.into_bytes();
1461		if payload.len() > super::datagram::MAX_DATAGRAM_PAYLOAD {
1462			return Err(Error::FrameTooLarge);
1463		}
1464		// Resolved before the state guard borrows `self`.
1465		let meter = self.stats.meter();
1466		let mut state = self.modify()?;
1467		// Normalize into the track's timescale, like frames (see `group::Producer::create_frame`).
1468		let timescale = state.info.as_ref().unwrap().timescale;
1469		let timestamp = timestamp.convert(timescale).map_err(|_| Error::TimestampMismatch)?;
1470		if let Some(fin) = state.final_sequence
1471			&& sequence >= fin
1472		{
1473			return Err(Error::Closed);
1474		}
1475		state.max_sequence = Some(state.max_sequence.unwrap_or(0).max(sequence));
1476		meter.datagram(payload.len() as u64);
1477		state.push_datagram(Datagram {
1478			sequence,
1479			timestamp,
1480			payload,
1481		});
1482		let cache = state.cache.clone();
1483		drop(state);
1484		cache.settle(None);
1485		Ok(())
1486	}
1487
1488	/// Create a group with a single frame, at the given presentation timestamp.
1489	///
1490	/// The timestamp is converted into the track's timescale. For data without
1491	/// a presentation time, pass [`Timestamp::now`] explicitly.
1492	pub fn write_frame<B: crate::IntoBytes>(&mut self, timestamp: Timestamp, frame: B) -> Result<()> {
1493		let frame = crate::IntoBytes::into_bytes(frame);
1494		if frame.len() as u64 > group::MAX_CACHE_BYTES {
1495			return Err(Error::FrameTooLarge);
1496		}
1497		let mut group = self.append_group()?;
1498		group.write_frame(timestamp, frame)?;
1499		group.finish()?;
1500		Ok(())
1501	}
1502
1503	/// End a locally closed session's receive track without declaring a wire boundary.
1504	pub(crate) fn close(self) -> Result<()> {
1505		let mut state = self.modify()?;
1506		state.sealed = true;
1507		// See `commit_abort`: a takeover resumes mid-group once the open group goes away.
1508		state.resume = state.resume_position();
1509		state.drop_open_groups();
1510		state.close_cache();
1511		state.close();
1512		Ok(())
1513	}
1514
1515	/// Abort the groups still receiving from a failed session before ending its track.
1516	pub(crate) fn abort_session(self, err: Error) -> Result<()> {
1517		let state = self.modify()?;
1518		let open: Vec<_> = state
1519			.lookup
1520			.values()
1521			.filter(|slot| !slot.group.is_finished())
1522			.map(|slot| slot.group.clone())
1523			.collect();
1524		// Snapshot the resume boundary before aborting groups releases their frames.
1525		commit_abort(state, err.clone());
1526		for group in open {
1527			let _ = group.abort(err.clone());
1528		}
1529		Ok(())
1530	}
1531
1532	/// Mark the track as finished after the last appended group.
1533	///
1534	/// Sets the final sequence to one past the current max_sequence.
1535	/// No new groups at or above this sequence can be appended.
1536	/// NOTE: Old groups with lower sequence numbers can still arrive.
1537	pub fn finish(&self) -> Result<()> {
1538		let mut state = self.modify()?;
1539		let final_sequence = match state.max_sequence {
1540			Some(max) => max.checked_add(1).ok_or(coding::BoundsExceeded)?,
1541			None => 0,
1542		};
1543		state.set_final(final_sequence)
1544	}
1545
1546	/// Declare the track's exclusive final sequence, possibly ahead of the live edge.
1547	///
1548	/// `final_sequence` is the first sequence that will never be produced, so a track
1549	/// whose last group is 89 finishes at `90`. Passing a boundary beyond the current
1550	/// max_sequence records a known ending before the remaining groups arrive (e.g.
1551	/// learning a track ends at group 89 while only 87 has been received). The boundary
1552	/// must be strictly greater than the highest produced group, otherwise it would
1553	/// orphan groups that already exist ([`Error::ProtocolViolation`]).
1554	///
1555	/// Groups below `final_sequence` may still be created afterwards; groups at or above
1556	/// it are rejected. Consumers only see end-of-stream once the live edge reaches the
1557	/// boundary. Use [`Self::finish`] to finish exactly at the live edge.
1558	pub fn finish_at(&mut self, final_sequence: u64) -> Result<()> {
1559		self.modify()?.set_final(final_sequence)
1560	}
1561
1562	/// Declare the first group the live feed serves (the wire's SUBSCRIBE_START,
1563	/// or the start the subscription itself requested): groups below `sequence`
1564	/// are not waited for, so a reader waiting for one fails over instead of
1565	/// stalling. One may still arrive late, and a fetch can still retrieve them.
1566	///
1567	/// Scoped to the current subscription's demand, so a later declaration
1568	/// replaces this one in either direction: a re-subscription may start
1569	/// earlier, and a narrowed subscription skips groups an earlier declaration
1570	/// still promised. Pass `None` to clear it, for demand at the live edge:
1571	/// its floor is unknown until the feed declares one.
1572	pub fn start_at(&mut self, sequence: impl Into<Option<u64>>) -> Result<()> {
1573		self.modify()?.set_start(sequence.into(), false);
1574		Ok(())
1575	}
1576
1577	/// Declare the floor a subscription asked for while the serving session has yet to
1578	/// resolve its start: nothing below `sequence` arrives, exactly as [`Self::start_at`],
1579	/// but [`Consumer::poll_start`] keeps waiting until a later [`Self::start_at`]
1580	/// resolves it.
1581	pub(crate) fn request_start(&mut self, sequence: Option<u64>) -> Result<()> {
1582		self.modify()?.set_start(sequence, true);
1583		Ok(())
1584	}
1585
1586	/// The declared first sequence of the live feed, once [`Self::start_at`]
1587	/// declared one. `None` while nothing was declared.
1588	#[cfg(test)]
1589	pub(crate) fn start_sequence(&self) -> Option<u64> {
1590		self.state.read().start_sequence
1591	}
1592
1593	/// The exclusive final sequence, once [`Self::finish`] or [`Self::finish_at`] declared one.
1594	///
1595	/// `None` while the track is still open ended. Both methods reject a second boundary, so
1596	/// callers that may have already declared one check here first.
1597	pub fn final_sequence(&self) -> Option<u64> {
1598		self.state.read().final_sequence
1599	}
1600
1601	/// Abort the track with the given error.
1602	///
1603	/// Consumes the handle, since nothing can be written to an aborted track. Consumers
1604	/// still draining get the finished groups, then the abort error. Open groups leave the
1605	/// cache; a consumer that already pulled one keeps its own handle and ends with it.
1606	/// The pool's idle expiry (see [`cache::Config::with_expiry`]) reclaims the rest, the
1607	/// latest group included, so a stale [`Consumer`] can't pin them in memory forever.
1608	///
1609	/// If the declared end had settled (the final sequence from
1610	/// [`finish_at`](Self::finish_at) was reached and every group below it finished),
1611	/// consumers get a clean end instead of the error, as after [`finish`](Self::finish).
1612	///
1613	/// [`finish`](Self::finish) is deliberately not terminal: it declares the final
1614	/// sequence, and lower-numbered groups may still be written afterwards.
1615	pub fn abort(self, err: Error) -> Result<()> {
1616		commit_abort(self.modify()?, err);
1617		Ok(())
1618	}
1619
1620	/// Abort an unused track, returning the producer unchanged if consumers remain.
1621	///
1622	/// Consumer creation and the unused check share a lock, so demand returning
1623	/// after [`poll_unused`](Self::poll_unused) prevents the abort. `Ok(())` means
1624	/// the track is closed, including when it was already closed; existing handles
1625	/// may still observe its final state. `Err(producer)` leaves the track unchanged
1626	/// so the caller can continue serving and wait for the next unused wake.
1627	#[expect(
1628		clippy::result_large_err,
1629		reason = "return the owned producer without allocating on an idle check"
1630	)]
1631	pub fn abort_unused(self, err: Error) -> std::result::Result<(), Self> {
1632		match self.state.write_unused() {
1633			kio::Unused::Idle(guard) => {
1634				commit_abort(guard, err);
1635				return Ok(());
1636			}
1637			kio::Unused::Closed => return Ok(()),
1638			kio::Unused::Used => {}
1639		}
1640		Err(self)
1641	}
1642
1643	/// Whether the track is open and anyone is consuming it right now.
1644	///
1645	/// A point-in-time snapshot for gating work on demand (on-demand capture,
1646	/// dropping cached state nobody is watching). Acting on it to *end* the track
1647	/// is the race [`abort_unused`](Self::abort_unused) exists for; use
1648	/// [`unused`](Self::unused) to wait for the edge.
1649	pub fn is_used(&self) -> bool {
1650		!self.is_closed() && self.state.is_used()
1651	}
1652
1653	/// Block until there are no active consumers.
1654	pub async fn unused(&self) -> Result<()> {
1655		self.state.unused().await.map_err(|_| self.abort_reason())
1656	}
1657
1658	/// Block until there is at least one active consumer.
1659	pub async fn used(&self) -> Result<()> {
1660		self.state.used().await.map_err(|_| self.abort_reason())
1661	}
1662
1663	/// Block until the track is closed or aborted, returning the cause.
1664	pub async fn closed(&self) -> Error {
1665		kio::wait(|waiter| self.poll_closed(waiter)).await
1666	}
1667
1668	/// Poll until the track is closed or aborted; ready with the cause.
1669	pub fn poll_closed(&self, waiter: &kio::Waiter) -> Poll<Error> {
1670		self.state.poll_closed(waiter).map(|()| self.abort_reason())
1671	}
1672
1673	/// The recorded abort reason, or [`Error::Dropped`] if the track closed without one.
1674	fn abort_reason(&self) -> Error {
1675		self.state.read().abort.clone().unwrap_or(Error::Dropped)
1676	}
1677
1678	/// Return true if the track has been closed.
1679	pub fn is_closed(&self) -> bool {
1680		self.state.read().is_closed()
1681	}
1682
1683	/// Return the latest sequence number successfully appended to the track.
1684	pub fn latest(&self) -> Option<u64> {
1685		self.state.read().max_sequence
1686	}
1687
1688	/// Return true if this is the same track.
1689	pub fn is_clone(&self, other: &Self) -> bool {
1690		self.state.same_channel(&other.state)
1691	}
1692
1693	/// Create a weak reference that doesn't prevent auto-close.
1694	pub(crate) fn weak(&self) -> TrackWeak {
1695		TrackWeak {
1696			name: self.name.clone(),
1697			state: self.state.weak(),
1698		}
1699	}
1700
1701	/// Create a [`Demand`]: a cloneable, watch-only handle to this track's
1702	/// subscriber demand.
1703	///
1704	/// Lets a publisher gate work (e.g. on-demand capture) on whether anyone is
1705	/// subscribed, without the ability to publish frames or close the track. The
1706	/// handle is weak, so holding one neither keeps the track alive nor pins its
1707	/// cached groups.
1708	pub fn demand(&self) -> Demand {
1709		Demand {
1710			name: self.name.clone(),
1711			state: self.state.weak(),
1712		}
1713	}
1714
1715	/// Get a consumer handle for this in-process track.
1716	///
1717	/// Unlike a wire subscription, the info is already known, so a subscription
1718	/// opened from this handle resolves immediately.
1719	pub fn consume(&self) -> Consumer {
1720		Consumer::plain(self.name.clone(), self.state.consume())
1721	}
1722
1723	/// Subscribing to this in-process track, resolving synchronously.
1724	///
1725	/// The info is fixed at creation, so there's nothing to wait for (no
1726	/// SUBSCRIBE_OK round trip). Pass `None` for [`Subscription::default`].
1727	///
1728	/// The read cursor starts at the group the subscription named (its floor), or 0.
1729	/// [`Subscription::max_age`] is what asks for data: delivery skips everything above
1730	/// the floor that the budget convicts, so the default budget of zero delivers only
1731	/// the latest group and a larger one reaches back over what it can still use.
1732	pub fn subscribe(&self, subscription: impl Into<Option<Subscription>>) -> Subscriber {
1733		let preferences = subscription.into().unwrap_or_default();
1734
1735		// Info is fixed at creation and survives a close/abort, so read it without
1736		// requiring a live producer state. If the track already ended, the returned
1737		// subscriber surfaces the close/abort on its first read; the preferences are
1738		// simply never registered (nothing aggregates them anymore).
1739		let info = self.info.clone();
1740		let min_sequence = floor_of(&preferences);
1741		let subscription = kio::Producer::new(preferences);
1742		register_subscription(self.state.read(), &subscription);
1743		let drift_anchor = kio::Producer::new(Anchor::default());
1744
1745		// Hoisted: an inline `read()` guard would live to the end of the struct literal,
1746		// deadlocking against the `consume()` below.
1747		let broadcast = self.state.read().broadcast.clone();
1748		Subscriber {
1749			name: self.name.clone(),
1750			broadcast,
1751			info,
1752			inner: SubscriberKind::Plain(PlainSubscriber {
1753				state: self.state.consume(),
1754				subscription,
1755				min_sequence,
1756				index: 0,
1757				datagram_index: 0,
1758				next_sequence: 0,
1759				end_sequence: None,
1760				parked: BTreeMap::new(),
1761				stale_cap: None,
1762				drift_anchor,
1763				stale: stats::Content::default(),
1764				seek_pending: BTreeMap::new(),
1765			}),
1766			// A producer-side (in-process) subscribe is not egress: stay untagged.
1767			stats: stats::Scope::default(),
1768			_stats_sub: stats::Subscription::default(),
1769		}
1770	}
1771
1772	/// Block until the aggregate subscription changes, then return the new value.
1773	///
1774	/// Yields the most demanding request across all live subscribers, or `None`
1775	/// once the last one drops. Used by relays to forward downstream demand
1776	/// upstream (e.g. SUBSCRIBE_UPDATE).
1777	pub async fn subscription_changed(&mut self) -> Result<Option<Subscription>> {
1778		kio::wait(|waiter| self.poll_subscription_changed(waiter)).await
1779	}
1780
1781	/// A non-blocking snapshot of the current aggregate subscription, or `None`
1782	/// when there are no live subscribers. Unlike [`Self::subscription`], this
1783	/// doesn't wait for a change or advance the change cursor.
1784	///
1785	/// The aggregate's [`Subscription::max_age`] is clamped to this track's
1786	/// [`Info::max_age`]: no subscriber can wait for a late group longer than the
1787	/// publisher keeps it.
1788	pub fn subscription(&self) -> Option<Subscription> {
1789		let state = self.state.read();
1790		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
1791		drop(state);
1792		snapshot_subscription(&subs, bound)
1793	}
1794
1795	/// Poll counterpart to [`subscription_changed`](Self::subscription_changed): the
1796	/// aggregate subscription whenever it changes, or `None` once nobody is subscribed.
1797	/// Errors once the track is aborted.
1798	pub fn poll_subscription_changed(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Subscription>>> {
1799		// Surface an abort as the stream ending. `poll_closed` parks on the closed
1800		// waiters, so per-group churn on the track state never wakes this poll.
1801		if self.state.poll_closed(waiter).is_ready() {
1802			let abort = self.state.read().abort.clone();
1803			return Poll::Ready(Err(abort.unwrap_or(Error::Dropped)));
1804		}
1805
1806		// Read the bound before locking `subs`, so the aggregation never nests the two locks.
1807		let state = self.state.read();
1808		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
1809		drop(state);
1810
1811		poll_combined_changed(&subs, bound, &mut self.prev_subscription, waiter).map(Ok)
1812	}
1813
1814	/// Poll for the producer becoming unused (every consumer dropped).
1815	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
1816		self.state.poll_unused(waiter).map(|used| match used {
1817			Some(()) => Ok(()),
1818			None => Err(self.abort_reason()),
1819		})
1820	}
1821
1822	/// Create a [`Dynamic`] handle that serves on-demand fetches of uncached
1823	/// (old) groups. Most producers never need this; a relay creates one to fetch
1824	/// past groups from upstream.
1825	pub fn dynamic(&self) -> Dynamic {
1826		Dynamic::new(self.name.clone(), self.state.clone(), self.alive.clone())
1827	}
1828
1829	fn modify(&self) -> Result<kio::Mut<'_, TrackState>> {
1830		TrackState::modify(&self.state)
1831	}
1832}
1833
1834/// Pop the next queued group fetch off the fetch queue and wrap it in a
1835/// [`group::Request`] bound to a fresh producer handle. Shared by every
1836/// [`Dynamic`] handle on the track.
1837fn poll_requested_group(
1838	state: &kio::Producer<TrackState>,
1839	fetch: &kio::Shared<FetchState>,
1840	waiter: &kio::Waiter,
1841) -> Poll<Result<group::Request>> {
1842	// Prefer serving a queued fetch, even if the track has since aborted.
1843	if let Poll::Ready(mut guard) = fetch.poll(waiter, |fetch| {
1844		if fetch.has_queued() {
1845			Poll::Ready(())
1846		} else {
1847			Poll::Pending
1848		}
1849	}) {
1850		let sequence = guard.pop().expect("predicate guaranteed a request");
1851		// The popped attempt stays pending, so a fetch in the window between hand-off
1852		// and accept joins it instead of queueing a duplicate.
1853		// `group::Request::{accept, reject, drop}` removes the entry.
1854		let pending = guard.get(&sequence).expect("popped key must be pending");
1855		let priority = pending.priority;
1856		let frame_start = pending.frame_start;
1857		let result = pending.result.clone();
1858		drop(guard);
1859		return Poll::Ready(Ok(group::Request {
1860			state: state.clone(),
1861			fetch: fetch.clone(),
1862			sequence,
1863			priority,
1864			frame_start,
1865			result,
1866			done: false,
1867		}));
1868	}
1869
1870	// No fetch queued: surface a track abort so the handler loop can exit.
1871	match state.poll_ref(waiter, |state| match &state.abort {
1872		Some(err) => Poll::Ready(err.clone()),
1873		None => Poll::Pending,
1874	}) {
1875		Poll::Ready(Ok(err)) => Poll::Ready(Err(err)),
1876		Poll::Ready(Err(closed)) => Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped))),
1877		Poll::Pending => Poll::Pending,
1878	}
1879}
1880
1881/// Serves on-demand fetches of uncached (old) groups for a track, the group-level
1882/// analogue of [`broadcast::Dynamic`].
1883///
1884/// Most tracks never serve old content, so this capability lives on a dedicated
1885/// handle rather than [`Producer`]: a relay creates one (via
1886/// [`Producer::dynamic`] or [`Request::dynamic`]) to pull past groups
1887/// from upstream. While at least one is alive the track will block a cache-miss
1888/// [`Consumer::fetch_group`] waiting to be served; with none, an accepted track's
1889/// miss fails fast with [`Error::NotFound`].
1890pub struct Dynamic {
1891	name: Arc<str>,
1892	// Kept to insert served groups into the cache and observe track abort.
1893	state: kio::Producer<TrackState>,
1894	// The fetch queue this handle drains; its `dynamic` count gates `fetch_group`.
1895	fetch: kio::Shared<FetchState>,
1896	// Shared with the track's producers: a handler still serving fetches keeps the
1897	// track alive, like a producer clone does.
1898	alive: Arc<Alive>,
1899}
1900
1901impl Dynamic {
1902	fn new(name: Arc<str>, state: kio::Producer<TrackState>, alive: Arc<Alive>) -> Self {
1903		let fetch = state.read().fetch.clone();
1904		fetch.lock().add_handler();
1905		Self {
1906			name,
1907			state,
1908			fetch,
1909			alive,
1910		}
1911	}
1912
1913	/// The track's name, unique within its broadcast.
1914	pub fn name(&self) -> &str {
1915		&self.name
1916	}
1917
1918	/// Block until a consumer fetches a group that isn't cached, returning a
1919	/// [`group::Request`] to serve via [`group::Request::accept`].
1920	///
1921	/// A relay issues a wire FETCH first; an origin already has the group cached, so
1922	/// the fetch resolves without ever reaching here. Errors once the track is aborted.
1923	pub async fn requested_group(&self) -> Result<group::Request> {
1924		kio::wait(|waiter| self.poll_requested_group(waiter)).await
1925	}
1926
1927	/// Poll counterpart to [`requested_group`](Self::requested_group).
1928	pub fn poll_requested_group(&self, waiter: &kio::Waiter) -> Poll<Result<group::Request>> {
1929		poll_requested_group(&self.state, &self.fetch, waiter)
1930	}
1931
1932	/// Poll for the track becoming unused (every consumer dropped).
1933	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
1934		self.state.poll_unused(waiter).map(|_| ())
1935	}
1936}
1937
1938impl Clone for Dynamic {
1939	fn clone(&self) -> Self {
1940		// Count each live handle (mirrors `broadcast::Dynamic`).
1941		self.fetch.lock().add_handler();
1942		Self {
1943			name: self.name.clone(),
1944			state: self.state.clone(),
1945			fetch: self.fetch.clone(),
1946			alive: self.alive.clone(),
1947		}
1948	}
1949}
1950
1951impl Drop for Dynamic {
1952	fn drop(&mut self) {
1953		// Unlike `broadcast::Dynamic`, dropping the last handle doesn't abort the track:
1954		// a live `Producer` may still be serving the subscription. It just stops fetch
1955		// serving. Queued attempts no handler will ever pop are dropped, closing their
1956		// result channels so every joined `Fetching` resolves NotFound; an attempt
1957		// already handed to a handler stays, resolved by its `group::Request` instead.
1958		let mut fetch = self.fetch.lock();
1959		if fetch.remove_handler() {
1960			fetch.drain_queued();
1961		}
1962	}
1963}
1964
1965/// Ends the track when the last [`Producer`] or [`Dynamic`] drops.
1966///
1967/// A refcount rather than a "am I the last one?" check inside `Drop`: that answer is a
1968/// snapshot, and acting on it is exactly what invalidates it. The track state's own
1969/// producer count can't answer it either, since a group settling its eviction debt
1970/// upgrades the account's weak handle and counts there for the duration (see
1971/// [`cache::Track::settle`]). Holding a producer of its own also keeps the state
1972/// writable until the teardown has run, whatever order the last owner's fields drop in.
1973struct Alive {
1974	name: Arc<str>,
1975	state: kio::Producer<TrackState>,
1976
1977	// Set when a `Producer` is first minted, so a `Request` nobody accepted (its
1978	// `Dynamic` holds this guard too) isn't reported as an abandoned publisher.
1979	published: AtomicBool,
1980
1981	// Ingress subscription for this track, opened by the tagged producer that claimed
1982	// it and closed when this guard drops.
1983	stats: OnceLock<stats::Subscription>,
1984}
1985
1986impl Alive {
1987	fn new(name: Arc<str>, state: kio::Producer<TrackState>) -> Arc<Self> {
1988		Arc::new(Self {
1989			name,
1990			state,
1991			published: Default::default(),
1992			stats: Default::default(),
1993		})
1994	}
1995
1996	/// Note that a [`Producer`] was minted from this track, optionally under a tagged
1997	/// broadcast's ingress scope (counted as one subscription for as long as the track
1998	/// has a publisher).
1999	fn publish(&self, stats: Option<&stats::Scope>) {
2000		self.published.store(true, Ordering::Relaxed);
2001		if let Some(scope) = stats {
2002			// At most one scope ever arrives: a track is minted either through
2003			// `Producer::new` (+ `with_stats`) or through `Request::accept`, never both.
2004			let _ = self.stats.set(scope.subscribe());
2005		}
2006	}
2007}
2008
2009impl Drop for Alive {
2010	fn drop(&mut self) {
2011		// A request nobody accepted was never publishing; there's nothing to tear down.
2012		if !self.published.load(Ordering::Relaxed) {
2013			return;
2014		}
2015		// The last producer going away ends the track: nothing protects its live edge
2016		// anymore, so the pool's idle expiry reclaims what a stale consumer would pin.
2017		// Without a finish it is an abrupt teardown, the same as an explicit abort:
2018		// the open groups go and the finished ones stay for readers still draining.
2019		// `abort()` closes the channel, so `write()` returns `Err(Ref)`. `finish()`
2020		// leaves it open with `final_sequence` set, so inspect both outcomes.
2021		match self.state.write() {
2022			Ok(mut state) => {
2023				if state.final_sequence.is_some() {
2024					// Groups still missing below the boundary can no longer arrive, so a
2025					// reader waiting on one ends cleanly instead of with `Dropped`.
2026					state.sealed = true;
2027					state.close_cache();
2028					return;
2029				}
2030				if state.abort.is_some() {
2031					return;
2032				}
2033				tracing::warn!(
2034					track = %self.name,
2035					"track::Producer dropped without finish() or abort()"
2036				);
2037				// See `abort`: keep the frame boundary once its open group goes away.
2038				state.resume = state.resume_position();
2039				state.drop_open_groups();
2040				state.close_cache();
2041			}
2042			Err(state) => {
2043				if state.sealed || state.final_sequence.is_some() || state.abort.is_some() {
2044					return;
2045				}
2046				tracing::warn!(
2047					track = %self.name,
2048					"track::Producer dropped without finish() or abort()"
2049				);
2050			}
2051		}
2052	}
2053}
2054
2055/// Poll until the aggregate across `subs` differs from `prev`, then store and return it.
2056///
2057/// Departed subscribers are pruned whenever the scan meets one, even when the aggregate
2058/// holds. Churn with a steady viewer's preferences never changes it, so otherwise every
2059/// wake would walk every departed entry the list has room for since its peak.
2060fn poll_combined_changed(
2061	subs: &kio::Shared<Subscriptions>,
2062	bound: Option<Duration>,
2063	prev: &mut Option<Subscription>,
2064	waiter: &kio::Waiter,
2065) -> Poll<Option<Subscription>> {
2066	loop {
2067		let mut next = None;
2068		let mut departed = false;
2069		let mut guard = ready!(subs.poll(waiter, |subs| {
2070			(next, departed) = combined_subscription(subs, bound, waiter);
2071			if departed || next != *prev {
2072				Poll::Ready(())
2073			} else {
2074				Poll::Pending
2075			}
2076		}));
2077		if departed {
2078			guard.retain(|sub| !sub.is_closed());
2079		}
2080		drop(guard);
2081		if next != *prev {
2082			*prev = next.clone();
2083			return Poll::Ready(next);
2084		}
2085		// Only pruned: a ready poll skips registering, so poll again to park on the list.
2086	}
2087}
2088
2089/// Aggregate every live subscriber's preferences into the most demanding request, and
2090/// report whether any departed subscriber is still listed.
2091///
2092/// Read-only: iterates the subscriptions immutably and registers `waiter` on each, so a
2093/// preference update (or a subscriber dropping) wakes the caller's poll. Callers decide
2094/// readiness from the returned value, then prune closed subscribers through the `Mut`.
2095fn combined_subscription(
2096	subs: &Subscriptions,
2097	bound: Option<Duration>,
2098	waiter: &kio::Waiter,
2099) -> (Option<Subscription>, bool) {
2100	let mut combined = None;
2101	let mut departed = false;
2102	for sub in subs.iter() {
2103		// A closed consumer means the subscriber dropped: it holds no live demand.
2104		// `Consumer::poll` evaluates the closure before the closed flag, so it would
2105		// still replay the final value into the aggregate; skip it explicitly so a
2106		// departed subscriber can't keep the aggregate pinned to its last request.
2107		if sub.is_closed() {
2108			departed = true;
2109			continue;
2110		}
2111		// Arm both waiters explicitly. `poll` registers on the value channel only
2112		// when it returns Pending, and a subscriber that contributes demand (always
2113		// the case for the first one) folds as Ready, so nothing would watch for its
2114		// departure or its next update: the last one leaving would never wake this
2115		// poll, and a reader lifting the cap it had set would leave the publisher's
2116		// upstream parked at that cap for good.
2117		let _ = sub.poll_closed(waiter);
2118		let _ = sub.poll(waiter, |_| Poll::<()>::Pending);
2119		if let Poll::Ready(merged) = sub.read().poll_combined(&combined) {
2120			combined = Some(merged);
2121		}
2122	}
2123	(clamp_combined(combined, bound), departed)
2124}
2125
2126/// A non-blocking aggregate of the current subscriptions, without arming any waiter.
2127fn snapshot_subscription(subs: &kio::Shared<Subscriptions>, bound: Option<Duration>) -> Option<Subscription> {
2128	let mut combined: Option<Subscription> = None;
2129	for sub in subs.read().iter() {
2130		// Skip dropped subscribers, matching `combined_subscription`.
2131		if sub.is_closed() {
2132			continue;
2133		}
2134		if let Poll::Ready(merged) = sub.read().poll_combined(&combined) {
2135			combined = Some(merged);
2136		}
2137	}
2138	clamp_combined(combined, bound)
2139}
2140
2141/// The highest sequence this subscriber could actually be handed: its read cursor's cap
2142/// ([`Subscriber::set_groups`]) and any cap imposed from outside, whichever is lower.
2143///
2144/// Deliberately not [`Subscription::end`]. That is a *request to the publisher*, folded
2145/// in with every other subscriber's, and it does not filter this handle (see
2146/// [`Subscriber`]): another unbounded subscriber widens the aggregate and the groups
2147/// arrive here anyway. Capping the drift anchor with it would pin the live edge at the
2148/// requested end while delivery ran past it, and everything above would then have nothing
2149/// newer to be late against.
2150///
2151/// `outer` is what a reader wrapping this cursor imposes. A spliced segment is
2152/// deliberately not given an inner cursor cap (it would park boundary-crossing groups out
2153/// of sight), so its reader passes down its own cap and the segment boundary that way.
2154/// Without it, a segment would anchor drift on groups it can never hand over.
2155fn servable_cap(cursor: Option<u64>, outer: Option<u64>) -> Option<u64> {
2156	super::subscription::min_some(cursor, outer)
2157}
2158
2159/// The read cursor's floor: the group the subscription named, or 0 (no floor).
2160///
2161/// A floor is the only thing a start contributes; [`Subscription::max_age`] is what asks
2162/// for data. Delivery walks everything at or above the floor and skips what the budget
2163/// convicts, so a zero budget (the default) delivers only the live edge, a larger one
2164/// reaches back over what it can still use, and a floor above the live edge simply waits
2165/// there (a resumed subscription naming where it left off). One bound decides both what
2166/// is sent and what is expired, so the two cannot disagree.
2167fn floor_of(subscription: &Subscription) -> u64 {
2168	subscription.start.map(|start| start.group).unwrap_or(0)
2169}
2170
2171/// Clamp a drift budget to the publisher's retention window: nobody can wait for a late
2172/// group longer than the publisher keeps it around.
2173///
2174/// The single clamp point. Subscribers hold their preferences verbatim, so what they asked
2175/// for stays readable, and it is applied here on both sides of the aggregate: to the
2176/// combined request the publisher sees ([`clamp_combined`]) and to one subscriber's own
2177/// budget when it decides a group is stale ([`TrackState::is_stale`]). Those agree because
2178/// `min` distributes over the `max` that combines them. `bound` is `None` on a track whose
2179/// info isn't known yet (an unaccepted [`Request`]), which imposes no window.
2180fn clamp_max_age(mut max_age: Duration, bound: Option<Duration>) -> Duration {
2181	if let Some(bound) = bound {
2182		max_age = max_age.min(bound);
2183	}
2184	max_age
2185}
2186
2187/// Clamp the aggregate's max age budget to the publisher's window; see [`clamp_max_age`].
2188fn clamp_combined(combined: Option<Subscription>, bound: Option<Duration>) -> Option<Subscription> {
2189	let mut combined = combined?;
2190	combined.max_age = clamp_max_age(combined.max_age, bound);
2191	Some(combined)
2192}
2193
2194/// Register a subscription if the track is live: clone the shared list out of the
2195/// state, release the track lock, then push under the list's own lock. A closed
2196/// track skips the push; nothing aggregates the preferences anymore.
2197///
2198/// Departed subscribers are pruned here too, so the list stays bounded on a track whose
2199/// aggregate nobody polls. Pruning on every push would make a burst of N joins O(N^2),
2200/// so, like `kio::WaiterList::register`, it only sweeps when the list is about to grow.
2201fn register_subscription(state: kio::Ref<'_, TrackState>, subscription: &kio::Producer<Subscription>) {
2202	if state.is_closed() {
2203		return;
2204	}
2205	let subs = state.subscriptions.clone();
2206	drop(state);
2207	let mut subs = subs.lock();
2208	if subs.len() == subs.capacity() {
2209		subs.retain(|sub| !sub.is_closed());
2210		// Leave at least half free, so each sweep is paid for by the pushes before it.
2211		let live = subs.len();
2212		subs.reserve(live);
2213	}
2214	subs.push(subscription.consume());
2215}
2216
2217/// A weak reference to a track that doesn't prevent auto-close.
2218#[derive(Clone)]
2219pub(crate) struct TrackWeak {
2220	name: Arc<str>,
2221	state: kio::ProducerWeak<TrackState>,
2222}
2223
2224impl TrackWeak {
2225	/// A [`Consumer`] for the cached track, or `None` once it has closed.
2226	///
2227	/// The count moves under the same lock the close takes, which is the other half
2228	/// of [`Producer::abort_unused`]: a lookup either gets a consumer in time to
2229	/// decline the teardown, or gets nothing and re-requests the track. It is never
2230	/// handed a track that is already on its way out.
2231	pub fn try_consume(&self) -> Option<Consumer> {
2232		Some(Consumer::plain(self.name.clone(), self.state.try_consume()?))
2233	}
2234
2235	/// The shared name handle, for use as a broadcast lookup key (clone is a
2236	/// refcount bump, and the same `Arc` is shared with the track's handles).
2237	pub(crate) fn name(&self) -> &Arc<str> {
2238		&self.name
2239	}
2240
2241	/// Reject a track nothing ever served, resolving its pending subscribes with `err`.
2242	///
2243	/// A track whose [`Producer`] was minted is left alone and this returns false;
2244	/// so is one a handler claimed, and one that already carries an abort reason. Fetched backfill can install
2245	/// [`Info`] before acceptance, so metadata alone does not prove a publisher exists.
2246	///
2247	/// Closes the state like [`Producer::abort`], so a [`Request`] still held by the
2248	/// publisher can't `accept` its way back to life afterwards.
2249	pub(crate) fn reject(&self, err: Error) -> bool {
2250		let Some(producer) = self.state.produce() else {
2251			return false;
2252		};
2253		let Ok(mut state) = producer.write() else {
2254			return false;
2255		};
2256		if state.published || state.claimed || state.abort.is_some() {
2257			return false;
2258		}
2259		state.abort = Some(err);
2260		state.close();
2261		true
2262	}
2263
2264	/// Whether anyone is consuming the track right now. A closed track doesn't
2265	/// count even if consumers linger to drain its cache: no new work is owed.
2266	pub(crate) fn is_used(&self) -> bool {
2267		!self.state.is_closed() && self.state.is_used()
2268	}
2269
2270	/// Park `waiter` for the next consumer appearing; a no-op once one exists.
2271	/// Feeds [`crate::broadcast::Demand`], which recomputes on wake.
2272	pub(crate) fn poll_used(&self, waiter: &kio::Waiter) {
2273		let _ = self.state.poll_used(waiter);
2274	}
2275
2276	/// Park `waiter` for the last consumer (or the track) going away; a no-op
2277	/// once none remain. Feeds [`crate::broadcast::Demand`].
2278	pub(crate) fn poll_unused(&self, waiter: &kio::Waiter) {
2279		let _ = self.state.poll_unused(waiter);
2280	}
2281}
2282
2283impl super::WeakEntry for TrackWeak {
2284	fn is_closed(&self) -> bool {
2285		self.state.is_closed()
2286	}
2287
2288	fn same_channel(&self, other: &Self) -> bool {
2289		self.state.same_channel(&other.state)
2290	}
2291}
2292
2293/// A cloneable, watch-only handle to a track's subscriber demand.
2294///
2295/// Obtained from [`Producer::demand`]. A publisher uses it to react to
2296/// whether anyone is subscribed (on-demand capture / encoding) without being able
2297/// to publish frames or close the track. It's a weak handle, so it neither keeps
2298/// the track alive nor pins its cached groups; once the owning [`Producer`]
2299/// goes away, [`used`](Self::used) / [`unused`](Self::unused) report the track's
2300/// closure.
2301#[derive(Clone)]
2302pub struct Demand {
2303	name: Arc<str>,
2304	state: kio::ProducerWeak<TrackState>,
2305}
2306
2307impl Demand {
2308	/// The track name this handle is bound to.
2309	pub fn name(&self) -> &str {
2310		&self.name
2311	}
2312
2313	/// Block until there is at least one active consumer.
2314	pub async fn used(&self) -> Result<()> {
2315		self.state.used().await.map_err(|_| self.abort_reason())
2316	}
2317
2318	/// Block until there are no active consumers.
2319	pub async fn unused(&self) -> Result<()> {
2320		self.state.unused().await.map_err(|_| self.abort_reason())
2321	}
2322
2323	/// Block until the track is closed or aborted, returning the cause.
2324	pub async fn closed(&self) -> Error {
2325		self.state.closed().await;
2326		self.abort_reason()
2327	}
2328
2329	/// The publisher's tie-break priority, as set in [`Info::priority`].
2330	pub(crate) fn priority(&self) -> u8 {
2331		// Always Some once the track exists; a closed one reads its last value.
2332		self.state
2333			.read()
2334			.info
2335			.as_ref()
2336			.map_or(DEFAULT_PRIORITY, |info| info.priority)
2337	}
2338
2339	/// Whether anyone is subscribed right now, without waiting.
2340	pub fn is_used(&self) -> bool {
2341		self.state.is_used()
2342	}
2343
2344	/// Poll-based variant of [`Self::used`].
2345	pub fn poll_used(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
2346		self.state.poll_used(waiter).map(|used| match used {
2347			Some(()) => Ok(()),
2348			None => Err(self.abort_reason()),
2349		})
2350	}
2351
2352	/// Poll-based variant of [`Self::unused`].
2353	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
2354		self.state.poll_unused(waiter).map(|used| match used {
2355			Some(()) => Ok(()),
2356			None => Err(self.abort_reason()),
2357		})
2358	}
2359
2360	/// Whether the track is gone, without waiting.
2361	pub(crate) fn is_closed(&self) -> bool {
2362		self.state.is_closed()
2363	}
2364
2365	/// Read the current demand and arm `waiter` for the next transition.
2366	///
2367	/// Level-triggered, unlike [`used`](Self::used) / [`unused`](Self::unused): it
2368	/// answers what the demand is *now* and wakes on whichever way it can move
2369	/// next, so a poll-driven caller watching several tracks at once doesn't have
2370	/// to track which edge it's waiting for.
2371	pub(crate) fn poll_state(&self, waiter: &kio::Waiter) -> DemandState {
2372		loop {
2373			match self.state.poll_used(waiter) {
2374				Poll::Ready(None) => return DemandState::Closed,
2375				// Not used, and armed for it becoming used.
2376				Poll::Pending => return DemandState::Idle,
2377				// Used, so arm for the reverse.
2378				Poll::Ready(Some(())) => match self.state.poll_unused(waiter) {
2379					Poll::Ready(None) => return DemandState::Closed,
2380					Poll::Pending => return DemandState::Active,
2381					// Went idle between the two reads, so neither poll armed anything.
2382					// Start over rather than returning a state with no waker behind it.
2383					Poll::Ready(Some(())) => continue,
2384				},
2385			}
2386		}
2387	}
2388
2389	/// The recorded abort reason, or [`Error::Dropped`] if the track closed without one.
2390	fn abort_reason(&self) -> Error {
2391		self.state.read().abort.clone().unwrap_or(Error::Dropped)
2392	}
2393}
2394
2395/// What [`Demand::poll_state`] found.
2396#[derive(Copy, Clone, Debug, Eq, PartialEq)]
2397pub(crate) enum DemandState {
2398	/// At least one subscriber.
2399	Active,
2400	/// No subscribers, but the track is still open.
2401	Idle,
2402	/// The track is gone; it will never be demanded again.
2403	Closed,
2404}
2405
2406/// A handle to a single track within a broadcast.
2407///
2408/// Obtained from [`broadcast::Consumer::track`]. Holding it sends nothing
2409/// to the publisher; it just names a track you can [`subscribe`](Self::subscribe)
2410/// to (a live, ongoing stream of groups) later. The same handle can be subscribed
2411/// to multiple times, and clones are cheap.
2412///
2413/// A track reached through a route-fed broadcast is *spliced*: it is backed by one
2414/// or more per-session tracks joined at group boundaries, and this handle reads
2415/// across them transparently.
2416#[derive(Clone)]
2417pub struct Consumer {
2418	name: Arc<str>,
2419	// The broadcast this track belongs to, so a catalog track can name the path its
2420	// relative references resolve against. Rebound by `broadcast::Consumer::track` to
2421	// that handle's view, which may name the broadcast differently than its producer did.
2422	broadcast: Arc<broadcast::Info>,
2423	inner: ConsumerKind,
2424	// Egress stats scope, set by a tagged [`broadcast::Consumer`] via
2425	// [`Self::with_stats`]. Empty (no-op) for an untagged track.
2426	stats: stats::Scope,
2427}
2428
2429#[derive(Clone)]
2430enum ConsumerKind {
2431	Plain(kio::Consumer<TrackState>),
2432	Spliced(super::resume::Consumer),
2433}
2434
2435impl Consumer {
2436	fn plain(name: Arc<str>, state: kio::Consumer<TrackState>) -> Self {
2437		let broadcast = state.read().broadcast.clone();
2438		Self {
2439			name,
2440			broadcast,
2441			inner: ConsumerKind::Plain(state),
2442			stats: stats::Scope::default(),
2443		}
2444	}
2445
2446	/// A consumer over a spliced logical track (a route-fed broadcast's track).
2447	pub(crate) fn spliced(name: Arc<str>, broadcast: Arc<broadcast::Info>, resume: super::resume::Consumer) -> Self {
2448		Self {
2449			name,
2450			broadcast,
2451			inner: ConsumerKind::Spliced(resume),
2452			stats: stats::Scope::default(),
2453		}
2454	}
2455
2456	/// Attach an egress stats scope, inherited by the subscriptions, fetches, and
2457	/// groups derived from this handle. Called by a tagged [`broadcast::Consumer`].
2458	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
2459		self.stats = scope;
2460		self
2461	}
2462
2463	/// Rebind the track to the broadcast handle it was reached through, so
2464	/// [`broadcast`](Self::broadcast) reports the path that handle was handed out at (see
2465	/// [`broadcast::Info::path`]). Called by [`broadcast::Consumer::track`].
2466	pub(crate) fn with_broadcast(mut self, broadcast: Arc<broadcast::Info>) -> Self {
2467		self.broadcast = broadcast;
2468		self
2469	}
2470
2471	/// Groups this copy still holds, so an origin can keep them after dropping the source.
2472	///
2473	/// Publisher-produced groups come back in arrival order, matching what
2474	/// `recv_group` would deliver; fetched backfill absent from arrival follows
2475	/// in sequence order for sequence fetches.
2476	pub(crate) fn cached_groups(&self) -> Vec<(group::Producer, bool)> {
2477		match &self.inner {
2478			ConsumerKind::Plain(state) => {
2479				let state = state.read();
2480				let mut out = Vec::with_capacity(state.lookup.len());
2481				for (sequence, stamp) in state.arrival.iter() {
2482					if let Some(slot) = state.lookup.get(sequence)
2483						&& slot.stamp == *stamp
2484						&& !slot.group.is_aborted()
2485					{
2486						out.push((slot.group.clone(), slot.visible));
2487					}
2488				}
2489				// Fetched backfill never enters arrival; keep it for sequence fetches.
2490				let mut copied: HashSet<u64> = out.iter().map(|(group, _)| group.sequence).collect();
2491				for (sequence, slot) in state.lookup.iter() {
2492					if !slot.group.is_aborted() && copied.insert(*sequence) {
2493						out.push((slot.group.clone(), slot.visible));
2494					}
2495				}
2496				out
2497			}
2498			ConsumerKind::Spliced(resume) => resume.cached_groups(),
2499		}
2500	}
2501
2502	/// A cached group that contains every frame from `frame_start` onward.
2503	///
2504	/// Unlike [`Self::peek_group`], this is suitable for satisfying a FETCH: a
2505	/// partial cached copy is a miss so the caller can ask upstream for its head.
2506	/// With `finished`, an unfinished copy is a miss too, for a cache nothing
2507	/// writes to anymore.
2508	pub(crate) fn cached_group(&self, sequence: u64, frame_start: u64, finished: bool) -> Option<group::Consumer> {
2509		match &self.inner {
2510			ConsumerKind::Plain(state) => {
2511				let state = state.read();
2512				let group = state.covering_group(sequence, frame_start)?;
2513				if finished && !group.is_finished() {
2514					return None;
2515				}
2516				group.cache_refresh();
2517				Some(group.consume())
2518			}
2519			ConsumerKind::Spliced(resume) => resume.cached_group(sequence, frame_start),
2520		}
2521	}
2522
2523	/// Publisher properties already resolved on this copy, if any.
2524	pub(crate) fn cached_info(&self) -> Option<Info> {
2525		match &self.inner {
2526			ConsumerKind::Plain(state) => state.read().info.clone(),
2527			ConsumerKind::Spliced(resume) => resume.cached_info(),
2528		}
2529	}
2530
2531	/// The track name this handle is bound to.
2532	pub fn name(&self) -> &str {
2533		&self.name
2534	}
2535
2536	/// The broadcast this track belongs to, as reached through the handle it came from.
2537	/// Its [`path`](broadcast::Info::path) is what a catalog's relative `broadcast`
2538	/// references resolve against.
2539	pub fn broadcast(&self) -> &broadcast::Info {
2540		&self.broadcast
2541	}
2542
2543	/// Open a live subscription.
2544	///
2545	/// Registers the subscription on the track and returns a [`kio::Pending`] that resolves to the
2546	/// [`Subscriber`] once the track info is available, or the track's abort error (or
2547	/// [`Error::Dropped`]) if it is already closed.
2548	///
2549	/// The read cursor starts at the group the subscription named (its floor), or 0.
2550	/// [`Subscription::max_age`] is what asks for data: delivery skips everything above
2551	/// the floor that the budget convicts, so the default budget of zero delivers only
2552	/// the latest group and a larger one reaches back over what it can still use.
2553	pub fn subscribe(&self, subscription: impl Into<Option<Subscription>>) -> kio::Pending<Subscribing> {
2554		let subscription = kio::Producer::new(subscription.into().unwrap_or_default());
2555
2556		let inner = match &self.inner {
2557			ConsumerKind::Plain(state) => {
2558				// Register the subscription if the track is live. If it is already closed, the
2559				// returned future resolves to the abort error via `Subscribing::poll_ok`.
2560				register_subscription(state.read(), &subscription);
2561				SubscribingKind::Plain(state.clone())
2562			}
2563			// A spliced subscription registers per segment once the subscriber polls.
2564			ConsumerKind::Spliced(resume) => SubscribingKind::Spliced(resume.clone()),
2565		};
2566
2567		kio::Pending::new(Subscribing {
2568			name: self.name.clone(),
2569			broadcast: self.broadcast.clone(),
2570			inner,
2571			subscription,
2572			stats: self.stats.clone(),
2573		})
2574	}
2575
2576	/// Poll for the first group the live feed serves, once the serving session has
2577	/// resolved it: `Some` for a declared start, `None` for none (the live edge, or a
2578	/// source that never declares one). Parks while a lite-06+ session still owes its
2579	/// SUBSCRIBE_START (see [`Producer::request_start`]); a closed track is ready with
2580	/// whatever it last declared, since nothing will resolve it anymore.
2581	pub(crate) fn poll_start(&self, waiter: &kio::Waiter) -> Poll<Option<u64>> {
2582		match &self.inner {
2583			ConsumerKind::Plain(state) => Self::poll_state_start(state, waiter),
2584			ConsumerKind::Spliced(_) => Poll::Ready(None),
2585		}
2586	}
2587
2588	/// [`Self::poll_start`] over one track's state, shared with [`Subscriber::poll_start`].
2589	fn poll_state_start(state: &kio::Consumer<TrackState>, waiter: &kio::Waiter) -> Poll<Option<u64>> {
2590		let res = state.poll(waiter, |state| match state.start_pending && state.abort.is_none() {
2591			true => Poll::Pending,
2592			false => Poll::Ready(state.start_sequence),
2593		});
2594		match ready!(res) {
2595			Ok(start) => Poll::Ready(start),
2596			Err(state) => Poll::Ready(state.start_sequence),
2597		}
2598	}
2599
2600	/// The newest group, when it is already cached: resolved synchronously, without
2601	/// counting as a fetch or a delivery. The IETF publisher snapshots its frame count to
2602	/// resolve Largest Object; a group that is not immediately available reads as no edge.
2603	pub(crate) fn peek_latest(&self) -> Option<group::Consumer> {
2604		match &self.inner {
2605			ConsumerKind::Plain(state) => {
2606				let sequence = state.read().max_sequence?;
2607				self.peek_group(sequence)
2608			}
2609			ConsumerKind::Spliced(resume) => resume.peek_latest(),
2610		}
2611	}
2612
2613	/// The live edge below the exclusive `cap` that drift is measured against; see
2614	/// [`TrackState::live_edge`]. A splice reports the newest across its segments.
2615	pub(crate) fn live_edge(&self, cap: Option<u64>) -> Option<LiveEdge> {
2616		match &self.inner {
2617			ConsumerKind::Plain(state) => {
2618				let edge = state.read().live_edge(cap)?;
2619				Some(LiveEdge {
2620					sequence: edge.sequence,
2621					timestamp: edge.timestamp,
2622					stamp: edge.stamp,
2623					track: state.weak(),
2624				})
2625			}
2626			ConsumerKind::Spliced(resume) => resume.live_edge(cap),
2627		}
2628	}
2629
2630	/// Where the first servable group in `from..cap` starts presenting: `None` while no
2631	/// such group is cached, `Some(None)` while it has no frame yet. Registers `waiter`
2632	/// for whatever could move that answer: a group arriving or leaving, or the unstamped
2633	/// one's first frame. A splice answers from the first segment holding one.
2634	pub(crate) fn poll_first_start(
2635		&self,
2636		waiter: &kio::Waiter,
2637		from: u64,
2638		cap: Option<u64>,
2639	) -> Option<Option<Timestamp>> {
2640		match &self.inner {
2641			ConsumerKind::Plain(state) => {
2642				let mut start = None;
2643				let _ = state.poll(waiter, |state| {
2644					while let Some(group) = state.first_servable(from, cap) {
2645						// A stamped group can abort without changing its track. Register
2646						// before checking it again, so an abort either wakes or re-resolves.
2647						let _ = group.poll_closed(waiter);
2648						let _ = group.poll_timestamp(waiter);
2649						if !group.is_aborted() {
2650							start = Some(group.timestamp());
2651							break;
2652						}
2653					}
2654					Poll::<()>::Pending
2655				});
2656				start
2657			}
2658			ConsumerKind::Spliced(resume) => resume.poll_first_start(waiter, from, cap),
2659		}
2660	}
2661
2662	/// The nearest cached group below `sequence`, under the same terms as
2663	/// [`Self::peek_group`]. Walks the cache's own order, so gaps in the group numbering
2664	/// are crossed and aborted (evicted) entries are skipped.
2665	pub(crate) fn peek_before(&self, sequence: u64) -> Option<group::Consumer> {
2666		match &self.inner {
2667			ConsumerKind::Plain(state) => {
2668				let state = state.read();
2669				state
2670					.lookup
2671					.range(..sequence)
2672					.rev()
2673					.map(|(_, slot)| &slot.group)
2674					.find(|group| !group.is_aborted())
2675					.map(|group| group.consume())
2676			}
2677			ConsumerKind::Spliced(resume) => resume.peek_before(sequence),
2678		}
2679	}
2680
2681	/// Poll for group `sequence` the way the live feed delivers it: `Some` once it is
2682	/// cached, `None` once the feed starts past it or ends before it, and pending while it
2683	/// still may land. A newer group is no verdict: QUIC does not order streams, so this one
2684	/// may still land behind it. A spliced copy has no feed of its own.
2685	pub(crate) fn poll_landed(&self, sequence: u64, waiter: &kio::Waiter) -> Poll<Option<group::Consumer>> {
2686		let ConsumerKind::Plain(state) = &self.inner else {
2687			return Poll::Ready(None);
2688		};
2689		let res = state.poll(waiter, |state| {
2690			if let Some(slot) = state.lookup.get(&sequence)
2691				&& !slot.group.is_aborted()
2692			{
2693				return Poll::Ready(Some(slot.group.consume()));
2694			}
2695			match state.start_sequence.is_some_and(|start| start > sequence)
2696				|| state.final_sequence.is_some_and(|fin| fin <= sequence)
2697			{
2698				true => Poll::Ready(None),
2699				false => Poll::Pending,
2700			}
2701		});
2702		match res {
2703			Poll::Ready(Ok(group)) => Poll::Ready(group),
2704			Poll::Ready(Err(_)) => Poll::Ready(None),
2705			Poll::Pending => Poll::Pending,
2706		}
2707	}
2708
2709	/// A cached group by sequence, under the same terms as [`Self::peek_latest`]. Unlike a
2710	/// fetch, a peek does not refresh the group's cache standing, so it never keeps a
2711	/// group alive over one a subscriber actually read; an aborted (evicted) group is a
2712	/// miss.
2713	pub(crate) fn peek_group(&self, sequence: u64) -> Option<group::Consumer> {
2714		match &self.inner {
2715			ConsumerKind::Plain(state) => {
2716				let state = state.read();
2717				let slot = state.lookup.get(&sequence)?;
2718				if slot.group.is_aborted() {
2719					return None;
2720				}
2721				Some(slot.group.consume())
2722			}
2723			ConsumerKind::Spliced(resume) => resume.peek_group(sequence),
2724		}
2725	}
2726
2727	/// Attach a subscription's drift policy to a cached group resolved outside its cursor.
2728	pub(crate) fn guard_group(
2729		&self,
2730		group: group::Consumer,
2731		subscription: kio::Consumer<Subscription>,
2732		anchor: kio::Consumer<Anchor>,
2733		bound: Option<u64>,
2734	) -> group::Consumer {
2735		let ConsumerKind::Plain(state) = &self.inner else {
2736			return group;
2737		};
2738		let sequence = group.sequence;
2739		group.with_expiry(Arc::new(GroupExpiry {
2740			state: state.weak(),
2741			subscription,
2742			anchor,
2743			bound,
2744			sequence,
2745		}))
2746	}
2747
2748	/// Poll for the newest cached group once its sequence passes `after`, as a producer
2749	/// handle: holding one keeps its frames when the track's own producer tears down
2750	/// abruptly, which otherwise releases every unfinished group. Pending once closed.
2751	pub(crate) fn poll_latest_group(&self, after: Option<u64>, waiter: &kio::Waiter) -> Poll<group::Producer> {
2752		let ConsumerKind::Plain(state) = &self.inner else {
2753			return Poll::Pending;
2754		};
2755		let res = state.poll(waiter, |state| {
2756			match state
2757				.latest_group
2758				.filter(|latest| after.is_none_or(|after| *latest > after))
2759			{
2760				Some(latest) => match state.lookup.get(&latest) {
2761					Some(slot) => Poll::Ready(slot.group.clone()),
2762					None => Poll::Pending,
2763				},
2764				None => Poll::Pending,
2765			}
2766		});
2767		match res {
2768			Poll::Ready(Ok(group)) => Poll::Ready(group),
2769			_ => Poll::Pending,
2770		}
2771	}
2772
2773	/// Poll for a cached group by sequence, parking `waiter` until it lands.
2774	///
2775	/// `Ready(None)` once it can never arrive: the track ended below the sequence, or
2776	/// closed. Unlike [`Self::fetch_group`] this never asks anyone to produce the group,
2777	/// so it only resolves for one a live subscription is already pulling. That is what
2778	/// the spliced reader in [`super::resume`] wants: it waits for the route serving a
2779	/// group to deliver it, rather than opening a redundant fetch alongside.
2780	pub(crate) fn poll_peek_group(&self, sequence: u64, waiter: &kio::Waiter) -> Poll<Option<group::Consumer>> {
2781		let ConsumerKind::Plain(state) = &self.inner else {
2782			// A segment's track is never itself spliced.
2783			return Poll::Pending;
2784		};
2785
2786		let res = state.poll(waiter, |state| {
2787			match state.lookup.get(&sequence) {
2788				Some(slot) if !slot.group.is_aborted() => Poll::Ready(Some(slot.group.consume())),
2789				// An aborted slot is a hole this route can no longer fill.
2790				Some(_) => Poll::Ready(None),
2791				// Past the declared end, so it can never exist.
2792				None if state.final_sequence.is_some_and(|fin| sequence >= fin) => Poll::Ready(None),
2793				// Below the declared start, so the live feed skipped it for good.
2794				None if state.start_sequence.is_some_and(|start| sequence < start) => Poll::Ready(None),
2795				None => Poll::Pending,
2796			}
2797		});
2798
2799		match res {
2800			Poll::Ready(Ok(res)) => Poll::Ready(res),
2801			// The track died; whatever it cached went with it.
2802			Poll::Ready(Err(_)) => Poll::Ready(None),
2803			Poll::Pending => Poll::Pending,
2804		}
2805	}
2806
2807	/// Poll for a live cached copy of `sequence` that can serve frame `index`,
2808	/// parking until one exists.
2809	///
2810	/// Unlike [`Self::poll_peek_group`] this never gives a verdict: a missing
2811	/// group parks (registered for its arrival) even below the declared start,
2812	/// since demand may move backward and revive it. The spliced reader uses it
2813	/// to reconsider a route it gave up on, so it must be exact about what
2814	/// "available" means: a copy that cannot start at `index` (its head is gone)
2815	/// leaves the route buried rather than reviving it into a peek that would
2816	/// bury it again. That exactness is load-bearing, since the reader consults
2817	/// this ahead of its terminal checks: relaxing it to "the group exists" makes
2818	/// revive and re-bury alternate forever inside one poll
2819	/// (`resume::test::misaligned_copy_is_lost_without_spinning`, where the
2820	/// regression surfaces as a hang).
2821	pub(crate) fn poll_serving_group(&self, sequence: u64, index: u64, waiter: &kio::Waiter) -> Poll<()> {
2822		let ConsumerKind::Plain(state) = &self.inner else {
2823			// A segment's track is never itself spliced.
2824			return Poll::Pending;
2825		};
2826		let res = state.poll(waiter, |state| match state.lookup.get(&sequence) {
2827			Some(slot) if !slot.group.is_aborted() => {
2828				// `start_at` clamps up, so landing higher means the copy no longer
2829				// holds this position.
2830				let mut group = slot.group.consume();
2831				group.start_at(index);
2832				match group.index() == index {
2833					true => Poll::Ready(()),
2834					false => Poll::Pending,
2835				}
2836			}
2837			_ => Poll::Pending,
2838		});
2839		match res {
2840			Poll::Ready(Ok(())) => Poll::Ready(()),
2841			// The track died; whatever would arrive never will, and the caller's
2842			// terminal checks settle the wait.
2843			Poll::Ready(Err(_)) | Poll::Pending => Poll::Pending,
2844		}
2845	}
2846
2847	/// Whether the cache is fed live, so the newest object it holds is the track's largest.
2848	/// A cache only fetches fill, or a relay's warm cache whose upstream producer closed, is not.
2849	pub(crate) fn is_live(&self) -> bool {
2850		match &self.inner {
2851			ConsumerKind::Plain(state) => {
2852				let state = state.read();
2853				state.published && !state.closed
2854			}
2855			ConsumerKind::Spliced(resume) => resume.is_live(),
2856		}
2857	}
2858
2859	/// Fetching a single past group, without holding a live subscription.
2860	///
2861	/// Returns a [`kio::Pending`] that resolves to the [`group::Consumer`]:
2862	/// immediately if the group is cached, otherwise once a [`Dynamic`] serves
2863	/// the request (a wire FETCH for a relay). `options` accepts `None`, a [`group::Fetch`],
2864	/// or `group::Fetch::default()`.
2865	///
2866	/// The returned future resolves to [`Error::NotFound`] when the group can never be served
2867	/// (past the final sequence, or no [`Dynamic`] on the track), the handler's rejection
2868	/// (a relay's upstream miss is [`StreamError::NotFound`](crate::StreamError::NotFound)),
2869	/// or the track's abort error if it's already closed. Concurrent fetches for the same sequence coalesce onto one
2870	/// handler request.
2871	pub fn fetch_group(&self, sequence: u64, options: impl Into<Option<group::Fetch>>) -> kio::Pending<Fetching> {
2872		let options = options.into().unwrap_or_default();
2873
2874		// One fetch per calling context, counted here (coalesced upstream work is
2875		// still one request served). Independent of `subscriptions` and the viewer
2876		// refcount.
2877		self.stats.fetch();
2878
2879		let state = match &self.inner {
2880			ConsumerKind::Plain(state) => state,
2881			// Spliced: routed to the newest segment's (plain) track, waiting for a
2882			// segment to exist if no route has served the track yet.
2883			ConsumerKind::Spliced(resume) => {
2884				return kio::Pending::new(Fetching {
2885					inner: FetchingKind::Spliced(resume.fetch_group(sequence, options)),
2886					stats: self.stats.clone(),
2887				});
2888			}
2889		};
2890
2891		let mut result = None;
2892
2893		// Queue a request only when the group isn't already resolvable from the track
2894		// (cached, aborted, or past-final all resolve through `Fetching::poll` without
2895		// a queue entry).
2896		let (fetch, cached) = {
2897			let state = state.read();
2898			(
2899				state.fetch.clone(),
2900				state.poll_fetch_cached(sequence, options.frame_start),
2901			)
2902		};
2903		let unresolved = cached.is_pending();
2904		// Hold a hit until the caller polls: the held consumer keeps the group wanted,
2905		// so an abandoned fetch filling it can't abort it in between.
2906		let hit = match cached {
2907			Poll::Ready(Ok(group)) => Some(Box::new(group)),
2908			_ => None,
2909		};
2910
2911		if unresolved {
2912			let mut fetch = fetch.lock();
2913			if let Some(pending) = fetch.join(&sequence) {
2914				// Join the in-flight attempt for this sequence (queued or already being
2915				// served): share its result channel, raising its priority if ours is higher
2916				// and widening its range if ours starts earlier.
2917				//
2918				// Widening only reaches the handler while the attempt is still queued;
2919				// once popped, the `group::Request` holds an immutable copy and its range is
2920				// already on the wire. What protects the late caller either way is the
2921				// coverage check above: it refuses a group starting above what it asked
2922				// for, so it fails cleanly and its retry queues a fresh attempt.
2923				pending.priority = pending.priority.max(options.priority);
2924				pending.frame_start = pending.frame_start.min(options.frame_start);
2925				result = Some(pending.result.consume());
2926			} else {
2927				// Queue a new attempt. The handler gate is atomic with a handler
2928				// dropping (no fetch stranded on a queue nobody drains); with no
2929				// handler, `Fetching::poll` fails fast instead.
2930				let producer = kio::Producer::<FetchOutcome>::default();
2931				let consumer = producer.consume();
2932				let attempt = PendingFetch {
2933					priority: options.priority,
2934					frame_start: options.frame_start,
2935					result: producer,
2936				};
2937				if fetch.insert(sequence, attempt).is_ok() {
2938					result = Some(consumer);
2939				}
2940			}
2941		}
2942
2943		kio::Pending::new(Fetching {
2944			inner: FetchingKind::Plain {
2945				state: state.clone(),
2946				fetch,
2947				sequence,
2948				frame_start: options.frame_start,
2949				result,
2950				hit,
2951			},
2952			stats: self.stats.clone(),
2953		})
2954	}
2955
2956	/// Resolve the track's [`Info`] without subscribing.
2957	///
2958	/// A [`Consumer`] is a lazy handle, so the info may not be known yet: this waits
2959	/// for the producer to [`Request::accept`] the track (a wire TRACK_INFO round-trip
2960	/// for a relay), and errors with the track's abort error if it closes first.
2961	/// [`Subscriber::info`] is the already-resolved counterpart.
2962	pub fn query(&self) -> kio::Pending<Querying> {
2963		kio::Pending::new(Querying {
2964			inner: match &self.inner {
2965				ConsumerKind::Plain(state) => QueryingKind::Plain(state.clone()),
2966				ConsumerKind::Spliced(resume) => QueryingKind::Spliced(resume.clone()),
2967			},
2968		})
2969	}
2970
2971	/// Return the latest group sequence in the track, or `None` before any group.
2972	pub fn latest(&self) -> Option<u64> {
2973		match &self.inner {
2974			ConsumerKind::Plain(state) => state.read().max_sequence,
2975			ConsumerKind::Spliced(resume) => resume.latest(),
2976		}
2977	}
2978
2979	/// The declared exclusive final sequence, or `None` while the track is open ended.
2980	///
2981	/// A spliced track answers for its newest segment, which is where fetches go.
2982	pub(crate) fn final_sequence(&self) -> Option<u64> {
2983		match &self.inner {
2984			ConsumerKind::Plain(state) => state.read().final_sequence,
2985			ConsumerKind::Spliced(resume) => resume.final_sequence(),
2986		}
2987	}
2988
2989	/// The frame-precise point a replacement route should resume from: one past the
2990	/// last frame this copy produced. `None` if it produced nothing.
2991	///
2992	/// Survives the track aborting, which is when a route change asks.
2993	pub(crate) fn resume_position(&self) -> Option<Position> {
2994		match &self.inner {
2995			ConsumerKind::Plain(state) => state.read().resume_position(),
2996			ConsumerKind::Spliced(resume) => resume.resume_position(),
2997		}
2998	}
2999
3000	/// Poll for the track reaching a terminal state: `Ok(())` once it is complete
3001	/// (the final group was produced), `Err` once it closed or aborted before
3002	/// completing. The origin's dispatcher uses this to tell a track that truly
3003	/// ended from one whose serving route died mid-stream.
3004	pub(crate) fn poll_complete(&self, waiter: &kio::Waiter) -> Poll<Result<()>> {
3005		let ConsumerKind::Plain(state) = &self.inner else {
3006			// Spliced tracks are compositions; the dispatcher never monitors one.
3007			return Poll::Pending;
3008		};
3009		match ready!(state.poll(waiter, |state| {
3010			// A local close without a declared end is a dead route, not finished content.
3011			if state.final_sequence.is_some() && state.is_complete() {
3012				Poll::Ready(())
3013			} else {
3014				Poll::Pending
3015			}
3016		})) {
3017			Ok(_) => Poll::Ready(Ok(())),
3018			// Closed before completing. Read through the returned guard: it holds
3019			// the lock, so re-locking the channel here would deadlock.
3020			Err(closed) => Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped))),
3021		}
3022	}
3023}
3024
3025/// The pollable state of a [`Consumer::subscribe`]; awaited via the
3026/// [`kio::Pending`] wrapper, whose `DerefMut` exposes [`Self::update`].
3027pub struct Subscribing {
3028	name: Arc<str>,
3029	broadcast: Arc<broadcast::Info>,
3030	inner: SubscribingKind,
3031	subscription: kio::Producer<Subscription>,
3032	stats: stats::Scope,
3033}
3034
3035enum SubscribingKind {
3036	Plain(kio::Consumer<TrackState>),
3037	Spliced(super::resume::Consumer),
3038}
3039
3040impl Subscribing {
3041	/// Poll until the peer confirms the subscription, yielding the [`Subscriber`].
3042	/// Errors if the track is aborted or not found.
3043	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<Subscriber>> {
3044		match &self.inner {
3045			SubscribingKind::Plain(state) => {
3046				// Wait until the track info is available
3047				let info = ready!(state.poll(waiter, |state| state.poll_info()))
3048					.map_err(|e| e.abort.clone().unwrap_or(Error::Dropped))??;
3049
3050				let drift_anchor = kio::Producer::new(Anchor::default());
3051				let min_sequence = floor_of(&self.subscription.read());
3052				Poll::Ready(Ok(Subscriber {
3053					name: self.name.clone(),
3054					broadcast: self.broadcast.clone(),
3055					info,
3056					inner: SubscriberKind::Plain(PlainSubscriber {
3057						state: state.clone(),
3058						subscription: self.subscription.clone(),
3059						min_sequence,
3060						index: 0,
3061						datagram_index: 0,
3062						next_sequence: 0,
3063						end_sequence: None,
3064						parked: BTreeMap::new(),
3065						stale_cap: None,
3066						drift_anchor,
3067						stale: stats::Content::default(),
3068						seek_pending: BTreeMap::new(),
3069					}),
3070					stats: self.stats.clone(),
3071					_stats_sub: self.stats.subscribe(),
3072				}))
3073			}
3074			SubscribingKind::Spliced(resume) => {
3075				// Resolved from the first segment's track. The publisher's max age
3076				// window is applied to each per-session aggregate, not here.
3077				let info = ready!(resume.poll_info(waiter))?;
3078
3079				Poll::Ready(Ok(Subscriber {
3080					name: self.name.clone(),
3081					broadcast: self.broadcast.clone(),
3082					info,
3083					inner: SubscriberKind::Spliced(Box::new(resume.subscribe_shared(self.subscription.clone()))),
3084					stats: self.stats.clone(),
3085					_stats_sub: self.stats.subscribe(),
3086				}))
3087			}
3088		}
3089	}
3090
3091	/// Change the subscription preferences before (or after) it resolves.
3092	///
3093	/// Returns [`Error::Closed`] if the track already ended; the update is
3094	/// meaningless at that point and can usually be ignored.
3095	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
3096		let mut state = self.subscription.write().map_err(|_| Error::Closed)?;
3097		*state = subscription;
3098		Ok(())
3099	}
3100}
3101
3102impl kio::Pollable for Subscribing {
3103	type Output = Result<Subscriber>;
3104
3105	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
3106		self.poll_ok(waiter)
3107	}
3108}
3109
3110/// The pollable state of a [`Consumer::query`]; awaited via the
3111/// [`kio::Pending`] wrapper.
3112pub struct Querying {
3113	inner: QueryingKind,
3114}
3115
3116enum QueryingKind {
3117	Plain(kio::Consumer<TrackState>),
3118	Spliced(super::resume::Consumer),
3119}
3120
3121impl Querying {
3122	/// Poll until the track's [`Info`] is known, without subscribing to its groups.
3123	pub fn poll_ok(&self, waiter: &kio::Waiter) -> Poll<Result<Info>> {
3124		match &self.inner {
3125			QueryingKind::Plain(state) => {
3126				// Wait until the track info is available
3127				let info = ready!(state.poll(waiter, |state| state.poll_info()))
3128					.map_err(|e| e.abort.clone().unwrap_or(Error::Dropped))??;
3129				Poll::Ready(Ok(info))
3130			}
3131			QueryingKind::Spliced(resume) => resume.poll_info(waiter),
3132		}
3133	}
3134}
3135
3136impl kio::Pollable for Querying {
3137	type Output = Result<Info>;
3138
3139	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
3140		self.poll_ok(waiter)
3141	}
3142}
3143
3144impl group::Request {
3145	/// The group sequence the consumer wants.
3146	pub fn sequence(&self) -> u64 {
3147		self.sequence
3148	}
3149
3150	/// The delivery priority the consumer requested for this group.
3151	pub fn priority(&self) -> u8 {
3152		self.priority
3153	}
3154
3155	/// The first frame of the group the consumer wants; 0 is the whole group.
3156	///
3157	/// The group [`accept`](Self::accept) returns already starts here, so the frames a
3158	/// handler writes carry the indices they have in the group rather than restarting at
3159	/// 0. A handler serving from elsewhere moves it with
3160	/// [`start_at`](group::Producer::start_at) before the first frame.
3161	///
3162	/// There is no end: the handler fetches through the end of the group so the result
3163	/// is cacheable for anyone (see [`group::Fetch::frame_start`]).
3164	pub fn frame_start(&self) -> u64 {
3165		self.frame_start
3166	}
3167
3168	/// Insert the fetched group into the track cache, resolving the waiting
3169	/// [`Consumer::fetch_group`], and return a [`group::Producer`] to fill.
3170	///
3171	/// The group's timescale comes from the track's [`Info`]. `info` sets that
3172	/// info if the track hasn't been accepted yet (a fetch with no live subscription),
3173	/// and is ignored once accepted. Returns [`Error::Duplicate`] if the group is
3174	/// already present, or the track's abort error if it closed while pending.
3175	pub fn accept(mut self, info: impl Into<Option<Info>>) -> Result<group::Producer> {
3176		self.done = true;
3177		// Cache the group before removing the attempt: the joined fetches resolve
3178		// through the cache, and removal closes their result channel (which alone
3179		// would read as NotFound).
3180		let res = TrackState::modify(&self.state)
3181			.and_then(|mut state| state.insert_group_request(self.sequence, self.frame_start, info.into()));
3182		self.remove();
3183		// A joined fetch the cached group can't cover (a wider start that joined after
3184		// the range went on the wire) fails rather than waits. Closing the channel says
3185		// the same, but a handler tracking the joined demand keeps it open.
3186		if let Ok(mut outcome) = self.result.write() {
3187			outcome.rejected = Some(Error::NotFound);
3188		}
3189		res
3190	}
3191
3192	/// Declare the track's exclusive final sequence, as the publisher answering this
3193	/// fetch reported it. A no-op once the track declared one, or holds a later group.
3194	pub(crate) fn finish_track_at(&self, final_sequence: u64) {
3195		if let Ok(mut state) = TrackState::modify(&self.state)
3196			&& state.final_sequence.is_none()
3197		{
3198			let _ = state.set_final(final_sequence);
3199		}
3200	}
3201
3202	/// Reject the fetch, resolving every joined [`Consumer::fetch_group`] with `err`.
3203	pub fn reject(mut self, err: Error) {
3204		self.done = true;
3205		// Remove before writing, so a fetch arriving now starts a fresh attempt
3206		// instead of joining a rejected one.
3207		self.remove();
3208		if let Ok(mut outcome) = self.result.write() {
3209			outcome.rejected = Some(err);
3210		}
3211	}
3212
3213	/// Ready once the cache or a terminal track state answers this request.
3214	pub(crate) fn poll_resolved(&mut self, waiter: &kio::Waiter) -> Poll<()> {
3215		let _ = ready!(self.state.poll_ref(waiter, |state| {
3216			state.poll_fetch_cached(self.sequence, self.frame_start)
3217		}));
3218		// Readers check the track before the result channel, including on its close.
3219		// They already have an answer, so retire the attempt without rejecting them.
3220		self.done = true;
3221		self.remove();
3222		Poll::Ready(())
3223	}
3224
3225	/// Poll for the request becoming unused (every waiting [`Consumer::fetch_group`]
3226	/// dropped), so a handler can stop serving and drop the request.
3227	///
3228	/// Once ready, a later fetch of the group starts a fresh request rather than
3229	/// joining this abandoned one.
3230	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
3231		loop {
3232			ready!(self.result.poll_unused(waiter));
3233			// Fetches join under the fetch lock, so re-checking under it makes the
3234			// withdrawal atomic with a join racing the last one leaving.
3235			let mut fetch = self.fetch.lock();
3236			if self.result.is_used() {
3237				continue;
3238			}
3239			fetch.remove_if(&self.sequence, |pending| pending.result.same_channel(&self.result));
3240			return Poll::Ready(());
3241		}
3242	}
3243
3244	/// Remove this attempt from the fetch state, unless a newer attempt for the same
3245	/// sequence has already replaced it.
3246	fn remove(&self) {
3247		self.fetch
3248			.lock()
3249			.remove_if(&self.sequence, |pending| pending.result.same_channel(&self.result));
3250	}
3251}
3252
3253impl Drop for group::Request {
3254	fn drop(&mut self) {
3255		if self.done {
3256			return;
3257		}
3258		self.remove();
3259		if let Ok(mut outcome) = self.result.write() {
3260			outcome.rejected = Some(Error::Dropped);
3261		}
3262	}
3263}
3264
3265/// The pollable state of a [`Consumer::fetch_group`].
3266///
3267/// Awaited via the [`kio::Pending`] wrapper; resolves to the
3268/// [`group::Consumer`] once the group lands in the track's cache (already present,
3269/// or produced after a wire FETCH), or [`Error::NotFound`] if it can never exist.
3270pub struct Fetching {
3271	inner: FetchingKind,
3272	// Egress stats scope, so the resolved group carries a payload meter (and counts
3273	// as one delivered group). Empty (no-op) for an untagged track.
3274	stats: stats::Scope,
3275}
3276
3277enum FetchingKind {
3278	Plain {
3279		state: kio::Consumer<TrackState>,
3280		fetch: kio::Shared<FetchState>,
3281		sequence: u64,
3282		// This caller's own start, so a cached group that begins above it is a miss
3283		// rather than a short answer.
3284		frame_start: u64,
3285		// The joined attempt's result channel; `None` on a cache hit, or when no handler
3286		// existed to queue on.
3287		result: Option<kio::Consumer<FetchOutcome>>,
3288		// The group already cached when the fetch was made, held so it stays wanted.
3289		// Boxed: a group consumer dwarfs the rest of the handle.
3290		hit: Option<Box<group::Consumer>>,
3291	},
3292	/// A spliced track's fetch: waits for a segment, then fetches from it.
3293	Spliced(kio::Pending<super::resume::Fetching>),
3294}
3295
3296impl kio::Pollable for Fetching {
3297	type Output = Result<group::Consumer>;
3298
3299	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
3300		let (state, fetch, sequence, frame_start, result, hit) = match &self.inner {
3301			FetchingKind::Plain {
3302				state,
3303				fetch,
3304				sequence,
3305				frame_start,
3306				result,
3307				hit,
3308			} => (state, fetch, *sequence, *frame_start, result.as_ref(), hit),
3309			FetchingKind::Spliced(spliced) => {
3310				// A fetched group is metered here (once), at the tagged handle: the
3311				// spliced source track it comes from is the origin's own, untagged.
3312				return kio::Pollable::poll(&**spliced, waiter)
3313					.map(|res| res.map(|group| group.with_meter(self.stats.meter())));
3314			}
3315		};
3316
3317		// Hand back a consumer sitting where the caller asked rather than at the group's
3318		// own start. Coverage was checked first, so this can only skip frames they excluded.
3319		let resolve = |mut group: group::Consumer| {
3320			group.start_at(frame_start);
3321			group.with_meter(self.stats.meter())
3322		};
3323		if let Some(group) = hit {
3324			return Poll::Ready(Ok(resolve((**group).clone())));
3325		}
3326
3327		// Track side: the cached group, the abort error, or past-final. The outer
3328		// error is the channel closing without any of those.
3329		let cached =
3330			|waiter: &kio::Waiter| match state.poll(waiter, |state| state.poll_fetch_cached(sequence, frame_start)) {
3331				Poll::Ready(Ok(res)) => Poll::Ready(res.map(resolve)),
3332				Poll::Ready(Err(closed)) => Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped))),
3333				Poll::Pending => Poll::Pending,
3334			};
3335		if let Poll::Ready(res) = cached(waiter) {
3336			return Poll::Ready(res);
3337		}
3338
3339		// Handler side.
3340		let Some(result) = result else {
3341			// Never queued: no handler existed when the fetch was made. Fail fast while
3342			// that's still true; a handler that appeared since may yet fill the cache.
3343			return match fetch.poll(waiter, |fetch| match fetch.has_handlers() {
3344				false => Poll::Ready(()),
3345				true => Poll::Pending,
3346			}) {
3347				Poll::Ready(_guard) => Poll::Ready(Err(Error::NotFound)),
3348				Poll::Pending => Poll::Pending,
3349			};
3350		};
3351
3352		// A written rejection fails every joined fetch. The channel closing without
3353		// one means the attempt was dropped unserved (its handlers went away).
3354		let err = match result.poll(waiter, |outcome| match &outcome.rejected {
3355			Some(err) => Poll::Ready(err.clone()),
3356			None => Poll::Pending,
3357		}) {
3358			Poll::Ready(Ok(err)) => err,
3359			Poll::Ready(Err(_closed)) => Error::NotFound,
3360			Poll::Pending => return Poll::Pending,
3361		};
3362		// An accept caches the group before it answers the channel, which may land
3363		// between the track check above and this one: look again before failing.
3364		match cached(waiter) {
3365			Poll::Ready(res) => Poll::Ready(res),
3366			Poll::Pending => Poll::Ready(Err(err)),
3367		}
3368	}
3369}
3370
3371/// A live subscription to a track, used to read its groups.
3372///
3373/// Created via [`Consumer::subscribe`](Consumer::subscribe), or
3374/// directly from a [`Producer`] for an in-process track. Carries this
3375/// subscriber's [`Subscription`] preferences, which feed the producer's aggregate.
3376///
3377/// # Local cursor vs wire preference
3378///
3379/// Group bounds exist at two levels, and setting one does not imply the other:
3380///
3381/// - [`Self::set_groups`] limits **this subscriber's reads**, filtering exactly what
3382///   this handle returns without changing the publisher's demand.
3383/// - [`Subscription::start`] / [`Subscription::end`], set via [`Self::update`],
3384///   are a **request to the publisher**. They're aggregated across every live subscriber
3385///   (earliest start, widest end), so they say what the publisher should send, not what
3386///   this subscriber sees.
3387///
3388/// They stay separate because their scopes differ: a subscriber can't filter by the
3389/// aggregate, since another subscriber can widen it, and the publisher can't honor a
3390/// cursor it's never told about. So setting only the cursor still transfers the skipped
3391/// groups, and setting only the preference still returns groups another subscriber asked
3392/// for. Set both to skip them *and* avoid the transfer.
3393///
3394/// The one place they meet is where the cursor comes from. A new subscriber's cursor is
3395/// floored at the group its own subscription named (or 0), and its
3396/// [`Subscription::max_age`] decides what above the floor is worth delivering. Every later
3397/// move is the caller's.
3398pub struct Subscriber {
3399	name: Arc<str>,
3400	// The broadcast this track belongs to; see [`Self::broadcast`].
3401	broadcast: Arc<broadcast::Info>,
3402	info: Info,
3403	inner: SubscriberKind,
3404	// Egress stats scope, used to meter the groups this subscriber reads. Empty
3405	// (no-op) for an untagged track.
3406	stats: stats::Scope,
3407	// The subscription guard: bumps `subscriptions` (and the egress viewer refcount)
3408	// while held, closing them on drop. Empty (no-op) for an untagged track.
3409	_stats_sub: stats::Subscription,
3410}
3411
3412enum SubscriberKind {
3413	Plain(PlainSubscriber),
3414	// Boxed: the spliced cursor set dwarfs the plain cursor.
3415	Spliced(Box<super::resume::Subscriber>),
3416}
3417
3418/// One poll's view of how far this subscription may drift: the clamped budget and the
3419/// live edge to measure a candidate group against. Resolved once, then applied to every
3420/// group that poll considers. `outer` is revalidated and `successor` resolved per
3421/// candidate, outside this track's lock.
3422#[derive(Clone)]
3423struct Drift {
3424	budget: Duration,
3425	edge: Edge,
3426	outer: Option<LiveEdge>,
3427	successor: Option<Successor>,
3428}
3429
3430/// Keeps one handed-out group tied to the subscription whose cursor selected it.
3431struct GroupExpiry {
3432	/// Weak so a handed-out group never joins the track's consumer count, which is what
3433	/// [`crate::broadcast::Demand`] reads to decide the track still has readers. A group
3434	/// outlives the cursor that produced it (a relay serves one for the life of its
3435	/// stream), and pinning demand on that would hold an upstream subscription open past
3436	/// the last real subscriber.
3437	state: kio::ConsumerWeak<TrackState>,
3438	subscription: kio::Consumer<Subscription>,
3439	anchor: kio::Consumer<Anchor>,
3440	bound: Option<u64>,
3441	sequence: u64,
3442}
3443
3444impl group::Expiry for GroupExpiry {
3445	fn is_expired(&self, waiter: &kio::Waiter) -> bool {
3446		let mut max_age = Duration::default();
3447		let _ = self.subscription.poll(waiter, |subscription| {
3448			max_age = subscription.max_age;
3449			Poll::<()>::Pending
3450		});
3451
3452		let mut anchor = Anchor::default();
3453		let _ = self.anchor.poll(waiter, |current| {
3454			anchor = (**current).clone();
3455			Poll::<()>::Pending
3456		});
3457		let anchor = anchor.capped(self.bound);
3458		let cap = anchor.cap;
3459		// Before this track's lock: both may name another track, and nesting deadlocks.
3460		let outer = anchor
3461			.edge
3462			.filter(LiveEdge::is_live)
3463			.map(|live| (live.sequence, live.timestamp));
3464		let successor = anchor
3465			.successor
3466			.as_ref()
3467			.and_then(|successor| successor.poll_start(waiter));
3468
3469		let mut expired = false;
3470		let _ = self.state.poll(waiter, |state| {
3471			let budget = clamp_max_age(max_age, state.max_age_bound());
3472			loop {
3473				// An abort can change reach even after the successor is stamped.
3474				// Register before judging, so a racing abort is observed or wakes us.
3475				// An abort only closes the group, never touching the track, so one that
3476				// landed before registering must re-select or the replacement goes unwatched.
3477				let next = loop {
3478					let next = state.first_servable(self.sequence.saturating_add(1), cap);
3479					match next {
3480						Some(group) if group.poll_closed(waiter).is_ready() && group.is_aborted() => continue,
3481						next => break next,
3482					}
3483				};
3484				let edge = state.drift_edge(cap, outer, successor);
3485				// Likewise the edge: its abort hands the edge to a lower group, which the
3486				// scan below skips.
3487				if let Some(live) = edge.presentation
3488					&& let Some(slot) = state.lookup.get(&live.sequence)
3489					&& slot.group.poll_closed(waiter).is_ready()
3490					&& slot.group.is_aborted()
3491				{
3492					continue;
3493				}
3494				expired = state.is_stale(self.sequence, &edge, budget);
3495				if expired {
3496					break;
3497				}
3498
3499				// A first timestamp can change the verdict without mutating the track:
3500				// on a group past the edge (a new edge), or on the candidate's
3501				// unstamped immediate successor (a reach where there was none).
3502				// Register on the candidate, its successor, and every servable group
3503				// past the edge. If one raced this scan, resolve the edge again before
3504				// Pending. Groups between the successor and the edge can move neither
3505				// while the edge stands (its abort is watched above), and walking them
3506				// would cost each of N parked serves the whole backlog, O(N^2) per
3507				// track change.
3508				let mut timestamp_raced = false;
3509				if let Some(slot) = state.lookup.get(&self.sequence) {
3510					let group = &slot.group;
3511					if group.timestamp().is_none()
3512						&& group.poll_timestamp(waiter).is_ready()
3513						&& group.timestamp().is_some()
3514					{
3515						timestamp_raced = true;
3516					}
3517				}
3518				let past = edge
3519					.presentation
3520					.map_or(self.sequence, |live| live.sequence.max(self.sequence));
3521				let beyond = state
3522					.lookup
3523					.range((std::ops::Bound::Excluded(past), std::ops::Bound::Unbounded))
3524					.map(|(_, slot)| slot)
3525					.take_while(|slot| super::subscription::before_end(slot.group.sequence, cap))
3526					.filter(|slot| slot.visible && !slot.group.is_aborted())
3527					.map(|slot| &slot.group);
3528				for group in next.into_iter().chain(beyond) {
3529					if group.timestamp().is_none()
3530						&& group.poll_timestamp(waiter).is_ready()
3531						&& group.timestamp().is_some()
3532					{
3533						timestamp_raced = true;
3534						break;
3535					}
3536				}
3537				if !timestamp_raced {
3538					break;
3539				}
3540			}
3541
3542			// Register on track changes even though the current answer is known: a
3543			// newer group can move either live edge while this group read is pending.
3544			Poll::<()>::Pending
3545		});
3546
3547		expired
3548	}
3549}
3550
3551/// The group a poll's drift is measured against, identified well enough to tell it apart
3552/// from whatever may occupy its sequence by the time a candidate is judged.
3553#[derive(Clone, Copy)]
3554struct Edge {
3555	/// This track's own edge, revalidated before it convicts anything.
3556	presentation: Option<PresentationEdge>,
3557	/// A newer edge on another track, already revalidated: its sequence and the newest
3558	/// frame it had presented. See [`Anchor::edge`].
3559	outer: Option<(u64, Timestamp)>,
3560	/// The cap the edge was resolved under, so per-candidate reach lookups measure
3561	/// against the same servable window.
3562	cap: Option<u64>,
3563	/// Where the next group past `cap` starts, already revalidated. See [`Anchor::successor`].
3564	successor: Option<Timestamp>,
3565}
3566
3567/// How a reader wrapping a cursor bounds its drift anchor from outside: pushed by a
3568/// splice onto each segment's cursor, and shared with the groups a cursor hands out.
3569#[derive(Clone, Default, PartialEq)]
3570pub(crate) struct Anchor {
3571	/// The exclusive sequence cap on what the reader could be handed; see [`servable_cap`].
3572	pub cap: Option<u64>,
3573	/// The newest edge across a splice's segments. Each segment is a separate track
3574	/// that only sees its own groups, so without this a parked segment measures against
3575	/// its own frozen edge while the logical track has moved on. Revalidated on its own
3576	/// track before it convicts anything, since it may be judged long after it was pushed.
3577	pub edge: Option<LiveEdge>,
3578	/// Where the reader's next group past `cap` starts presenting, when another track
3579	/// serves it (a splice's next segment). The last group below the cap has no
3580	/// successor in its own track, so without this nothing bounds its reach and it is
3581	/// never judged stale. Resolved afresh on every judgment rather than cached, so a
3582	/// group that goes away hands the bound to whichever group serves there next.
3583	pub successor: Option<Successor>,
3584}
3585
3586impl Anchor {
3587	/// This anchor under a further `cap`. A lower cap drops the successor: it named
3588	/// where the reader continues past the old cap, which is no longer served.
3589	pub fn capped(mut self, cap: Option<u64>) -> Self {
3590		let capped = servable_cap(cap, self.cap);
3591		if capped != self.cap {
3592			self.cap = capped;
3593			self.successor = None;
3594		}
3595		self
3596	}
3597}
3598
3599/// The newest stamped group of a track: its sequence and the newest frame it presented,
3600/// plus enough identity for a reader on another track to revalidate it.
3601#[derive(Clone)]
3602pub(crate) struct LiveEdge {
3603	pub sequence: u64,
3604	pub timestamp: Timestamp,
3605	stamp: u32,
3606	track: kio::ConsumerWeak<TrackState>,
3607}
3608
3609impl LiveEdge {
3610	/// Whether the edge still names the same servable group on its own track, the
3611	/// check [`TrackState::is_stale`] runs on a local edge. An eviction or abort since
3612	/// the splice resolved it must not convict anything. Takes that track's lock, so
3613	/// never call it under another's.
3614	fn is_live(&self) -> bool {
3615		self.track.read().holds(self.sequence, self.stamp)
3616	}
3617}
3618
3619impl PartialEq for LiveEdge {
3620	fn eq(&self, other: &Self) -> bool {
3621		self.sequence == other.sequence
3622			&& self.timestamp == other.timestamp
3623			&& self.stamp == other.stamp
3624			&& self.track.same_channel(&other.track)
3625	}
3626}
3627
3628/// The newest servable group that has presented at least one frame.
3629#[derive(Clone, Copy)]
3630struct PresentationEdge {
3631	sequence: u64,
3632	/// The slot incarnation this timestamp was read from, so an eviction or a re-served
3633	/// sequence between resolving the anchor and using it is detectable.
3634	stamp: u32,
3635	/// The newest frame this group has presented, not its first. The candidate side of the
3636	/// comparison is an upper bound on what a group could still reach, so the edge side has
3637	/// to be the newest content that actually exists, or the two meet and nothing is ever
3638	/// convicted. Only ever grows as the group fills.
3639	timestamp: Timestamp,
3640}
3641
3642/// The cursor state for a subscription over a single (per-session) track.
3643struct PlainSubscriber {
3644	state: kio::Consumer<TrackState>,
3645
3646	subscription: kio::Producer<Subscription>,
3647	/// Arrival-order cursor used by `recv_group`.
3648	index: usize,
3649	/// Arrival-order cursor used by `recv_datagram`, independent of groups.
3650	datagram_index: usize,
3651	/// Minimum sequence to return from any `recv` method. Set by `start_at`.
3652	min_sequence: u64,
3653	/// One past the highest sequence returned by `next_group`.
3654	/// Used only by that method to skip late arrivals; does not affect `recv_group`.
3655	next_sequence: u64,
3656	/// Exclusive upper sequence bound for `next_group` and `recv_group`, in the form
3657	/// [`Cap::exclusive`] produces. `None` means no cap. Set by `end_at`;
3658	/// can be raised, lowered, or unset at any time. Groups at or past the cap stay in
3659	/// the producer's cache and become eligible again when the cap rises (or is removed).
3660	/// `Some(0)` is the empty range.
3661	end_sequence: Option<u64>,
3662	/// Groups received beyond the [`Self::end_sequence`] cap, held for `recv_group`
3663	/// until the cap rises (arrival-order reads consume the shared cursor, so they
3664	/// are parked here instead of dropped). Keyed by sequence so the lowest is
3665	/// re-offered first.
3666	parked: BTreeMap<u64, group::Consumer>,
3667	/// A cap imposed by a reader wrapping this cursor (a [`super::resume::Subscriber`]
3668	/// segment), folded into the drift anchor only. Delivery is still bounded by
3669	/// `end_sequence`, which stays unset on a segment so its completion is visible.
3670	stale_cap: Option<u64>,
3671	/// Shared effective anchor used by groups after this cursor hands them out. The
3672	/// only copy of the outer edge a wrapping reader pushed (see [`Anchor::edge`]).
3673	drift_anchor: kio::Producer<Anchor>,
3674	/// Groups the drift budget skipped since the count was last drained. Accumulated
3675	/// here rather than metered in place because the handle that owns the stats scope
3676	/// is the outer [`Subscriber`], which may be reading this cursor through a
3677	/// [`super::resume::Subscriber`] segment (untagged, so the outer wrapper is the
3678	/// only place attribution happens once).
3679	stale: stats::Content,
3680	/// Groups the seek path has convicted but whose sequences no caller has committed
3681	/// past yet, keyed by sequence; see [`Self::commit_seek_stale`].
3682	seek_pending: BTreeMap<u64, stats::Content>,
3683}
3684
3685impl PlainSubscriber {
3686	/// The drift anchor for a read bounded by `end`. Every read folds `end_sequence`
3687	/// into `end`, so capping the shared anchor (which already holds it) is exact.
3688	fn anchor(&self, end: Option<u64>) -> Anchor {
3689		self.drift_anchor.read().clone().capped(end)
3690	}
3691
3692	/// Publish the `outer` anchor under this cursor's own cap.
3693	fn update_drift_anchor(&mut self, outer: Anchor) {
3694		let anchor = outer.capped(self.end_sequence);
3695		// Skip a no-op write: every handed-out group's expiry watches this channel.
3696		if *self.drift_anchor.read() != anchor
3697			&& let Ok(mut current) = self.drift_anchor.write()
3698		{
3699			*current = anchor;
3700		}
3701	}
3702
3703	// A helper to automatically apply Dropped if the state is closed without an error.
3704	fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
3705	where
3706		F: Fn(&kio::Ref<'_, TrackState>) -> Poll<Result<R>>,
3707	{
3708		Poll::Ready(match ready!(self.state.poll(waiter, f)) {
3709			Ok(res) => res,
3710			// We try to clone abort just in case the function forgot to check for terminal state.
3711			Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
3712		})
3713	}
3714
3715	/// Take the groups skipped since the last call, for the owner to meter.
3716	fn take_stale(&mut self) -> stats::Content {
3717		std::mem::take(&mut self.stale)
3718	}
3719
3720	/// Note a group an outer reader skipped on this cursor's behalf, so it lands in the
3721	/// same counter as the ones skipped here.
3722	fn note_stale(&mut self, group: &group::Consumer) {
3723		self.stale.add(group.content());
3724	}
3725
3726	/// This subscriber's clamped drift budget and the live edge to measure against,
3727	/// resolved once per poll.
3728	///
3729	/// `cap` bounds the anchor: the caller passes the same window it reads from, so a
3730	/// group is only ever judged against content that could actually be served in its
3731	/// place. Read fresh each poll, so a mid-stream [`Control::update`] applies
3732	/// to the very next group, and shared across every candidate that poll walks off, so
3733	/// discarding a backlog of N groups costs one scan rather than N. Only ever
3734	/// [`Poll::Ready`]; the track ending surfaces as the error the caller was going to
3735	/// get anyway.
3736	fn poll_drift(&self, anchor: Anchor, waiter: &kio::Waiter) -> Poll<Result<Drift>> {
3737		let mut max_age = Duration::default();
3738		let _ = self.subscription.poll(waiter, |subscription| {
3739			max_age = subscription.max_age;
3740			Poll::<()>::Pending
3741		});
3742		let cap = anchor.cap;
3743		let outer = anchor.edge;
3744		let successor = anchor.successor;
3745		self.poll(waiter, |state| {
3746			// Local edge only. The pushed edge and successor are revalidated in
3747			// [`Self::poll_stale`], outside this lock.
3748			Poll::Ready(Ok(Drift {
3749				budget: clamp_max_age(max_age, state.max_age_bound()),
3750				edge: state.drift_edge(cap, None, None),
3751				outer: outer.clone(),
3752				successor: successor.clone(),
3753			}))
3754		})
3755	}
3756
3757	/// Whether the drift budget says to skip `group`, against a [`Drift`] already resolved
3758	/// for this poll.
3759	fn poll_stale(&self, group: &group::Consumer, drift: &Drift, waiter: &kio::Waiter) -> Poll<Result<bool>> {
3760		// Revalidate before this track's lock. Both can name another track, including
3761		// one whose own judgment is waiting on this one.
3762		let outer = drift
3763			.outer
3764			.as_ref()
3765			.filter(|live| live.is_live())
3766			.map(|live| (live.sequence, live.timestamp));
3767		let successor = drift
3768			.successor
3769			.as_ref()
3770			.and_then(|successor| successor.poll_start(waiter));
3771		let presentation = drift.edge.presentation;
3772		let cap = drift.edge.cap;
3773		let budget = drift.budget;
3774		self.poll(waiter, move |state| {
3775			let edge = Edge {
3776				presentation,
3777				outer,
3778				cap,
3779				successor,
3780			};
3781			Poll::Ready(Ok(state.is_stale(group.sequence, &edge, budget)))
3782		})
3783	}
3784
3785	fn with_expiry(&self, group: group::Consumer) -> group::Consumer {
3786		let sequence = group.sequence;
3787		group.with_expiry(Arc::new(GroupExpiry {
3788			state: self.state.weak(),
3789			subscription: self.subscription.consume(),
3790			anchor: self.drift_anchor.consume(),
3791			bound: None,
3792			sequence,
3793		}))
3794	}
3795
3796	fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
3797		// An eviction aborts a parked group without touching any cursor this
3798		// subscriber polls, so each entry needs a waiter or this poll would never
3799		// rerun. `poll_closed` observes-or-registers under one lock: `Pending`
3800		// parks the waiter while the group is open (an open group cannot be
3801		// aborted), and `Ready` means closed, where only an abort invalidates the
3802		// entry. Checking `is_aborted` separately from the registration would leave
3803		// a window where an abort lands between the two and wakes nobody.
3804		let watch = |group: &group::Consumer| match group.poll_closed(waiter) {
3805			Poll::Pending => true,
3806			Poll::Ready(()) => !group.is_aborted(),
3807		};
3808
3809		// A raised `start_at` drops parked groups it overtook, and eviction/expiry
3810		// (which aborts a cached group) drops its parked entry. The latter is what
3811		// bounds parking: a subscription capped indefinitely holds only what the
3812		// track's cache policy still retains, not every group it ever observed.
3813		let min_sequence = self.min_sequence;
3814		self.parked
3815			.retain(|sequence, group| *sequence >= min_sequence && watch(group));
3816
3817		// One scan for the whole poll, so walking a backlog off stays linear in its size.
3818		let drift = ready!(self.poll_drift(self.anchor(self.end_sequence), waiter))?;
3819
3820		loop {
3821			// Re-offer the lowest parked group back inside the cap once it rises,
3822			// ahead of the arrival cursor: it is the oldest thing still owed.
3823			let consumer = match self.parked.keys().next().copied() {
3824				Some(sequence) if super::subscription::before_end(sequence, self.end_sequence) => {
3825					let group = self.parked.remove(&sequence).expect("just looked it up");
3826					// A re-offer is a delivery: stamp it like a fresh hand-out.
3827					group.cache_refresh();
3828					group
3829				}
3830				_ => {
3831					let Some((producer, found_index)) =
3832						ready!(self.poll(waiter, |state| state.poll_recv_group(self.index, self.min_sequence))?)
3833					else {
3834						// Parked groups survive a finished track: they become deliverable
3835						// again if the cap rises, so the stream isn't over while any are held.
3836						if self.parked.is_empty() {
3837							return Poll::Ready(Ok(None));
3838						}
3839						return Poll::Pending;
3840					};
3841					let consumer = producer.consume();
3842					// Stamp with the track guard released, so delivery never nests the
3843					// group's state lock under the track's.
3844					consumer.cache_refresh();
3845					self.index = found_index + 1;
3846
3847					// Park a group beyond the cap instead of dropping it, and keep scanning
3848					// so an in-range group that arrived behind it still flows.
3849					if !super::subscription::before_end(consumer.sequence, self.end_sequence) {
3850						// Watch it from the moment it parks: the retain pass above already
3851						// ran, so an entry admitted here would otherwise sit unwatched for
3852						// the rest of this poll, and an abort could wake nobody.
3853						if watch(&consumer) {
3854							self.parked.insert(consumer.sequence, consumer);
3855						}
3856						continue;
3857					}
3858					consumer
3859				}
3860			};
3861
3862			// Drop a group the drift budget has given up on and keep scanning, so one
3863			// poll walks a whole backlog off rather than handing it out group by group.
3864			if ready!(self.poll_stale(&consumer, &drift, waiter))? {
3865				self.stale.add(consumer.content());
3866				continue;
3867			}
3868			return Poll::Ready(Ok(Some(self.with_expiry(consumer))));
3869		}
3870	}
3871
3872	fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
3873		let Some((datagram, found_index)) =
3874			ready!(self.poll(waiter, |state| state.poll_recv_datagram(self.datagram_index))?)
3875		else {
3876			return Poll::Ready(Ok(None));
3877		};
3878
3879		self.datagram_index = found_index + 1;
3880		Poll::Ready(Ok(Some(datagram)))
3881	}
3882
3883	fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
3884		let floor = self.next_sequence.max(self.min_sequence);
3885		let Some(group) = ready!(self.poll_seek_group(floor, self.end_sequence, waiter))? else {
3886			return Poll::Ready(Ok(None));
3887		};
3888		self.next_sequence = group.sequence.saturating_add(1);
3889		// The delivery commits everything the seek stepped over to reach this group.
3890		self.commit_seek_stale(self.next_sequence);
3891		// Delivery is a cache access, same as the arrival-order path.
3892		group.cache_refresh();
3893		Poll::Ready(Ok(Some(group)))
3894	}
3895
3896	/// Seek the lowest servable group in `floor..end` without advancing any cursor,
3897	/// walking off everything the drift budget has convicted on the way.
3898	///
3899	/// The drift anchor is built from the same window the seek reads (`end` folded into
3900	/// the cap), so a candidate is only judged against content that could be served in
3901	/// its place. A nested splice depends on this: its outer boundary arrives only as
3902	/// this call's `end`, and an anchor past it would convict groups the caller still
3903	/// owes its reader.
3904	///
3905	/// Repeated polls return the same group until the caller's own floor passes it,
3906	/// which is the point: the spliced sequence path picks the lowest candidate across
3907	/// segments and must not commit a segment past a group it has not delivered.
3908	fn poll_seek_group(
3909		&mut self,
3910		floor: u64,
3911		end: Option<u64>,
3912		waiter: &kio::Waiter,
3913	) -> Poll<Result<Option<group::Consumer>>> {
3914		let mut floor = floor.max(self.min_sequence);
3915		let end = super::subscription::min_some(end, self.end_sequence);
3916		// One scan for the whole poll, so walking a backlog off stays linear in its size.
3917		let drift = ready!(self.poll_drift(self.anchor(end), waiter))?;
3918
3919		loop {
3920			let Some(producer) = ready!(self.poll(waiter, |state| state.poll_next_in_range(floor, end))?) else {
3921				// Deliberately no flush of `seek_pending` here: only a delivery commit
3922				// may count a conviction. This `None` can be an artifact of a floor
3923				// that will lower again, and a mid-group boundary can serve a
3924				// convicted sequence through another segment even after this cursor
3925				// retires. A conviction never committed is dropped uncounted with the
3926				// cursor, the same tail bound a retired segment already has.
3927				return Poll::Ready(Ok(None));
3928			};
3929			let group = producer.consume();
3930
3931			// Skip a group the budget has given up on and keep scanning, so one poll
3932			// walks a whole backlog off rather than handing it out group by group.
3933			if ready!(self.poll_stale(&group, &drift, waiter))? {
3934				// Not counted yet: a seek advances no cursor, and a conviction is not
3935				// permanent (the budget can widen, the edge can be evicted), so the
3936				// group may still be delivered. Re-snapshot on every re-examination so
3937				// the eventual count reflects the group's latest observed content.
3938				self.seek_pending.insert(group.sequence, group.content());
3939				floor = group.sequence.saturating_add(1);
3940				continue;
3941			}
3942
3943			// A conviction the budget walked back: the group is handed over after all,
3944			// so it must never reach the stale count.
3945			self.seek_pending.remove(&group.sequence);
3946			return Poll::Ready(Ok(Some(self.with_expiry(group))));
3947		}
3948	}
3949
3950	/// Count the seek path's convictions below `committed` into [`Self::stale`],
3951	/// exactly once each.
3952	///
3953	/// A seek is speculative: the spliced path seeks every segment and delivers from
3954	/// one, so a conviction only becomes a real skip once a delivery commits past it.
3955	/// Until then the entry waits in [`Self::seek_pending`], where a delivered group
3956	/// removes itself (see [`Self::poll_seek_group`]). `committed` must be the
3957	/// deliverer's sequence watermark (one past its last delivery), which only rises.
3958	/// The seek's floor is NOT that: it includes `start_at`, which can be lowered
3959	/// again, and a conviction it flushed could then be delivered after all.
3960	fn commit_seek_stale(&mut self, committed: u64) {
3961		let keep = self.seek_pending.split_off(&committed);
3962		for (_, content) in std::mem::replace(&mut self.seek_pending, keep) {
3963			self.stale.add(content);
3964		}
3965	}
3966
3967	/// Drop a conviction for `sequence` without counting it: the sequence was
3968	/// delivered by another cursor over the same content (a splice boundary inside
3969	/// the group leaves a copy in two segments), so its content is not stale.
3970	fn discard_seek_conviction(&mut self, sequence: u64) {
3971		self.seek_pending.remove(&sequence);
3972	}
3973}
3974
3975/// A cloneable handle to a subscriber's delivery preferences.
3976///
3977/// This updates the same subscription as the owning [`Subscriber`] without
3978/// borrowing its read cursor, so callers can change delivery priority, the max age
3979/// budget, or group bounds while another task is waiting for groups.
3980#[derive(Clone)]
3981pub struct Control {
3982	subscription: kio::Producer<Subscription>,
3983}
3984
3985impl Control {
3986	/// This subscriber's current preferences.
3987	pub fn subscription(&self) -> Subscription {
3988		self.subscription.read().clone()
3989	}
3990
3991	/// Replace this subscriber's preferences, updating the producer's aggregate.
3992	///
3993	/// Returns [`Error::Closed`] if the track already ended; the update is
3994	/// meaningless at that point and can usually be ignored.
3995	pub fn update(&self, subscription: Subscription) -> Result<()> {
3996		let mut state = self.subscription.write().map_err(|_| Error::Closed)?;
3997		*state = subscription;
3998		Ok(())
3999	}
4000}
4001
4002impl Subscriber {
4003	/// The track's [`Info`], resolved when the subscription was established.
4004	///
4005	/// Free, unlike [`Consumer::query`]: subscribing already waited for the info
4006	/// (SUBSCRIBE_OK on the wire), so a subscriber always has it.
4007	pub fn info(&self) -> &Info {
4008		&self.info
4009	}
4010
4011	/// The track's name, unique within its broadcast.
4012	pub fn name(&self) -> &str {
4013		&self.name
4014	}
4015
4016	/// The broadcast this track belongs to, as reached through the handle it came from.
4017	/// Its [`path`](broadcast::Info::path) is what a catalog's relative `broadcast`
4018	/// references resolve against.
4019	pub fn broadcast(&self) -> &broadcast::Info {
4020		&self.broadcast
4021	}
4022
4023	/// Attribute the groups the drift budget skipped since the last read.
4024	///
4025	/// Drained rather than metered where the skip happens, for the same reason a
4026	/// delivered group is metered here: a spliced subscriber reads through untagged
4027	/// per-segment cursors, so this wrapper is the one place that counts exactly once.
4028	fn count_stale(&mut self, meter: &stats::Meter) {
4029		// An untagged subscriber leaves the count where it is. A spliced reader polls
4030		// its segments through this same method, and those segments are untagged, so
4031		// draining here would throw the count away before the tagged handle wrapping
4032		// them ever sees it.
4033		if meter.is_tracked() {
4034			meter.stale(self.take_stale());
4035		}
4036	}
4037
4038	/// Take the groups the drift budget skipped since the last call, so a nesting
4039	/// handle (a spliced subscriber reading this one as a segment) can attribute them.
4040	pub(crate) fn take_stale(&mut self) -> stats::Content {
4041		match &mut self.inner {
4042			SubscriberKind::Plain(plain) => plain.take_stale(),
4043			SubscriberKind::Spliced(spliced) => spliced.take_stale(),
4044		}
4045	}
4046
4047	/// Bound the drift anchor from outside, for a reader that caps this subscriber
4048	/// without capping its cursor, and splices it with other tracks.
4049	///
4050	/// A [`super::resume::Subscriber`] segment is deliberately left uncapped
4051	/// ([`Self::set_groups`] would park boundary-crossing groups where its completion can't
4052	/// be seen), so its own cap has to reach the anchor this way or the segment measures
4053	/// drift against groups its reader will never be served. The same goes for the edge:
4054	/// a segment's track never sees the groups of the segments after it. A spliced
4055	/// segment folds the anchor into what it pushes onto its own segments, so it reaches
4056	/// the plain cursors at the leaves however deep the splices nest.
4057	pub(crate) fn set_anchor(&mut self, anchor: Anchor) {
4058		match &mut self.inner {
4059			SubscriberKind::Plain(plain) => {
4060				plain.stale_cap = anchor.cap;
4061				plain.update_drift_anchor(anchor);
4062			}
4063			SubscriberKind::Spliced(spliced) => spliced.set_anchor(anchor),
4064		}
4065	}
4066
4067	/// Count the seek path's convictions below the deliverer's `committed` watermark
4068	/// as stale, exactly once each; see [`PlainSubscriber::commit_seek_stale`].
4069	///
4070	/// The spliced seek calls this on every segment before it delivers, so a losing
4071	/// segment's convictions are counted once the winning delivery moves the cursor
4072	/// past them, and never before.
4073	pub(crate) fn commit_seek_stale(&mut self, committed: u64) {
4074		match &mut self.inner {
4075			SubscriberKind::Plain(plain) => plain.commit_seek_stale(committed),
4076			SubscriberKind::Spliced(spliced) => spliced.commit_seek_stale(committed),
4077		}
4078	}
4079
4080	/// Drop any conviction for `sequence` without counting it; see
4081	/// [`PlainSubscriber::discard_seek_conviction`]. The spliced deliverer calls this
4082	/// for the sequence it just handed out, since a boundary inside that group leaves
4083	/// a convictable copy in the next segment that the delivery splices into.
4084	pub(crate) fn discard_seek_conviction(&mut self, sequence: u64) {
4085		match &mut self.inner {
4086			SubscriberKind::Plain(plain) => plain.discard_seek_conviction(sequence),
4087			SubscriberKind::Spliced(spliced) => spliced.discard_seek_conviction(sequence),
4088		}
4089	}
4090
4091	/// Whether the drift budget says to skip `group`, for a reader holding a group this
4092	/// subscriber handed it earlier (a parked one, re-offered once a cap rose). Counts
4093	/// the skip here so it reaches the stats with the rest. A spliced subscriber asks
4094	/// the segment whose window covers the group, so a nested park is re-checked
4095	/// against the same anchor a fresh read would use.
4096	pub(crate) fn poll_stale(&mut self, group: &group::Consumer, waiter: &kio::Waiter) -> Poll<Result<bool>> {
4097		let plain = match &mut self.inner {
4098			SubscriberKind::Plain(plain) => plain,
4099			SubscriberKind::Spliced(spliced) => return spliced.poll_stale(group, waiter),
4100		};
4101		let drift = ready!(plain.poll_drift(plain.anchor(plain.end_sequence), waiter))?;
4102		let stale = ready!(plain.poll_stale(group, &drift, waiter))?;
4103		if stale {
4104			plain.note_stale(group);
4105		}
4106		Poll::Ready(Ok(stale))
4107	}
4108
4109	/// Create a handle for updating this subscriber's delivery preferences.
4110	pub fn control(&self) -> Control {
4111		Control {
4112			subscription: match &self.inner {
4113				SubscriberKind::Plain(plain) => plain.subscription.clone(),
4114				SubscriberKind::Spliced(spliced) => spliced.prefs(),
4115			},
4116		}
4117	}
4118
4119	/// Poll for the next group in arrival order, without blocking.
4120	///
4121	/// Returns each group it delivers exactly once, in the order it landed on the wire,
4122	/// which may be out of sequence due to network reordering or loss. Use
4123	/// [`Self::ordered`] if you only want groups whose sequence number is higher than any
4124	/// previously returned.
4125	///
4126	/// Groups are semi-reliable, and the [`Subscription::max_age`] budget is the other
4127	/// thing (alongside eviction and a moving start) that decides which of them arrive:
4128	/// one that has drifted further behind the live edge than the budget tolerates is
4129	/// skipped rather than handed over, so a single poll walks off a whole backlog. The
4130	/// default is [`Duration::ZERO`], which takes the live
4131	/// edge and writes the rest off; raise it to read history. [`Self::set_groups`] and
4132	/// [`Subscription::start`] are filters, not exemptions: backfill needs a budget that
4133	/// covers it. [`Consumer::fetch_group`] is the way to ask for one old group outright.
4134	/// The budget remains attached to a returned group: if it stalls while newer data
4135	/// advances, its pending frame read ends with [`Error::Old`].
4136	///
4137	/// Honors the group range set by [`Self::set_groups`]:
4138	/// a group beyond the cap is parked (not dropped) and re-offered once the cap rises
4139	/// (lowest sequence first), without blocking in-range groups that arrive behind it.
4140	/// A parked group that the producer evicts or expires in the meantime is dropped,
4141	/// so parking never outlives the track's cache policy.
4142	///
4143	/// Returns `Poll::Ready(Ok(Some(group)))` when a group is available,
4144	/// `Poll::Ready(Ok(None))` when the track is finished,
4145	/// `Poll::Ready(Err(e))` when the track has been aborted, or
4146	/// `Poll::Pending` when no group is available yet.
4147	pub fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
4148		let meter = self.stats.meter();
4149		let res = match &mut self.inner {
4150			SubscriberKind::Plain(plain) => plain.poll_recv_group(waiter),
4151			SubscriberKind::Spliced(spliced) => spliced.poll_recv_group(waiter),
4152		};
4153		self.count_stale(&meter);
4154		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
4155	}
4156
4157	/// Receive the next group in arrival order.
4158	///
4159	/// Every group is returned exactly once, in the order it landed on the wire, which may
4160	/// be out of sequence due to network reordering or loss. Use [`Self::ordered`] if you
4161	/// only want groups whose sequence number is higher than any previously returned.
4162	/// See [`Self::poll_recv_group`] for how [`Self::set_groups`] applies.
4163	pub async fn recv_group(&mut self) -> Result<Option<group::Consumer>> {
4164		kio::wait(|waiter| self.poll_recv_group(waiter)).await
4165	}
4166
4167	/// Poll for the next datagram in arrival order, without blocking.
4168	///
4169	/// Datagrams are a separate best-effort channel from groups (see
4170	/// [`Producer::append_datagram`]); they share only the sequence namespace, and
4171	/// neither cursor moves the other. A consumer that falls too far behind silently
4172	/// loses the oldest datagrams.
4173	///
4174	/// Returns `Poll::Ready(Ok(Some(datagram)))` when one is available,
4175	/// `Poll::Ready(Ok(None))` when the track is finished, `Poll::Ready(Err(e))` when the track
4176	/// is aborted, or `Poll::Pending` when none is buffered yet.
4177	pub fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
4178		let meter = self.stats.meter();
4179		let res = match &mut self.inner {
4180			SubscriberKind::Plain(plain) => plain.poll_recv_datagram(waiter),
4181			SubscriberKind::Spliced(spliced) => spliced.poll_recv_datagram(waiter),
4182		};
4183		// Unlike a group (metered lazily as its frames are read), a datagram is
4184		// delivered whole here, so count it as the single-frame group it stands in for.
4185		if let Poll::Ready(Ok(Some(datagram))) = &res {
4186			meter.datagram(datagram.payload.len() as u64);
4187		}
4188		res
4189	}
4190
4191	/// Receive the next datagram in arrival order.
4192	///
4193	/// A best-effort channel parallel to [`Self::recv_group`]; the two share only the sequence
4194	/// namespace. To receive both concurrently from one subscriber, poll
4195	/// [`Self::poll_recv_group`] and [`Self::poll_recv_datagram`] together in a single `poll`
4196	/// closure (sequential `&mut` borrows), rather than awaiting the two `recv` futures at once.
4197	pub async fn recv_datagram(&mut self) -> Result<Option<Datagram>> {
4198		kio::wait(|waiter| self.poll_recv_datagram(waiter)).await
4199	}
4200
4201	/// The sequence cursor behind [`Ordered`], which owns the only public door to it.
4202	fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
4203		let meter = self.stats.meter();
4204		let res = match &mut self.inner {
4205			SubscriberKind::Plain(plain) => plain.poll_next_group(waiter),
4206			SubscriberKind::Spliced(spliced) => spliced.poll_next_group(waiter),
4207		};
4208		self.count_stale(&meter);
4209		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
4210	}
4211
4212	/// Seek the lowest cached group in `floor..end` without advancing any cursor.
4213	/// Crate-visible for [`super::resume::Subscriber`], whose sequence path picks the
4214	/// lowest candidate across its segments and advances only its own floor; see
4215	/// [`PlainSubscriber::poll_seek_group`].
4216	pub(crate) fn poll_seek_group(
4217		&mut self,
4218		floor: u64,
4219		end: Option<u64>,
4220		waiter: &kio::Waiter,
4221	) -> Poll<Result<Option<group::Consumer>>> {
4222		let meter = self.stats.meter();
4223		let res = match &mut self.inner {
4224			SubscriberKind::Plain(plain) => plain.poll_seek_group(floor, end, waiter),
4225			SubscriberKind::Spliced(spliced) => spliced.poll_seek_group(floor, end, waiter),
4226		};
4227		self.count_stale(&meter);
4228		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
4229	}
4230
4231	/// Read this track's groups in sequence order instead of arrival order.
4232	///
4233	/// Consumes the subscriber, so one handle carries exactly one cursor: an
4234	/// arrival-order [`Subscriber`] or a sequence-order [`Ordered`], never both at once.
4235	/// The two advance independently, and interleaving them produces a group stream that
4236	/// is neither, which is why the choice is a handle rather than a method.
4237	///
4238	/// Datagrams come along: they are a separate cursor either way, so the choice of group
4239	/// order says nothing about them.
4240	pub fn ordered(self) -> Ordered {
4241		Ordered { inner: self }
4242	}
4243
4244	/// Whether `other` was cloned from this subscriber (shares the same underlying state).
4245	pub fn is_clone(&self, other: &Self) -> bool {
4246		match (&self.inner, &other.inner) {
4247			(SubscriberKind::Plain(a), SubscriberKind::Plain(b)) => a.state.same_channel(&b.state),
4248			(SubscriberKind::Spliced(a), SubscriberKind::Spliced(b)) => a.is_clone(b),
4249			_ => false,
4250		}
4251	}
4252
4253	/// Poll for where the source's feed starts, raised to this cursor's floor, once
4254	/// resolved; see [`Consumer::poll_start`]. A feed starting below the floor serves the
4255	/// floor's group too. `None` when the source declares none.
4256	pub(crate) fn poll_start(&mut self, waiter: &kio::Waiter) -> Poll<Option<u64>> {
4257		match &mut self.inner {
4258			SubscriberKind::Plain(plain) => {
4259				let start = ready!(Consumer::poll_state_start(&plain.state, waiter));
4260				Poll::Ready(start.map(|start| start.max(plain.min_sequence)))
4261			}
4262			SubscriberKind::Spliced(spliced) => spliced.poll_start(waiter),
4263		}
4264	}
4265
4266	/// Poll for the track's declared final sequence, without blocking.
4267	pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
4268		match &mut self.inner {
4269			SubscriberKind::Plain(plain) => plain.poll(waiter, |state| state.poll_finished()),
4270			SubscriberKind::Spliced(spliced) => spliced.poll_finished(waiter),
4271		}
4272	}
4273
4274	/// Observe a declared end or a clean local close for an origin's spliced cursor.
4275	pub(crate) fn poll_end(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<u64>>> {
4276		match &mut self.inner {
4277			SubscriberKind::Plain(plain) => plain.poll(waiter, |state| state.poll_end()),
4278			SubscriberKind::Spliced(spliced) => spliced.poll_end(waiter),
4279		}
4280	}
4281
4282	/// Block until the track declares its end, returning the exclusive final sequence
4283	/// (also the total group count), or the cause on an abort.
4284	///
4285	/// Resolves as soon as the boundary is known, which may be ahead of the live edge
4286	/// when the producer finished via [`Producer::finish_at`]. This reports the declared
4287	/// end, not that every group has arrived. A local session close without a declared
4288	/// end returns [`Error::Closed`]. Drive [`Self::recv_group`] (or
4289	/// [`Ordered::next_group`]) until it yields `None` to observe the track fully drained.
4290	pub async fn finished(&mut self) -> Result<u64> {
4291		kio::wait(|waiter| self.poll_finished(waiter)).await
4292	}
4293
4294	/// Limit subsequent reads to these group sequences without rewinding read progress.
4295	///
4296	/// `2..=5` includes groups 2 through 5; `2..5` excludes group 5. An omitted
4297	/// start preserves the current floor, and an omitted end removes the cap.
4298	/// Groups above the end remain buffered and can be read after raising the cap.
4299	/// This changes only local delivery; use [`Subscription::with_groups`] and
4300	/// [`Self::update`] to change the requested groups.
4301	pub fn set_groups(&mut self, groups: impl RangeBounds<u64>) {
4302		let (start, end) = super::subscription::sequence_bounds(groups);
4303		self.raise_start_to(start);
4304		self.end_at(end.map_or(Bound::Unbounded, Bound::Excluded));
4305	}
4306
4307	/// Start this subscriber's read cursor at the given sequence.
4308	///
4309	/// A local filter, not a request: it doesn't tell the publisher anything, so the
4310	/// skipped groups are still delivered and simply not returned. To ask the publisher
4311	/// to start there instead, set [`Subscription::start`] via [`Self::update`].
4312	/// See [Local cursor vs wire preference](Self#local-cursor-vs-wire-preference).
4313	pub(crate) fn start_at(&mut self, sequence: u64) {
4314		match &mut self.inner {
4315			SubscriberKind::Plain(plain) => plain.min_sequence = sequence,
4316			SubscriberKind::Spliced(spliced) => spliced.start_at(sequence),
4317		}
4318	}
4319
4320	/// Raise the read cursor's floor to `sequence`, keeping any higher floor already set.
4321	///
4322	/// The spliced layer positions a segment's inner cursor with this instead of
4323	/// [`Self::set_groups`]: the inner subscription already resolved a start from its own
4324	/// budget and floor, and an assignment would discard it.
4325	pub(crate) fn raise_start_to(&mut self, sequence: u64) {
4326		match &mut self.inner {
4327			SubscriberKind::Plain(plain) => plain.min_sequence = plain.min_sequence.max(sequence),
4328			SubscriberKind::Spliced(spliced) => spliced.raise_start_to(sequence),
4329		}
4330	}
4331
4332	/// Cap this subscriber's read cursor at `end`, or remove the cap with `..`.
4333	///
4334	/// The range says whether its sequence is delivered: `..=5` serves through group 5,
4335	/// `..5` stops before it. `..0` is the empty range: no group is delivered.
4336	/// [`Position::group_end`] translates a [`Subscription::end`].
4337	///
4338	/// A local filter, not a request; [`Subscription::end`] is the wire-level
4339	/// counterpart. See [Local cursor vs wire preference](Self#local-cursor-vs-wire-preference).
4340	///
4341	/// Groups beyond the cap are held rather than skipped past, so a later call to
4342	/// [`Self::set_groups`] with a higher bound (or unbounded) makes them available again.
4343	/// Lowering the cap below the consumer's current cursor parks the consumer until the
4344	/// cap is raised.
4345	pub(crate) fn end_at(&mut self, end: impl Into<Cap>) {
4346		let end = end.into();
4347		match &mut self.inner {
4348			SubscriberKind::Plain(plain) => {
4349				plain.end_sequence = end.exclusive();
4350				// A successor dropped by a lower cap stays dropped until the wrapping
4351				// reader pushes its anchor again, which it does on every poll.
4352				let outer = Anchor {
4353					cap: plain.stale_cap,
4354					..plain.drift_anchor.read().clone()
4355				};
4356				plain.update_drift_anchor(outer);
4357			}
4358			SubscriberKind::Spliced(spliced) => spliced.end_at(end),
4359		}
4360	}
4361
4362	/// This subscriber's current preferences.
4363	pub fn subscription(&self) -> Subscription {
4364		self.control().subscription()
4365	}
4366
4367	/// Replace this subscriber's delivery preferences.
4368	///
4369	/// Stored verbatim; the publisher's max age window is applied to the aggregate, not
4370	/// here (see [`Producer::subscription`]). Returns [`Error::Closed`] if the track
4371	/// already ended; the update is meaningless at that point and can usually be ignored.
4372	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
4373		match &mut self.inner {
4374			SubscriberKind::Plain(plain) => {
4375				let mut state = plain.subscription.write().map_err(|_| Error::Closed)?;
4376				*state = subscription;
4377			}
4378			SubscriberKind::Spliced(spliced) => spliced.update(subscription),
4379		}
4380		Ok(())
4381	}
4382
4383	/// Return the latest sequence number in the track.
4384	pub fn latest(&self) -> Option<u64> {
4385		match &self.inner {
4386			SubscriberKind::Plain(plain) => plain.state.read().max_sequence,
4387			SubscriberKind::Spliced(spliced) => spliced.latest(),
4388		}
4389	}
4390}
4391
4392/// A [`Subscriber`] that reads groups in sequence order.
4393///
4394/// Created by [`Subscriber::ordered`], which consumes the subscriber, so a track is read
4395/// one way or the other and never both. Every group it returns has a higher sequence
4396/// than the last, so a late arrival (network reordering, or a gap the cache filled after
4397/// the fact) is skipped rather than delivered out of turn.
4398///
4399/// # Age and skipping
4400///
4401/// [`Subscription::max_age`] applies as this cursor reads, exactly as it does on the
4402/// arrival cursor: a group is skipped once its *reach*, where its immediate successor
4403/// begins, is that far behind the newest frame on the track. Nothing weaker convicts it,
4404/// because the reach is the only proof that *every* frame it could still hold is past the
4405/// budget: a group's own timestamps say where it starts presenting, not where it stops,
4406/// and an unstamped successor leaves it unbounded and therefore kept. A backlog inside
4407/// the budget is still delivered whole, as a burst in order, which is what a decoder
4408/// reading a gap-free sequence needs.
4409///
4410/// The same budget follows a group already handed out: once newer content pulls that far
4411/// ahead, a read still waiting on it ends with [`Error::Old`] and the cursor moves on. A
4412/// gap costs nothing either way, since the cursor seeks to the lowest cached group in
4413/// range rather than waiting for the sequence it would have read next.
4414///
4415/// The budget is updatable mid-stream through [`Self::control`]. [`Duration::ZERO`] (the
4416/// default) keeps only what nothing newer has superseded, so a consumer that stalls and
4417/// resumes rejoins the live edge instead of replaying at 1x.
4418pub struct Ordered {
4419	inner: Subscriber,
4420}
4421
4422impl Ordered {
4423	/// The track's [`Info`], resolved when the subscription was established.
4424	pub fn info(&self) -> &Info {
4425		self.inner.info()
4426	}
4427
4428	/// The track's name, unique within its broadcast.
4429	pub fn name(&self) -> &str {
4430		self.inner.name()
4431	}
4432
4433	/// The broadcast this track belongs to, as reached through the handle it came from.
4434	pub fn broadcast(&self) -> &broadcast::Info {
4435		self.inner.broadcast()
4436	}
4437
4438	/// Poll for the next group with a higher sequence number than any previously
4439	/// returned, without blocking.
4440	///
4441	/// Honors the group range set by [`Self::set_groups`]:
4442	/// a group past the cap stays in the producer's cache and becomes eligible again if
4443	/// the cap rises or is removed.
4444	///
4445	/// Returns `Poll::Ready(Ok(Some(group)))` when a group is available,
4446	/// `Poll::Ready(Ok(None))` when the track is finished (including an abort after its
4447	/// declared end settled, see [`Producer::abort`]),
4448	/// `Poll::Ready(Err(e))` when the track has been aborted short of its end, or
4449	/// `Poll::Pending` when no group is available yet.
4450	pub fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
4451		self.inner.poll_next_group(waiter)
4452	}
4453
4454	/// Return the next group with a higher sequence number than any previously returned.
4455	pub async fn next_group(&mut self) -> Result<Option<group::Consumer>> {
4456		kio::wait(|waiter| self.poll_next_group(waiter)).await
4457	}
4458
4459	/// Poll for the next datagram in arrival order, without blocking.
4460	///
4461	/// Datagrams are a separate best-effort channel from groups (see
4462	/// [`Producer::append_datagram`]); they share only the sequence namespace, and neither
4463	/// cursor moves the other. Unordered by construction, so this behaves identically on
4464	/// either handle; it is here so a track carrying both channels needs one subscription
4465	/// rather than two.
4466	pub fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
4467		self.inner.poll_recv_datagram(waiter)
4468	}
4469
4470	/// Receive the next datagram in arrival order.
4471	///
4472	/// To read groups and datagrams concurrently from one handle, poll
4473	/// [`Self::poll_next_group`] and [`Self::poll_recv_datagram`] together in a single
4474	/// `poll` closure (sequential `&mut` borrows), rather than awaiting the two `recv`
4475	/// futures at once.
4476	pub async fn recv_datagram(&mut self) -> Result<Option<Datagram>> {
4477		kio::wait(|waiter| self.poll_recv_datagram(waiter)).await
4478	}
4479
4480	/// Limit subsequent reads to these group sequences without rewinding read progress.
4481	///
4482	/// See [`Subscriber::set_groups`] for inclusive, exclusive, and omitted bounds.
4483	pub fn set_groups(&mut self, groups: impl RangeBounds<u64>) {
4484		self.inner.set_groups(groups);
4485	}
4486
4487	/// Create a handle for updating this subscriber's delivery preferences without
4488	/// borrowing the read cursor.
4489	pub fn control(&self) -> Control {
4490		self.inner.control()
4491	}
4492
4493	/// This subscriber's current preferences.
4494	pub fn subscription(&self) -> Subscription {
4495		self.inner.subscription()
4496	}
4497
4498	/// Replace this subscriber's delivery preferences.
4499	///
4500	/// Returns [`Error::Closed`] if the track already ended.
4501	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
4502		self.inner.update(subscription)
4503	}
4504
4505	/// Poll for the track's declared final sequence, without blocking.
4506	pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
4507		self.inner.poll_finished(waiter)
4508	}
4509
4510	/// Block until the track declares its end, returning the exclusive final sequence.
4511	///
4512	/// See [`Subscriber::finished`]: this reports the declared end, not that every group
4513	/// has arrived.
4514	pub async fn finished(&mut self) -> Result<u64> {
4515		kio::wait(|waiter| self.poll_finished(waiter)).await
4516	}
4517
4518	/// The latest sequence number in the track.
4519	pub fn latest(&self) -> Option<u64> {
4520		self.inner.latest()
4521	}
4522
4523	/// Whether `other` reads the same underlying track state.
4524	pub fn is_clone(&self, other: &Self) -> bool {
4525		self.inner.is_clone(&other.inner)
4526	}
4527}
4528
4529/// A subscriber asked for a track this broadcast doesn't have yet.
4530///
4531/// Yielded by [`broadcast::Dynamic::requested_track`](crate::broadcast::Dynamic::requested_track),
4532/// or created up front with [`broadcast::Producer::reserve_track`](crate::broadcast::Producer::reserve_track).
4533/// Subscribers block until the request is
4534/// resolved: call [`accept`](Self::accept) to serve it with a [`Producer`], or
4535/// [`reject`](Self::reject) to fail them. Dropping it without either rejects with
4536/// [`Error::Dropped`].
4537///
4538/// Concurrent requests for one name are coalesced, so exactly one of these exists per
4539/// name at a time.
4540pub struct Request {
4541	name: Arc<str>,
4542	// The parent broadcast's info, threaded into the [`Producer`] on accept.
4543	broadcast: Arc<broadcast::Info>,
4544	state: kio::Producer<TrackState>,
4545
4546	// The previous subscription that was combined, used to detect changes.
4547	prev_subscription: Option<Subscription>,
4548
4549	// Shared with the accepted [`Producer`] and every [`Dynamic`]: its `Drop` is the
4550	// teardown, and it stays inert until a producer is minted.
4551	alive: Arc<Alive>,
4552
4553	// A requested track is served on demand, so it counts as fetch-capable from
4554	// birth: a consumer's cache-miss `fetch_group` waits to be served instead of
4555	// racing the producer (e.g. a relay) into creating its own handler. Released
4556	// when the request is accepted or dropped; by then the relay holds its own.
4557	_dynamic: Dynamic,
4558
4559	// Ingress stats scope, threaded into the accepted [`Producer`]. Empty (no-op)
4560	// unless this request was reserved on a tagged broadcast.
4561	stats: stats::Scope,
4562
4563	// The serving session resolves the start of each subscription itself, so the
4564	// accepted track's start is unknown until it says (see [`Self::resolving_start`]).
4565	resolving_start: bool,
4566}
4567
4568impl Request {
4569	pub(crate) fn new(broadcast: Arc<broadcast::Info>, name: impl Into<Arc<str>>) -> Self {
4570		let name = name.into();
4571		let state = TrackState::spawn(broadcast.clone());
4572		let alive = Alive::new(name.clone(), state.clone());
4573		let dynamic = Dynamic::new(name.clone(), state.clone(), alive.clone());
4574		Self {
4575			name,
4576			broadcast,
4577			state,
4578			prev_subscription: None,
4579			alive,
4580			_dynamic: dynamic,
4581			stats: stats::Scope::default(),
4582			resolving_start: false,
4583		}
4584	}
4585
4586	/// Mark the track as served by a session that resolves each subscription's start
4587	/// (lite-06+), so [`Consumer::poll_start`] waits for its declaration instead of
4588	/// reading the requested floor as the start. Applied atomically with
4589	/// [`Self::accept`], before any reader can see the track.
4590	pub(crate) fn resolving_start(mut self) -> Self {
4591		self.resolving_start = true;
4592		self
4593	}
4594
4595	/// Attach an ingress stats scope, applied to the [`Producer`] on accept. Set by
4596	/// a tagged [`broadcast::Producer::reserve_track`].
4597	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
4598		self.stats = scope;
4599		self
4600	}
4601
4602	/// The requested track name.
4603	pub fn name(&self) -> &str {
4604		&self.name
4605	}
4606
4607	/// A [`Consumer`] for the eventual track, usable before the request is accepted.
4608	pub fn consume(&self) -> Consumer {
4609		Consumer::plain(self.name.clone(), self.state.consume())
4610	}
4611
4612	/// Create a [`Dynamic`] handle that serves on-demand fetches of uncached
4613	/// groups, before [`Self::accept`] is even called. A relay creates one to fetch
4614	/// past groups from upstream while (or instead of) serving a live subscription.
4615	pub fn dynamic(&self) -> Dynamic {
4616		Dynamic::new(self.name.clone(), self.state.clone(), self.alive.clone())
4617	}
4618
4619	/// Poll for the request becoming unused (every consumer dropped), so a relay can
4620	/// stop serving and drop the request.
4621	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
4622		self.state.poll_unused(waiter).map(|_| ())
4623	}
4624
4625	/// Mark this request as taken by a dynamic handler, which alone decides its answer.
4626	pub(crate) fn claim(self) -> Self {
4627		if let Ok(mut state) = self.state.write() {
4628			state.claimed = true;
4629		}
4630		self
4631	}
4632
4633	/// Reject only while no consumer needs this pending track. Demand and the check
4634	/// share one lock, so demand returning after `poll_unused` wins the race, and the
4635	/// close under that lock stops a later consumer attaching to a dead request.
4636	pub(crate) fn reject_unused(&self, err: Error) -> bool {
4637		match self.state.write_unused() {
4638			kio::Unused::Idle(guard) => {
4639				commit_abort(guard, err);
4640				true
4641			}
4642			kio::Unused::Closed => true,
4643			kio::Unused::Used => false,
4644		}
4645	}
4646
4647	/// Serve the request with the given track, resolving every waiting subscriber.
4648	///
4649	/// The name is taken from [`Self::name`]; `info` supplies the remaining knobs
4650	/// (`None` for the defaults). If the track was already aborted, the returned
4651	/// [`Producer`] is inert: writes fail with the abort error, as if it had been
4652	/// aborted immediately after accepting.
4653	pub fn accept(self, info: impl Into<Option<Info>>) -> Producer {
4654		let info = TrackState::normalize_info(&self.broadcast, info.into().unwrap_or_default());
4655		// A closed state means the track was aborted under us. Mirror `reject` and
4656		// tolerate it: the Producer we hand back simply can't write.
4657		if let Ok(mut state) = self.state.write() {
4658			state.accept(info.clone());
4659			state.start_pending = self.resolving_start;
4660		}
4661		// Accepting the request creates the track producer: count it as one ingress
4662		// subscription (closed when the last handle drops). No-op when untagged.
4663		self.alive.publish(Some(&self.stats));
4664		Producer {
4665			name: self.name,
4666			info,
4667			broadcast: self.broadcast,
4668			state: self.state,
4669			prev_subscription: None,
4670			alive: self.alive,
4671			stats: self.stats,
4672		}
4673	}
4674
4675	/// Reject the request, waking all waiting subscribers with `err`.
4676	pub fn reject(self, err: Error) {
4677		if let Ok(mut state) = self.state.write() {
4678			state.abort = Some(err);
4679		}
4680	}
4681
4682	/// The delivery preferences aggregated across everyone waiting on this request,
4683	/// or `None` if nobody is waiting. Useful for sizing the track before accepting.
4684	pub fn subscription(&self) -> Option<Subscription> {
4685		let state = self.state.read();
4686		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
4687		drop(state);
4688		snapshot_subscription(&subs, bound)
4689	}
4690
4691	/// Block until the aggregate [`subscription`](Self::subscription) changes,
4692	/// yielding `None` once nobody is waiting.
4693	pub async fn subscription_changed(&mut self) -> Option<Subscription> {
4694		kio::wait(|waiter| self.poll_subscription_changed(waiter)).await
4695	}
4696
4697	/// Poll counterpart to [`subscription_changed`](Self::subscription_changed).
4698	pub fn poll_subscription_changed(&mut self, waiter: &kio::Waiter) -> Poll<Option<Subscription>> {
4699		let state = self.state.read();
4700		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
4701		drop(state);
4702
4703		poll_combined_changed(&subs, bound, &mut self.prev_subscription, waiter)
4704	}
4705
4706	pub(super) fn weak(&self) -> TrackWeak {
4707		TrackWeak {
4708			name: self.name.clone(),
4709			state: self.state.weak(),
4710		}
4711	}
4712}
4713
4714#[cfg(test)]
4715use futures::FutureExt;
4716
4717#[cfg(test)]
4718#[allow(missing_docs)] // test-only assertion helpers
4719impl Subscriber {
4720	pub fn assert_group(&mut self) -> group::Consumer {
4721		self.recv_group()
4722			.now_or_never()
4723			.expect("group would have blocked")
4724			.expect("would have errored")
4725			.expect("track was closed")
4726	}
4727
4728	pub fn assert_no_group(&mut self) {
4729		assert!(
4730			self.recv_group().now_or_never().is_none(),
4731			"recv_group would not have blocked"
4732		);
4733	}
4734
4735	pub fn assert_not_closed(&mut self) {
4736		assert!(self.finished().now_or_never().is_none(), "should not be closed");
4737	}
4738
4739	pub fn assert_closed(&mut self) {
4740		assert!(self.finished().now_or_never().is_some(), "should be closed");
4741	}
4742
4743	// TODO assert specific errors after implementing PartialEq
4744	pub fn assert_error(&mut self) {
4745		assert!(
4746			self.finished().now_or_never().expect("should not block").is_err(),
4747			"should be error"
4748		);
4749	}
4750
4751	pub fn assert_is_clone(&self, other: &Self) {
4752		assert!(self.is_clone(other), "should be clone");
4753	}
4754
4755	pub fn assert_not_clone(&self, other: &Self) {
4756		assert!(!self.is_clone(other), "should not be clone");
4757	}
4758}
4759
4760#[cfg(test)]
4761mod test {
4762	use super::*;
4763	use crate::frame;
4764	use crate::model::test_tracing::count_drop_warnings;
4765	use std::time::Duration;
4766
4767	/// Mint a track for tests with a default parent broadcast, since tracks are
4768	/// normally born from a [`broadcast::Producer`].
4769	fn track_producer(name: impl Into<Arc<str>>, info: impl Into<Option<Info>>) -> Producer {
4770		Producer::new(Arc::new(broadcast::Info::default()), name, info)
4771	}
4772
4773	/// A bounded replay window for tests whose subject requires every buffered group.
4774	fn replay() -> Subscription {
4775		Subscription::default().with_max_age(Duration::from_secs(30))
4776	}
4777
4778	/// Helper: count live cached groups in state.
4779	fn live_groups(state: &TrackState) -> usize {
4780		state.lookup.len()
4781	}
4782
4783	/// Helper: get the sequence number of the first live group in arrival order.
4784	fn first_live_sequence(state: &TrackState) -> u64 {
4785		state
4786			.arrival
4787			.iter()
4788			.find(|(sequence, stamp)| state.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp))
4789			.map(|(sequence, _)| *sequence)
4790			.unwrap()
4791	}
4792
4793	/// Helper: non-blocking datagram receive that must be ready with a datagram.
4794	fn recv_datagram(dg: &mut Subscriber) -> Datagram {
4795		dg.recv_datagram()
4796			.now_or_never()
4797			.expect("datagram would have blocked")
4798			.expect("would have errored")
4799			.expect("track was closed")
4800	}
4801
4802	/// A declared start (SUBSCRIBE_START) resolves a peek below it as a permanent
4803	/// miss, while a group that is already cached below the start stays readable
4804	/// (a fetch can create one; the declaration only covers the live feed).
4805	#[tokio::test]
4806	async fn peek_resolves_below_the_declared_start() {
4807		let mut producer = track_producer("test", None);
4808		let consumer = producer.consume();
4809
4810		let mut cached = producer.create_group(group::Info { sequence: 1 }).unwrap();
4811		cached.write_frame(Timestamp::ZERO, b"backfill".to_vec()).unwrap();
4812		cached.finish().unwrap();
4813
4814		// Nothing declared yet: a missing group parks.
4815		let waiter = kio::Waiter::noop();
4816		assert!(consumer.poll_peek_group(0, &waiter).is_pending());
4817
4818		// The declaration turns the missing group into a permanent miss, but the
4819		// cached one below it still resolves.
4820		producer.start_at(3).unwrap();
4821		assert!(matches!(consumer.poll_peek_group(0, &waiter), Poll::Ready(None)));
4822		assert!(matches!(consumer.poll_peek_group(1, &waiter), Poll::Ready(Some(_))));
4823		assert!(consumer.poll_peek_group(3, &waiter).is_pending());
4824
4825		// The declaration follows the demand in either direction: forward retires
4826		// the skipped range, backward reopens it.
4827		producer.start_at(4).unwrap();
4828		assert!(matches!(consumer.poll_peek_group(3, &waiter), Poll::Ready(None)));
4829		producer.start_at(0).unwrap();
4830		assert!(consumer.poll_peek_group(0, &waiter).is_pending());
4831	}
4832
4833	#[tokio::test]
4834	async fn append_datagram_shares_group_sequence() {
4835		let mut producer = track_producer("test", None);
4836		let ts = Timestamp::from_millis(10).unwrap();
4837
4838		// Interleave groups and datagrams: they draw from one monotonic counter.
4839		assert_eq!(producer.append_group().unwrap().sequence, 0);
4840		assert_eq!(producer.append_datagram(ts, &b"a"[..]).unwrap(), 1);
4841		assert_eq!(producer.append_group().unwrap().sequence, 2);
4842		assert_eq!(producer.append_datagram(ts, &b"b"[..]).unwrap(), 3);
4843		assert_eq!(producer.latest(), Some(3));
4844	}
4845
4846	#[tokio::test]
4847	async fn append_datagram_roundtrip() {
4848		let mut producer = track_producer("test", None);
4849		let mut dg = producer.subscribe(None);
4850
4851		let ts = Timestamp::from_millis(42).unwrap();
4852		let seq = producer.append_datagram(ts, &b"hello"[..]).unwrap();
4853
4854		let got = recv_datagram(&mut dg);
4855		assert_eq!(got.sequence, seq);
4856		assert_eq!(got.timestamp, ts);
4857		assert_eq!(&got.payload[..], b"hello");
4858	}
4859
4860	#[tokio::test]
4861	async fn insert_datagram_preserves_sequence() {
4862		let mut producer = track_producer("test", None);
4863		let mut dg = producer.subscribe(None);
4864
4865		let ts = Timestamp::from_millis(5).unwrap();
4866		// A relay forwarding an upstream datagram keeps its sequence number.
4867		producer
4868			.insert_datagram(100, ts, bytes::Bytes::from_static(b"x"))
4869			.unwrap();
4870
4871		assert_eq!(recv_datagram(&mut dg).sequence, 100);
4872		// max_sequence advanced, so the next appended group/datagram continues past it.
4873		assert_eq!(producer.append_group().unwrap().sequence, 101);
4874	}
4875
4876	#[tokio::test]
4877	async fn insert_datagram_leaves_a_gap() {
4878		let mut producer = track_producer("test", None);
4879		let mut dg = producer.subscribe(None);
4880		let ts = Timestamp::from_millis(0).unwrap();
4881
4882		producer
4883			.insert_datagram(10, ts, bytes::Bytes::from_static(b"gap"))
4884			.unwrap();
4885		assert_eq!(recv_datagram(&mut dg).sequence, 10);
4886		assert_eq!(producer.append_datagram(ts, &b"next"[..]).unwrap(), 11);
4887		assert_eq!(producer.append_group().unwrap().sequence, 12);
4888	}
4889
4890	#[tokio::test]
4891	async fn insert_datagram_out_of_order_does_not_rewind() {
4892		let mut producer = track_producer("test", None);
4893		let mut dg = producer.subscribe(None);
4894		let ts = Timestamp::from_millis(0).unwrap();
4895
4896		producer
4897			.insert_datagram(10, ts, bytes::Bytes::from_static(b"high"))
4898			.unwrap();
4899		producer
4900			.insert_datagram(5, ts, bytes::Bytes::from_static(b"low"))
4901			.unwrap();
4902
4903		assert_eq!(recv_datagram(&mut dg).sequence, 10);
4904		assert_eq!(recv_datagram(&mut dg).sequence, 5);
4905		assert_eq!(producer.append_datagram(ts, &b"next"[..]).unwrap(), 11);
4906	}
4907
4908	#[tokio::test]
4909	async fn insert_datagram_duplicate_is_best_effort() {
4910		let mut producer = track_producer("test", None);
4911		let mut dg = producer.subscribe(None);
4912		let ts = Timestamp::from_millis(0).unwrap();
4913
4914		producer
4915			.insert_datagram(3, ts, bytes::Bytes::from_static(b"first"))
4916			.unwrap();
4917		producer
4918			.insert_datagram(3, ts, bytes::Bytes::from_static(b"again"))
4919			.unwrap();
4920
4921		assert_eq!(&recv_datagram(&mut dg).payload[..], b"first");
4922		assert_eq!(&recv_datagram(&mut dg).payload[..], b"again");
4923		assert_eq!(producer.append_datagram(ts, &b"next"[..]).unwrap(), 4);
4924	}
4925
4926	#[tokio::test]
4927	async fn insert_datagram_stale_does_not_rewind_after_append() {
4928		let mut producer = track_producer("test", None);
4929		let mut dg = producer.subscribe(None);
4930		let ts = Timestamp::from_millis(0).unwrap();
4931
4932		assert_eq!(producer.append_datagram(ts, &b"0"[..]).unwrap(), 0);
4933		assert_eq!(producer.append_datagram(ts, &b"1"[..]).unwrap(), 1);
4934		producer
4935			.insert_datagram(0, ts, bytes::Bytes::from_static(b"stale"))
4936			.unwrap();
4937
4938		assert_eq!(recv_datagram(&mut dg).sequence, 0);
4939		assert_eq!(recv_datagram(&mut dg).sequence, 1);
4940		assert_eq!(recv_datagram(&mut dg).sequence, 0);
4941		assert_eq!(producer.append_datagram(ts, &b"2"[..]).unwrap(), 2);
4942		assert_eq!(producer.append_group().unwrap().sequence, 3);
4943	}
4944
4945	#[tokio::test]
4946	async fn insert_datagram_cloned_producers_share_counter() {
4947		let mut producer = track_producer("test", None);
4948		let mut other = producer.clone();
4949		let mut dg = producer.subscribe(None);
4950		let ts = Timestamp::from_millis(0).unwrap();
4951
4952		producer
4953			.insert_datagram(4, ts, bytes::Bytes::from_static(b"a"))
4954			.unwrap();
4955		assert_eq!(other.append_datagram(ts, &b"b"[..]).unwrap(), 5);
4956		other.insert_datagram(8, ts, bytes::Bytes::from_static(b"c")).unwrap();
4957		assert_eq!(producer.append_group().unwrap().sequence, 9);
4958
4959		assert_eq!(recv_datagram(&mut dg).sequence, 4);
4960		assert_eq!(recv_datagram(&mut dg).sequence, 5);
4961		assert_eq!(recv_datagram(&mut dg).sequence, 8);
4962	}
4963
4964	#[test]
4965	fn insert_datagram_after_finish_is_closed() {
4966		let mut producer = track_producer("test", None);
4967		let ts = Timestamp::from_millis(0).unwrap();
4968		producer.finish().unwrap();
4969		assert!(matches!(
4970			producer.insert_datagram(0, ts, bytes::Bytes::from_static(b"x")),
4971			Err(Error::Closed)
4972		));
4973		assert!(matches!(producer.append_datagram(ts, &b"x"[..]), Err(Error::Closed)));
4974	}
4975
4976	#[test]
4977	fn insert_datagram_after_abort_fails() {
4978		let producer = track_producer("test", None);
4979		let mut other = producer.clone();
4980		let ts = Timestamp::from_millis(0).unwrap();
4981		producer.abort(Error::Cancel).unwrap();
4982		assert!(other.insert_datagram(0, ts, bytes::Bytes::from_static(b"x")).is_err());
4983	}
4984
4985	#[test]
4986	fn insert_datagram_respects_finish_at() {
4987		let mut producer = track_producer("test", None);
4988		let ts = Timestamp::from_millis(0).unwrap();
4989		producer.finish_at(10).unwrap();
4990		producer
4991			.insert_datagram(5, ts, bytes::Bytes::from_static(b"ok"))
4992			.unwrap();
4993		assert!(matches!(
4994			producer.insert_datagram(10, ts, bytes::Bytes::from_static(b"late")),
4995			Err(Error::Closed)
4996		));
4997		assert_eq!(producer.append_group().unwrap().sequence, 6);
4998	}
4999
5000	/// Datagram sequence advances do not move a route takeover past an open group.
5001	#[test]
5002	fn resume_position_uses_the_latest_group() {
5003		let mut datagram_only = track_producer("datagram-only", None);
5004		let datagram_only_consumer = datagram_only.consume();
5005		datagram_only
5006			.insert_datagram(8, Timestamp::ZERO, bytes::Bytes::from_static(b"x"))
5007			.unwrap();
5008		assert_eq!(
5009			datagram_only_consumer.resume_position(),
5010			None,
5011			"a datagram creates no group position to resume"
5012		);
5013
5014		let mut producer = track_producer("mixed", None);
5015		let consumer = producer.consume();
5016		let mut group = producer.create_group(group::Info { sequence: 3 }).unwrap();
5017		group
5018			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
5019			.unwrap();
5020		producer
5021			.insert_datagram(8, Timestamp::ZERO, bytes::Bytes::from_static(b"x"))
5022			.unwrap();
5023
5024		assert_eq!(
5025			consumer.resume_position(),
5026			Some(Position { group: 3, frame: 1 }),
5027			"the replacement must continue the open group"
5028		);
5029		group.abort(Error::Cancel).unwrap();
5030	}
5031
5032	/// Datagrams and groups are separate channels that share only a sequence namespace,
5033	/// so consuming one must not move the other's cursor.
5034	#[tokio::test]
5035	async fn recv_datagram_leaves_the_ordered_cursor_alone() {
5036		let mut producer = track_producer("test", None);
5037		let mut datagrams = producer.subscribe(None);
5038		let mut subscriber = producer.subscribe(None).ordered();
5039		let ts = Timestamp::from_millis(5).unwrap();
5040
5041		producer
5042			.insert_datagram(5, ts, bytes::Bytes::from_static(b"x"))
5043			.unwrap();
5044		assert_eq!(recv_datagram(&mut datagrams).sequence, 5);
5045
5046		producer.create_group(group::Info { sequence: 3 }).unwrap();
5047		producer.create_group(group::Info { sequence: 6 }).unwrap();
5048
5049		let mut next = || {
5050			subscriber
5051				.next_group()
5052				.now_or_never()
5053				.expect("group would have blocked")
5054				.expect("would have errored")
5055				.expect("track was closed")
5056				.sequence
5057		};
5058		assert_eq!(next(), 3, "the datagram at sequence 5 did not consume group 3");
5059		assert_eq!(next(), 6);
5060	}
5061
5062	#[tokio::test]
5063	async fn datagram_normalized_to_track_timescale() {
5064		let info = Info::default().with_timescale(Timescale::MICRO);
5065		let mut producer = track_producer("test", info);
5066		let mut dg = producer.subscribe(None);
5067
5068		// Supplied at millis; stored/emitted at the track's micro timescale.
5069		producer
5070			.append_datagram(Timestamp::from_millis(2).unwrap(), &b"z"[..])
5071			.unwrap();
5072		let got = recv_datagram(&mut dg);
5073		assert_eq!(got.timestamp.scale(), Timescale::MICRO);
5074		assert_eq!(got.timestamp.value(), 2_000);
5075	}
5076
5077	#[tokio::test]
5078	async fn datagram_rejects_oversized() {
5079		let mut producer = track_producer("test", None);
5080		let big = bytes::Bytes::from(vec![0u8; crate::model::datagram::MAX_DATAGRAM_PAYLOAD + 1]);
5081		let ts = Timestamp::from_millis(0).unwrap();
5082		assert!(matches!(
5083			producer.append_datagram(ts, big.clone()),
5084			Err(Error::FrameTooLarge)
5085		));
5086		assert!(matches!(
5087			producer.insert_datagram(0, ts, big),
5088			Err(Error::FrameTooLarge)
5089		));
5090	}
5091
5092	#[tokio::test]
5093	async fn datagram_fanout_to_subscribers() {
5094		let mut producer = track_producer("test", None);
5095		// Two independent subscribers, each with its own datagram cursor.
5096		let mut a = producer.subscribe(None);
5097		let mut b = producer.subscribe(None);
5098		let ts = Timestamp::from_millis(1).unwrap();
5099
5100		producer.append_datagram(ts, &b"first"[..]).unwrap();
5101		producer.append_datagram(ts, &b"second"[..]).unwrap();
5102
5103		// Both receive every datagram in order, independently.
5104		assert_eq!(&recv_datagram(&mut a).payload[..], b"first");
5105		assert_eq!(&recv_datagram(&mut a).payload[..], b"second");
5106		assert_eq!(&recv_datagram(&mut b).payload[..], b"first");
5107		assert_eq!(&recv_datagram(&mut b).payload[..], b"second");
5108	}
5109
5110	#[test]
5111	fn datagram_buffer_drops_oldest_at_capacity() {
5112		let mut producer = track_producer("test", None);
5113		let mut slow = producer.subscribe(None);
5114		let mut fast = producer.subscribe(None);
5115		let count = MAX_DATAGRAMS * 3;
5116		for sequence in 0..count {
5117			producer.append_datagram(Timestamp::ZERO, b"x".as_slice()).unwrap();
5118			assert_eq!(recv_datagram(&mut fast).sequence, sequence as u64);
5119		}
5120		assert_eq!(producer.state.read().datagrams.len(), MAX_DATAGRAMS);
5121		for sequence in count - MAX_DATAGRAMS..count {
5122			assert_eq!(recv_datagram(&mut slow).sequence, sequence as u64);
5123		}
5124		assert!(slow.poll_recv_datagram(&kio::Waiter::noop()).is_pending());
5125	}
5126
5127	#[tokio::test]
5128	async fn datagram_recv_pends_until_written() {
5129		let mut producer = track_producer("test", None);
5130		let mut dg = producer.subscribe(None);
5131
5132		assert!(
5133			dg.recv_datagram().now_or_never().is_none(),
5134			"should block with no datagrams"
5135		);
5136
5137		producer
5138			.append_datagram(Timestamp::from_millis(0).unwrap(), &b"go"[..])
5139			.unwrap();
5140		assert_eq!(&recv_datagram(&mut dg).payload[..], b"go");
5141	}
5142
5143	/// Exercises the full producer -> publisher-encode -> subscriber-decode -> producer seam
5144	/// (everything but the QUIC datagram send/recv), catching any field-order mismatch between
5145	/// the wire codec and the model.
5146	#[tokio::test]
5147	async fn datagram_wire_roundtrip_between_tracks() {
5148		use crate::coding::{Decode, Encode};
5149		use crate::lite;
5150
5151		let version = lite::Version::Lite05;
5152
5153		// Origin publishes a datagram; the publisher reads it and encodes the wire body.
5154		let mut origin = track_producer("test", None);
5155		let mut origin_dg = origin.subscribe(None);
5156		let ts = Timestamp::from_millis(7).unwrap();
5157		let seq = origin.append_datagram(ts, &b"payload"[..]).unwrap();
5158
5159		let d = recv_datagram(&mut origin_dg);
5160		let body = lite::Datagram {
5161			subscribe: 5,
5162			sequence: d.sequence,
5163			timestamp: d.timestamp.value(),
5164			payload: d.payload.clone(),
5165		}
5166		.encode_bytes(version)
5167		.unwrap();
5168
5169		// Subscriber decodes the body and writes it downstream, preserving the sequence.
5170		let mut slice = &body[..];
5171		let wire = lite::Datagram::decode(&mut slice, version).unwrap();
5172		let mut downstream = track_producer("test", None);
5173		let mut downstream_dg = downstream.subscribe(None);
5174		downstream
5175			.insert_datagram(
5176				wire.sequence,
5177				Timestamp::new(wire.timestamp, Timescale::MILLI).unwrap(),
5178				wire.payload,
5179			)
5180			.unwrap();
5181
5182		let got = recv_datagram(&mut downstream_dg);
5183		assert_eq!(got.sequence, seq);
5184		assert_eq!(got.timestamp, ts);
5185		assert_eq!(&got.payload[..], b"payload");
5186	}
5187
5188	#[tokio::test]
5189	async fn evict_expired_groups() {
5190		let producer = track_producer("test", None);
5191
5192		// Create 3 groups at time 0.
5193		producer.append_group().unwrap(); // seq 0
5194		producer.append_group().unwrap(); // seq 1
5195		producer.append_group().unwrap(); // seq 2
5196
5197		{
5198			let state = producer.state.read();
5199			assert_eq!(live_groups(&state), 3);
5200			assert_eq!(state.offset, 0);
5201		}
5202
5203		// Advance time past the pool's LRU window.
5204		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
5205
5206		// Append a new group to trigger eviction.
5207		producer.append_group().unwrap(); // seq 3
5208
5209		// Groups 0, 1, 2 are expired but seq 3 (the live edge) is kept. Their arrival
5210		// entries no longer resolve, so the leading ones are trimmed and the offset
5211		// advances past them.
5212		{
5213			let state = producer.state.read();
5214			assert_eq!(live_groups(&state), 1);
5215			assert_eq!(first_live_sequence(&state), 3);
5216			assert_eq!(state.offset, 3);
5217			assert!(!state.lookup.contains_key(&0));
5218			assert!(!state.lookup.contains_key(&1));
5219			assert!(!state.lookup.contains_key(&2));
5220			assert!(state.lookup.contains_key(&3));
5221		}
5222	}
5223
5224	/// A group whose frames outlive `max_age` is aged out when the next group starts, but
5225	/// a subscriber that already drained it must still see the clean end of group. Otherwise a
5226	/// track with long groups (a per-minute rollup, say) fails its readers at every boundary.
5227	#[tokio::test]
5228	async fn aging_out_a_finished_group_keeps_the_clean_end() {
5229		let producer = track_producer("test", None);
5230		let mut group = producer.create_group(group::Info { sequence: 0 }).unwrap();
5231		let mut consumer = group.consume();
5232
5233		group
5234			.write_frame(Timestamp::from_millis(0).unwrap(), b"hello".as_slice())
5235			.unwrap();
5236		assert_eq!(consumer.next_frame().await.unwrap().unwrap().size, 5);
5237
5238		// The group stays open well past the LRU window, then the next period starts.
5239		crate::model::clock::advance(cache::DEFAULT_EXPIRY * 2);
5240		group.finish().unwrap();
5241		let _next = producer.create_group(group::Info { sequence: 1 }).unwrap();
5242
5243		assert!(consumer.next_frame().await.unwrap().is_none());
5244	}
5245
5246	/// An actively-read group is not expired out from under its reader: every frame
5247	/// read restarts the retention clock. A group nobody reads still ages out on
5248	/// schedule, so reclamation stays intact.
5249	#[tokio::test]
5250	async fn active_reader_survives_expiry() {
5251		let producer = track_producer("test", None);
5252		let mut subscriber = producer.subscribe(None);
5253
5254		// A finished group with one frame per step of the read loop below.
5255		let mut group = producer.create_group(0u64.into()).unwrap();
5256		for _ in 0..10 {
5257			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5258		}
5259		group.finish().unwrap();
5260		let mut reading = subscriber.assert_group();
5261
5262		// A sibling written at the same time that nobody ever reads.
5263		producer.create_group(1u64.into()).unwrap().finish().unwrap();
5264
5265		// Each step stays well inside the retention window, but the whole read
5266		// spans several windows. New groups keep the expiry scan running.
5267		for seq in 2..12u64 {
5268			crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2);
5269			let frame = reading.next_frame().await;
5270			assert!(
5271				matches!(frame, Ok(Some(_))),
5272				"an actively-read group must not expire mid-read (step {seq})"
5273			);
5274			producer.create_group(seq.into()).unwrap().finish().unwrap();
5275		}
5276
5277		let state = producer.state.read();
5278		assert!(state.lookup.contains_key(&0), "the read group survived");
5279		assert!(!state.lookup.contains_key(&1), "the unread group still expired");
5280	}
5281
5282	/// Whole-frame reads served from the prefetch batch must also keep the group
5283	/// alive: the batch is filled (and stamped) once per `Prefetch::CAP` frames,
5284	/// which bounds frames, not elapsed time, so a slow `read_frame` reader has to
5285	/// re-stamp on a time bound between refills.
5286	#[tokio::test]
5287	async fn slow_prefetch_reader_survives_expiry() {
5288		let producer = track_producer("test", None);
5289		let mut subscriber = producer.subscribe(None);
5290
5291		let mut group = producer.create_group(0u64.into()).unwrap();
5292		for _ in 0..20 {
5293			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5294		}
5295		group.finish().unwrap();
5296		let mut reading = subscriber.assert_group();
5297
5298		// One whole-frame read per half-window: most are served straight from the
5299		// prefetch without locking. New groups keep the expiry scan running.
5300		for seq in 1..20u64 {
5301			crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2);
5302			let frame = reading.read_frame().await;
5303			assert!(
5304				matches!(frame, Ok(Some(_))),
5305				"a slow prefetch reader must not expire mid-read (step {seq})"
5306			);
5307			producer.create_group(seq.into()).unwrap().finish().unwrap();
5308		}
5309	}
5310
5311	/// Receiving a group is itself a cache access: a subscriber that takes
5312	/// delivery just before the group would age out still gets to read it a full
5313	/// window later.
5314	#[tokio::test]
5315	async fn delivery_restarts_the_expiry_clock() {
5316		let producer = track_producer("test", None);
5317		let mut subscriber = producer.subscribe(replay());
5318
5319		let mut group = producer.create_group(0u64.into()).unwrap();
5320		group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5321		group.finish().unwrap();
5322		// A second group so seq 0 leaves the protected live edge.
5323		producer.create_group(1u64.into()).unwrap().finish().unwrap();
5324
5325		// Deliver just inside the window: the delivery stamps the group.
5326		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
5327		let mut reading = subscriber.assert_group();
5328
5329		// Almost another full window passes: far beyond the write, inside the
5330		// delivery stamp. The new group runs the expiry scan.
5331		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
5332		producer.create_group(2u64.into()).unwrap().finish().unwrap();
5333
5334		let frame = reading.read_frame().await.unwrap();
5335		assert!(frame.is_some(), "a just-delivered group must not expire unread");
5336	}
5337
5338	/// Streaming chunks into an in-flight frame is a write access: a straggler
5339	/// group (behind the live edge) trickling a large frame across several
5340	/// retention windows must not be expired mid-write.
5341	#[tokio::test]
5342	async fn streaming_frame_writes_keep_the_group_alive() {
5343		let producer = track_producer("test", None);
5344		let mut straggler = producer.create_group(0u64.into()).unwrap();
5345		// The live edge moves on, so the straggler is demoted and expirable.
5346		producer.create_group(1u64.into()).unwrap().finish().unwrap();
5347
5348		let mut frame = straggler
5349			.create_frame(frame::Info {
5350				size: 10,
5351				timestamp: Timestamp::ZERO,
5352			})
5353			.unwrap();
5354		// One chunk per half-window; the whole frame spans several windows. New
5355		// groups keep the expiry scan running.
5356		for seq in 2..12u64 {
5357			crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2);
5358			frame.write(bytes::Bytes::from_static(b"x")).unwrap();
5359			producer.create_group(seq.into()).unwrap().finish().unwrap();
5360		}
5361		frame.finish().unwrap();
5362		straggler.finish().unwrap();
5363
5364		let state = producer.state.read();
5365		assert!(
5366			state.lookup.contains_key(&0),
5367			"a group streaming a frame survives expiry"
5368		);
5369	}
5370
5371	/// The wire ingest coalesces its chunk wakes to the poll boundary, so a payload
5372	/// whose tail arrives all at once completes without a single `frame_notify`.
5373	/// Committing is itself a write access: a group that just finished a frame must
5374	/// not be expired by the next track write on its stale frame-open stamp.
5375	#[tokio::test]
5376	async fn coalesced_frame_completion_keeps_the_group_alive() {
5377		let producer = track_producer("test", None);
5378		let mut straggler = producer.create_group(0u64.into()).unwrap();
5379		// The live edge moves on, so the straggler is demoted and expirable.
5380		producer.create_group(1u64.into()).unwrap().finish().unwrap();
5381
5382		let mut frame = straggler
5383			.create_frame_owned(
5384				frame::Info {
5385					size: 3,
5386					timestamp: Timestamp::ZERO,
5387				},
5388				&Default::default(),
5389			)
5390			.unwrap();
5391
5392		// The sender stalls past the retention window, then the whole payload lands in
5393		// one poll turn: the loop never returns `Pending`, so `notify` is never reached
5394		// and `finish` is the only write the charge sees.
5395		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
5396		frame.write(bytes::Bytes::from_static(b"abc")).unwrap();
5397		frame.finish().unwrap();
5398		straggler.finish().unwrap();
5399
5400		// A new group runs the expiry scan.
5401		producer.create_group(2u64.into()).unwrap().finish().unwrap();
5402
5403		let state = producer.state.read();
5404		assert!(
5405			state.lookup.contains_key(&0),
5406			"a group whose frame just completed must not expire"
5407		);
5408	}
5409
5410	/// Re-offering a parked group (once the cap rises) is a delivery: it restarts
5411	/// the expiry clock so the subscriber gets to read what it was just handed.
5412	#[tokio::test]
5413	async fn parked_reoffer_restarts_the_expiry_clock() {
5414		let producer = track_producer("test", None);
5415		let mut subscriber = producer.subscribe(None);
5416		subscriber.set_groups(..1);
5417
5418		for seq in 0..2u64 {
5419			let mut group = producer.create_group(seq.into()).unwrap();
5420			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5421			group.finish().unwrap();
5422		}
5423
5424		// Group 0 is in range; group 1 is beyond the cap and parks.
5425		assert_eq!(subscriber.assert_group().sequence, 0);
5426		subscriber.assert_no_group();
5427
5428		// Just inside the window, the cap rises and the re-offer stamps group 1.
5429		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
5430		subscriber.set_groups(..2);
5431		let mut reading = subscriber.assert_group();
5432		assert_eq!(reading.sequence, 1);
5433
5434		// Almost another full window passes: far beyond the write, inside the
5435		// re-offer stamp. The new group runs the expiry scan.
5436		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
5437		producer.create_group(2u64.into()).unwrap().finish().unwrap();
5438
5439		let frame = reading.read_frame().await.unwrap();
5440		assert!(frame.is_some(), "a just-re-offered group must not expire unread");
5441	}
5442
5443	#[tokio::test]
5444	async fn evict_keeps_max_sequence() {
5445		let producer = track_producer("test", None);
5446		producer.append_group().unwrap(); // seq 0
5447
5448		// Advance time past the LRU window.
5449		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
5450
5451		// Append another group; seq 0 is expired and evicted.
5452		producer.append_group().unwrap(); // seq 1
5453
5454		{
5455			let state = producer.state.read();
5456			assert_eq!(live_groups(&state), 1);
5457			assert_eq!(first_live_sequence(&state), 1);
5458			assert_eq!(state.offset, 1);
5459		}
5460	}
5461
5462	#[tokio::test]
5463	async fn no_eviction_when_fresh() {
5464		let producer = track_producer("test", None);
5465		producer.append_group().unwrap(); // seq 0
5466		producer.append_group().unwrap(); // seq 1
5467		producer.append_group().unwrap(); // seq 2
5468
5469		{
5470			let state = producer.state.read();
5471			assert_eq!(live_groups(&state), 3);
5472			assert_eq!(state.offset, 0);
5473		}
5474	}
5475
5476	#[tokio::test]
5477	async fn consumer_skips_evicted_groups() {
5478		let producer = track_producer("test", None);
5479		producer.append_group().unwrap(); // seq 0
5480
5481		let mut consumer = producer.subscribe(None);
5482
5483		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
5484		producer.append_group().unwrap(); // seq 1
5485
5486		// Group 0 was evicted. Consumer should get group 1.
5487		let group = consumer.assert_group();
5488		assert_eq!(group.sequence, 1);
5489	}
5490
5491	/// Mint a track under an origin whose pool has the given wall-clock LRU window.
5492	fn track_producer_expiring(name: impl Into<Arc<str>>, expiry: impl Into<Option<Duration>>) -> Producer {
5493		track_producer_pooled(name, cache::Pool::new(cache::Config::default().with_expiry(expiry)))
5494	}
5495
5496	/// Mint a track under an origin caching into `pool`.
5497	fn track_producer_pooled(name: impl Into<Arc<str>>, pool: cache::Pool) -> Producer {
5498		Producer::new(
5499			Arc::new(broadcast::Info {
5500				pool,
5501				..Default::default()
5502			}),
5503			name,
5504			None,
5505		)
5506	}
5507
5508	/// The write path is not the only thing that runs expiry: a pool sweep reclaims a
5509	/// track's idle groups even when the track never writes again, which is the only
5510	/// bound on a publisher that stalls with a group still open.
5511	#[tokio::test]
5512	async fn pool_sweep_expires_without_a_write() {
5513		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
5514		let producer = track_producer_pooled("test", pool.clone());
5515		let mut stalled = producer.append_group().unwrap(); // seq 0, left open
5516		stalled.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5517		producer.append_group().unwrap(); // seq 1, the live edge
5518
5519		crate::model::clock::advance(Duration::from_secs(2));
5520		pool.sweep();
5521
5522		assert!(
5523			!producer.state.read().lookup.contains_key(&0),
5524			"the sweep reclaimed an idle open group with no write behind it"
5525		);
5526	}
5527
5528	#[test]
5529	fn cache_gc_dates_activity_before_expiring_it() {
5530		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
5531		let now = crate::model::clock::now();
5532		assert_eq!(pool.gc(now), Some(now + Duration::from_millis(500)));
5533		let producer = track_producer_pooled("test", pool.clone());
5534		let mut group = producer.append_group().unwrap();
5535		group.write_frame(Timestamp::ZERO, b"first".as_slice()).unwrap();
5536		producer.append_group().unwrap();
5537		// Activity written while cleanup was idle gets the new supplied time.
5538		let later = now + Duration::from_secs(60);
5539		pool.gc(later);
5540		assert!(producer.state.read().lookup.contains_key(&0));
5541		pool.gc(later + Duration::from_secs(2));
5542		assert!(!producer.state.read().lookup.contains_key(&0));
5543	}
5544
5545	#[test]
5546	fn cache_gc_reaches_old_entries_behind_a_fresh_front() {
5547		let expiry = Duration::from_secs(1);
5548		let pool = cache::Pool::new(cache::Config::default().with_expiry(expiry));
5549		let producer = track_producer_pooled("test", pool.clone());
5550		let now = crate::model::clock::now();
5551		let groups: Vec<_> = (0..EVICT_SCAN * 3).map(|_| producer.append_group().unwrap()).collect();
5552		producer.append_group().unwrap();
5553		pool.gc(now);
5554		// Keep more than a write scan's worth of leading entries fresh.
5555		for group in &groups[..EVICT_SCAN * 2] {
5556			group.cache_refresh();
5557		}
5558		pool.gc(now + expiry * 2);
5559		let state = producer.state.read();
5560		for sequence in 0..EVICT_SCAN * 2 {
5561			assert!(state.lookup.contains_key(&(sequence as u64)), "fresh front survives");
5562		}
5563		for sequence in EVICT_SCAN * 2..EVICT_SCAN * 3 {
5564			assert!(!state.lookup.contains_key(&(sequence as u64)), "old tail is reclaimed");
5565		}
5566	}
5567
5568	/// One sweep drains a whole idle backlog, not a rotating window of it: a quiet
5569	/// track has no writes left to revisit the rest of the queue with, so a bounded
5570	/// pass would leave the oldest groups parked for a backlog's length in windows.
5571	#[tokio::test]
5572	async fn pool_sweep_drains_a_deep_backlog() {
5573		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
5574		let producer = track_producer_pooled("test", pool.clone());
5575
5576		// Comfortably more than one write-driven scan window (EVICT_SCAN).
5577		let backlog = 4 * EVICT_SCAN;
5578		for _ in 0..backlog {
5579			let mut group = producer.append_group().unwrap();
5580			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5581		}
5582		producer.append_group().unwrap(); // the live edge, always protected
5583
5584		crate::model::clock::advance(Duration::from_secs(2));
5585		pool.sweep();
5586
5587		let state = producer.state.read();
5588		let stale = (0..backlog as u64).filter(|seq| state.lookup.contains_key(seq)).count();
5589		assert_eq!(stale, 0, "one sweep reclaimed the whole idle backlog");
5590	}
5591
5592	#[tokio::test]
5593	async fn pool_expiry_controls_eviction() {
5594		// A shorter LRU window on the pool evicts sooner than the default.
5595		let producer = track_producer_expiring("test", Duration::from_secs(1));
5596		producer.append_group().unwrap(); // seq 0
5597
5598		// Past the pool's window but well within cache::DEFAULT_EXPIRY.
5599		crate::model::clock::advance(Duration::from_secs(2));
5600		producer.append_group().unwrap(); // seq 1
5601
5602		// Seq 0 is gone because the pool only keeps idle groups for 1s.
5603		let state = producer.state.read();
5604		assert_eq!(live_groups(&state), 1);
5605		assert_eq!(first_live_sequence(&state), 1);
5606	}
5607
5608	#[tokio::test]
5609	async fn small_frame_write_expires_idle_siblings() {
5610		let producer = track_producer_expiring("test", Duration::from_secs(1));
5611		producer.append_group().unwrap().finish().unwrap(); // seq 0
5612		let mut live = producer.append_group().unwrap(); // seq 1
5613
5614		crate::model::clock::advance(Duration::from_secs(2));
5615		live.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5616
5617		let expired = !producer.state.read().lookup.contains_key(&0);
5618		assert!(expired, "a small frame write runs expiry");
5619	}
5620
5621	#[tokio::test]
5622	async fn fresh_expiry_scan_does_not_wake_track_consumers() {
5623		use std::sync::atomic::{AtomicBool, Ordering};
5624
5625		let producer = track_producer_expiring("test", cache::DEFAULT_EXPIRY);
5626		producer.append_group().unwrap().finish().unwrap();
5627		let mut live = producer.append_group().unwrap();
5628		let mut consumer = producer.subscribe(None);
5629		assert_eq!(consumer.assert_group().sequence, 0);
5630		assert_eq!(consumer.assert_group().sequence, 1);
5631
5632		let woken = Arc::new(AtomicBool::new(false));
5633		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
5634		assert!(consumer.poll_recv_group(&waiter).is_pending());
5635
5636		live.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5637		assert!(
5638			!woken.load(Ordering::SeqCst),
5639			"a no-op expiry scan must not wake track consumers"
5640		);
5641	}
5642
5643	#[tokio::test]
5644	async fn streaming_frame_write_expires_idle_siblings() {
5645		let producer = track_producer_expiring("test", Duration::from_secs(1));
5646		producer.append_group().unwrap().finish().unwrap(); // seq 0
5647		let mut live = producer.append_group().unwrap(); // seq 1
5648		let mut frame = live
5649			.create_frame(frame::Info {
5650				size: 1,
5651				timestamp: Timestamp::ZERO,
5652			})
5653			.unwrap();
5654
5655		crate::model::clock::advance(Duration::from_secs(2));
5656		frame.write(b"x".as_slice()).unwrap();
5657
5658		let expired = !producer.state.read().lookup.contains_key(&0);
5659		assert!(expired, "a streamed chunk runs expiry");
5660	}
5661
5662	#[tokio::test]
5663	async fn appended_datagram_expires_idle_groups() {
5664		let mut producer = track_producer_expiring("test", Duration::from_secs(1));
5665		producer.append_group().unwrap().finish().unwrap(); // seq 0
5666		producer.append_group().unwrap().finish().unwrap(); // seq 1
5667
5668		crate::model::clock::advance(Duration::from_secs(2));
5669		producer.append_datagram(Timestamp::ZERO, b"x".as_slice()).unwrap();
5670
5671		let expired = !producer.state.read().lookup.contains_key(&0);
5672		assert!(expired, "an appended datagram runs expiry");
5673	}
5674
5675	#[tokio::test]
5676	async fn forwarded_datagram_expires_idle_groups() {
5677		let mut producer = track_producer_expiring("test", Duration::from_secs(1));
5678		producer.append_group().unwrap().finish().unwrap(); // seq 0
5679		producer.append_group().unwrap().finish().unwrap(); // seq 1
5680
5681		crate::model::clock::advance(Duration::from_secs(2));
5682		producer
5683			.insert_datagram(2, Timestamp::ZERO, bytes::Bytes::from_static(b"x"))
5684			.unwrap();
5685
5686		let expired = !producer.state.read().lookup.contains_key(&0);
5687		assert!(expired, "a forwarded datagram runs expiry");
5688	}
5689
5690	/// A track's `max_age` is a media-timestamp budget: it does not drive wall-clock
5691	/// eviction, so a stall (no accesses, no timestamp progress) shorter than the
5692	/// pool's LRU window can't age content out no matter how small the window is.
5693	#[tokio::test]
5694	async fn max_age_does_not_drive_wall_eviction() {
5695		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(1)));
5696		producer.append_group().unwrap(); // seq 0
5697
5698		// Far past max_age in wall time, but inside the pool's LRU window.
5699		crate::model::clock::advance(Duration::from_secs(10));
5700		producer.append_group().unwrap(); // seq 1
5701
5702		let state = producer.state.read();
5703		assert_eq!(live_groups(&state), 2, "max_age is media time, not a wall clock");
5704	}
5705
5706	/// Disabling the pool's expiry keeps idle groups until byte pressure reclaims them.
5707	#[tokio::test]
5708	async fn disabled_pool_expiry_never_reclaims() {
5709		let producer = track_producer_expiring("test", None);
5710		producer.append_group().unwrap(); // seq 0
5711
5712		crate::model::clock::advance(Duration::from_secs(3600));
5713		producer.append_group().unwrap(); // seq 1
5714
5715		let state = producer.state.read();
5716		assert_eq!(live_groups(&state), 2);
5717	}
5718
5719	#[test]
5720	fn max_age_clamped_to_cache() {
5721		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2)));
5722
5723		// A max age budget beyond the cache is capped in the aggregate; a group can't be
5724		// waited for longer than the publisher keeps it. The subscriber's own preference
5725		// is stored verbatim, so what it asked for stays readable.
5726		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
5727		assert_eq!(subscriber.subscription().max_age, Duration::from_secs(10));
5728		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5729
5730		// A budget within the cache is left alone, and ZERO (skip immediately) stays ZERO.
5731		subscriber
5732			.update(Subscription::default().with_max_age(Duration::from_millis(500)))
5733			.unwrap();
5734		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_millis(500));
5735
5736		subscriber
5737			.update(Subscription::default().with_max_age(Duration::ZERO))
5738			.unwrap();
5739		assert_eq!(producer.subscription().unwrap().max_age, Duration::ZERO);
5740	}
5741
5742	/// Mint a track under an origin whose retention ceiling is `cap`, so the
5743	/// track's own window is clamped down to it on bind.
5744	fn track_producer_capped(name: impl Into<Arc<str>>, info: Info, cap: Duration) -> Producer {
5745		Producer::new(
5746			Arc::new(broadcast::Info {
5747				cache_duration: cap,
5748				..Default::default()
5749			}),
5750			name,
5751			info,
5752		)
5753	}
5754
5755	#[test]
5756	fn origin_cache_duration_clamps_max_age() {
5757		// A publisher asking to keep groups for a minute is capped to the origin's 1s
5758		// ceiling; a publisher already below the ceiling is left alone (it's a min).
5759		let capped = track_producer_capped(
5760			"test",
5761			Info::default().with_max_age(Duration::from_secs(60)),
5762			Duration::from_secs(1),
5763		);
5764		assert_eq!(capped.state.read().max_age_bound(), Some(Duration::from_secs(1)));
5765		assert_eq!(capped.subscribe(None).info().max_age, Duration::from_secs(1));
5766
5767		let under = track_producer_capped(
5768			"test",
5769			Info::default().with_max_age(Duration::from_millis(500)),
5770			Duration::from_secs(1),
5771		);
5772		assert_eq!(under.state.read().max_age_bound(), Some(Duration::from_millis(500)));
5773	}
5774
5775	/// The origin ceiling clamps the media-timestamp budget only; wall-clock
5776	/// reclamation belongs to the pool's LRU window, not the ceiling.
5777	#[tokio::test]
5778	async fn origin_cache_duration_does_not_wall_evict() {
5779		let producer = track_producer_capped(
5780			"test",
5781			Info::default().with_max_age(Duration::from_secs(60)),
5782			Duration::from_secs(1),
5783		);
5784		producer.append_group().unwrap(); // seq 0
5785
5786		// Far past the ceiling in wall time, but inside the pool's LRU window.
5787		crate::model::clock::advance(Duration::from_secs(2));
5788		producer.append_group().unwrap(); // seq 1
5789
5790		let state = producer.state.read();
5791		assert_eq!(live_groups(&state), 2);
5792	}
5793
5794	#[test]
5795	fn max_age_clamped_via_every_update_path() {
5796		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2)));
5797		let over = Subscription::default().with_max_age(Duration::from_secs(10));
5798
5799		// The clamp lives in the aggregation, so it applies no matter which entry point
5800		// wrote the raw preference. Previously only `Subscriber::update` clamped.
5801		let mut subscriber = producer.subscribe(over.clone());
5802		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5803
5804		subscriber.control().update(over.clone()).unwrap();
5805		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5806
5807		subscriber.update(over).unwrap();
5808		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5809	}
5810
5811	#[test]
5812	fn max_age_aggregate_clamps_across_subscribers() {
5813		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2)));
5814
5815		// The aggregate takes the max, then clamps once. Equivalent to clamping each
5816		// subscriber first, since `min` distributes over `max`.
5817		let _a = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
5818		let _b = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
5819
5820		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5821	}
5822
5823	#[test]
5824	fn churned_subscribers_do_not_accumulate() {
5825		let producer = track_producer("test", None);
5826		let consumer = producer.consume();
5827		let _steady = producer.subscribe(None);
5828
5829		// Nobody polls the aggregate here, so registration alone has to bound the list.
5830		for _ in 0..100 {
5831			drop(producer.subscribe(None));
5832			drop(consumer.subscribe(None));
5833		}
5834
5835		// Registration sweeps only when the list is about to grow, so it holds a small
5836		// multiple of the peak live count (2) rather than all 200 departures.
5837		let subs = producer.state.read().subscriptions.clone();
5838		let len = subs.read().len();
5839		assert!(len <= 8, "departed subscribers accumulated: {len}");
5840	}
5841
5842	#[test]
5843	fn aggregate_poll_prunes_after_a_peak() {
5844		let mut producer = track_producer("test", None);
5845		let waiter = kio::Waiter::noop();
5846		let _steady = producer.subscribe(None);
5847		assert!(producer.poll_subscription_changed(&waiter).is_ready());
5848
5849		// A departed burst leaves room for many entries, and identical preferences never
5850		// change the aggregate, so each wake has to prune what it walks.
5851		drop((0..1000).map(|_| producer.subscribe(None)).collect::<Vec<_>>());
5852		for _ in 0..100 {
5853			drop(producer.subscribe(None));
5854			assert!(producer.poll_subscription_changed(&waiter).is_pending());
5855
5856			let subs = producer.state.read().subscriptions.clone();
5857			assert_eq!(subs.read().len(), 1, "the poll walked past departed subscribers");
5858		}
5859	}
5860
5861	/// Append a finished group presenting at `millis`, so the track carries a media
5862	/// timeline for the drift budget to measure against.
5863	fn append_at(producer: &mut Producer, millis: u64) -> u64 {
5864		let mut group = producer.append_group().unwrap();
5865		group
5866			.write_frame(Timestamp::from_millis(millis).unwrap(), bytes::Bytes::from_static(b"x"))
5867			.unwrap();
5868		group.finish().unwrap();
5869		group.sequence
5870	}
5871
5872	/// Every group the subscriber can read right now, in delivery order.
5873	fn drain(subscriber: &mut Subscriber) -> Vec<u64> {
5874		let mut sequences = Vec::new();
5875		while let Some(Ok(Some(group))) = subscriber.recv_group().now_or_never() {
5876			sequences.push(group.sequence);
5877		}
5878		sequences
5879	}
5880
5881	#[test]
5882	fn real_time_skips_a_backlog_to_the_live_edge() {
5883		let mut producer = track_producer("test", None);
5884		for second in 0..5 {
5885			append_at(&mut producer, second * 1000);
5886		}
5887
5888		// The default budget is REAL_TIME: a subscriber joining a track that already
5889		// holds five seconds of history takes the live edge, not the history. This is
5890		// the ceiling a takeover backlog runs into.
5891		let mut subscriber = producer.subscribe(None);
5892		assert_eq!(drain(&mut subscriber), vec![4]);
5893
5894		// And it stays caught up: the next group is live when it lands.
5895		append_at(&mut producer, 5000);
5896		assert_eq!(drain(&mut subscriber), vec![5]);
5897	}
5898
5899	#[test]
5900	fn real_time_skips_a_backlog_after_catching_up() {
5901		let mut producer = track_producer("test", None);
5902		append_at(&mut producer, 0);
5903		let mut subscriber = producer.subscribe(None);
5904		assert_eq!(drain(&mut subscriber), vec![0]);
5905
5906		// Catch-up is a subscriber state, not a startup-only choice. A reader can pause
5907		// between groups and still needs to shed the backlog that accumulated meanwhile.
5908		for second in 1..6 {
5909			append_at(&mut producer, second * 1000);
5910		}
5911		assert_eq!(drain(&mut subscriber), vec![5]);
5912	}
5913
5914	#[test]
5915	fn a_newer_edge_changes_an_active_catch_up() {
5916		let mut producer = track_producer("test", None);
5917		for second in 0..5 {
5918			append_at(&mut producer, second * 1000);
5919		}
5920		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(1)));
5921		assert_eq!(
5922			subscriber
5923				.recv_group()
5924				.now_or_never()
5925				.unwrap()
5926				.unwrap()
5927				.unwrap()
5928				.sequence,
5929			3
5930		);
5931
5932		// Group 4 was the edge at the first delivery. Advancing twice puts it outside
5933		// the budget before the subscriber asks for another group.
5934		append_at(&mut producer, 5000);
5935		append_at(&mut producer, 10000);
5936		assert_eq!(drain(&mut subscriber), vec![5, 6]);
5937	}
5938
5939	#[test]
5940	fn a_growing_edge_changes_an_active_catch_up() {
5941		let mut producer = track_producer("test", None);
5942		append_at(&mut producer, 0);
5943		append_at(&mut producer, 1000);
5944		let mut edge = producer.append_group().unwrap();
5945		edge.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"a"))
5946			.unwrap();
5947
5948		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(2)));
5949		assert_eq!(
5950			subscriber
5951				.recv_group()
5952				.now_or_never()
5953				.unwrap()
5954				.unwrap()
5955				.unwrap()
5956				.sequence,
5957			0
5958		);
5959
5960		// The edge is still the same group, but its newest presentation moved far
5961		// enough that group 1 has fallen out of range.
5962		edge.write_frame(Timestamp::from_millis(5000).unwrap(), bytes::Bytes::from_static(b"b"))
5963			.unwrap();
5964		assert_eq!(drain(&mut subscriber), vec![2]);
5965	}
5966
5967	#[test]
5968	fn a_budget_admits_groups_within_it() {
5969		let mut producer = track_producer("test", None);
5970		for second in 0..5 {
5971			append_at(&mut producer, second * 1000);
5972		}
5973
5974		// Two seconds of tolerance keeps the groups presenting within 2s of the live
5975		// edge (2s, 3s, 4s) and drops the two below it. Group 1 reaches exactly 2s behind
5976		// the edge, and the reach bound is exclusive, so every frame it could hold is
5977		// already past the budget.
5978		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(2)));
5979		assert_eq!(drain(&mut subscriber), vec![2, 3, 4]);
5980	}
5981
5982	#[test]
5983	fn a_budget_reaches_back_over_the_cache() {
5984		let mut producer = track_producer("test", None);
5985		for second in 0..5 {
5986			append_at(&mut producer, second * 1000);
5987		}
5988
5989		// The default zero budget calls every non-latest group stale, so a subscription
5990		// that says nothing joins at the live edge.
5991		let mut live = producer.subscribe(None);
5992		assert_eq!(drain(&mut live), vec![4]);
5993
5994		// Two seconds of tolerance covers the groups presenting within 2s of the live
5995		// edge, so the same join is handed the head of what it can still use. One bound
5996		// decides both what is sent and what is expired, so a subscriber is never sent
5997		// history it would discard on arrival.
5998		let budget = Subscription::default().with_max_age(Duration::from_secs(2));
5999		let mut subscriber = producer.subscribe(budget);
6000		assert_eq!(drain(&mut subscriber), vec![2, 3, 4]);
6001	}
6002
6003	#[test]
6004	fn a_named_start_is_a_floor_not_a_request() {
6005		let mut producer = track_producer("test", None);
6006		for second in 0..5 {
6007			append_at(&mut producer, second * 1000);
6008		}
6009
6010		// The budget is the only thing that asks for data; a named start only bounds how
6011		// far back it may reach. Naming group 1 at real time still delivers the live edge
6012		// alone, since the zero budget calls everything older stale.
6013		let named = Subscription::default().with_start(Position::group(1));
6014		let mut subscriber = producer.subscribe(named);
6015		assert_eq!(drain(&mut subscriber), vec![4]);
6016
6017		// A budget reaching further back than the floor is cut off at it.
6018		let floored = Subscription::default()
6019			.with_start(Position::group(3))
6020			.with_max_age(Duration::from_secs(10));
6021		let mut subscriber = producer.subscribe(floored);
6022		assert_eq!(drain(&mut subscriber), vec![3, 4]);
6023
6024		// A floor below what the budget admits changes nothing.
6025		let slack = Subscription::default()
6026			.with_start(Position::group(1))
6027			.with_max_age(Duration::from_secs(2));
6028		let mut subscriber = producer.subscribe(slack);
6029		assert_eq!(drain(&mut subscriber), vec![2, 3, 4]);
6030	}
6031
6032	#[test]
6033	fn a_floor_above_the_live_edge_waits_there() {
6034		let mut producer = track_producer("test", None);
6035		for second in 0..3 {
6036			append_at(&mut producer, second * 1000);
6037		}
6038
6039		// A resumed subscription names where it left off, which may not exist yet. The
6040		// cursor sits at the floor rather than sliding back to what is cached.
6041		let resumed = Subscription::default()
6042			.with_start(Position::group(7))
6043			.with_max_age(Duration::from_secs(10));
6044		let mut subscriber = producer.subscribe(resumed);
6045		assert_eq!(drain(&mut subscriber), Vec::<u64>::new());
6046		append_at(&mut producer, 3000); // sequence 3: still below the floor
6047		assert_eq!(drain(&mut subscriber), Vec::<u64>::new());
6048		for second in 4..8 {
6049			append_at(&mut producer, second * 1000);
6050		}
6051		assert_eq!(drain(&mut subscriber), vec![7]);
6052	}
6053
6054	#[test]
6055	fn a_late_lower_group_within_the_budget_is_delivered() {
6056		let producer = track_producer("test", None);
6057		for (sequence, millis) in [(5, 0), (6, 1000), (7, 2000)] {
6058			let mut group = producer.create_group(group::Info { sequence }).unwrap();
6059			group
6060				.write_frame(Timestamp::from_millis(millis).unwrap(), bytes::Bytes::from_static(b"x"))
6061				.unwrap();
6062			group.finish().unwrap();
6063		}
6064
6065		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(5)));
6066		assert_eq!(drain(&mut subscriber), vec![5, 6, 7]);
6067
6068		// Arriving below everything already delivered is not what makes content stale:
6069		// the budget is the only gate, and this straggler's timestamp is within it. A
6070		// consumer that needs sequence order reorders (or drops) it itself.
6071		let mut late = producer.create_group(group::Info { sequence: 4 }).unwrap();
6072		late.write_frame(Timestamp::from_millis(500).unwrap(), bytes::Bytes::from_static(b"late"))
6073			.unwrap();
6074		late.finish().unwrap();
6075		assert_eq!(drain(&mut subscriber), vec![4]);
6076	}
6077
6078	#[test]
6079	fn drift_is_measured_in_presentation_time_not_arrival_time() {
6080		let mut producer = track_producer("test", None);
6081		// A relay ingesting a backlog creates every group at once, so arrival time says
6082		// they are all equally fresh. Their timestamps say otherwise, which is the whole
6083		// point of measuring in presentation time.
6084		for second in 0..4 {
6085			append_at(&mut producer, second * 1000);
6086		}
6087
6088		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(1500)));
6089		assert_eq!(drain(&mut subscriber), vec![1, 2, 3]);
6090	}
6091
6092	#[tokio::test]
6093	async fn a_stamped_successor_expires_an_unstamped_group() {
6094		let mut producer = track_producer("test", None);
6095		let mut subscriber = producer.subscribe(None);
6096		producer.append_group().unwrap(); // seq 0 stalls before its first frame
6097
6098		append_at(&mut producer, 1000); // seq 1 proves the live feed moved on
6099
6100		// The candidate needs no timestamp of its own: its reach is where its stamped
6101		// successor begins, which the zero budget already puts out of range.
6102		assert_eq!(drain(&mut subscriber), vec![1]);
6103	}
6104
6105	#[tokio::test]
6106	async fn a_handed_out_group_expires_while_its_first_frame_is_stalled() {
6107		let mut producer = track_producer("test", None);
6108		let mut subscriber = producer.subscribe(None);
6109		producer.append_group().unwrap();
6110
6111		let mut stalled = subscriber.recv_group().await.unwrap().expect("stalled group");
6112		let pending = tokio::spawn(async move { stalled.read_frame().await });
6113		tokio::task::yield_now().await;
6114		assert!(
6115			!pending.is_finished(),
6116			"the empty live edge still waits for its first frame"
6117		);
6118
6119		crate::model::clock::advance(Duration::from_secs(1));
6120		append_at(&mut producer, 1000);
6121
6122		// It ends rather than fails: the reader took every frame the group ever had
6123		// (none), so nothing was truncated. What it was waiting for was the producer,
6124		// and a group abandoned where its reader stands looks exactly like one that
6125		// ended there.
6126		let result = pending.await.unwrap();
6127		assert!(matches!(result, Ok(None)), "the held group ends: {result:?}");
6128	}
6129
6130	/// A rewound successor can keep a drained group within budget until its abort
6131	/// moves the reach to the next cached group, without changing the track itself.
6132	#[tokio::test]
6133	async fn aborted_stamped_successor_wakes_a_parked_read() {
6134		tokio::time::pause();
6135		let mut producer = track_producer("test", None);
6136		let mut head = producer.append_group().unwrap();
6137		head.write_frame(Timestamp::ZERO, b"head".as_slice()).unwrap();
6138		let mut successor = producer.append_group().unwrap();
6139		successor
6140			.write_frame(Timestamp::from_millis(30_000).unwrap(), b"next".as_slice())
6141			.unwrap();
6142		append_at(&mut producer, 1000);
6143		append_at(&mut producer, 20_000);
6144		let mut sub = producer.subscribe(None);
6145		let mut reading = sub.recv_group().await.unwrap().unwrap();
6146		assert_eq!(reading.sequence, 0, "the rewound successor extends the reach");
6147		assert!(reading.read_frame().await.unwrap().is_some());
6148
6149		let woken = Arc::new(std::sync::atomic::AtomicBool::new(false));
6150		let waker = futures::task::waker(Arc::new(FlagWake(woken.clone())));
6151		let mut cx = std::task::Context::from_waker(&waker);
6152		let mut next = std::pin::pin!(reading.read_frame());
6153		assert!(next.as_mut().poll(&mut cx).is_pending());
6154		successor.abort(Error::Cancel).unwrap();
6155		assert!(
6156			woken.load(Ordering::SeqCst),
6157			"the stamped successor's abort lost its wakeup"
6158		);
6159		let result = next.as_mut().poll(&mut cx);
6160		assert!(matches!(result, Poll::Ready(Ok(None))), "the head is stale: {result:?}");
6161	}
6162
6163	/// A first timestamp on a *newer* group can convict a held one, so the held reader has
6164	/// to be woken by it. The conviction needs a group beyond the held one's successor:
6165	/// a group is bounded by where its successor begins, so the successor itself never
6166	/// proves it stale.
6167	#[tokio::test]
6168	async fn a_handed_out_group_wakes_when_a_newer_group_gets_its_first_timestamp() {
6169		let mut producer = track_producer("test", None);
6170		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6171		let mut old = producer.append_group().unwrap();
6172		old.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"old"))
6173			.unwrap();
6174
6175		// Group 1 bounds group 0's reach at 1s.
6176		append_at(&mut producer, 1000);
6177
6178		let mut held = subscriber.recv_group().await.unwrap().expect("old group");
6179		assert!(held.read_frame().await.unwrap().is_some());
6180
6181		let mut live = producer.append_group().unwrap();
6182		let pending = tokio::spawn(async move { held.read_frame().await });
6183		tokio::task::yield_now().await;
6184		assert!(!pending.is_finished(), "the newer group has no timestamp yet");
6185
6186		// 2s edge against group 0's 1s reach: a full second past the budget.
6187		live.write_frame(
6188			Timestamp::from_millis(2000).unwrap(),
6189			bytes::Bytes::from_static(b"live"),
6190		)
6191		.unwrap();
6192		tokio::task::yield_now().await;
6193
6194		assert!(pending.is_finished(), "the new presentation edge wakes the held reader");
6195		// Drained, so the budget ends the group rather than truncating it.
6196		let result = pending.await.unwrap();
6197		assert!(matches!(result, Ok(None)), "the held group ends: {result:?}");
6198	}
6199
6200	/// A parked read registers only where a first timestamp can change its verdict: its
6201	/// immediate successor and the groups past the edge. Walking every group between them
6202	/// costs each parked publisher stream the whole backlog, enough for a 2s audio backlog
6203	/// at 400 groups/s to pin a publisher's runtime and starve its connection.
6204	#[test]
6205	fn a_parked_read_ignores_first_frames_between_its_successor_and_the_edge() {
6206		let mut producer = track_producer("test", None);
6207		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
6208		let mut head = producer.append_group().unwrap();
6209		head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
6210			.unwrap();
6211		append_at(&mut producer, 1000); // the successor bounds the head's reach
6212		let mut between = producer.append_group().unwrap();
6213		append_at(&mut producer, 3000); // the edge
6214
6215		let mut held = subscriber
6216			.recv_group()
6217			.now_or_never()
6218			.unwrap()
6219			.unwrap()
6220			.expect("head group");
6221		assert_eq!(held.sequence, 0);
6222		assert!(held.read_frame().now_or_never().unwrap().unwrap().is_some());
6223
6224		let woken = Arc::new(std::sync::atomic::AtomicBool::new(false));
6225		let waker = futures::task::waker(Arc::new(FlagWake(woken.clone())));
6226		let mut cx = std::task::Context::from_waker(&waker);
6227		let mut next = std::pin::pin!(held.read_frame());
6228		assert!(next.as_mut().poll(&mut cx).is_pending());
6229
6230		between
6231			.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"x"))
6232			.unwrap();
6233		assert!(
6234			!woken.load(Ordering::SeqCst),
6235			"a first frame below the edge moves neither the reach nor the edge"
6236		);
6237
6238		// Control: a first frame past the edge is still observed.
6239		let mut beyond = producer.append_group().unwrap();
6240		assert!(next.as_mut().poll(&mut cx).is_pending());
6241		woken.store(false, Ordering::SeqCst);
6242		beyond
6243			.write_frame(Timestamp::from_millis(4000).unwrap(), bytes::Bytes::from_static(b"x"))
6244			.unwrap();
6245		assert!(woken.load(Ordering::SeqCst), "a new edge wakes the parked read");
6246	}
6247
6248	/// An aborted successor hands the reach to the next group, which sits below the edge
6249	/// and so is watched only because the read re-selects its successor.
6250	#[test]
6251	fn a_parked_read_watches_the_replacement_for_an_aborted_successor() {
6252		let mut producer = track_producer("test", None);
6253		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6254		let mut head = producer.append_group().unwrap();
6255		head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
6256			.unwrap();
6257		let successor = producer.append_group().unwrap();
6258		let mut replacement = producer.append_group().unwrap();
6259		append_at(&mut producer, 20_000); // the edge
6260
6261		let mut held = subscriber
6262			.recv_group()
6263			.now_or_never()
6264			.unwrap()
6265			.unwrap()
6266			.expect("head group");
6267		assert_eq!(held.sequence, 0, "an unstamped successor leaves the reach unbounded");
6268		assert!(held.read_frame().now_or_never().unwrap().unwrap().is_some());
6269
6270		let woken = Arc::new(std::sync::atomic::AtomicBool::new(false));
6271		let waker = futures::task::waker(Arc::new(FlagWake(woken.clone())));
6272		let mut cx = std::task::Context::from_waker(&waker);
6273		let mut next = std::pin::pin!(held.read_frame());
6274		assert!(next.as_mut().poll(&mut cx).is_pending());
6275
6276		successor.abort(Error::Cancel).unwrap();
6277		assert!(
6278			woken.load(Ordering::SeqCst),
6279			"the successor's abort wakes the parked read"
6280		);
6281		woken.store(false, Ordering::SeqCst);
6282		assert!(
6283			next.as_mut().poll(&mut cx).is_pending(),
6284			"the unstamped replacement leaves the reach unbounded"
6285		);
6286
6287		replacement
6288			.write_frame(Timestamp::from_millis(1000).unwrap(), bytes::Bytes::from_static(b"x"))
6289			.unwrap();
6290		assert!(
6291			woken.load(Ordering::SeqCst),
6292			"the replacement's first frame bounds the reach"
6293		);
6294		let result = next.as_mut().poll(&mut cx);
6295		assert!(matches!(result, Poll::Ready(Ok(None))), "the head is stale: {result:?}");
6296	}
6297
6298	/// An aborted edge hands the edge back to a lower group, which sits below the old edge
6299	/// and so is watched only because the read observes the edge's abort. Neither the
6300	/// abort nor that group's first frame touches the track.
6301	#[test]
6302	fn a_parked_read_watches_its_edge_abort() {
6303		let mut producer = track_producer("test", None);
6304		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(5)));
6305		let mut head = producer.append_group().unwrap();
6306		head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
6307			.unwrap();
6308		append_at(&mut producer, 1000); // the successor bounds the head's reach
6309		let mut between = producer.append_group().unwrap();
6310		let mut edge = producer.append_group().unwrap();
6311		edge.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"x"))
6312			.unwrap();
6313		edge.finish().unwrap();
6314
6315		let mut held = subscriber
6316			.recv_group()
6317			.now_or_never()
6318			.unwrap()
6319			.unwrap()
6320			.expect("head group");
6321		assert_eq!(held.sequence, 0);
6322		assert!(held.read_frame().now_or_never().unwrap().unwrap().is_some());
6323
6324		let woken = Arc::new(std::sync::atomic::AtomicBool::new(false));
6325		let waker = futures::task::waker(Arc::new(FlagWake(woken.clone())));
6326		let mut cx = std::task::Context::from_waker(&waker);
6327		let mut next = std::pin::pin!(held.read_frame());
6328		assert!(next.as_mut().poll(&mut cx).is_pending());
6329
6330		edge.abort(Error::Cancel).unwrap();
6331		between
6332			.write_frame(Timestamp::from_millis(10_000).unwrap(), bytes::Bytes::from_static(b"x"))
6333			.unwrap();
6334		assert!(
6335			woken.load(Ordering::SeqCst),
6336			"the edge's abort, then a new edge below it, lost the wakeup"
6337		);
6338		let result = next.as_mut().poll(&mut cx);
6339		assert!(matches!(result, Poll::Ready(Ok(None))), "the head is stale: {result:?}");
6340	}
6341
6342	/// The ordinary live case, at the default real-time budget: 2s GOPs produced one at
6343	/// a time and read as they arrive. The budget must take the live edge without
6344	/// shortening the group the reader is already on, so every frame of every group is
6345	/// delivered and each group *ends* rather than fails at its boundary.
6346	///
6347	/// Two things would break this. Measuring drift from a group's first frame rather
6348	/// than its reader's position convicts every group the moment its successor opens,
6349	/// since a live reader is always a little behind the edge. And the reader is parked
6350	/// at its group's end when that happens, because the FIN and the next group's first
6351	/// frame are separate events and the wire does not order them.
6352	#[tokio::test]
6353	async fn real_time_reads_a_live_stream_without_truncating_it() {
6354		let producer = track_producer("test", None);
6355		let mut subscriber = producer.subscribe(None);
6356
6357		let gop = |n: u64| {
6358			[
6359				Timestamp::from_millis(n * 2000).unwrap(),
6360				Timestamp::from_millis(n * 2000 + 1900).unwrap(),
6361			]
6362		};
6363		let write = |group: &mut group::Producer, timestamp| {
6364			group.write_frame(timestamp, bytes::Bytes::from_static(b"x")).unwrap();
6365		};
6366
6367		let mut open = producer.append_group().unwrap();
6368		write(&mut open, gop(0)[0]);
6369		write(&mut open, gop(0)[1]);
6370		let mut reading = subscriber.recv_group().await.unwrap().expect("the live group");
6371
6372		let mut read = Vec::new();
6373		for n in 1..5u64 {
6374			let sequence = reading.sequence;
6375			let mut frames = 0;
6376			while let Some(res) = reading.read_frame().now_or_never() {
6377				match res.expect("no truncation while draining") {
6378					Some(_) => frames += 1,
6379					None => panic!("group {sequence} ended early"),
6380				}
6381			}
6382			read.push((sequence, frames));
6383
6384			let next = {
6385				// Parked at the end of the current group: every frame is read and no FIN
6386				// has landed.
6387				let mut end = std::pin::pin!(reading.read_frame());
6388				assert!(futures::poll!(end.as_mut()).is_pending(), "parked on the FIN");
6389
6390				// The next keyframe opens its group. The verdict is taken here, in the
6391				// window before the previous group's FIN arrives.
6392				let mut opened = producer.append_group().unwrap();
6393				write(&mut opened, gop(n)[0]);
6394				let verdict = futures::poll!(end.as_mut());
6395
6396				open.finish().unwrap();
6397				let res = match verdict {
6398					Poll::Ready(res) => res,
6399					Poll::Pending => end.await,
6400				};
6401				assert!(
6402					matches!(res, Ok(None)),
6403					"group {sequence} ends at the boundary rather than failing: {res:?}"
6404				);
6405				opened
6406			};
6407			let mut next = next;
6408			write(&mut next, gop(n)[1]);
6409
6410			reading = subscriber.recv_group().await.unwrap().expect("the next live group");
6411			open = next;
6412		}
6413
6414		assert_eq!(read, vec![(0, 2), (1, 2), (2, 2), (3, 2)], "every frame of every group");
6415	}
6416
6417	/// A budget is spent from where the reader stands, not from where its group opened.
6418	///
6419	/// A 2s GOP with a 1s budget: the reader has drained to 1900ms when the next group
6420	/// opens at 2000ms, so it is 100ms behind the live edge and well inside what it
6421	/// asked for. A straggling frame of the old group arriving after the new one opened
6422	/// (which is ordinary, the two are separate streams) must still reach it.
6423	///
6424	/// Measuring from the group's first frame instead makes the drift 2000ms, so a
6425	/// budget shorter than one GOP would drop the tail of every GOP.
6426	#[tokio::test]
6427	async fn a_budget_is_measured_from_the_readers_position() {
6428		let producer = track_producer("test", None);
6429		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(1)));
6430
6431		let mut open = producer.append_group().unwrap();
6432		open.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"key"))
6433			.unwrap();
6434		open.write_frame(
6435			Timestamp::from_millis(1900).unwrap(),
6436			bytes::Bytes::from_static(b"tail"),
6437		)
6438		.unwrap();
6439
6440		let mut reading = subscriber.recv_group().await.unwrap().expect("the live group");
6441		assert!(reading.read_frame().await.unwrap().is_some());
6442		assert!(reading.read_frame().await.unwrap().is_some());
6443
6444		let mut end = std::pin::pin!(reading.read_frame());
6445		assert!(futures::poll!(end.as_mut()).is_pending(), "parked at 1900ms");
6446
6447		// The next GOP opens. The reader is 100ms behind it, inside its 1s budget.
6448		let mut next = producer.append_group().unwrap();
6449		next.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"key"))
6450			.unwrap();
6451		assert!(
6452			futures::poll!(end.as_mut()).is_pending(),
6453			"a reader inside its budget is not expired by the next group opening"
6454		);
6455
6456		// A straggler from the old group, still within the budget.
6457		open.write_frame(
6458			Timestamp::from_millis(1950).unwrap(),
6459			bytes::Bytes::from_static(b"late"),
6460		)
6461		.unwrap();
6462		let late = end.await.expect("the straggler is not truncated");
6463		assert_eq!(
6464			late.map(|frame| frame.timestamp),
6465			Some(Timestamp::from_millis(1950).unwrap())
6466		);
6467	}
6468
6469	/// A group the budget ends rather than fails stays ended. `expired` alone would
6470	/// turn the clean answer into [`Error::Old`] on the next poll, and a caller is
6471	/// allowed to probe again past the end of a group.
6472	#[tokio::test]
6473	async fn an_ended_group_stays_ended_when_probed_again() {
6474		let mut producer = track_producer("test", None);
6475		let mut subscriber = producer.subscribe(None);
6476		let mut open = producer.append_group().unwrap();
6477		open.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"a"))
6478			.unwrap();
6479
6480		let mut group = subscriber.recv_group().await.unwrap().expect("group");
6481		assert!(group.read_frame().await.unwrap().is_some());
6482
6483		let probes = tokio::spawn(async move {
6484			let first = group.read_frame().await;
6485			let second = group.read_frame().await;
6486			let finished = group.finished().await;
6487			(first, second, finished)
6488		});
6489		tokio::task::yield_now().await;
6490
6491		append_at(&mut producer, 1000);
6492
6493		let (first, second, finished) = probes.await.unwrap();
6494		assert!(matches!(first, Ok(None)), "the group ends: {first:?}");
6495		assert!(matches!(second, Ok(None)), "and stays ended: {second:?}");
6496		assert!(matches!(finished, Ok(1)), "reporting what it delivered: {finished:?}");
6497	}
6498
6499	#[tokio::test]
6500	async fn a_drained_group_finishes_cleanly_after_the_live_edge_advances() {
6501		let mut producer = track_producer("test", None);
6502		let mut subscriber = producer.subscribe(None);
6503		append_at(&mut producer, 0);
6504
6505		let mut group = subscriber.recv_group().await.unwrap().expect("first group");
6506		assert!(group.read_frame().await.unwrap().is_some());
6507
6508		append_at(&mut producer, 1000);
6509
6510		assert!(group.read_frame().await.unwrap().is_none());
6511		assert!(!group.latency_expired());
6512	}
6513
6514	#[tokio::test]
6515	async fn a_handed_out_partial_frame_expires_while_its_payload_is_stalled() {
6516		let mut producer = track_producer("test", None);
6517		let mut subscriber = producer.subscribe(None);
6518		let mut source = producer.append_group().unwrap();
6519		let mut writing = source
6520			.create_frame(frame::Info {
6521				size: 6,
6522				timestamp: Timestamp::ZERO,
6523			})
6524			.unwrap();
6525		writing.write(bytes::Bytes::from_static(b"old")).unwrap();
6526
6527		let mut group = subscriber.recv_group().await.unwrap().expect("partial group");
6528		let mut frame = group.next_frame().await.unwrap().expect("partial frame");
6529		assert_eq!(
6530			frame.read_chunk().await.unwrap(),
6531			Some(bytes::Bytes::from_static(b"old"))
6532		);
6533		let pending = tokio::spawn(async move { frame.read_chunk().await });
6534		tokio::task::yield_now().await;
6535		assert!(!pending.is_finished(), "the partial payload is still stalled");
6536
6537		crate::model::clock::advance(Duration::from_secs(1));
6538		append_at(&mut producer, 1000);
6539
6540		let result = pending.await.unwrap();
6541		assert!(
6542			matches!(result, Err(Error::Old)),
6543			"the in-flight frame expires: {result:?}"
6544		);
6545		writing.abort(Error::Cancel).unwrap();
6546	}
6547
6548	#[test]
6549	fn max_age_bounds_the_budget() {
6550		// The publisher only keeps a group around for 500ms, so a subscriber asking to
6551		// wait ten seconds for one still gives up at 500ms: the same clamp the aggregate
6552		// applies, on the subscriber's own side of it.
6553		let mut producer = track_producer("test", Info::default().with_max_age(Duration::from_millis(500)));
6554		append_at(&mut producer, 0);
6555		append_at(&mut producer, 1000);
6556		append_at(&mut producer, 2000);
6557
6558		// Group 0 reaches 1s, a full second behind the 2s edge, so the clamped 500ms
6559		// budget drops it. An unclamped ten seconds would have kept it.
6560		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
6561		assert_eq!(drain(&mut subscriber), vec![1, 2]);
6562	}
6563
6564	#[test]
6565	fn an_explicit_start_gets_no_exemption_from_the_budget() {
6566		let mut producer = track_producer("test", None);
6567		for second in 0..4 {
6568			append_at(&mut producer, second * 1000);
6569		}
6570
6571		// Asking to start at the beginning is a filter, not a request for reliability.
6572		// Backfill needs a budget that covers it; without one the live edge still wins.
6573		let mut subscriber = producer.subscribe(Subscription::default().with_start(Position::group(0)));
6574		subscriber.start_at(0);
6575		assert_eq!(drain(&mut subscriber), vec![3]);
6576
6577		let mut patient = producer.subscribe(
6578			Subscription::default()
6579				.with_start(Position::group(0))
6580				.with_max_age(Duration::from_secs(10)),
6581		);
6582		patient.start_at(0);
6583		assert_eq!(drain(&mut patient), vec![0, 1, 2, 3]);
6584	}
6585
6586	/// A group's age is where its content *ends*, not where it began. A long group whose
6587	/// tail is level with the live edge still owes the reader every frame in it, so
6588	/// judging it by its first timestamp would discard exactly the group being filled.
6589	#[test]
6590	fn a_long_group_is_not_stale_while_its_tail_reaches_the_edge() {
6591		let mut producer = track_producer("test", None);
6592
6593		// Group 0 spans 0..2000ms; group 1 starts at 2000ms, where group 0 ends.
6594		let mut long = producer.append_group().unwrap();
6595		for ms in [0u64, 500, 1000, 1500, 2000] {
6596			long.write_frame(Timestamp::from_millis(ms).unwrap(), bytes::Bytes::from_static(b"x"))
6597				.unwrap();
6598		}
6599		long.finish().unwrap();
6600		append_at(&mut producer, 2000);
6601
6602		// A budget far shorter than the group's own span still keeps it.
6603		let mut sub = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6604		assert_eq!(drain(&mut sub), vec![0, 1]);
6605	}
6606
6607	/// The same group, once its tail has fallen behind, is stale like any other.
6608	/// The same long group, once its *successor* has itself fallen behind. A frame's
6609	/// duration is not on the wire, so group 0's own last timestamp proves nothing about
6610	/// where it ends; only group 1's start bounds it. Group 0 is convicted once that bound
6611	/// is further behind the edge than the budget allows.
6612	#[test]
6613	fn a_long_group_is_stale_once_its_successor_falls_behind() {
6614		let mut producer = track_producer("test", None);
6615
6616		let mut long = producer.append_group().unwrap();
6617		for ms in [0u64, 500, 1000] {
6618			long.write_frame(Timestamp::from_millis(ms).unwrap(), bytes::Bytes::from_static(b"x"))
6619				.unwrap();
6620		}
6621		long.finish().unwrap();
6622		// Group 0 reaches at most 3s (where group 1 starts), a full second behind the 4s
6623		// edge and so past the budget. Group 1 reaches the edge itself and is kept.
6624		append_at(&mut producer, 3000);
6625		append_at(&mut producer, 4000);
6626
6627		let mut sub = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6628		assert_eq!(drain(&mut sub), vec![1, 2]);
6629	}
6630
6631	/// An unstamped immediate successor leaves a group's reach unbounded: a later
6632	/// stamped group proves nothing about where the successor will begin, and
6633	/// shrinking the bound is the unsafe direction.
6634	#[tokio::test]
6635	async fn an_unstamped_immediate_successor_leaves_reach_unbounded() {
6636		let mut producer = track_producer("test", None);
6637		let mut subscriber = producer.subscribe(None);
6638
6639		append_at(&mut producer, 0); // seq 0
6640		producer.append_group().unwrap(); // seq 1 stalls before its first frame
6641		append_at(&mut producer, 10_000); // seq 2
6642
6643		// Group 1's reach is group 2's start, a full edge behind: stale at zero budget.
6644		// Group 0's reach is unknown until group 1 presents its first frame, so it is
6645		// kept rather than convicted on a bound that could shrink the wrong way.
6646		assert_eq!(drain(&mut subscriber), vec![0, 2]);
6647	}
6648
6649	/// Reach is the *immediate* successor's start, never a minimum across later groups.
6650	/// Timestamps need not rise with sequence: a rewind can put a much earlier timestamp on
6651	/// a much later group, and that group proves nothing about where the candidate's own
6652	/// successor begins. Taking the minimum would shrink the bound and discard content that
6653	/// is still well inside the budget.
6654	#[test]
6655	fn reach_follows_the_immediate_successor_not_a_later_rewind() {
6656		let mut producer = track_producer("test", None);
6657
6658		// Group 0 is bounded by group 1 at 10s. Groups 2 and 3 rewind to 1s and 2s.
6659		append_at(&mut producer, 0);
6660		append_at(&mut producer, 10_000);
6661		append_at(&mut producer, 1_000);
6662		append_at(&mut producer, 2_000);
6663
6664		// Group 0 reaches 10s, so nothing here proves it is past a 500ms budget: it could
6665		// hold frames through nearly 10s. A minimum over later groups would put its reach
6666		// at 1s and drop it.
6667		let mut sub = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6668		assert!(
6669			drain(&mut sub).contains(&0),
6670			"group 0 is bounded by its successor at 10s, not by a later rewind"
6671		);
6672	}
6673
6674	/// Datagrams are unordered by construction, so the sequence cursor carries them too:
6675	/// a track using both channels needs one subscription, not two.
6676	#[tokio::test]
6677	async fn ordered_carries_datagrams() {
6678		let mut producer = track_producer("test", None);
6679		let mut sub = producer.subscribe(None).ordered();
6680
6681		producer
6682			.insert_datagram(5, Timestamp::from_millis(5).unwrap(), bytes::Bytes::from_static(b"x"))
6683			.unwrap();
6684		producer.create_group(group::Info { sequence: 3 }).unwrap();
6685
6686		let datagram = sub
6687			.recv_datagram()
6688			.now_or_never()
6689			.expect("datagram would have blocked")
6690			.expect("would have errored")
6691			.expect("track was closed");
6692		assert_eq!(datagram.sequence, 5);
6693
6694		// The datagram did not consume the group cursor.
6695		let group = sub
6696			.next_group()
6697			.now_or_never()
6698			.expect("group would have blocked")
6699			.expect("would have errored")
6700			.expect("track was closed");
6701		assert_eq!(group.sequence, 3);
6702	}
6703
6704	/// Every group the ordered cursor can read right now, in sequence order.
6705	fn drain_ordered(subscriber: &mut Ordered) -> Vec<u64> {
6706		let mut sequences = Vec::new();
6707		while let Some(Ok(Some(group))) = subscriber.next_group().now_or_never() {
6708			sequences.push(group.sequence);
6709		}
6710		sequences
6711	}
6712
6713	/// A cached backlog is not free to deliver: a consumer that stalls and resumes would
6714	/// otherwise replay it at 1x and stay behind forever, since nothing it reads ever
6715	/// blocks. The budget applies as the cursor reads, exactly as on the arrival cursor.
6716	#[test]
6717	fn next_group_sheds_a_stale_backlog() {
6718		let mut producer = track_producer("test", None);
6719		for second in 0..4 {
6720			append_at(&mut producer, second * 1000);
6721		}
6722
6723		let mut subscriber = producer.subscribe(None).ordered();
6724		assert_eq!(drain_ordered(&mut subscriber), vec![3]);
6725
6726		// The arrival cursor, which the relay forwarders use, sheds the same backlog.
6727		let mut arrival = producer.subscribe(None);
6728		assert_eq!(drain(&mut arrival), vec![3]);
6729	}
6730
6731	/// Only what is provably too old goes. A group is convicted by its *reach* (where its
6732	/// successor begins), so a budget spanning part of the backlog keeps every group that
6733	/// could still present something inside it, and the burst is delivered gap-free.
6734	#[test]
6735	fn next_group_keeps_a_backlog_inside_the_budget() {
6736		let mut producer = track_producer("test", None);
6737		for second in 0..4 {
6738			append_at(&mut producer, second * 1000);
6739		}
6740
6741		// Group 0 reaches 1s, a full 2s behind the 3s edge, so it is gone. Group 1
6742		// reaches 2s and could still present up to it: inside a 1.5s budget.
6743		let mut subscriber = producer
6744			.subscribe(Subscription::default().with_max_age(Duration::from_millis(1500)))
6745			.ordered();
6746		assert_eq!(drain_ordered(&mut subscriber), vec![1, 2, 3]);
6747
6748		// A budget covering the whole history still bursts it in full.
6749		let mut replay = producer.subscribe(replay()).ordered();
6750		assert_eq!(drain_ordered(&mut replay), vec![0, 1, 2, 3]);
6751	}
6752
6753	/// A group whose immediate successor has not presented a frame has no proven reach,
6754	/// so nothing says its frames are too old and the ordered cursor keeps it.
6755	#[test]
6756	fn next_group_keeps_a_group_with_no_proven_reach() {
6757		let mut producer = track_producer("test", None);
6758		append_at(&mut producer, 0); // seq 0
6759		producer.append_group().unwrap(); // seq 1 stalls before its first frame
6760		append_at(&mut producer, 10_000); // seq 2
6761
6762		// Group 1 reaches 10s, a full edge behind: convicted. Group 0 is bounded only by
6763		// group 1, which has yet to say where it begins.
6764		let mut subscriber = producer.subscribe(None).ordered();
6765		assert_eq!(drain_ordered(&mut subscriber), vec![0, 2]);
6766	}
6767
6768	#[tokio::test]
6769	async fn real_time_skips_older_sequences_with_equal_ages() {
6770		let mut producer = track_producer("test", None);
6771		append_at(&mut producer, 0);
6772		append_at(&mut producer, 0);
6773
6774		let mut subscriber = producer.subscribe(None);
6775		assert_eq!(drain(&mut subscriber), vec![1]);
6776	}
6777
6778	#[test]
6779	fn fetch_ignores_the_budget() {
6780		let mut producer = track_producer("test", None);
6781		for second in 0..4 {
6782			append_at(&mut producer, second * 1000);
6783		}
6784
6785		// A fetch names one old group explicitly, so there is no live edge to drift
6786		// from: the budget bounds a subscription, not a request for a specific group.
6787		let consumer = producer.consume();
6788		let group = consumer.fetch_group(0, None).now_or_never().unwrap().unwrap();
6789		assert_eq!(group.sequence, 0);
6790	}
6791
6792	/// A one-shot fetch populates the shared cache but never the live arrival cursor,
6793	/// so it cannot make content available to a subscription look stale.
6794	#[tokio::test]
6795	async fn fetched_group_is_not_a_live_drift_edge() {
6796		let mut producer = track_producer("test", None);
6797		let dynamic = producer.dynamic();
6798		let consumer = producer.consume();
6799		append_at(&mut producer, 0);
6800
6801		let pending = consumer.fetch_group(100, None);
6802		let req = dynamic
6803			.requested_group()
6804			.now_or_never()
6805			.expect("fetch request is ready")
6806			.unwrap();
6807		let mut fetched = req.accept(None).unwrap();
6808		fetched
6809			.write_frame(
6810				Timestamp::from_millis(100_000).unwrap(),
6811				bytes::Bytes::from_static(b"fetched"),
6812			)
6813			.unwrap();
6814		fetched.finish().unwrap();
6815		pending.await.unwrap();
6816
6817		let mut groups = producer.subscribe(None);
6818		assert_eq!(groups.assert_group().sequence, 0);
6819		groups.assert_no_group();
6820	}
6821
6822	/// The live edge is resolved once per poll and applied to every group that poll
6823	/// walks off, so it has to be revalidated before it convicts anything: an eviction
6824	/// in between would otherwise discard a group on the strength of content that is no
6825	/// longer there to jump to.
6826	#[test]
6827	fn an_evicted_live_edge_convicts_nothing() {
6828		let mut producer = track_producer("test", None);
6829		append_at(&mut producer, 0);
6830		let edge = append_at(&mut producer, 30_000);
6831
6832		let state = producer.state.read();
6833		let drift = Drift {
6834			budget: Duration::ZERO,
6835			edge: state.drift_edge(None, None, None),
6836			outer: None,
6837			successor: None,
6838		};
6839		assert!(
6840			state.is_stale(0, &drift.edge, drift.budget),
6841			"stale against a live edge"
6842		);
6843		drop(state);
6844
6845		// The edge dies between resolving it and judging the candidate.
6846		let slot = producer.modify().unwrap().lookup.remove(&edge).unwrap();
6847		let _ = slot.group.abort(Error::Evicted);
6848
6849		let state = producer.state.read();
6850		assert!(
6851			!state.is_stale(0, &drift.edge, drift.budget),
6852			"a vanished edge is no reason to drop what is left"
6853		);
6854	}
6855
6856	/// The same holds for an edge a splice pushed from another segment: a handed-out
6857	/// group judges it long after the splice resolved it, so it is revalidated on its own
6858	/// track before it convicts anything.
6859	#[tokio::test]
6860	async fn an_evicted_outer_edge_convicts_nothing() {
6861		let mut producer = track_producer("a", None);
6862		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(1)));
6863		let mut open = producer.append_group().unwrap();
6864		open.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"a"))
6865			.unwrap();
6866		let mut group = subscriber.recv_group().await.unwrap().expect("group");
6867		assert!(group.read_frame().await.unwrap().is_some());
6868		// A successor bounds the open group's reach, without being late against it.
6869		append_at(&mut producer, 10);
6870
6871		let mut next = track_producer("b", None);
6872		append_at(&mut next, 30_000);
6873		let edge = append_at(&mut next, 30_010);
6874		subscriber.set_anchor(Anchor {
6875			cap: None,
6876			edge: next.consume().live_edge(None),
6877			successor: None,
6878		});
6879
6880		let mut control = group.clone();
6881		assert!(
6882			matches!(control.read_frame().now_or_never(), Some(Ok(None))),
6883			"the open group ends against the outer edge"
6884		);
6885
6886		// The outer edge dies before the group is judged again.
6887		let slot = next.modify().unwrap().lookup.remove(&edge).unwrap();
6888		let _ = slot.group.abort(Error::Evicted);
6889
6890		assert!(
6891			group.read_frame().now_or_never().is_none(),
6892			"a vanished outer edge is no reason to drop what is left"
6893		);
6894		assert!(!group.latency_expired());
6895		open.finish().unwrap();
6896	}
6897
6898	#[tokio::test]
6899	async fn a_lower_sequence_is_never_the_live_edge() {
6900		let producer = track_producer("test", None);
6901		// A high timestamp on a lower sequence is not a live edge: backfill served on
6902		// demand sits there, and so does the tail of a timeline the publisher rewound.
6903		// Only groups above the candidate anchor the measure, so neither can convict
6904		// the group that follows it.
6905		let mut straggler = producer.create_group(0u64.into()).unwrap();
6906		straggler
6907			.write_frame(Timestamp::from_millis(60_000).unwrap(), bytes::Bytes::from_static(b"x"))
6908			.unwrap();
6909		straggler.finish().unwrap();
6910
6911		let mut rewound = producer.create_group(1u64.into()).unwrap();
6912		rewound
6913			.write_frame(Timestamp::from_millis(0).unwrap(), bytes::Bytes::from_static(b"x"))
6914			.unwrap();
6915		rewound.finish().unwrap();
6916
6917		let mut subscriber = producer.subscribe(replay());
6918		assert_eq!(drain(&mut subscriber), vec![0, 1]);
6919	}
6920
6921	#[test]
6922	fn a_requested_end_does_not_cap_the_live_edge() {
6923		let mut producer = track_producer("test", None);
6924		for second in 0..4 {
6925			append_at(&mut producer, second * 1000);
6926		}
6927
6928		// `Subscription::end` is a request to the publisher, folded in with every other
6929		// subscriber's, and it does not filter this handle: the groups above it arrive
6930		// anyway. Capping the live edge with it would pin the edge below them, leaving
6931		// everything past it with nothing newer to be late against.
6932		let mut subscriber = producer.subscribe(Subscription::default().with_end(Position::after_group(1)));
6933		assert_eq!(drain(&mut subscriber), vec![3]);
6934	}
6935
6936	#[test]
6937	fn a_capped_subscriber_measures_drift_against_its_cap() {
6938		let mut producer = track_producer("test", None);
6939		append_at(&mut producer, 0);
6940		append_at(&mut producer, 1000);
6941
6942		// Capped at group 0: the route running on past the cap is data this subscriber
6943		// can never be served, so it isn't a live edge to be late against. This is what
6944		// keeps a spliced segment from dropping the groups either side of a takeover
6945		// boundary.
6946		let mut subscriber = producer.subscribe(Subscription::default().with_end(Position::after_group(0)));
6947		subscriber.set_groups(..1);
6948		assert_eq!(drain(&mut subscriber), vec![0]);
6949
6950		// Raising the cap re-offers the parked group, now measured against the wider
6951		// edge it just admitted.
6952		subscriber.set_groups(..);
6953		assert_eq!(drain(&mut subscriber), vec![1]);
6954	}
6955
6956	#[test]
6957	fn subscriber_control_updates_while_read_future_is_pending() {
6958		let producer = track_producer("test", None);
6959		let mut subscriber = producer.subscribe(None);
6960		let control = subscriber.control();
6961
6962		let mut recv = Box::pin(subscriber.recv_group());
6963		assert!(recv.as_mut().now_or_never().is_none());
6964
6965		control.update(Subscription::default().with_priority(7)).unwrap();
6966
6967		let aggregate = producer.subscription().expect("expected an active subscription");
6968		assert_eq!(aggregate.priority, 7);
6969	}
6970
6971	#[test]
6972	fn dropped_subscriber_leaves_no_ghost_in_aggregate() {
6973		// Regression (#2351): a departed subscriber must not keep contributing its
6974		// last subscription to the aggregate. When it did, a relay's linger loop
6975		// never observed the track going idle, and an identical viewer reconnecting
6976		// within the linger window was reset when the stale timer fired.
6977		let mut producer = track_producer("test", None);
6978		let a = producer.subscribe(Subscription::default().with_priority(5));
6979
6980		// Prime the change cursor: the aggregate currently has one subscriber.
6981		let waiter = kio::Waiter::noop();
6982		assert!(
6983			matches!(producer.poll_subscription_changed(&waiter), Poll::Ready(Ok(Some(_)))),
6984			"one live subscriber should aggregate to Some",
6985		);
6986
6987		// The only subscriber leaves.
6988		drop(a);
6989
6990		// The aggregate must report the drop to None, not the ghost's last value.
6991		assert!(
6992			matches!(producer.poll_subscription_changed(&waiter), Poll::Ready(Ok(None))),
6993			"a dropped subscriber must not linger in the aggregate",
6994		);
6995
6996		// And the snapshot used by the linger loop must agree.
6997		assert!(
6998			producer.subscription().is_none(),
6999			"snapshot must exclude a dropped subscriber",
7000		);
7001	}
7002
7003	#[test]
7004	fn dropped_subscriber_wakes_the_aggregate() {
7005		// The value being right isn't enough: nothing re-polls the aggregate on its
7006		// own, so the drop has to wake the waiter. A subscriber contributing demand
7007		// takes `kio::Consumer::poll`'s Ready path, which registers no waiter, so
7008		// the departure needs the closed waiter armed explicitly. Without it a relay
7009		// never learns the last viewer left and holds the upstream subscription (and
7010		// the upstream's viewer count) open forever.
7011		use std::sync::atomic::{AtomicBool, Ordering};
7012
7013		let mut producer = track_producer("test", None);
7014		let a = producer.subscribe(Subscription::default().with_priority(5));
7015
7016		let woken = Arc::new(AtomicBool::new(false));
7017		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
7018
7019		// Prime the cursor, then confirm the next poll parks.
7020		assert!(matches!(
7021			producer.poll_subscription_changed(&waiter),
7022			Poll::Ready(Ok(Some(_)))
7023		));
7024		assert!(
7025			producer.poll_subscription_changed(&waiter).is_pending(),
7026			"the aggregate is unchanged, so this poll must park",
7027		);
7028		assert!(!woken.load(Ordering::SeqCst), "nothing happened yet");
7029
7030		drop(a);
7031		assert!(
7032			woken.load(Ordering::SeqCst),
7033			"the last subscriber leaving must wake the aggregate watcher",
7034		);
7035	}
7036
7037	#[test]
7038	fn widest_subscriber_update_wakes_the_aggregate() {
7039		// The value counterpart of the drop above. A subscriber that widens the
7040		// fold takes the Ready path too, so its next update registered no waiter.
7041		// A relay whose upstream cap came from a downstream reader then never
7042		// learned that the reader lifted it, and the groups parked upstream never
7043		// resumed: every session stayed up and nothing flowed.
7044		use std::sync::atomic::{AtomicBool, Ordering};
7045
7046		let mut producer = track_producer("test", None);
7047		let _narrow = producer.subscribe(Subscription::default().with_end(Position::after_group(3)));
7048		let mut wide = producer.subscribe(Subscription::default());
7049
7050		let woken = Arc::new(AtomicBool::new(false));
7051		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
7052
7053		assert!(matches!(
7054			producer.poll_subscription_changed(&waiter),
7055			Poll::Ready(Ok(Some(_)))
7056		));
7057		assert!(producer.poll_subscription_changed(&waiter).is_pending());
7058		assert!(!woken.load(Ordering::SeqCst), "nothing happened yet");
7059
7060		wide.update(Subscription::default().with_end(Position::after_group(5)))
7061			.unwrap();
7062		assert!(
7063			woken.load(Ordering::SeqCst),
7064			"the widest subscriber changing must wake the aggregate watcher",
7065		);
7066		match producer.poll_subscription_changed(&waiter) {
7067			Poll::Ready(Ok(Some(sub))) => assert_eq!(sub.end, Position::after_group(5)),
7068			other => panic!("expected the narrowed aggregate, got {other:?}"),
7069		}
7070	}
7071
7072	/// An [`ArcWake`] that just records that it was woken.
7073	struct FlagWake(Arc<std::sync::atomic::AtomicBool>);
7074
7075	impl futures::task::ArcWake for FlagWake {
7076		fn wake_by_ref(arc_self: &Arc<Self>) {
7077			arc_self.0.store(true, std::sync::atomic::Ordering::SeqCst);
7078		}
7079	}
7080
7081	#[tokio::test]
7082	async fn out_of_order_max_sequence_at_front() {
7083		let producer = track_producer("test", None);
7084
7085		// Arrive out of order: seq 5 first, then 3, then 4.
7086		producer.create_group(group::Info { sequence: 5 }).unwrap();
7087		producer.create_group(group::Info { sequence: 3 }).unwrap();
7088		producer.create_group(group::Info { sequence: 4 }).unwrap();
7089
7090		// max_sequence = 5, which is at the front of the VecDeque.
7091		{
7092			let state = producer.state.read();
7093			assert_eq!(state.max_sequence, Some(5));
7094		}
7095
7096		// Expire all three groups.
7097		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
7098
7099		// Append seq 6 (becomes new max_sequence).
7100		producer.append_group().unwrap(); // seq 6
7101
7102		// Seq 3, 4, 5 are all expired. Seq 5 was the old max_sequence but now 6 is.
7103		// All old groups are evicted.
7104		{
7105			let state = producer.state.read();
7106			assert_eq!(live_groups(&state), 1);
7107			assert_eq!(first_live_sequence(&state), 6);
7108			assert!(!state.lookup.contains_key(&3));
7109			assert!(!state.lookup.contains_key(&4));
7110			assert!(!state.lookup.contains_key(&5));
7111			assert!(state.lookup.contains_key(&6));
7112		}
7113	}
7114
7115	#[tokio::test]
7116	async fn max_sequence_at_front_blocks_trim() {
7117		let producer = track_producer("test", None);
7118
7119		// Arrive: seq 5, then seq 3.
7120		producer.create_group(group::Info { sequence: 5 }).unwrap();
7121
7122		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
7123
7124		// Seq 3 arrives late; max_sequence is still 5 (at front).
7125		producer.create_group(group::Info { sequence: 3 }).unwrap();
7126
7127		// Seq 5 is max_sequence (protected). Seq 3 is not expired (just created).
7128		// Nothing should be evicted.
7129		{
7130			let state = producer.state.read();
7131			assert_eq!(live_groups(&state), 2);
7132			assert_eq!(state.offset, 0);
7133		}
7134
7135		// Expire seq 3 as well.
7136		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
7137
7138		// Seq 2 arrives late, triggering eviction.
7139		producer.create_group(group::Info { sequence: 2 }).unwrap();
7140
7141		// Seq 5 is the live edge (protected) and still resolves at the front of
7142		// `arrival`, so nothing is trimmed and the offset stays. Seq 3 expired out of
7143		// `lookup`, leaving a hole its arrival entry no longer resolves; seq 2 is
7144		// fresh and kept.
7145		{
7146			let state = producer.state.read();
7147			assert_eq!(live_groups(&state), 2);
7148			assert_eq!(state.offset, 0);
7149			assert!(state.lookup.contains_key(&5));
7150			assert!(!state.lookup.contains_key(&3));
7151			assert!(state.lookup.contains_key(&2));
7152		}
7153
7154		// Consumer should still be able to read through the hole.
7155		let mut consumer = producer.subscribe(None);
7156		let group = consumer.assert_group();
7157		// consume() starts at index 0; the first arrival entry that still resolves is seq 5.
7158		assert_eq!(group.sequence, 5);
7159	}
7160
7161	#[tokio::test]
7162	async fn abort_drops_open_groups() {
7163		let producer = track_producer("test", None);
7164		producer.append_group().unwrap();
7165		producer.append_group().unwrap();
7166
7167		let mut consumer = producer.subscribe(None);
7168		assert_eq!(live_groups(&producer.state.read()), 2);
7169
7170		producer.clone().abort(Error::Cancel).unwrap();
7171
7172		// Nobody will finish them, so they leave the cache rather than park a reader.
7173		assert!(
7174			producer.state.read().lookup.is_empty(),
7175			"open groups are dropped on abort"
7176		);
7177		let result = consumer.recv_group().now_or_never().expect("should not block");
7178		assert!(matches!(result, Err(Error::Cancel)));
7179	}
7180
7181	#[tokio::test]
7182	async fn drop_unfinished_clears_cached_groups() {
7183		let producer = track_producer("test", None);
7184		let writer = producer.clone();
7185		writer.append_group().unwrap();
7186
7187		// A stale consumer keeps the channel (and thus the cache) alive.
7188		let mut consumer = producer.subscribe(None);
7189		assert_eq!(live_groups(&producer.state.read()), 1);
7190
7191		// Drop every producer without finishing: the cache is released.
7192		drop(writer);
7193		drop(producer);
7194
7195		let result = consumer.recv_group().now_or_never().expect("should not block");
7196		assert!(matches!(result, Err(Error::Dropped)));
7197	}
7198
7199	#[tokio::test]
7200	async fn drop_after_abort_does_not_warn() {
7201		// abort() closes the channel after recording `abort`. Drop must treat the
7202		// read-only guard returned by write() as clean or it emits a false WARN.
7203		let warns = count_drop_warnings("track::Producer dropped without finish", || {
7204			let producer = track_producer("test", None);
7205			let keep = producer.clone();
7206			let writer = producer.clone();
7207			let group = writer.append_group().unwrap();
7208			group.finish().unwrap();
7209			let _consumer = producer.subscribe(None);
7210			writer.abort(Error::Cancel).unwrap();
7211			drop(keep);
7212		});
7213		assert_eq!(warns, 0, "abort-then-drop must not emit unfinished-producer WARN");
7214	}
7215
7216	#[tokio::test]
7217	async fn drop_unfinished_warns() {
7218		let warns = count_drop_warnings("track::Producer dropped without finish", || {
7219			let producer = track_producer("test", None);
7220			let writer = producer.clone();
7221			writer.append_group().unwrap();
7222			let _consumer = producer.subscribe(None);
7223			drop(writer);
7224			drop(producer);
7225		});
7226		assert!(warns >= 1, "unfinished drop must emit unfinished-producer WARN");
7227	}
7228
7229	#[tokio::test]
7230	async fn drop_finished_keeps_cached_groups() {
7231		let producer = track_producer("test", None);
7232		producer.append_group().unwrap();
7233		producer.finish().unwrap();
7234
7235		let mut consumer = producer.subscribe(None);
7236		drop(producer);
7237
7238		// A cleanly finished track keeps its cache so the consumer can still drain.
7239		assert_eq!(consumer.assert_group().sequence, 0);
7240		let done = consumer.recv_group().now_or_never().expect("should not block").unwrap();
7241		assert!(done.is_none(), "consumer should drain then see clean finish");
7242	}
7243
7244	/// A boundary declared ahead of the live edge, then the last producer dropping, means
7245	/// the missing groups will never come: the reader ends cleanly rather than with
7246	/// `Dropped`, as it would for a track that never declared its end.
7247	#[tokio::test]
7248	async fn drop_short_of_the_boundary_ends_cleanly() {
7249		let mut producer = track_producer("test", None);
7250		producer.append_group().unwrap();
7251		producer.finish_at(3).unwrap();
7252
7253		let mut consumer = producer.subscribe(None);
7254		assert_eq!(consumer.assert_group().sequence, 0);
7255		assert!(
7256			consumer.recv_group().now_or_never().is_none(),
7257			"groups 1 and 2 are owed"
7258		);
7259
7260		drop(producer);
7261		let done = consumer.recv_group().now_or_never().expect("should not block").unwrap();
7262		assert!(
7263			done.is_none(),
7264			"the track ends at its boundary without the missing groups"
7265		);
7266	}
7267
7268	/// The sequence cursor ends the same way. A gap below the boundary is skipped,
7269	/// a later cached group is still delivered, and the read finishes cleanly.
7270	#[tokio::test]
7271	async fn ordered_ends_cleanly_when_sealed_short_of_the_boundary() {
7272		let mut producer = track_producer("test", None);
7273		producer.create_group(group::Info { sequence: 0 }).unwrap();
7274		producer.create_group(group::Info { sequence: 2 }).unwrap();
7275		producer.finish_at(4).unwrap();
7276
7277		let mut ordered = producer.subscribe(None).ordered();
7278		drop(producer);
7279
7280		let first = ordered.next_group().now_or_never().expect("group 0").unwrap().unwrap();
7281		assert_eq!(first.sequence, 0);
7282		let second = ordered.next_group().now_or_never().expect("group 2").unwrap().unwrap();
7283		assert_eq!(second.sequence, 2);
7284		let done = ordered.next_group().now_or_never().expect("end").unwrap();
7285		assert!(done.is_none(), "missing groups below the boundary are not an error");
7286	}
7287
7288	#[tokio::test]
7289	async fn cached_groups_preserve_arrival_order() {
7290		let producer = track_producer("test", None);
7291		producer.create_group(group::Info { sequence: 5 }).unwrap();
7292		producer.create_group(group::Info { sequence: 3 }).unwrap();
7293
7294		let groups = producer.consume().cached_groups();
7295		let sequences: Vec<u64> = groups.iter().map(|(group, _)| group.sequence).collect();
7296		assert_eq!(
7297			sequences,
7298			vec![5, 3],
7299			"warm snapshot must follow arrival, not sequence order"
7300		);
7301	}
7302
7303	#[test]
7304	fn append_finish_cannot_be_rewritten() {
7305		let producer = track_producer("test", None);
7306
7307		// Finishing an empty track is valid (fin = 0, total groups = 0).
7308		assert!(producer.finish().is_ok());
7309		assert!(producer.finish().is_err());
7310		assert!(producer.append_group().is_err());
7311	}
7312
7313	#[test]
7314	fn finish_after_groups() {
7315		let producer = track_producer("test", None);
7316
7317		producer.append_group().unwrap();
7318		assert!(producer.finish().is_ok());
7319		assert!(producer.finish().is_err());
7320		assert!(producer.append_group().is_err());
7321	}
7322
7323	#[test]
7324	fn finish_at_rejects_a_boundary_at_or_below_the_live_edge() {
7325		let mut producer = track_producer("test", None);
7326		producer.create_group(group::Info { sequence: 5 }).unwrap();
7327
7328		// The boundary is exclusive, so it must be strictly above the highest produced
7329		// group. 5 or below would orphan groups that already exist.
7330		assert!(producer.finish_at(4).is_err());
7331		assert!(producer.finish_at(5).is_err());
7332		assert!(producer.finish_at(6).is_ok());
7333
7334		{
7335			let state = producer.state.read();
7336			assert_eq!(state.final_sequence, Some(6));
7337		}
7338
7339		// Re-finishing is rejected, and no group at or above the boundary can be created.
7340		assert!(producer.finish_at(6).is_err());
7341		assert!(producer.create_group(group::Info { sequence: 4 }).is_ok());
7342		assert!(producer.create_group(group::Info { sequence: 6 }).is_err());
7343	}
7344
7345	#[test]
7346	fn final_sequence_reports_the_declared_boundary() {
7347		let mut producer = track_producer("test", None);
7348		assert_eq!(producer.final_sequence(), None);
7349
7350		producer.create_group(group::Info { sequence: 5 }).unwrap();
7351		assert_eq!(producer.final_sequence(), None, "a group does not declare a boundary");
7352
7353		producer.finish_at(9).unwrap();
7354		assert_eq!(producer.final_sequence(), Some(9));
7355
7356		// finish() would try to declare a second boundary, so callers check first.
7357		assert!(producer.finish().is_err());
7358	}
7359
7360	#[test]
7361	fn final_sequence_reports_the_live_edge_after_finish() {
7362		let producer = track_producer("test", None);
7363		producer.create_group(group::Info { sequence: 5 }).unwrap();
7364		producer.finish().unwrap();
7365		assert_eq!(producer.final_sequence(), Some(6));
7366	}
7367
7368	#[tokio::test]
7369	async fn finish_at_declares_a_future_boundary() {
7370		let mut producer = track_producer("test", None);
7371		producer.create_group(group::Info { sequence: 5 }).unwrap();
7372
7373		// Learn the track ends at group 6 (exclusive 7) while the live edge is still 5.
7374		producer.finish_at(7).unwrap();
7375
7376		let mut consumer = producer.subscribe(None);
7377		assert_eq!(consumer.assert_group().sequence, 5);
7378
7379		// The boundary is known immediately, but the track isn't done: group 6 is still
7380		// outstanding, so the consumer parks rather than seeing end-of-stream.
7381		let boundary = consumer
7382			.finished()
7383			.now_or_never()
7384			.expect("boundary is known immediately")
7385			.expect("would have errored");
7386		assert_eq!(boundary, 7);
7387		assert!(
7388			consumer.recv_group().now_or_never().is_none(),
7389			"should wait for the outstanding group"
7390		);
7391
7392		// The trailing group arrives (below the boundary), then the track completes.
7393		producer.create_group(group::Info { sequence: 6 }).unwrap();
7394		assert_eq!(consumer.assert_group().sequence, 6);
7395		let done = consumer
7396			.recv_group()
7397			.now_or_never()
7398			.expect("should not block")
7399			.expect("would have errored");
7400		assert!(done.is_none(), "track completes once the boundary is reached");
7401	}
7402
7403	/// An abort before the declared end settled wins over it: the boundary was reached,
7404	/// but a group below it was still open, so the track was cut off rather than ended.
7405	#[tokio::test]
7406	async fn abort_before_the_end_settles_wins() {
7407		let mut producer = track_producer("test", None);
7408		let mut consumer = producer.subscribe(None);
7409
7410		let head = producer.create_group(group::Info { sequence: 0 }).unwrap();
7411		head.finish().unwrap();
7412		let _tail = producer.create_group(group::Info { sequence: 1 }).unwrap();
7413		producer.finish_at(2).unwrap();
7414		assert_eq!(consumer.assert_group().sequence, 0);
7415
7416		producer.abort(Error::Timeout).unwrap();
7417		let res = consumer.recv_group().now_or_never().expect("should not block");
7418		assert!(matches!(res, Err(Error::Timeout)));
7419	}
7420
7421	#[tokio::test]
7422	async fn local_close_keeps_finished_groups_without_declaring_an_end() {
7423		let producer = track_producer("local", None);
7424		let mut consumer = producer.consume().subscribe(None).await.unwrap();
7425		producer.append_group().unwrap().finish().unwrap();
7426		producer.clone().close().unwrap();
7427		assert!(producer.final_sequence().is_none());
7428		assert!(matches!(consumer.finished().await, Err(Error::Closed)));
7429		assert!(consumer.recv_group().await.unwrap().is_some());
7430		assert!(consumer.recv_group().await.unwrap().is_none());
7431	}
7432
7433	#[tokio::test]
7434	async fn local_close_cannot_mask_an_abort_before_declared_end_settles() {
7435		let mut producer = track_producer("local", None);
7436		let mut consumer = producer.consume().subscribe(None).await.unwrap();
7437		let _group = producer.append_group().unwrap();
7438		producer.finish_at(2).unwrap();
7439		producer.clone().abort(Error::Timeout).unwrap();
7440		assert!(producer.close().is_err());
7441		assert!(matches!(consumer.recv_group().await, Err(Error::Timeout)));
7442	}
7443
7444	/// A local close is a dead route to the origin, not finished content, so a
7445	/// replacement route can take over where it left off.
7446	#[test]
7447	fn local_close_is_not_completion_and_keeps_the_resume_position() {
7448		let producer = track_producer("local", None);
7449		let consumer = producer.consume();
7450		producer.append_group().unwrap().finish().unwrap();
7451		let mut group = producer.append_group().unwrap();
7452		group
7453			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"a"))
7454			.unwrap();
7455		group
7456			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"b"))
7457			.unwrap();
7458		producer.close().unwrap();
7459		assert!(matches!(
7460			consumer.poll_complete(&kio::Waiter::noop()),
7461			Poll::Ready(Err(_))
7462		));
7463		assert_eq!(consumer.resume_position(), Some(Position { group: 1, frame: 2 }));
7464	}
7465
7466	/// An abort short of the end keeps the groups that finished for a reader that has not
7467	/// pulled them yet: it gets them, then the abort. The open group nobody will finish
7468	/// is gone, on both cursors.
7469	#[tokio::test]
7470	async fn abort_keeps_finished_groups_for_a_slow_reader() {
7471		let producer = track_producer("test", None);
7472		let mut arrival = producer.subscribe(None);
7473		let mut ordered = producer.subscribe(None).ordered();
7474
7475		for sequence in 0..2 {
7476			producer
7477				.create_group(group::Info { sequence })
7478				.unwrap()
7479				.finish()
7480				.unwrap();
7481		}
7482		let _open = producer.create_group(group::Info { sequence: 2 }).unwrap();
7483		producer.abort(Error::Timeout).unwrap();
7484
7485		assert_eq!(arrival.assert_group().sequence, 0);
7486		assert_eq!(arrival.assert_group().sequence, 1);
7487		let res = arrival.recv_group().now_or_never().expect("should not block");
7488		assert!(matches!(res, Err(Error::Timeout)), "expected the abort");
7489
7490		assert_eq!(drain_ordered(&mut ordered), [0, 1]);
7491		let res = ordered
7492			.next_group()
7493			.now_or_never()
7494			.expect("should not block")
7495			.map(|group| group.map(|group| group.sequence));
7496		assert!(matches!(res, Err(Error::Timeout)), "expected the abort, got {res:?}");
7497	}
7498
7499	/// The last producer dropping without a finish keeps the finished groups the same way.
7500	#[tokio::test]
7501	async fn dropped_producer_keeps_finished_groups_for_a_slow_reader() {
7502		let producer = track_producer("test", None);
7503		let mut consumer = producer.subscribe(None);
7504		producer
7505			.create_group(group::Info { sequence: 0 })
7506			.unwrap()
7507			.finish()
7508			.unwrap();
7509		drop(producer);
7510
7511		assert_eq!(consumer.assert_group().sequence, 0);
7512		let res = consumer.recv_group().now_or_never().expect("should not block");
7513		assert!(matches!(res, Err(Error::Dropped)), "expected the drop");
7514	}
7515
7516	/// A closed track's groups, its latest included, expire once idle, so a stale
7517	/// consumer cannot pin them. A live track's latest stays protected.
7518	#[tokio::test]
7519	async fn closed_track_expires_its_latest_group() {
7520		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
7521
7522		let live = track_producer_pooled("live", pool.clone());
7523		live.append_group().unwrap().finish().unwrap();
7524
7525		let aborted = track_producer_pooled("aborted", pool.clone());
7526		aborted.append_group().unwrap().finish().unwrap();
7527		let stale_aborted = aborted.consume();
7528		aborted.abort(Error::Timeout).unwrap();
7529
7530		let finished = track_producer_pooled("finished", pool.clone());
7531		finished.append_group().unwrap().finish().unwrap();
7532		finished.finish().unwrap();
7533		let stale_finished = finished.consume();
7534		drop(finished);
7535
7536		// The first pass dates the activity it has not seen yet; the next one expires it.
7537		for _ in 0..2 {
7538			crate::model::clock::advance(Duration::from_secs(2));
7539			pool.sweep();
7540		}
7541
7542		assert!(live.consume().peek_group(0).is_some(), "a live track keeps its latest");
7543		assert!(
7544			stale_aborted.peek_group(0).is_none(),
7545			"an aborted track's latest expired"
7546		);
7547		assert!(
7548			stale_finished.peek_group(0).is_none(),
7549			"a sealed track's latest expired"
7550		);
7551	}
7552
7553	/// An ended track that shares a group with a live one leaves it while it is the live
7554	/// track's latest, and expires it like any other idle group once it is not. A group
7555	/// only ended tracks share expires even while stale consumers keep them all.
7556	#[tokio::test]
7557	async fn closed_track_expires_a_shared_group_unless_live_edge() {
7558		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
7559		// The first pass dates the activity it has not seen yet; the next one expires it.
7560		let sweep = || {
7561			for _ in 0..2 {
7562				crate::model::clock::advance(Duration::from_secs(2));
7563				pool.sweep();
7564			}
7565		};
7566		let adopt = |name: &str, group: &group::Producer| {
7567			let mut track = track_producer_pooled(name, pool.clone());
7568			track.adopt_group(group.clone(), true).unwrap();
7569			track.finish().unwrap();
7570			let stale = track.consume();
7571			drop(track);
7572			stale
7573		};
7574
7575		let live = track_producer_pooled("live", pool.clone());
7576		let group = live.append_group().unwrap();
7577		group.finish().unwrap();
7578		let stale = adopt("ended", &group);
7579		drop(group);
7580
7581		sweep();
7582		assert!(live.consume().peek_group(0).is_some(), "the live edge stays");
7583		assert!(
7584			stale.peek_group(0).is_some(),
7585			"and so does the ended track's share of it"
7586		);
7587
7588		live.append_group().unwrap().finish().unwrap();
7589		sweep();
7590		assert!(stale.peek_group(0).is_none(), "once demoted, it expires");
7591
7592		let group = live.append_group().unwrap();
7593		group.finish().unwrap();
7594		let (first, second) = (adopt("first", &group), adopt("second", &group));
7595		drop(group);
7596		live.finish().unwrap();
7597		drop(live);
7598
7599		sweep();
7600		assert!(first.peek_group(2).is_none(), "ended tracks alone don't keep it");
7601		assert!(second.peek_group(2).is_none(), "for each other");
7602	}
7603
7604	/// An abort after every group below the declared end finished leaves the end standing.
7605	#[tokio::test]
7606	async fn abort_after_the_end_settles_ends_clean() {
7607		let mut producer = track_producer("test", None);
7608		let mut consumer = producer.subscribe(None);
7609
7610		for sequence in 0..2 {
7611			let group = producer.create_group(group::Info { sequence }).unwrap();
7612			group.finish().unwrap();
7613		}
7614		producer.finish_at(2).unwrap();
7615		assert_eq!(consumer.assert_group().sequence, 0);
7616		assert_eq!(consumer.assert_group().sequence, 1);
7617
7618		producer.abort(Error::Timeout).unwrap();
7619		let res = consumer.recv_group().now_or_never().expect("should not block");
7620		assert!(matches!(res, Ok(None)));
7621	}
7622
7623	/// The settled end stands on the ordered cursor too: a reader that starts after the
7624	/// abort gets every group below the end, then the clean end, not the abort.
7625	#[tokio::test]
7626	async fn abort_after_the_end_settles_ends_clean_for_an_ordered_reader() {
7627		let mut producer = track_producer("test", None);
7628		let mut consumer = producer.subscribe(None).ordered();
7629
7630		for sequence in 0..2 {
7631			let group = producer.create_group(group::Info { sequence }).unwrap();
7632			group.finish().unwrap();
7633		}
7634		producer.finish_at(2).unwrap();
7635		producer.abort(Error::Timeout).unwrap();
7636
7637		assert_eq!(drain_ordered(&mut consumer), [0, 1]);
7638		let end = consumer
7639			.next_group()
7640			.now_or_never()
7641			.expect("should not block")
7642			.map(|group| group.map(|group| group.sequence));
7643		assert!(matches!(end, Ok(None)), "expected the clean end, got {end:?}");
7644	}
7645
7646	/// The settled end stands on the ordered cursor even where it has nothing to read:
7647	/// capped below the end, and with a gap below the cap. Parking is not an option on a
7648	/// closed track (the channel would turn it into the abort), so these end clean.
7649	#[tokio::test]
7650	async fn abort_after_the_end_settles_ends_clean_below_the_cap() {
7651		let mut producer = track_producer("test", None);
7652		let mut consumer = producer.subscribe(None).ordered();
7653		for sequence in 0..2 {
7654			producer
7655				.create_group(group::Info { sequence })
7656				.unwrap()
7657				.finish()
7658				.unwrap();
7659		}
7660		producer.finish_at(2).unwrap();
7661		producer.abort(Error::Timeout).unwrap();
7662
7663		consumer.set_groups(..1);
7664		assert_eq!(drain_ordered(&mut consumer), [0]);
7665		let end = consumer
7666			.next_group()
7667			.now_or_never()
7668			.expect("should not block")
7669			.map(|group| group.map(|group| group.sequence));
7670		assert!(
7671			matches!(end, Ok(None)),
7672			"capped at the end: expected the clean end, got {end:?}"
7673		);
7674
7675		let mut producer = track_producer("test", None);
7676		let mut consumer = producer.subscribe(None).ordered();
7677		for sequence in [0, 2] {
7678			producer
7679				.create_group(group::Info { sequence })
7680				.unwrap()
7681				.finish()
7682				.unwrap();
7683		}
7684		producer.finish_at(3).unwrap();
7685		producer.abort(Error::Timeout).unwrap();
7686
7687		consumer.set_groups(..2);
7688		assert_eq!(drain_ordered(&mut consumer), [0]);
7689		let end = consumer
7690			.next_group()
7691			.now_or_never()
7692			.expect("should not block")
7693			.map(|group| group.map(|group| group.sequence));
7694		assert!(
7695			matches!(end, Ok(None)),
7696			"gap below the cap: expected the clean end, got {end:?}"
7697		);
7698	}
7699
7700	#[tokio::test]
7701	async fn recv_group_finishes_without_waiting_for_gaps() {
7702		let producer = track_producer("test", None);
7703		producer.create_group(group::Info { sequence: 1 }).unwrap();
7704		producer.finish().unwrap();
7705
7706		let mut consumer = producer.subscribe(None);
7707		assert_eq!(consumer.assert_group().sequence, 1);
7708
7709		let done = consumer
7710			.recv_group()
7711			.now_or_never()
7712			.expect("should not block")
7713			.expect("would have errored");
7714		assert!(done.is_none(), "track should finish without waiting for gaps");
7715	}
7716
7717	#[tokio::test]
7718	async fn next_group_skips_late_arrivals() {
7719		let producer = track_producer("test", None);
7720		let mut consumer = producer.subscribe(None).ordered();
7721
7722		// Seq 5 arrives first.
7723		producer.create_group(group::Info { sequence: 5 }).unwrap();
7724		let group = consumer
7725			.next_group()
7726			.now_or_never()
7727			.expect("should not block")
7728			.expect("would have errored")
7729			.expect("track should not be closed");
7730		assert_eq!(group.sequence, 5);
7731
7732		// Seq 3 arrives late, skipped because 3 <= 5.
7733		producer.create_group(group::Info { sequence: 3 }).unwrap();
7734		// Seq 4 arrives late and is also skipped.
7735		producer.create_group(group::Info { sequence: 4 }).unwrap();
7736		// Seq 7 arrives and is returned.
7737		producer.create_group(group::Info { sequence: 7 }).unwrap();
7738
7739		let group = consumer
7740			.next_group()
7741			.now_or_never()
7742			.expect("should not block")
7743			.expect("would have errored")
7744			.expect("track should not be closed");
7745		assert_eq!(group.sequence, 7);
7746
7747		// No more groups. This would block.
7748		assert!(
7749			consumer.next_group().now_or_never().is_none(),
7750			"should block waiting for a higher sequence"
7751		);
7752	}
7753
7754	#[tokio::test]
7755	async fn next_group_returns_arrivals_in_order() {
7756		let producer = track_producer("test", None);
7757		let mut consumer = producer.subscribe(replay()).ordered();
7758
7759		// Seq 3 arrives first, then seq 5. Both should be returned in arrival order.
7760		producer.create_group(group::Info { sequence: 3 }).unwrap();
7761		producer.create_group(group::Info { sequence: 5 }).unwrap();
7762
7763		let group = consumer
7764			.next_group()
7765			.now_or_never()
7766			.expect("should not block")
7767			.expect("would have errored")
7768			.expect("track should not be closed");
7769		assert_eq!(group.sequence, 3);
7770
7771		let group = consumer
7772			.next_group()
7773			.now_or_never()
7774			.expect("should not block")
7775			.expect("would have errored")
7776			.expect("track should not be closed");
7777		assert_eq!(group.sequence, 5);
7778	}
7779
7780	#[tokio::test]
7781	async fn ordered_and_arrival_cursors_are_independent() {
7782		let producer = track_producer("test", None);
7783		let mut ordered = producer.subscribe(replay()).ordered();
7784		let mut arrival = producer.subscribe(replay());
7785
7786		// Out-of-order arrivals: seq 5 first, then seq 3.
7787		producer.create_group(group::Info { sequence: 5 }).unwrap();
7788		producer.create_group(group::Info { sequence: 3 }).unwrap();
7789
7790		// The ordered handle returns the smallest sequence first, regardless of
7791		// arrival order.
7792		let group = ordered
7793			.next_group()
7794			.now_or_never()
7795			.expect("should not block")
7796			.expect("would have errored")
7797			.expect("track should not be closed");
7798		assert_eq!(group.sequence, 3);
7799
7800		// The plain handle walks arrivals, so it still starts at seq 5.
7801		assert_eq!(arrival.assert_group().sequence, 5);
7802	}
7803
7804	#[tokio::test]
7805	async fn end_at_caps_next_group() {
7806		let producer = track_producer("test", None);
7807		let mut consumer = producer.subscribe(replay()).ordered();
7808
7809		for s in 0..6 {
7810			producer.create_group(group::Info { sequence: s }).unwrap();
7811		}
7812
7813		consumer.set_groups(..3);
7814
7815		// Groups 0, 1, 2 are within the cap.
7816		assert_eq!(
7817			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7818			0
7819		);
7820		assert_eq!(
7821			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7822			1
7823		);
7824		assert_eq!(
7825			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7826			2
7827		);
7828
7829		// Group 3 is beyond the cap: next_group parks even though cached groups exist.
7830		assert!(
7831			consumer.next_group().now_or_never().is_none(),
7832			"capped consumer must block instead of returning out-of-range groups"
7833		);
7834	}
7835
7836	#[tokio::test]
7837	async fn end_at_release_drains_cached_groups() {
7838		let producer = track_producer("test", None);
7839		let mut consumer = producer.subscribe(replay()).ordered();
7840
7841		for s in 0..6 {
7842			producer.create_group(group::Info { sequence: s }).unwrap();
7843		}
7844
7845		consumer.set_groups(..2);
7846		assert_eq!(
7847			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7848			0
7849		);
7850		assert_eq!(
7851			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7852			1
7853		);
7854		assert!(consumer.next_group().now_or_never().is_none(), "capped at 2");
7855
7856		// Raise the cap; previously-blocked cached groups become available again.
7857		consumer.set_groups(..5);
7858		assert_eq!(
7859			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7860			2
7861		);
7862		assert_eq!(
7863			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7864			3
7865		);
7866		assert_eq!(
7867			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7868			4
7869		);
7870		assert!(consumer.next_group().now_or_never().is_none(), "capped at 5");
7871
7872		// Remove the cap; everything remaining flows.
7873		consumer.set_groups(..);
7874		assert_eq!(
7875			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7876			5
7877		);
7878		assert!(consumer.next_group().now_or_never().is_none(), "no more groups");
7879	}
7880
7881	#[tokio::test]
7882	async fn end_at_lower_than_cursor_parks_consumer() {
7883		let producer = track_producer("test", None);
7884		let mut consumer = producer.subscribe(replay()).ordered();
7885
7886		for s in 0..3 {
7887			producer.create_group(group::Info { sequence: s }).unwrap();
7888		}
7889
7890		// Drain everything with no cap.
7891		assert_eq!(
7892			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7893			0
7894		);
7895		assert_eq!(
7896			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7897			1
7898		);
7899		assert_eq!(
7900			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7901			2
7902		);
7903
7904		// Lower the cap below the cursor. New groups beyond the cap are blocked.
7905		consumer.set_groups(..2);
7906		producer.create_group(group::Info { sequence: 3 }).unwrap();
7907		producer.create_group(group::Info { sequence: 4 }).unwrap();
7908		assert!(
7909			consumer.next_group().now_or_never().is_none(),
7910			"cap is below cursor; nothing returnable until cap rises"
7911		);
7912
7913		// Restoring the cap to no-limit (or any value >= cursor) releases them.
7914		consumer.set_groups(..);
7915		assert_eq!(
7916			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7917			3
7918		);
7919		assert_eq!(
7920			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7921			4
7922		);
7923	}
7924
7925	#[tokio::test]
7926	async fn end_at_toggling_around_late_arrivals() {
7927		let producer = track_producer("test", None);
7928		let mut consumer = producer.subscribe(replay()).ordered();
7929
7930		consumer.set_groups(..6);
7931
7932		// Out-of-order arrivals all within the cap.
7933		producer.create_group(group::Info { sequence: 2 }).unwrap();
7934		producer.create_group(group::Info { sequence: 5 }).unwrap();
7935		producer.create_group(group::Info { sequence: 3 }).unwrap();
7936		// One beyond the cap; should be held even though it arrived in the middle.
7937		producer.create_group(group::Info { sequence: 8 }).unwrap();
7938		producer.create_group(group::Info { sequence: 4 }).unwrap();
7939
7940		// next_group walks in sequence order through everything <= cap.
7941		assert_eq!(
7942			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7943			2
7944		);
7945		assert_eq!(
7946			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7947			3
7948		);
7949		assert_eq!(
7950			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7951			4
7952		);
7953		assert_eq!(
7954			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7955			5
7956		);
7957		// Now blocked: 8 is still beyond the cap.
7958		assert!(consumer.next_group().now_or_never().is_none());
7959
7960		// Raise the cap; cached seq 8 is finally served.
7961		consumer.set_groups(..11);
7962		assert_eq!(
7963			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7964			8
7965		);
7966	}
7967
7968	/// `recv_group` (arrival order) honors the `end_at` cap by parking, like
7969	/// `next_group`: beyond-cap groups are held, not dropped, and a raised cap
7970	/// re-offers them, even after the track finishes.
7971	#[tokio::test]
7972	async fn end_at_parks_recv_group() {
7973		let producer = track_producer("test", None);
7974		let mut consumer = producer.subscribe(replay());
7975
7976		for s in 0..3 {
7977			producer.create_group(group::Info { sequence: s }).unwrap();
7978		}
7979
7980		consumer.set_groups(..2);
7981		assert_eq!(
7982			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7983			0
7984		);
7985		assert_eq!(
7986			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7987			1
7988		);
7989		assert!(consumer.recv_group().now_or_never().is_none(), "capped at 2");
7990
7991		// A finished track keeps the parked group claimable: the cap may rise.
7992		producer.finish().unwrap();
7993		assert!(
7994			consumer.recv_group().now_or_never().is_none(),
7995			"still parked after finish"
7996		);
7997
7998		consumer.set_groups(..);
7999		assert_eq!(
8000			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8001			2
8002		);
8003		assert!(
8004			matches!(consumer.recv_group().now_or_never(), Some(Ok(None))),
8005			"finished once the parked group drains"
8006		);
8007	}
8008
8009	/// A group beyond the cap must not block in-range groups that arrive behind
8010	/// it: a relay can ingest a burst micro-reordered (newest first).
8011	#[tokio::test]
8012	async fn recv_group_serves_arrivals_behind_the_cap() {
8013		let producer = track_producer("test", None);
8014		let mut consumer = producer.subscribe(replay());
8015
8016		consumer.set_groups(..2);
8017
8018		// Reordered burst: the beyond-cap group arrives first.
8019		producer.create_group(group::Info { sequence: 2 }).unwrap();
8020		producer.create_group(group::Info { sequence: 0 }).unwrap();
8021		producer.create_group(group::Info { sequence: 1 }).unwrap();
8022
8023		assert_eq!(
8024			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8025			0
8026		);
8027		assert_eq!(
8028			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8029			1
8030		);
8031		assert!(consumer.recv_group().now_or_never().is_none(), "capped at 2");
8032
8033		consumer.set_groups(..3);
8034		assert_eq!(
8035			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8036			2
8037		);
8038	}
8039
8040	#[tokio::test]
8041	async fn group_ranges_preserve_the_floor_when_the_cap_changes() {
8042		let producer = track_producer("test", None);
8043		let mut consumer = producer.subscribe(None);
8044		consumer.set_groups(2..=2);
8045		producer.create_group(group::Info { sequence: 2 }).unwrap();
8046		assert_eq!(consumer.recv_group().await.unwrap().unwrap().sequence, 2);
8047		consumer.set_groups(..4);
8048		producer.create_group(group::Info { sequence: 1 }).unwrap();
8049		producer.create_group(group::Info { sequence: 3 }).unwrap();
8050		assert_eq!(consumer.recv_group().await.unwrap().unwrap().sequence, 3);
8051		consumer.set_groups(0..=4);
8052		producer.create_group(group::Info { sequence: 0 }).unwrap();
8053		producer.create_group(group::Info { sequence: 4 }).unwrap();
8054		assert_eq!(consumer.recv_group().await.unwrap().unwrap().sequence, 4);
8055	}
8056
8057	/// A raised `start_at` drops parked groups it overtook instead of re-offering
8058	/// them once the cap rises.
8059	#[tokio::test]
8060	async fn start_at_drops_parked_recv_groups() {
8061		let producer = track_producer("test", None);
8062		let mut consumer = producer.subscribe(None);
8063
8064		consumer.set_groups(..1);
8065		producer.create_group(group::Info { sequence: 1 }).unwrap();
8066		assert!(
8067			consumer.recv_group().now_or_never().is_none(),
8068			"group 1 parked at the cap"
8069		);
8070
8071		consumer.start_at(2);
8072		consumer.set_groups(..);
8073		producer.create_group(group::Info { sequence: 2 }).unwrap();
8074		assert_eq!(
8075			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8076			2,
8077			"the overtaken parked group is dropped, not re-offered"
8078		);
8079	}
8080
8081	/// A parked group the producer aborts (eviction/expiry) is dropped: it is
8082	/// neither delivered once the cap rises nor allowed to hold the stream open
8083	/// after the track finishes. This is what bounds parking by the cache policy.
8084	#[tokio::test]
8085	async fn evicted_parked_recv_groups_are_dropped() {
8086		let producer = track_producer("test", None);
8087		let mut consumer = producer.subscribe(None);
8088
8089		producer.create_group(group::Info { sequence: 0 }).unwrap();
8090		assert_eq!(
8091			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8092			0
8093		);
8094
8095		consumer.set_groups(..1);
8096		let straggler = producer.create_group(group::Info { sequence: 1 }).unwrap();
8097		assert!(
8098			consumer.recv_group().now_or_never().is_none(),
8099			"group 1 parked at the cap"
8100		);
8101
8102		// The cache evicts the parked group (abort-as-tombstone), then the track ends.
8103		straggler.abort(Error::Old).unwrap();
8104		producer.finish().unwrap();
8105
8106		consumer.set_groups(..);
8107		assert!(
8108			matches!(consumer.recv_group().now_or_never(), Some(Ok(None))),
8109			"a dead parked group must not be delivered or hold the stream open"
8110		);
8111	}
8112
8113	/// Eviction aborts a parked group behind a sleeping subscriber's back. Nothing
8114	/// else will poll it (the track already finished), so the entry has to carry a
8115	/// waiter or the subscription sleeps forever holding its stream open.
8116	#[tokio::test]
8117	async fn evicted_parked_group_wakes_the_clean_end() {
8118		use std::sync::atomic::{AtomicUsize, Ordering};
8119		use std::task::{Context, Wake};
8120
8121		/// A waker that counts its wakes, for asserting a pending poll left a live
8122		/// registration behind.
8123		struct CountWaker(AtomicUsize);
8124		impl Wake for CountWaker {
8125			fn wake(self: std::sync::Arc<Self>) {
8126				self.0.fetch_add(1, Ordering::SeqCst);
8127			}
8128		}
8129
8130		let producer = track_producer("test", None);
8131		let mut consumer = producer.subscribe(None);
8132
8133		producer.create_group(group::Info { sequence: 0 }).unwrap();
8134		assert_eq!(
8135			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8136			0
8137		);
8138
8139		consumer.set_groups(..1);
8140		let straggler = producer.create_group(group::Info { sequence: 1 }).unwrap();
8141		assert!(consumer.recv_group().now_or_never().is_none(), "parked at the cap");
8142		producer.finish().unwrap();
8143
8144		let counter = std::sync::Arc::new(CountWaker(AtomicUsize::new(0)));
8145		let waker = std::task::Waker::from(counter.clone());
8146		let mut cx = Context::from_waker(&waker);
8147		let mut fut = std::pin::pin!(consumer.recv_group());
8148		assert!(
8149			fut.as_mut().poll(&mut cx).is_pending(),
8150			"the parked group holds it open"
8151		);
8152
8153		straggler.abort(Error::Old).unwrap();
8154		assert!(counter.0.load(Ordering::SeqCst) > 0, "the eviction wakeup was lost");
8155		assert!(matches!(fut.as_mut().poll(&mut cx), Poll::Ready(Ok(None))));
8156	}
8157
8158	/// An exclusive cap at 0 is the empty range: no group is delivered, even group 0.
8159	#[tokio::test]
8160	async fn end_at_zero_is_the_empty_range() {
8161		let producer = track_producer("test", None);
8162		let mut consumer = producer.subscribe(replay());
8163		producer.create_group(group::Info { sequence: 0 }).unwrap();
8164		producer.create_group(group::Info { sequence: 1 }).unwrap();
8165
8166		consumer.set_groups(..0);
8167		assert!(
8168			consumer.recv_group().now_or_never().is_none(),
8169			"empty cap delivers nothing"
8170		);
8171
8172		consumer.set_groups(..1);
8173		assert_eq!(
8174			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8175			0
8176		);
8177		assert!(consumer.recv_group().now_or_never().is_none(), "group 1 stays parked");
8178	}
8179
8180	/// A requested empty range plus a local empty cap still parks while another
8181	/// subscriber keeps aggregate demand unbounded.
8182	#[tokio::test]
8183	async fn empty_local_cap_holds_while_another_subscriber_requests_everything() {
8184		let producer = track_producer("test", None);
8185		let mut everything = producer.subscribe(replay());
8186		let mut empty = producer.subscribe(Subscription::default().with_end(Position::group(0)));
8187		empty.set_groups((Bound::Unbounded, Position::group(0).group_end()));
8188
8189		for s in 0..3 {
8190			producer.create_group(group::Info { sequence: s }).unwrap();
8191		}
8192
8193		assert_eq!(
8194			everything
8195				.recv_group()
8196				.now_or_never()
8197				.unwrap()
8198				.unwrap()
8199				.unwrap()
8200				.sequence,
8201			0
8202		);
8203		assert!(
8204			empty.recv_group().now_or_never().is_none(),
8205			"local empty cap must not ride the unbounded aggregate"
8206		);
8207
8208		empty.set_groups(..2);
8209		assert_eq!(empty.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence, 0);
8210		assert_eq!(empty.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence, 1);
8211		assert!(empty.recv_group().now_or_never().is_none(), "still capped at 2");
8212
8213		empty.set_groups(..);
8214		assert_eq!(empty.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence, 2);
8215	}
8216
8217	/// A frame-limited exclusive end includes the last group and stops before that frame.
8218	#[tokio::test]
8219	async fn end_at_frame_limited_last_group() {
8220		let producer = track_producer("test", None);
8221		let mut consumer = producer.subscribe(replay()).ordered();
8222		let end = Position::after(1, 1).unwrap();
8223		consumer.set_groups((Bound::Unbounded, end.group_end()));
8224
8225		for s in 0..3u64 {
8226			let mut group = producer.create_group(group::Info { sequence: s }).unwrap();
8227			for i in 0..3u8 {
8228				group.write_frame(Timestamp::ZERO, vec![i]).unwrap();
8229			}
8230			group.finish().unwrap();
8231		}
8232
8233		assert_eq!(
8234			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8235			0
8236		);
8237		let mut last = consumer.next_group().now_or_never().unwrap().unwrap().unwrap();
8238		assert_eq!(last.sequence, 1);
8239		last.set_frames(..end.frame);
8240		assert_eq!(
8241			last.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
8242			0
8243		);
8244		assert_eq!(
8245			last.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
8246			1
8247		);
8248		assert!(
8249			last.read_frame().now_or_never().unwrap().unwrap().is_none(),
8250			"frame cap is exclusive"
8251		);
8252		assert!(
8253			consumer.next_group().now_or_never().is_none(),
8254			"group 2 is past the exclusive group cap"
8255		);
8256	}
8257
8258	/// An inclusive bound at the last group withholds nothing.
8259	#[tokio::test]
8260	async fn end_at_maximum_group_is_unbounded() {
8261		let producer = track_producer("test", None);
8262		let mut consumer = producer.subscribe(replay()).ordered();
8263		consumer.set_groups(..=u64::MAX);
8264
8265		producer.create_group(group::Info { sequence: 0 }).unwrap();
8266		producer.create_group(group::Info { sequence: u64::MAX }).unwrap();
8267
8268		assert_eq!(
8269			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8270			0
8271		);
8272		assert_eq!(
8273			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8274			u64::MAX
8275		);
8276	}
8277
8278	#[test]
8279	fn write_frame_rejects_an_oversized_frame_before_appending_its_group() {
8280		let mut producer = track_producer("test", None);
8281		let frame = bytes::Bytes::from(vec![0; group::MAX_CACHE_BYTES as usize + 1]);
8282
8283		assert!(matches!(
8284			producer.write_frame(Timestamp::ZERO, frame),
8285			Err(Error::FrameTooLarge)
8286		));
8287		assert_eq!(producer.latest(), None, "the rejected frame did not publish a group");
8288	}
8289
8290	#[test]
8291	fn append_group_returns_bounds_exceeded_on_sequence_overflow() {
8292		let producer = track_producer("test", None);
8293		{
8294			let mut state = producer.state.write().ok().unwrap();
8295			state.max_sequence = Some(u64::MAX);
8296		}
8297
8298		assert!(matches!(producer.append_group(), Err(Error::BoundsExceeded(_))));
8299	}
8300
8301	#[tokio::test]
8302	async fn fetch_cache_hit() {
8303		let producer = track_producer("test", None);
8304
8305		// Produce a cached group.
8306		let mut group = producer.append_group().unwrap(); // seq 0
8307		group
8308			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hello"))
8309			.unwrap();
8310		group.finish().unwrap();
8311
8312		// A cached group resolves immediately and never queues a request. `peek_group`
8313		// also returns it synchronously.
8314		let dynamic = producer.dynamic();
8315		let consumer = producer.consume();
8316		assert!(consumer.peek_group(0).is_some());
8317		let mut g = consumer.fetch_group(0, None).await.unwrap();
8318		assert_eq!(g.sequence, 0);
8319		assert_eq!(&g.read_frame().await.unwrap().unwrap().payload[..], b"hello");
8320
8321		// Nothing was queued for the dynamic handler to serve.
8322		assert!(dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending());
8323	}
8324
8325	#[tokio::test]
8326	async fn fetch_miss_signals_dynamic() {
8327		let producer = track_producer("test", None);
8328		let dynamic = producer.dynamic();
8329		let consumer = producer.consume();
8330
8331		// A cache miss isn't in `peek_group`, but a dynamic handler exists, so
8332		// `fetch_group` stays pending and queues a request. `*pending` derefs the
8333		// wrapper to the inner `Fetching` (a `kio::Pollable`).
8334		assert!(consumer.peek_group(5).is_none());
8335		let pending = consumer.fetch_group(5, group::Fetch::default().with_priority(7));
8336		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
8337
8338		let req = dynamic
8339			.requested_group()
8340			.now_or_never()
8341			.expect("should not block")
8342			.unwrap();
8343		assert_eq!(req.sequence(), 5);
8344		assert_eq!(req.priority(), 7);
8345
8346		// Serve it by accepting the request; the fetch then resolves.
8347		let mut group = req.accept(None).unwrap();
8348		group
8349			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi"))
8350			.unwrap();
8351		group.finish().unwrap();
8352
8353		let mut g = pending.await.unwrap();
8354		assert_eq!(g.sequence, 5);
8355		assert_eq!(&g.read_frame().await.unwrap().unwrap().payload[..], b"hi");
8356	}
8357
8358	#[tokio::test]
8359	async fn fetch_miss_rejects() {
8360		let producer = track_producer("test", None);
8361		let dynamic = producer.dynamic();
8362		let consumer = producer.consume();
8363
8364		let pending = consumer.fetch_group(5, None);
8365		let req = dynamic
8366			.requested_group()
8367			.now_or_never()
8368			.expect("should not block")
8369			.unwrap();
8370
8371		req.reject(Error::Cancel);
8372		assert!(matches!(pending.await, Err(Error::Cancel)));
8373		let fetch = producer.state.read().fetch.clone();
8374		assert!(fetch.read().is_empty());
8375	}
8376
8377	#[tokio::test]
8378	async fn fetch_miss_drop_rejects() {
8379		let producer = track_producer("test", None);
8380		let dynamic = producer.dynamic();
8381		let consumer = producer.consume();
8382
8383		let pending = consumer.fetch_group(5, None);
8384		let req = dynamic
8385			.requested_group()
8386			.now_or_never()
8387			.expect("should not block")
8388			.unwrap();
8389
8390		drop(req);
8391		assert!(matches!(pending.await, Err(Error::Dropped)));
8392	}
8393
8394	/// A request stays wanted while any joined fetch waits, and once the last leaves it
8395	/// is withdrawn: a later fetch starts a fresh request instead of joining it.
8396	#[tokio::test]
8397	async fn fetch_request_unused_once_every_fetch_leaves() {
8398		let producer = track_producer("test", None);
8399		let dynamic = producer.dynamic();
8400		let consumer = producer.consume();
8401
8402		let first = consumer.fetch_group(5, None);
8403		let req = dynamic
8404			.requested_group()
8405			.now_or_never()
8406			.expect("should not block")
8407			.unwrap();
8408		let second = consumer.fetch_group(5, None);
8409
8410		drop(first);
8411		assert!(req.poll_unused(&kio::Waiter::noop()).is_pending());
8412		drop(second);
8413		assert!(req.poll_unused(&kio::Waiter::noop()).is_ready());
8414
8415		let retry = consumer.fetch_group(5, None);
8416		let fresh = dynamic
8417			.requested_group()
8418			.now_or_never()
8419			.expect("the retry queues a fresh request")
8420			.unwrap();
8421		drop(req);
8422		assert!(fresh.poll_unused(&kio::Waiter::noop()).is_pending());
8423		fresh.accept(None).unwrap().finish().unwrap();
8424		assert_eq!(retry.await.unwrap().sequence, 5);
8425	}
8426
8427	/// Dropping an auto trait from a published type is a semver break, so the group
8428	/// consumer a cached fetch holds must not cost `Fetching` its unwind safety.
8429	#[test]
8430	fn fetching_is_unwind_safe() {
8431		fn assert_unwind_safe<T: std::panic::UnwindSafe + std::panic::RefUnwindSafe>() {}
8432		assert_unwind_safe::<Fetching>();
8433		assert_unwind_safe::<group::Consumer>();
8434	}
8435
8436	/// A fetch that hits the cache holds the group until polled, so a handler that aborts
8437	/// the group once nobody wants it (an abandoned upstream fetch) leaves it alone.
8438	#[tokio::test]
8439	async fn fetch_hit_keeps_the_group_wanted() {
8440		let producer = track_producer("test", None);
8441		let dynamic = producer.dynamic();
8442		let consumer = producer.consume();
8443
8444		let first = consumer.fetch_group(5, None);
8445		let req = dynamic
8446			.requested_group()
8447			.now_or_never()
8448			.expect("should not block")
8449			.unwrap();
8450		let mut group = req.accept(None).unwrap();
8451		group
8452			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
8453			.unwrap();
8454		drop(first.await.unwrap());
8455
8456		let hit = consumer.fetch_group(5, None);
8457		assert!(!group.abort_unused(Error::Cancel), "the pending hit wants the group");
8458		let mut fetched = hit.await.unwrap();
8459		assert_eq!(&fetched.read_frame().await.unwrap().unwrap().payload[..], b"head");
8460	}
8461
8462	/// An accepted group starts where its request did before anyone can see it, so a
8463	/// wider fetch arriving before the handler writes misses instead of being handed a
8464	/// reader that would skip the head it asked for.
8465	#[tokio::test]
8466	async fn accepted_group_starts_at_the_request() {
8467		let producer = track_producer("test", None);
8468		let dynamic = producer.dynamic();
8469		let consumer = producer.consume();
8470
8471		let _tail = consumer.fetch_group(5, group::Fetch::default().with_frame_start(3));
8472		let req = dynamic
8473			.requested_group()
8474			.now_or_never()
8475			.expect("should not block")
8476			.unwrap();
8477		let _group = req.accept(None).unwrap();
8478
8479		let whole = consumer.fetch_group(5, None);
8480		let req = dynamic
8481			.requested_group()
8482			.now_or_never()
8483			.expect("the wider fetch misses and queues its own request")
8484			.unwrap();
8485		assert_eq!(req.frame_start(), 0);
8486		assert!(kio::Pollable::poll(&*whole, &kio::Waiter::noop()).is_pending());
8487	}
8488
8489	/// Once a group nobody wanted is aborted, a later fetch misses rather than reading
8490	/// the abort, and queues a fresh request.
8491	#[tokio::test]
8492	async fn fetch_misses_an_abandoned_group() {
8493		let producer = track_producer("test", None);
8494		let dynamic = producer.dynamic();
8495		let consumer = producer.consume();
8496
8497		let first = consumer.fetch_group(5, None);
8498		let req = dynamic
8499			.requested_group()
8500			.now_or_never()
8501			.expect("should not block")
8502			.unwrap();
8503		let group = req.accept(None).unwrap();
8504		drop(first.await.unwrap());
8505		assert!(group.abort_unused(Error::Cancel));
8506
8507		let retry = consumer.fetch_group(5, None);
8508		let req = dynamic
8509			.requested_group()
8510			.now_or_never()
8511			.expect("the retry queues a fresh request")
8512			.unwrap();
8513		req.accept(None).unwrap().finish().unwrap();
8514		assert!(retry.await.unwrap().read_frame().await.unwrap().is_none());
8515	}
8516
8517	#[tokio::test]
8518	async fn fetch_reject_does_not_poison_retry() {
8519		let producer = track_producer("test", None);
8520		let dynamic = producer.dynamic();
8521		let consumer = producer.consume();
8522
8523		let pending = consumer.fetch_group(5, None);
8524		let req = dynamic
8525			.requested_group()
8526			.now_or_never()
8527			.expect("should not block")
8528			.unwrap();
8529		req.reject(Error::Cancel);
8530		assert!(matches!(pending.await, Err(Error::Cancel)));
8531
8532		let retry = consumer.fetch_group(5, None);
8533		let req = dynamic
8534			.requested_group()
8535			.now_or_never()
8536			.expect("should not block")
8537			.unwrap();
8538		let mut group = req.accept(None).unwrap();
8539		group
8540			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"retry"))
8541			.unwrap();
8542		group.finish().unwrap();
8543
8544		let mut group = retry.await.unwrap();
8545		assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"retry");
8546	}
8547
8548	/// A group cached from a frame-bounded subscription starts partway in. Serving it to
8549	/// someone who asked for the whole group would silently hand back a tail, so it is a
8550	/// miss and the fetch goes upstream instead.
8551	#[tokio::test]
8552	async fn fetch_ignores_a_group_that_starts_too_late() {
8553		let producer = track_producer("test", None);
8554		let dynamic = producer.dynamic();
8555		let consumer = producer.consume();
8556
8557		// The live subscription resumed mid-group, so only the tail is cached.
8558		let mut group = producer.create_group(group::Info { sequence: 0 }).unwrap();
8559		group.start_at(3).unwrap();
8560		group
8561			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"tail"))
8562			.unwrap();
8563		group.finish().unwrap();
8564
8565		// A fetch for the tail is covered and resolves from the cache.
8566		let fetch = consumer.fetch_group(0, group::Fetch::default().with_frame_start(3));
8567		let cached = fetch.now_or_never().expect("covered by the cache").unwrap();
8568		assert_eq!(cached.index(), 3);
8569
8570		// A fetch for the whole group is not, so it queues for a handler rather than
8571		// resolving to the tail.
8572		let mut fetch = std::pin::pin!(consumer.fetch_group(0, None));
8573		assert!(
8574			futures::poll!(fetch.as_mut()).is_pending(),
8575			"must not answer from the tail"
8576		);
8577
8578		let request = dynamic.requested_group().await.unwrap();
8579		assert_eq!((request.sequence(), request.frame_start()), (0, 0));
8580
8581		// Serving it replaces the too-narrow entry rather than colliding with it.
8582		let mut whole = request.accept(None).unwrap();
8583		whole
8584			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
8585			.unwrap();
8586		whole.finish().unwrap();
8587
8588		let mut served = fetch.await.unwrap();
8589		assert_eq!(served.index(), 0);
8590		assert_eq!(
8591			served.read_frame().await.unwrap().unwrap().payload,
8592			bytes::Bytes::from_static(b"head")
8593		);
8594	}
8595
8596	/// Joining a queued fetch widens its range, and a caller that arrives once the range
8597	/// is already on the wire fails cleanly rather than being handed the narrower group.
8598	#[tokio::test]
8599	async fn fetch_widens_or_fails_cleanly() {
8600		let producer = track_producer("test", None);
8601		let dynamic = producer.dynamic();
8602		let consumer = producer.consume();
8603
8604		let _narrow = consumer.fetch_group(0, group::Fetch::default().with_frame_start(5));
8605		let mut narrow = std::pin::pin!(_narrow);
8606		assert!(futures::poll!(narrow.as_mut()).is_pending());
8607
8608		// Still queued: widening is honored, because nothing has read the range yet.
8609		let _wider = consumer.fetch_group(0, group::Fetch::default().with_frame_start(2));
8610		let mut wider = std::pin::pin!(_wider);
8611		assert!(futures::poll!(wider.as_mut()).is_pending());
8612
8613		let request = dynamic.requested_group().await.unwrap();
8614		assert_eq!(request.frame_start(), 2, "widened while queued");
8615
8616		// Handed off now: the handler holds its own copy of the range, so a later wider
8617		// caller cannot move what is already being served.
8618		let _widest = consumer.fetch_group(0, group::Fetch::default().with_frame_start(0));
8619		let mut widest = std::pin::pin!(_widest);
8620		assert!(futures::poll!(widest.as_mut()).is_pending());
8621		assert_eq!(request.frame_start(), 2, "the in-flight range is already on the wire");
8622
8623		// A handler may keep tracking the joined demand past the accept, as the lite
8624		// subscriber does, which holds the result channel open.
8625		let _joined = request.result.clone();
8626		let mut group = request.accept(None).unwrap();
8627		// A handler numbers the frames from where it was asked to start, as `serve_fetch`
8628		// does; that offset is what makes the cached group too narrow for `widest`.
8629		group.start_at(2).unwrap();
8630		group
8631			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"from2"))
8632			.unwrap();
8633
8634		// The two callers it covers resolve; the one it doesn't fails cleanly rather
8635		// than being handed a group that starts above what it asked for, even while
8636		// the group is still being written.
8637		assert_eq!(narrow.await.unwrap().index(), 5);
8638		assert_eq!(wider.await.unwrap().index(), 2);
8639		assert!(matches!(widest.await, Err(Error::NotFound)));
8640		group.finish().unwrap();
8641	}
8642
8643	#[tokio::test]
8644	async fn fetch_coalesces_concurrent() {
8645		let producer = track_producer("test", None);
8646		let dynamic = producer.dynamic();
8647		let consumer = producer.consume();
8648
8649		// Two fetches for the same uncached group produce ONE handler request,
8650		// carrying the higher of the two priorities.
8651		let first = consumer.fetch_group(5, group::Fetch::default().with_priority(1));
8652		let second = consumer.fetch_group(5, group::Fetch::default().with_priority(7));
8653		assert!(kio::Pollable::poll(&*first, &kio::Waiter::noop()).is_pending());
8654
8655		let req = dynamic
8656			.requested_group()
8657			.now_or_never()
8658			.expect("should not block")
8659			.unwrap();
8660		assert_eq!(req.sequence(), 5);
8661		assert_eq!(req.priority(), 7);
8662		assert!(
8663			dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending(),
8664			"the second fetch queued a duplicate request"
8665		);
8666
8667		// A fetch arriving while the request is already in flight joins it too.
8668		let third = consumer.fetch_group(5, None);
8669
8670		// One accept resolves all of them.
8671		let mut group = req.accept(None).unwrap();
8672		group
8673			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi"))
8674			.unwrap();
8675		group.finish().unwrap();
8676
8677		assert_eq!(first.await.unwrap().sequence, 5);
8678		assert_eq!(second.await.unwrap().sequence, 5);
8679		assert_eq!(third.await.unwrap().sequence, 5);
8680	}
8681
8682	#[tokio::test]
8683	async fn fetch_coalesced_reject_fails_all() {
8684		let producer = track_producer("test", None);
8685		let dynamic = producer.dynamic();
8686		let consumer = producer.consume();
8687
8688		let first = consumer.fetch_group(5, None);
8689		let second = consumer.fetch_group(5, None);
8690		let req = dynamic
8691			.requested_group()
8692			.now_or_never()
8693			.expect("should not block")
8694			.unwrap();
8695		req.reject(Error::Cancel);
8696
8697		assert!(matches!(first.await, Err(Error::Cancel)));
8698		assert!(matches!(second.await, Err(Error::Cancel)));
8699
8700		// The rejected attempt is gone: a retry starts a fresh one.
8701		let retry = consumer.fetch_group(5, None);
8702		assert!(kio::Pollable::poll(&*retry, &kio::Waiter::noop()).is_pending());
8703		let req = dynamic
8704			.requested_group()
8705			.now_or_never()
8706			.expect("should not block")
8707			.unwrap();
8708		assert_eq!(req.sequence(), 5);
8709	}
8710
8711	#[tokio::test]
8712	async fn fetch_queued_fails_when_handlers_leave() {
8713		let producer = track_producer("test", None);
8714		let dynamic = producer.dynamic();
8715		let consumer = producer.consume();
8716
8717		// Queued but never popped: the last handler leaving fails it fast.
8718		let pending = consumer.fetch_group(5, None);
8719		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
8720		drop(dynamic);
8721		assert!(matches!(pending.await, Err(Error::NotFound)));
8722
8723		// And the attempt didn't leak.
8724		let fetch = producer.state.read().fetch.clone();
8725		assert!(fetch.read().is_empty());
8726	}
8727
8728	#[tokio::test]
8729	async fn fetch_miss_no_dynamic_not_found() {
8730		// A track with no `Dynamic` can't serve old content, so a cache miss
8731		// resolves to NotFound instead of blocking forever.
8732		let producer = track_producer("test", None);
8733		producer.append_group().unwrap(); // seq 0, but we miss on seq 5
8734		let consumer = producer.consume();
8735		assert!(matches!(consumer.fetch_group(5, None).await, Err(Error::NotFound)));
8736	}
8737
8738	#[tokio::test]
8739	async fn fetch_past_final_not_found() {
8740		let producer = track_producer("test", None);
8741		producer.append_group().unwrap(); // seq 0
8742		producer.finish().unwrap(); // final_sequence = 1
8743
8744		// A group at or past the final sequence can never exist, even with a handler,
8745		// so it resolves to NotFound.
8746		let dynamic = producer.dynamic();
8747		let consumer = producer.consume();
8748		assert!(matches!(consumer.fetch_group(5, None).await, Err(Error::NotFound)));
8749
8750		// And it doesn't signal the dynamic handler.
8751		assert!(dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending());
8752	}
8753
8754	/// Mint a track whose groups charge into a bounded [`cache::Pool`].
8755	fn pooled_producer(capacity: u64) -> (Producer, cache::Pool) {
8756		let config = cache::Config::default()
8757			.with_capacity(capacity)
8758			.with_expiry(cache::DEFAULT_EXPIRY);
8759		let pool = cache::Pool::new(config);
8760		let broadcast = broadcast::Info {
8761			pool: pool.clone(),
8762			..Default::default()
8763		};
8764		let producer = Producer::new(Arc::new(broadcast), "test", None);
8765		(producer, pool)
8766	}
8767
8768	fn finished_group(producer: &mut Producer, size: usize) -> u64 {
8769		let mut group = producer.append_group().unwrap();
8770		group
8771			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; size]))
8772			.unwrap();
8773		group.finish().unwrap();
8774		group.sequence
8775	}
8776
8777	/// While the pool is over capacity, every append accrues debt and pays it by
8778	/// evicting this track's own oldest groups, so the newest content survives.
8779	#[tokio::test]
8780	async fn debt_evicts_oldest_group() {
8781		// Fits one 10k group; each additional group pushes the pool over budget.
8782		let (mut producer, pool) = pooled_producer(10_000);
8783
8784		finished_group(&mut producer, 10_000); // seq 0
8785		finished_group(&mut producer, 10_000); // seq 1: over budget, debt starts accruing
8786		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
8787
8788		let consumer = producer.consume();
8789		assert!(consumer.peek_group(0).is_none(), "oldest group is evicted");
8790		assert!(consumer.peek_group(2).is_some(), "latest group survives");
8791		// Steady state carries the protected live edge plus the just-demoted group
8792		// (debt is charged before the demotion, so eviction lags one append).
8793		assert!(
8794			pool.used() <= 2 * (10_000 + cache::ENTRY_OVERHEAD),
8795			"usage hovers near capacity: {}",
8796			pool.used()
8797		);
8798
8799		// A fresh subscriber skips the evicted groups entirely.
8800		let mut subscriber = producer.subscribe(replay());
8801		assert!(subscriber.assert_group().sequence > 0, "evicted group is not delivered");
8802	}
8803
8804	/// The latest group is never in the eviction order, so it survives any budget.
8805	#[tokio::test]
8806	async fn latest_group_never_evicted() {
8807		// Far too small for even one group: the latest survives anyway.
8808		let (mut producer, pool) = pooled_producer(100);
8809		finished_group(&mut producer, 1000); // seq 0
8810		assert!(pool.used() > 100, "the latest may exceed the budget");
8811
8812		// Later writes evict the demoted seq 0; each new latest is untouchable in turn.
8813		finished_group(&mut producer, 1000); // seq 1: demotes seq 0
8814		finished_group(&mut producer, 1000); // seq 2: pays by evicting seq 0
8815
8816		let consumer = producer.consume();
8817		assert!(consumer.peek_group(0).is_none());
8818		let mut group = consumer.peek_group(2).expect("latest survives");
8819		assert_eq!(group.read_frame().await.unwrap().unwrap().payload.len(), 1000);
8820	}
8821
8822	/// A FETCH cache hit refreshes the group's access time: anything accessed more
8823	/// recently than the pool-wide average is protected, so the eviction walk skips
8824	/// it and evicts a never-read group instead, even one that arrived later.
8825	#[tokio::test]
8826	async fn fetch_refresh_survives_eviction() {
8827		let (mut producer, _pool) = pooled_producer(10_000);
8828		let consumer = producer.consume();
8829
8830		finished_group(&mut producer, 3_000); // seq 0
8831		crate::model::clock::advance(Duration::from_secs(1));
8832		finished_group(&mut producer, 3_000); // seq 1
8833		crate::model::clock::advance(Duration::from_secs(1));
8834		finished_group(&mut producer, 3_000); // seq 2
8835		crate::model::clock::advance(Duration::from_millis(500));
8836
8837		// FETCH seq 0: the cache hit lifts its access time above the average.
8838		let mut fetched = consumer.fetch_group(0, None).await.unwrap();
8839		assert_eq!(fetched.read_frame().await.unwrap().unwrap().payload.len(), 3_000);
8840		crate::model::clock::advance(Duration::from_millis(500));
8841
8842		// Pressure: seq 0 is first in eviction order but freshly accessed, so it
8843		// rotates to the back and the never-read seq 1 dies instead.
8844		finished_group(&mut producer, 3_000); // seq 3
8845		crate::model::clock::advance(Duration::from_secs(1));
8846		finished_group(&mut producer, 3_000); // seq 4
8847
8848		assert!(consumer.peek_group(0).is_some(), "refreshed group survives");
8849		assert!(consumer.peek_group(1).is_none(), "unread group is evicted instead");
8850	}
8851
8852	/// A consumer holding an evicted group surfaces the eviction, not a hang or a
8853	/// truncated clean end.
8854	#[tokio::test]
8855	async fn eviction_aborts_readers() {
8856		let (mut producer, _pool) = pooled_producer(10_000);
8857		let mut subscriber = producer.subscribe(None);
8858
8859		finished_group(&mut producer, 10_000); // seq 0
8860		let mut group0 = subscriber.assert_group();
8861
8862		finished_group(&mut producer, 10_000); // seq 1: demotes seq 0
8863		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
8864
8865		let read = group0.read_frame().await;
8866		assert!(matches!(read, Err(Error::Evicted)), "expected Evicted, got {read:?}");
8867	}
8868
8869	/// A write smaller than the next victim carries debt instead of evicting: a
8870	/// large group dies only once enough debt accumulates, never to pay off a
8871	/// far smaller write.
8872	#[tokio::test]
8873	async fn small_writes_carry_debt() {
8874		// Payloads dwarf the fixed per-group charge, so the budget arithmetic below is
8875		// about bytes written rather than bookkeeping.
8876		let unit = 100 * cache::ENTRY_OVERHEAD;
8877		let (mut producer, pool) = pooled_producer(22 * unit);
8878		let consumer = producer.consume();
8879
8880		finished_group(&mut producer, 20 * unit as usize); // seq 0, the large victim-to-be
8881
8882		// The first few small writes owe far less than seq 0's size: the debt
8883		// carries over instead of evicting it.
8884		for _ in 0..3 {
8885			finished_group(&mut producer, unit as usize);
8886		}
8887		assert!(consumer.peek_group(0).is_some(), "debt smaller than the victim carries");
8888
8889		// Enough small writes accumulate the debt to finally evict it.
8890		for _ in 0..20 {
8891			finished_group(&mut producer, unit as usize);
8892		}
8893		assert!(
8894			consumer.peek_group(0).is_none(),
8895			"accumulated debt evicts the large group"
8896		);
8897		// Steady state hovers within about one group of capacity: a victim smaller
8898		// than the outstanding debt is never evicted, so the excess stays bounded.
8899		assert!(pool.used() <= 24 * unit, "usage hovers near capacity: {}", pool.used());
8900	}
8901
8902	/// One write pays at most twice what it produced, so a capacity shrink (or one
8903	/// track's burst) drains gradually instead of one writer dumping its whole
8904	/// backlog in a single call.
8905	#[tokio::test]
8906	async fn payment_capped_per_write() {
8907		let (mut producer, pool) = pooled_producer(1 << 40);
8908		for _ in 0..10 {
8909			finished_group(&mut producer, 1_000);
8910		}
8911
8912		// The governor slashes the target; nothing is reclaimed synchronously.
8913		pool.resize(100);
8914		let before = pool.used();
8915
8916		// One 1k write may evict at most ~2k of backlog, not all ten groups.
8917		finished_group(&mut producer, 1_000);
8918
8919		let consumer = producer.consume();
8920		assert!(consumer.peek_group(0).is_none(), "the oldest groups are evicted");
8921		assert!(consumer.peek_group(1).is_none());
8922		assert!(consumer.peek_group(2).is_some(), "the backlog drains gradually");
8923		assert!(pool.used() > before - 4_000, "one write must not dump the backlog");
8924	}
8925
8926	/// Accepting a track after pre-accept backfill must keep the same write
8927	/// counter: the counter is owned by the track state, so replacing the info
8928	/// can't strand the bytes already-created groups keep charging.
8929	#[tokio::test]
8930	async fn accept_preserves_write_accounting() {
8931		let config = cache::Config::default()
8932			.with_capacity(12_000)
8933			.with_expiry(cache::DEFAULT_EXPIRY);
8934		let pool = cache::Pool::new(config);
8935		let broadcast = broadcast::Info {
8936			pool: pool.clone(),
8937			..Default::default()
8938		};
8939		let request = Request::new(Arc::new(broadcast), "test");
8940		let dynamic = request.dynamic();
8941		let consumer = request.consume();
8942
8943		// Serve a backfill before the track is accepted, then grow it.
8944		let pending = consumer.fetch_group(0, None);
8945		let req = dynamic
8946			.requested_group()
8947			.now_or_never()
8948			.expect("should not block")
8949			.unwrap();
8950		let mut backfill = req.accept(None).unwrap();
8951		pending.await.unwrap();
8952		backfill
8953			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 30_000]))
8954			.unwrap();
8955
8956		// Accept with a fresh Info: the pre-accept group's writes must still be
8957		// drained by this track's future charges.
8958		let producer = request.accept(None);
8959		producer.append_group().unwrap().finish().unwrap();
8960		producer.append_group().unwrap().finish().unwrap();
8961
8962		assert!(
8963			producer.consume().peek_group(0).is_none(),
8964			"pre-accept backfill growth is reclaimed after accept"
8965		);
8966		assert!(pool.used() <= 13_000, "usage converges: {}", pool.used());
8967	}
8968
8969	/// A cache only a fetch handler fills has no live feed, so its newest fetched object
8970	/// is not the track's largest. Accepting the request feeds it live until the producer goes.
8971	#[tokio::test]
8972	async fn a_fetch_only_cache_is_not_live() {
8973		let request = Request::new(Arc::new(broadcast::Info::default()), "test");
8974		let dynamic = request.dynamic();
8975		let consumer = request.consume();
8976
8977		let pending = consumer.fetch_group(2, None);
8978		let req = dynamic
8979			.requested_group()
8980			.now_or_never()
8981			.expect("should not block")
8982			.unwrap();
8983		let mut fetched = req.accept(None).unwrap();
8984		fetched
8985			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"2-0"))
8986			.unwrap();
8987		fetched.finish().unwrap();
8988		pending.await.unwrap();
8989		assert!(!consumer.is_live(), "a fetch-only cache is not live");
8990
8991		let producer = request.accept(None);
8992		assert!(consumer.is_live(), "an accepted track is live");
8993
8994		// The fetch handler keeps the track alive like a producer clone, so both go.
8995		drop(dynamic);
8996		drop(producer);
8997		assert!(!consumer.is_live(), "a track whose producer went is not live");
8998	}
8999
9000	/// Re-serving a sequence many times must not accumulate eviction hints: stale
9001	/// hints die on stamp mismatch and compaction reclaims them.
9002	#[tokio::test]
9003	async fn recreated_sequence_bounds_eviction_hints() {
9004		let (producer, _pool) = pooled_producer(1 << 40);
9005		producer.create_group(5u64.into()).unwrap().finish().unwrap();
9006
9007		for _ in 0..200 {
9008			let group = producer.create_group(1u64.into()).unwrap();
9009			group.abort(Error::Cancel).unwrap();
9010		}
9011
9012		let state = producer.state.read();
9013		assert!(
9014			state.evict.len() <= 2 * state.lookup.len() + EVICT_SLACK,
9015			"stale hints are compacted: {} entries for {} slots",
9016			state.evict.len(),
9017			state.lookup.len()
9018		);
9019	}
9020
9021	/// A frame write within the same coarse tick still outranks merely-inserted
9022	/// content, so the freshly-written group survives and the empty one pays.
9023	#[tokio::test]
9024	async fn same_tick_write_outranks_inserted() {
9025		// Payloads dwarf the fixed per-group charge, so the budget arithmetic below is
9026		// about bytes written rather than bookkeeping.
9027		let unit = 100 * cache::ENTRY_OVERHEAD;
9028		// No time advances: every stamp lands in the same tick.
9029		let (mut producer, _pool) = pooled_producer(10 * unit);
9030
9031		producer.append_group().unwrap().finish().unwrap(); // seq 0: empty
9032		finished_group(&mut producer, 3 * unit as usize); // seq 1: written
9033		finished_group(&mut producer, 3 * unit as usize); // seq 2
9034		finished_group(&mut producer, 3 * unit as usize); // seq 3
9035		finished_group(&mut producer, 3 * unit as usize); // seq 4: over budget, pays
9036
9037		let consumer = producer.consume();
9038		assert!(consumer.peek_group(0).is_none(), "insert-only content pays first");
9039		assert!(consumer.peek_group(1).is_some(), "same-tick written content survives");
9040	}
9041
9042	/// A track that only appends frames to an open group, never inserting another
9043	/// group, still settles its eviction debt once enough bytes accumulate.
9044	#[tokio::test]
9045	async fn frame_only_writer_pays() {
9046		let (producer, pool) = pooled_producer(2_000);
9047		let mut demoted = producer.append_group().unwrap(); // seq 0
9048		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
9049
9050		// One large frame crosses the charge threshold: the write itself pays,
9051		// with no further group insert on this track.
9052		demoted
9053			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 300_000]))
9054			.unwrap();
9055
9056		assert!(
9057			pool.used() <= 5_000,
9058			"the frame write settled the debt: {}",
9059			pool.used()
9060		);
9061		assert!(matches!(demoted.finish(), Err(Error::Evicted)));
9062	}
9063
9064	/// One `Info` describing several tracks must not join their eviction accounting:
9065	/// each track opens its own account against the pool.
9066	#[tokio::test]
9067	async fn each_track_owns_its_account() {
9068		let broadcast = Arc::new(broadcast::Info::default());
9069		let info = Info::default();
9070		let a = Producer::new(broadcast.clone(), "a", info.clone());
9071		let b = Producer::new(broadcast, "b", info);
9072
9073		let a = a.state.read().cache.clone();
9074		let b = b.state.read().cache.clone();
9075		assert!(!Arc::ptr_eq(&a, &b), "each track owns its account");
9076	}
9077
9078	/// A `Dynamic` still serving fetches keeps the track alive, so the publisher
9079	/// letting go isn't an abrupt teardown: the handler can still serve the cache.
9080	#[tokio::test]
9081	async fn a_dynamic_defers_teardown() {
9082		let (mut producer, pool) = pooled_producer(1 << 40);
9083		let dynamic = producer.dynamic();
9084		finished_group(&mut producer, 100);
9085
9086		drop(producer);
9087		assert!(pool.used() > 0, "the handler still serves the cache");
9088
9089		drop(dynamic);
9090		assert_eq!(pool.used(), 0, "the last handle tears it down");
9091	}
9092
9093	/// A finished track releases everything once every handle is gone.
9094	///
9095	/// Its groups hold the cache account, and the account links back here, so that link
9096	/// has to be weak: anything stronger makes the state (and every cached frame in it)
9097	/// immortal, even with no producer or consumer left.
9098	#[tokio::test]
9099	async fn finished_track_frees_its_cache() {
9100		let (mut producer, pool) = pooled_producer(1 << 40);
9101		finished_group(&mut producer, 100);
9102		producer.finish().unwrap();
9103
9104		let state = producer.state.downgrade();
9105		drop(producer);
9106
9107		assert!(state.upgrade().is_none(), "the track state is freed");
9108		assert_eq!(pool.used(), 0, "so are its cached bytes");
9109	}
9110
9111	/// A group settling its eviction debt upgrades the account's weak handle, which
9112	/// counts as a producer on the track state. Teardown must not mistake that for a
9113	/// surviving publisher, or an abrupt drop silently behaves like a clean finish.
9114	#[tokio::test]
9115	async fn teardown_ignores_a_settling_group() {
9116		let (producer, pool) = pooled_producer(1 << 40);
9117		// Open, so only the abrupt teardown releases it.
9118		let mut group = producer.append_group().unwrap();
9119		group
9120			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
9121			.unwrap();
9122		drop(group);
9123
9124		// Stand in for a concurrent `cache::Track::settle`, mid-upgrade.
9125		let settling = producer.state.downgrade().upgrade().expect("open");
9126		drop(producer);
9127
9128		assert_eq!(pool.used(), 0, "the abrupt teardown still released the cache");
9129		drop(settling);
9130	}
9131
9132	/// A subscriber holding one cached group must not pin the whole track: a group
9133	/// carries the track's properties by value, not a handle back to its state.
9134	#[tokio::test]
9135	async fn cached_group_outlives_its_track() {
9136		let (mut producer, pool) = pooled_producer(1 << 40);
9137		let sequence = finished_group(&mut producer, 100);
9138		let group = producer.consume().peek_group(sequence).expect("cached");
9139		producer.finish().unwrap();
9140
9141		let state = producer.state.downgrade();
9142		drop(producer);
9143		assert!(state.upgrade().is_none(), "the track state is freed");
9144		assert!(pool.used() > 0, "the retained group keeps its own bytes");
9145
9146		drop(group);
9147		assert_eq!(pool.used(), 0, "which it releases when dropped");
9148	}
9149
9150	/// A backfill served before the track was accepted settles its own debt: the
9151	/// account exists from the moment the state does, so acceptance replacing the
9152	/// `Info` can't leave already-created groups writing for free.
9153	#[tokio::test]
9154	async fn pre_accept_backfill_settles_late_writes() {
9155		let config = cache::Config::default()
9156			.with_capacity(2_000)
9157			.with_expiry(cache::DEFAULT_EXPIRY);
9158		let pool = cache::Pool::new(config);
9159		let broadcast = broadcast::Info {
9160			pool: pool.clone(),
9161			..Default::default()
9162		};
9163		let request = Request::new(Arc::new(broadcast), "test");
9164		let dynamic = request.dynamic();
9165		let consumer = request.consume();
9166
9167		// Serve backfill seq 0 before the track is accepted.
9168		let pending = consumer.fetch_group(0, None);
9169		let req = dynamic
9170			.requested_group()
9171			.now_or_never()
9172			.expect("should not block")
9173			.unwrap();
9174		let mut backfill = req.accept(None).unwrap();
9175		pending.await.unwrap();
9176
9177		// Accept, then demote the backfill with a live group.
9178		let producer = request.accept(None);
9179		producer.append_group().unwrap().finish().unwrap();
9180
9181		// No further insert: the late write into the demoted backfill is the only
9182		// thing that can pay the debt it just took on.
9183		backfill
9184			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 300_000]))
9185			.unwrap();
9186
9187		assert!(
9188			pool.used() <= 5_000,
9189			"the frame write settled the debt: {}",
9190			pool.used()
9191		);
9192	}
9193
9194	/// A late frame write restarts the LRU clock (the window measures time since
9195	/// last written or fetched), so an actively-growing group is not expired as
9196	/// idle mid-write.
9197	#[tokio::test]
9198	async fn write_restarts_retention_clock() {
9199		let (producer, _pool) = pooled_producer(1 << 40);
9200		let mut straggler = producer.append_group().unwrap(); // seq 0
9201		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
9202
9203		// Idle past the window, then the straggler receives a late frame.
9204		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
9205		straggler
9206			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
9207			.unwrap();
9208		producer.append_group().unwrap().finish().unwrap(); // seq 2 runs expiry
9209
9210		let consumer = producer.consume();
9211		assert!(consumer.peek_group(0).is_some(), "the write restarted the clock");
9212
9213		// Once the writes stop, the group ages out normally.
9214		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
9215		producer.append_group().unwrap().finish().unwrap(); // seq 3 runs expiry
9216		assert!(consumer.peek_group(0).is_none(), "idle content still expires");
9217	}
9218
9219	/// Continuously refreshed entries at the front of the eviction order must not
9220	/// starve expiry of entries behind them: the scan cursor rotates.
9221	#[tokio::test]
9222	async fn refreshed_front_does_not_starve_expiry() {
9223		let (producer, _pool) = pooled_producer(1 << 40);
9224		let dynamic = producer.dynamic();
9225		let consumer = producer.consume();
9226
9227		producer.create_group(10u64.into()).unwrap().finish().unwrap();
9228		for sequence in 1..=5u64 {
9229			let pending = consumer.fetch_group(sequence, None);
9230			let req = dynamic
9231				.requested_group()
9232				.now_or_never()
9233				.expect("should not block")
9234				.unwrap();
9235			let mut group = req.accept(None).unwrap();
9236			group
9237				.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
9238				.unwrap();
9239			group.finish().unwrap();
9240			pending.await.unwrap();
9241		}
9242
9243		// Age everything out, then refresh the first four backfills so they sit
9244		// fresh at the front of the eviction order, hiding the expired fifth.
9245		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
9246		for sequence in 1..=4u64 {
9247			consumer.fetch_group(sequence, None).await.unwrap();
9248		}
9249
9250		// The rotating cursor reaches the fifth entry within a few writes.
9251		for _ in 0..3 {
9252			producer.append_group().unwrap().finish().unwrap();
9253		}
9254		assert!(consumer.peek_group(5).is_none(), "expired backfill is reclaimed");
9255		assert!(consumer.peek_group(1).is_some(), "refreshed backfill survives");
9256	}
9257
9258	/// A publisher re-creating an aborted sequence is delivered exactly once, at
9259	/// its actual arrival position: the historical arrival entry is dead.
9260	#[tokio::test]
9261	async fn recreated_sequence_delivered_once() {
9262		let (producer, _pool) = pooled_producer(1 << 40);
9263
9264		producer.create_group(0u64.into()).unwrap().finish().unwrap();
9265		let aborted = producer.create_group(1u64.into()).unwrap();
9266		aborted.abort(Error::Cancel).unwrap();
9267		producer.create_group(2u64.into()).unwrap().finish().unwrap();
9268		producer.create_group(1u64.into()).unwrap().finish().unwrap();
9269
9270		let mut subscriber = producer.subscribe(replay());
9271		assert_eq!(subscriber.assert_group().sequence, 0);
9272		assert_eq!(subscriber.assert_group().sequence, 2);
9273		assert_eq!(
9274			subscriber.assert_group().sequence,
9275			1,
9276			"replacement arrives at its own position"
9277		);
9278		subscriber.assert_no_group();
9279	}
9280
9281	/// Datagrams share `max_sequence` but must not break group demotion: the live
9282	/// edge is tracked per group, so interleaving datagrams can't strand groups
9283	/// outside the eviction order and bypass the budget.
9284	#[tokio::test]
9285	async fn datagrams_do_not_block_eviction() {
9286		let (mut producer, pool) = pooled_producer(1_000);
9287		for _ in 0..10 {
9288			finished_group(&mut producer, 1_000);
9289			producer.append_datagram(Timestamp::ZERO, &b"beat"[..]).unwrap();
9290		}
9291
9292		let consumer = producer.consume();
9293		assert!(consumer.peek_group(0).is_none(), "old groups still evict");
9294		assert!(
9295			pool.used() < 4 * 1_256,
9296			"interleaved datagrams must not bypass the budget: {}",
9297			pool.used()
9298		);
9299	}
9300
9301	/// An aborted group releases its access sample along with its bytes, from any
9302	/// handle: ghost samples must not linger in the pool mean where they'd hold it
9303	/// in the past and over-protect every live group.
9304	#[tokio::test]
9305	async fn aborted_group_leaves_no_ghost_sample() {
9306		let (producer, pool) = pooled_producer(1 << 40);
9307		let group0 = producer.append_group().unwrap();
9308		producer.append_group().unwrap(); // demotes seq 0 into the mean
9309
9310		assert!(pool.average().is_some(), "demoted group is sampled");
9311		group0.abort(Error::Cancel).unwrap();
9312		assert_eq!(pool.average(), None, "the abort must remove the sample");
9313	}
9314
9315	/// Empty groups still carry fixed overhead; they must repay the budget when
9316	/// evicted rather than being unevictable freeloaders.
9317	#[tokio::test]
9318	async fn empty_groups_repay_overhead() {
9319		let (producer, pool) = pooled_producer(1_000);
9320		for _ in 0..100 {
9321			let group = producer.append_group().unwrap();
9322			group.finish().unwrap();
9323		}
9324
9325		assert!(
9326			pool.used() <= 3_000,
9327			"empty-group overhead must stay near the budget: {}",
9328			pool.used()
9329		);
9330	}
9331
9332	/// Late growth on an already-demoted group is billed: the gross-write counter
9333	/// feeds debt on the next append, so a straggler can't grow unbounded.
9334	#[tokio::test]
9335	async fn growth_on_demoted_group_is_billed() {
9336		let (producer, pool) = pooled_producer(2_000);
9337		let mut straggler = producer.append_group().unwrap(); // seq 0
9338		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
9339
9340		// The demoted group balloons: no eviction yet (nothing ran), but billed.
9341		straggler
9342			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 10_000]))
9343			.unwrap();
9344
9345		// The next append observes the growth and evicts the straggler.
9346		producer.append_group().unwrap().finish().unwrap(); // seq 2
9347
9348		let consumer = producer.consume();
9349		assert!(consumer.peek_group(0).is_none(), "the ballooned group is evicted");
9350		assert!(pool.used() <= 3_000, "growth is reclaimed: {}", pool.used());
9351	}
9352
9353	/// A stale arrival entry whose sequence was later re-served by fetched backfill
9354	/// must not leak the replacement into arrival-order subscriptions.
9355	#[tokio::test]
9356	async fn refilled_sequence_stays_out_of_subscriptions() {
9357		let (producer, _pool) = pooled_producer(1 << 40);
9358		let dynamic = producer.dynamic();
9359		let consumer = producer.consume();
9360
9361		producer.create_group(0u64.into()).unwrap().finish().unwrap();
9362		let aborted = producer.create_group(1u64.into()).unwrap();
9363		aborted.abort(Error::Cancel).unwrap();
9364		producer.create_group(2u64.into()).unwrap().finish().unwrap();
9365
9366		// Re-serve seq 1 as backfill; its slot replaces the aborted one, and the
9367		// old arrival entry for seq 1 now resolves to it.
9368		let pending = consumer.fetch_group(1, None);
9369		let req = dynamic
9370			.requested_group()
9371			.now_or_never()
9372			.expect("should not block")
9373			.unwrap();
9374		let mut group = req.accept(None).unwrap();
9375		group
9376			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill"))
9377			.unwrap();
9378		group.finish().unwrap();
9379		pending.await.unwrap();
9380
9381		// The backfill serves by sequence, but never in arrival order.
9382		assert!(consumer.peek_group(1).is_some());
9383		let mut subscriber = producer.subscribe(replay());
9384		assert_eq!(subscriber.assert_group().sequence, 0);
9385		assert_eq!(subscriber.assert_group().sequence, 2);
9386		subscriber.assert_no_group();
9387	}
9388
9389	/// An expired backfill can't hide behind a refreshed one: the eviction-order
9390	/// expiry scans a bounded prefix instead of stopping at the first fresh entry.
9391	#[tokio::test]
9392	async fn expired_backfill_behind_refreshed_reclaimed() {
9393		let (producer, _pool) = pooled_producer(1 << 40);
9394		let dynamic = producer.dynamic();
9395		let consumer = producer.consume();
9396
9397		producer.create_group(5u64.into()).unwrap().finish().unwrap();
9398		for sequence in [2u64, 3u64] {
9399			let pending = consumer.fetch_group(sequence, None);
9400			let req = dynamic
9401				.requested_group()
9402				.now_or_never()
9403				.expect("should not block")
9404				.unwrap();
9405			let mut group = req.accept(None).unwrap();
9406			group
9407				.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
9408				.unwrap();
9409			group.finish().unwrap();
9410			pending.await.unwrap();
9411		}
9412
9413		// Keep seq 2 fresh while seq 3 (behind it in eviction order) expires.
9414		crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2 + Duration::from_secs(1));
9415		consumer.fetch_group(2, None).await.unwrap();
9416		crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2 + Duration::from_secs(1));
9417		producer.create_group(6u64.into()).unwrap().finish().unwrap();
9418
9419		let consumer = producer.consume();
9420		assert!(consumer.peek_group(2).is_some(), "refreshed backfill survives");
9421		assert!(consumer.peek_group(3).is_none(), "expired backfill is reclaimed");
9422	}
9423
9424	/// A FETCH hit within the same coarse clock tick still protects the group: the
9425	/// refresh stamps one tick ahead, so it reads strictly newer than the mean.
9426	#[tokio::test]
9427	async fn same_tick_fetch_protects() {
9428		// No time advances at all: every timestamp lands in the same tick.
9429		let (mut producer, _pool) = pooled_producer(10_000);
9430		let consumer = producer.consume();
9431
9432		finished_group(&mut producer, 3_000); // seq 0
9433		finished_group(&mut producer, 3_000); // seq 1
9434		finished_group(&mut producer, 3_000); // seq 2
9435
9436		consumer.fetch_group(0, None).await.unwrap();
9437
9438		finished_group(&mut producer, 3_000); // seq 3
9439		finished_group(&mut producer, 3_000); // seq 4
9440
9441		assert!(consumer.peek_group(0).is_some(), "same-tick refresh protects");
9442		assert!(consumer.peek_group(1).is_none(), "the unread group dies instead");
9443	}
9444
9445	/// A refetched group that reclaims max_sequence is the live edge again: it must
9446	/// not re-enter the eviction order, or memory pressure could evict the newest
9447	/// content.
9448	#[tokio::test]
9449	async fn refetched_latest_stays_protected() {
9450		let (producer, _pool) = pooled_producer(10_000);
9451		let dynamic = producer.dynamic();
9452		let consumer = producer.consume();
9453
9454		let straggler = producer.append_group().unwrap(); // seq 0
9455
9456		// The publisher aborts its own latest group; the sequence stays at the live edge.
9457		let latest = producer.append_group().unwrap(); // seq 1
9458		latest.abort(Error::Cancel).unwrap();
9459
9460		// Re-fetch it: the replacement takes over max_sequence.
9461		let pending = consumer.fetch_group(1, None);
9462		let req = dynamic
9463			.requested_group()
9464			.now_or_never()
9465			.expect("should not block")
9466			.unwrap();
9467		let mut group = req.accept(None).unwrap();
9468		group
9469			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000]))
9470			.unwrap();
9471		group.finish().unwrap();
9472		pending.await.unwrap();
9473
9474		// The refetched latest is protected by omission: it has no entry in the
9475		// eviction order, so no amount of debt can select it.
9476		{
9477			let state = producer.state.read();
9478			assert!(state.lookup.contains_key(&1), "refetched group is cached");
9479			assert!(
9480				state.evict.iter().all(|(sequence, _)| *sequence != 1),
9481				"the live edge must not be an eviction candidate"
9482			);
9483		}
9484		drop(straggler);
9485	}
9486
9487	/// An evicted group is a cache miss, so a fetch re-fetches it and the accepted
9488	/// replacement serves the sequence again (not `Error::Duplicate`).
9489	#[tokio::test]
9490	async fn eviction_allows_refetch() {
9491		let (mut producer, _pool) = pooled_producer(10_000);
9492		let dynamic = producer.dynamic();
9493
9494		finished_group(&mut producer, 10_000); // seq 0
9495		finished_group(&mut producer, 10_000); // seq 1: demotes seq 0
9496		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
9497
9498		let consumer = producer.consume();
9499		assert!(consumer.peek_group(0).is_none());
9500		let pending = consumer.fetch_group(0, None);
9501
9502		let req = dynamic
9503			.requested_group()
9504			.now_or_never()
9505			.expect("should not block")
9506			.unwrap();
9507		assert_eq!(req.sequence(), 0);
9508
9509		let mut group = req.accept(None).unwrap();
9510		group
9511			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"refetched"))
9512			.unwrap();
9513		group.finish().unwrap();
9514
9515		let mut group = pending.await.unwrap();
9516		assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"refetched");
9517	}
9518
9519	/// An aborted group is dead whatever frame it starts at: the sequence is claimable
9520	/// again, and a cache lookup misses rather than handing back a slot that can no
9521	/// longer serve it.
9522	///
9523	/// The abort and the group's first frame decide this together, so they are read
9524	/// under one guard. Read separately, the abort can land between them and the slot
9525	/// answers as a live duplicate on the strength of an offset it only still has
9526	/// because it died. That interleaving is what the single guard rules out; this
9527	/// pins the committed semantics it has to preserve.
9528	#[test]
9529	fn an_aborted_group_releases_its_sequence() {
9530		let producer = track_producer("test", None);
9531		let consumer = producer.consume();
9532
9533		let mut group = producer.create_group(group::Info { sequence: 3 }).unwrap();
9534		group
9535			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
9536			.unwrap();
9537
9538		// While it lives the slot answers, so the sequence is taken.
9539		assert!(matches!(
9540			producer.create_group(group::Info { sequence: 3 }),
9541			Err(Error::Duplicate)
9542		));
9543		assert!(consumer.peek_group(3).is_some());
9544
9545		group.abort(Error::Cancel).unwrap();
9546
9547		assert!(consumer.peek_group(3).is_none(), "an aborted slot is a cache miss");
9548		producer
9549			.create_group(group::Info { sequence: 3 })
9550			.expect("an aborted slot releases its sequence")
9551			.finish()
9552			.unwrap();
9553	}
9554
9555	/// A fetched (backfill) group is served by sequence but never replayed to
9556	/// arrival-order subscribers.
9557	#[tokio::test]
9558	async fn fetched_backfill_not_subscribed() {
9559		let (producer, _pool) = pooled_producer(1 << 40);
9560		let dynamic = producer.dynamic();
9561		let consumer = producer.consume();
9562
9563		// The publisher starts at seq 5; earlier groups exist only upstream.
9564		producer.create_group(5u64.into()).unwrap().finish().unwrap();
9565		producer.create_group(6u64.into()).unwrap().finish().unwrap();
9566
9567		// Fetch the gap: it lands in the cache and resolves the fetch...
9568		let pending = consumer.fetch_group(2, None);
9569		let req = dynamic
9570			.requested_group()
9571			.now_or_never()
9572			.expect("should not block")
9573			.unwrap();
9574		let mut group = req.accept(None).unwrap();
9575		group
9576			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill"))
9577			.unwrap();
9578		group.finish().unwrap();
9579		let mut fetched = pending.await.unwrap();
9580		assert_eq!(&fetched.read_frame().await.unwrap().unwrap().payload[..], b"backfill");
9581		assert!(consumer.peek_group(2).is_some(), "backfill is cached for later fetches");
9582
9583		// ...but an arrival-order subscriber only sees the live groups.
9584		let mut subscriber = producer.subscribe(replay());
9585		assert_eq!(subscriber.assert_group().sequence, 5);
9586		assert_eq!(subscriber.assert_group().sequence, 6);
9587		subscriber.assert_no_group();
9588	}
9589
9590	/// Fetched backfill isn't in arrival order, so it ages out through the eviction
9591	/// order instead of lingering until the track closes.
9592	#[tokio::test]
9593	async fn expired_backfill_reclaimed() {
9594		let (producer, pool) = pooled_producer(1 << 40);
9595		let dynamic = producer.dynamic();
9596		let consumer = producer.consume();
9597
9598		producer.create_group(5u64.into()).unwrap().finish().unwrap();
9599
9600		// Serve a backfill fetch for an old sequence.
9601		let pending = consumer.fetch_group(2, None);
9602		let req = dynamic
9603			.requested_group()
9604			.now_or_never()
9605			.expect("should not block")
9606			.unwrap();
9607		let mut group = req.accept(None).unwrap();
9608		group
9609			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000]))
9610			.unwrap();
9611		group.finish().unwrap();
9612		pending.await.unwrap();
9613		let used = pool.used();
9614
9615		// Age past the pool's LRU window; the next write reclaims the backfill.
9616		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
9617		producer.create_group(6u64.into()).unwrap().finish().unwrap();
9618
9619		assert!(consumer.peek_group(2).is_none(), "expired backfill is reclaimed");
9620		assert!(pool.used() < used, "its bytes are released");
9621	}
9622
9623	#[tokio::test]
9624	async fn fetch_aborts_with_track() {
9625		let producer = track_producer("test", None);
9626		let dynamic = producer.dynamic();
9627		let consumer = producer.consume();
9628
9629		let pending = consumer.fetch_group(3, None);
9630		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
9631
9632		producer.abort(Error::Cancel).unwrap();
9633		assert!(pending.await.is_err());
9634		drop(dynamic);
9635	}
9636}