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	/// Poll for the request becoming unused (every waiting [`Consumer::fetch_group`]
3214	/// dropped), so a handler can stop serving and drop the request.
3215	///
3216	/// Once ready, a later fetch of the group starts a fresh request rather than
3217	/// joining this abandoned one.
3218	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
3219		loop {
3220			ready!(self.result.poll_unused(waiter));
3221			// Fetches join under the fetch lock, so re-checking under it makes the
3222			// withdrawal atomic with a join racing the last one leaving.
3223			let mut fetch = self.fetch.lock();
3224			if self.result.is_used() {
3225				continue;
3226			}
3227			fetch.remove_if(&self.sequence, |pending| pending.result.same_channel(&self.result));
3228			return Poll::Ready(());
3229		}
3230	}
3231
3232	/// Remove this attempt from the fetch state, unless a newer attempt for the same
3233	/// sequence has already replaced it.
3234	fn remove(&self) {
3235		self.fetch
3236			.lock()
3237			.remove_if(&self.sequence, |pending| pending.result.same_channel(&self.result));
3238	}
3239}
3240
3241impl Drop for group::Request {
3242	fn drop(&mut self) {
3243		if self.done {
3244			return;
3245		}
3246		self.remove();
3247		if let Ok(mut outcome) = self.result.write() {
3248			outcome.rejected = Some(Error::Dropped);
3249		}
3250	}
3251}
3252
3253/// The pollable state of a [`Consumer::fetch_group`].
3254///
3255/// Awaited via the [`kio::Pending`] wrapper; resolves to the
3256/// [`group::Consumer`] once the group lands in the track's cache (already present,
3257/// or produced after a wire FETCH), or [`Error::NotFound`] if it can never exist.
3258pub struct Fetching {
3259	inner: FetchingKind,
3260	// Egress stats scope, so the resolved group carries a payload meter (and counts
3261	// as one delivered group). Empty (no-op) for an untagged track.
3262	stats: stats::Scope,
3263}
3264
3265enum FetchingKind {
3266	Plain {
3267		state: kio::Consumer<TrackState>,
3268		fetch: kio::Shared<FetchState>,
3269		sequence: u64,
3270		// This caller's own start, so a cached group that begins above it is a miss
3271		// rather than a short answer.
3272		frame_start: u64,
3273		// The joined attempt's result channel; `None` on a cache hit, or when no handler
3274		// existed to queue on.
3275		result: Option<kio::Consumer<FetchOutcome>>,
3276		// The group already cached when the fetch was made, held so it stays wanted.
3277		// Boxed: a group consumer dwarfs the rest of the handle.
3278		hit: Option<Box<group::Consumer>>,
3279	},
3280	/// A spliced track's fetch: waits for a segment, then fetches from it.
3281	Spliced(kio::Pending<super::resume::Fetching>),
3282}
3283
3284impl kio::Pollable for Fetching {
3285	type Output = Result<group::Consumer>;
3286
3287	fn poll(&self, waiter: &kio::Waiter) -> Poll<Self::Output> {
3288		let (state, fetch, sequence, frame_start, result, hit) = match &self.inner {
3289			FetchingKind::Plain {
3290				state,
3291				fetch,
3292				sequence,
3293				frame_start,
3294				result,
3295				hit,
3296			} => (state, fetch, *sequence, *frame_start, result.as_ref(), hit),
3297			FetchingKind::Spliced(spliced) => {
3298				// A fetched group is metered here (once), at the tagged handle: the
3299				// spliced source track it comes from is the origin's own, untagged.
3300				return kio::Pollable::poll(&**spliced, waiter)
3301					.map(|res| res.map(|group| group.with_meter(self.stats.meter())));
3302			}
3303		};
3304
3305		// Hand back a consumer sitting where the caller asked rather than at the group's
3306		// own start. Coverage was checked first, so this can only skip frames they excluded.
3307		let resolve = |mut group: group::Consumer| {
3308			group.start_at(frame_start);
3309			group.with_meter(self.stats.meter())
3310		};
3311		if let Some(group) = hit {
3312			return Poll::Ready(Ok(resolve((**group).clone())));
3313		}
3314
3315		// Track side: the cached group, the abort error, or past-final. The outer
3316		// error is the channel closing without any of those.
3317		let cached =
3318			|waiter: &kio::Waiter| match state.poll(waiter, |state| state.poll_fetch_cached(sequence, frame_start)) {
3319				Poll::Ready(Ok(res)) => Poll::Ready(res.map(resolve)),
3320				Poll::Ready(Err(closed)) => Poll::Ready(Err(closed.abort.clone().unwrap_or(Error::Dropped))),
3321				Poll::Pending => Poll::Pending,
3322			};
3323		if let Poll::Ready(res) = cached(waiter) {
3324			return Poll::Ready(res);
3325		}
3326
3327		// Handler side.
3328		let Some(result) = result else {
3329			// Never queued: no handler existed when the fetch was made. Fail fast while
3330			// that's still true; a handler that appeared since may yet fill the cache.
3331			return match fetch.poll(waiter, |fetch| match fetch.has_handlers() {
3332				false => Poll::Ready(()),
3333				true => Poll::Pending,
3334			}) {
3335				Poll::Ready(_guard) => Poll::Ready(Err(Error::NotFound)),
3336				Poll::Pending => Poll::Pending,
3337			};
3338		};
3339
3340		// A written rejection fails every joined fetch. The channel closing without
3341		// one means the attempt was dropped unserved (its handlers went away).
3342		let err = match result.poll(waiter, |outcome| match &outcome.rejected {
3343			Some(err) => Poll::Ready(err.clone()),
3344			None => Poll::Pending,
3345		}) {
3346			Poll::Ready(Ok(err)) => err,
3347			Poll::Ready(Err(_closed)) => Error::NotFound,
3348			Poll::Pending => return Poll::Pending,
3349		};
3350		// An accept caches the group before it answers the channel, which may land
3351		// between the track check above and this one: look again before failing.
3352		match cached(waiter) {
3353			Poll::Ready(res) => Poll::Ready(res),
3354			Poll::Pending => Poll::Ready(Err(err)),
3355		}
3356	}
3357}
3358
3359/// A live subscription to a track, used to read its groups.
3360///
3361/// Created via [`Consumer::subscribe`](Consumer::subscribe), or
3362/// directly from a [`Producer`] for an in-process track. Carries this
3363/// subscriber's [`Subscription`] preferences, which feed the producer's aggregate.
3364///
3365/// # Local cursor vs wire preference
3366///
3367/// Group bounds exist at two levels, and setting one does not imply the other:
3368///
3369/// - [`Self::set_groups`] limits **this subscriber's reads**, filtering exactly what
3370///   this handle returns without changing the publisher's demand.
3371/// - [`Subscription::start`] / [`Subscription::end`], set via [`Self::update`],
3372///   are a **request to the publisher**. They're aggregated across every live subscriber
3373///   (earliest start, widest end), so they say what the publisher should send, not what
3374///   this subscriber sees.
3375///
3376/// They stay separate because their scopes differ: a subscriber can't filter by the
3377/// aggregate, since another subscriber can widen it, and the publisher can't honor a
3378/// cursor it's never told about. So setting only the cursor still transfers the skipped
3379/// groups, and setting only the preference still returns groups another subscriber asked
3380/// for. Set both to skip them *and* avoid the transfer.
3381///
3382/// The one place they meet is where the cursor comes from. A new subscriber's cursor is
3383/// floored at the group its own subscription named (or 0), and its
3384/// [`Subscription::max_age`] decides what above the floor is worth delivering. Every later
3385/// move is the caller's.
3386pub struct Subscriber {
3387	name: Arc<str>,
3388	// The broadcast this track belongs to; see [`Self::broadcast`].
3389	broadcast: Arc<broadcast::Info>,
3390	info: Info,
3391	inner: SubscriberKind,
3392	// Egress stats scope, used to meter the groups this subscriber reads. Empty
3393	// (no-op) for an untagged track.
3394	stats: stats::Scope,
3395	// The subscription guard: bumps `subscriptions` (and the egress viewer refcount)
3396	// while held, closing them on drop. Empty (no-op) for an untagged track.
3397	_stats_sub: stats::Subscription,
3398}
3399
3400enum SubscriberKind {
3401	Plain(PlainSubscriber),
3402	// Boxed: the spliced cursor set dwarfs the plain cursor.
3403	Spliced(Box<super::resume::Subscriber>),
3404}
3405
3406/// One poll's view of how far this subscription may drift: the clamped budget and the
3407/// live edge to measure a candidate group against. Resolved once, then applied to every
3408/// group that poll considers. `outer` is revalidated and `successor` resolved per
3409/// candidate, outside this track's lock.
3410#[derive(Clone)]
3411struct Drift {
3412	budget: Duration,
3413	edge: Edge,
3414	outer: Option<LiveEdge>,
3415	successor: Option<Successor>,
3416}
3417
3418/// Keeps one handed-out group tied to the subscription whose cursor selected it.
3419struct GroupExpiry {
3420	/// Weak so a handed-out group never joins the track's consumer count, which is what
3421	/// [`crate::broadcast::Demand`] reads to decide the track still has readers. A group
3422	/// outlives the cursor that produced it (a relay serves one for the life of its
3423	/// stream), and pinning demand on that would hold an upstream subscription open past
3424	/// the last real subscriber.
3425	state: kio::ConsumerWeak<TrackState>,
3426	subscription: kio::Consumer<Subscription>,
3427	anchor: kio::Consumer<Anchor>,
3428	bound: Option<u64>,
3429	sequence: u64,
3430}
3431
3432impl group::Expiry for GroupExpiry {
3433	fn is_expired(&self, waiter: &kio::Waiter) -> bool {
3434		let mut max_age = Duration::default();
3435		let _ = self.subscription.poll(waiter, |subscription| {
3436			max_age = subscription.max_age;
3437			Poll::<()>::Pending
3438		});
3439
3440		let mut anchor = Anchor::default();
3441		let _ = self.anchor.poll(waiter, |current| {
3442			anchor = (**current).clone();
3443			Poll::<()>::Pending
3444		});
3445		let anchor = anchor.capped(self.bound);
3446		let cap = anchor.cap;
3447		// Before this track's lock: both may name another track, and nesting deadlocks.
3448		let outer = anchor
3449			.edge
3450			.filter(LiveEdge::is_live)
3451			.map(|live| (live.sequence, live.timestamp));
3452		let successor = anchor
3453			.successor
3454			.as_ref()
3455			.and_then(|successor| successor.poll_start(waiter));
3456
3457		let mut expired = false;
3458		let _ = self.state.poll(waiter, |state| {
3459			let budget = clamp_max_age(max_age, state.max_age_bound());
3460			loop {
3461				// An abort can change reach even after the successor is stamped.
3462				// Register before judging, so a racing abort is observed or wakes us.
3463				// An abort only closes the group, never touching the track, so one that
3464				// landed before registering must re-select or the replacement goes unwatched.
3465				let next = loop {
3466					let next = state.first_servable(self.sequence.saturating_add(1), cap);
3467					match next {
3468						Some(group) if group.poll_closed(waiter).is_ready() && group.is_aborted() => continue,
3469						next => break next,
3470					}
3471				};
3472				let edge = state.drift_edge(cap, outer, successor);
3473				// Likewise the edge: its abort hands the edge to a lower group, which the
3474				// scan below skips.
3475				if let Some(live) = edge.presentation
3476					&& let Some(slot) = state.lookup.get(&live.sequence)
3477					&& slot.group.poll_closed(waiter).is_ready()
3478					&& slot.group.is_aborted()
3479				{
3480					continue;
3481				}
3482				expired = state.is_stale(self.sequence, &edge, budget);
3483				if expired {
3484					break;
3485				}
3486
3487				// A first timestamp can change the verdict without mutating the track:
3488				// on a group past the edge (a new edge), or on the candidate's
3489				// unstamped immediate successor (a reach where there was none).
3490				// Register on the candidate, its successor, and every servable group
3491				// past the edge. If one raced this scan, resolve the edge again before
3492				// Pending. Groups between the successor and the edge can move neither
3493				// while the edge stands (its abort is watched above), and walking them
3494				// would cost each of N parked serves the whole backlog, O(N^2) per
3495				// track change.
3496				let mut timestamp_raced = false;
3497				if let Some(slot) = state.lookup.get(&self.sequence) {
3498					let group = &slot.group;
3499					if group.timestamp().is_none()
3500						&& group.poll_timestamp(waiter).is_ready()
3501						&& group.timestamp().is_some()
3502					{
3503						timestamp_raced = true;
3504					}
3505				}
3506				let past = edge
3507					.presentation
3508					.map_or(self.sequence, |live| live.sequence.max(self.sequence));
3509				let beyond = state
3510					.lookup
3511					.range((std::ops::Bound::Excluded(past), std::ops::Bound::Unbounded))
3512					.map(|(_, slot)| slot)
3513					.take_while(|slot| super::subscription::before_end(slot.group.sequence, cap))
3514					.filter(|slot| slot.visible && !slot.group.is_aborted())
3515					.map(|slot| &slot.group);
3516				for group in next.into_iter().chain(beyond) {
3517					if group.timestamp().is_none()
3518						&& group.poll_timestamp(waiter).is_ready()
3519						&& group.timestamp().is_some()
3520					{
3521						timestamp_raced = true;
3522						break;
3523					}
3524				}
3525				if !timestamp_raced {
3526					break;
3527				}
3528			}
3529
3530			// Register on track changes even though the current answer is known: a
3531			// newer group can move either live edge while this group read is pending.
3532			Poll::<()>::Pending
3533		});
3534
3535		expired
3536	}
3537}
3538
3539/// The group a poll's drift is measured against, identified well enough to tell it apart
3540/// from whatever may occupy its sequence by the time a candidate is judged.
3541#[derive(Clone, Copy)]
3542struct Edge {
3543	/// This track's own edge, revalidated before it convicts anything.
3544	presentation: Option<PresentationEdge>,
3545	/// A newer edge on another track, already revalidated: its sequence and the newest
3546	/// frame it had presented. See [`Anchor::edge`].
3547	outer: Option<(u64, Timestamp)>,
3548	/// The cap the edge was resolved under, so per-candidate reach lookups measure
3549	/// against the same servable window.
3550	cap: Option<u64>,
3551	/// Where the next group past `cap` starts, already revalidated. See [`Anchor::successor`].
3552	successor: Option<Timestamp>,
3553}
3554
3555/// How a reader wrapping a cursor bounds its drift anchor from outside: pushed by a
3556/// splice onto each segment's cursor, and shared with the groups a cursor hands out.
3557#[derive(Clone, Default, PartialEq)]
3558pub(crate) struct Anchor {
3559	/// The exclusive sequence cap on what the reader could be handed; see [`servable_cap`].
3560	pub cap: Option<u64>,
3561	/// The newest edge across a splice's segments. Each segment is a separate track
3562	/// that only sees its own groups, so without this a parked segment measures against
3563	/// its own frozen edge while the logical track has moved on. Revalidated on its own
3564	/// track before it convicts anything, since it may be judged long after it was pushed.
3565	pub edge: Option<LiveEdge>,
3566	/// Where the reader's next group past `cap` starts presenting, when another track
3567	/// serves it (a splice's next segment). The last group below the cap has no
3568	/// successor in its own track, so without this nothing bounds its reach and it is
3569	/// never judged stale. Resolved afresh on every judgment rather than cached, so a
3570	/// group that goes away hands the bound to whichever group serves there next.
3571	pub successor: Option<Successor>,
3572}
3573
3574impl Anchor {
3575	/// This anchor under a further `cap`. A lower cap drops the successor: it named
3576	/// where the reader continues past the old cap, which is no longer served.
3577	pub fn capped(mut self, cap: Option<u64>) -> Self {
3578		let capped = servable_cap(cap, self.cap);
3579		if capped != self.cap {
3580			self.cap = capped;
3581			self.successor = None;
3582		}
3583		self
3584	}
3585}
3586
3587/// The newest stamped group of a track: its sequence and the newest frame it presented,
3588/// plus enough identity for a reader on another track to revalidate it.
3589#[derive(Clone)]
3590pub(crate) struct LiveEdge {
3591	pub sequence: u64,
3592	pub timestamp: Timestamp,
3593	stamp: u32,
3594	track: kio::ConsumerWeak<TrackState>,
3595}
3596
3597impl LiveEdge {
3598	/// Whether the edge still names the same servable group on its own track, the
3599	/// check [`TrackState::is_stale`] runs on a local edge. An eviction or abort since
3600	/// the splice resolved it must not convict anything. Takes that track's lock, so
3601	/// never call it under another's.
3602	fn is_live(&self) -> bool {
3603		self.track.read().holds(self.sequence, self.stamp)
3604	}
3605}
3606
3607impl PartialEq for LiveEdge {
3608	fn eq(&self, other: &Self) -> bool {
3609		self.sequence == other.sequence
3610			&& self.timestamp == other.timestamp
3611			&& self.stamp == other.stamp
3612			&& self.track.same_channel(&other.track)
3613	}
3614}
3615
3616/// The newest servable group that has presented at least one frame.
3617#[derive(Clone, Copy)]
3618struct PresentationEdge {
3619	sequence: u64,
3620	/// The slot incarnation this timestamp was read from, so an eviction or a re-served
3621	/// sequence between resolving the anchor and using it is detectable.
3622	stamp: u32,
3623	/// The newest frame this group has presented, not its first. The candidate side of the
3624	/// comparison is an upper bound on what a group could still reach, so the edge side has
3625	/// to be the newest content that actually exists, or the two meet and nothing is ever
3626	/// convicted. Only ever grows as the group fills.
3627	timestamp: Timestamp,
3628}
3629
3630/// The cursor state for a subscription over a single (per-session) track.
3631struct PlainSubscriber {
3632	state: kio::Consumer<TrackState>,
3633
3634	subscription: kio::Producer<Subscription>,
3635	/// Arrival-order cursor used by `recv_group`.
3636	index: usize,
3637	/// Arrival-order cursor used by `recv_datagram`, independent of groups.
3638	datagram_index: usize,
3639	/// Minimum sequence to return from any `recv` method. Set by `start_at`.
3640	min_sequence: u64,
3641	/// One past the highest sequence returned by `next_group`.
3642	/// Used only by that method to skip late arrivals; does not affect `recv_group`.
3643	next_sequence: u64,
3644	/// Exclusive upper sequence bound for `next_group` and `recv_group`, in the form
3645	/// [`Cap::exclusive`] produces. `None` means no cap. Set by `end_at`;
3646	/// can be raised, lowered, or unset at any time. Groups at or past the cap stay in
3647	/// the producer's cache and become eligible again when the cap rises (or is removed).
3648	/// `Some(0)` is the empty range.
3649	end_sequence: Option<u64>,
3650	/// Groups received beyond the [`Self::end_sequence`] cap, held for `recv_group`
3651	/// until the cap rises (arrival-order reads consume the shared cursor, so they
3652	/// are parked here instead of dropped). Keyed by sequence so the lowest is
3653	/// re-offered first.
3654	parked: BTreeMap<u64, group::Consumer>,
3655	/// A cap imposed by a reader wrapping this cursor (a [`super::resume::Subscriber`]
3656	/// segment), folded into the drift anchor only. Delivery is still bounded by
3657	/// `end_sequence`, which stays unset on a segment so its completion is visible.
3658	stale_cap: Option<u64>,
3659	/// Shared effective anchor used by groups after this cursor hands them out. The
3660	/// only copy of the outer edge a wrapping reader pushed (see [`Anchor::edge`]).
3661	drift_anchor: kio::Producer<Anchor>,
3662	/// Groups the drift budget skipped since the count was last drained. Accumulated
3663	/// here rather than metered in place because the handle that owns the stats scope
3664	/// is the outer [`Subscriber`], which may be reading this cursor through a
3665	/// [`super::resume::Subscriber`] segment (untagged, so the outer wrapper is the
3666	/// only place attribution happens once).
3667	stale: stats::Content,
3668	/// Groups the seek path has convicted but whose sequences no caller has committed
3669	/// past yet, keyed by sequence; see [`Self::commit_seek_stale`].
3670	seek_pending: BTreeMap<u64, stats::Content>,
3671}
3672
3673impl PlainSubscriber {
3674	/// The drift anchor for a read bounded by `end`. Every read folds `end_sequence`
3675	/// into `end`, so capping the shared anchor (which already holds it) is exact.
3676	fn anchor(&self, end: Option<u64>) -> Anchor {
3677		self.drift_anchor.read().clone().capped(end)
3678	}
3679
3680	/// Publish the `outer` anchor under this cursor's own cap.
3681	fn update_drift_anchor(&mut self, outer: Anchor) {
3682		let anchor = outer.capped(self.end_sequence);
3683		// Skip a no-op write: every handed-out group's expiry watches this channel.
3684		if *self.drift_anchor.read() != anchor
3685			&& let Ok(mut current) = self.drift_anchor.write()
3686		{
3687			*current = anchor;
3688		}
3689	}
3690
3691	// A helper to automatically apply Dropped if the state is closed without an error.
3692	fn poll<F, R>(&self, waiter: &kio::Waiter, f: F) -> Poll<Result<R>>
3693	where
3694		F: Fn(&kio::Ref<'_, TrackState>) -> Poll<Result<R>>,
3695	{
3696		Poll::Ready(match ready!(self.state.poll(waiter, f)) {
3697			Ok(res) => res,
3698			// We try to clone abort just in case the function forgot to check for terminal state.
3699			Err(state) => Err(state.abort.clone().unwrap_or(Error::Dropped)),
3700		})
3701	}
3702
3703	/// Take the groups skipped since the last call, for the owner to meter.
3704	fn take_stale(&mut self) -> stats::Content {
3705		std::mem::take(&mut self.stale)
3706	}
3707
3708	/// Note a group an outer reader skipped on this cursor's behalf, so it lands in the
3709	/// same counter as the ones skipped here.
3710	fn note_stale(&mut self, group: &group::Consumer) {
3711		self.stale.add(group.content());
3712	}
3713
3714	/// This subscriber's clamped drift budget and the live edge to measure against,
3715	/// resolved once per poll.
3716	///
3717	/// `cap` bounds the anchor: the caller passes the same window it reads from, so a
3718	/// group is only ever judged against content that could actually be served in its
3719	/// place. Read fresh each poll, so a mid-stream [`Control::update`] applies
3720	/// to the very next group, and shared across every candidate that poll walks off, so
3721	/// discarding a backlog of N groups costs one scan rather than N. Only ever
3722	/// [`Poll::Ready`]; the track ending surfaces as the error the caller was going to
3723	/// get anyway.
3724	fn poll_drift(&self, anchor: Anchor, waiter: &kio::Waiter) -> Poll<Result<Drift>> {
3725		let mut max_age = Duration::default();
3726		let _ = self.subscription.poll(waiter, |subscription| {
3727			max_age = subscription.max_age;
3728			Poll::<()>::Pending
3729		});
3730		let cap = anchor.cap;
3731		let outer = anchor.edge;
3732		let successor = anchor.successor;
3733		self.poll(waiter, |state| {
3734			// Local edge only. The pushed edge and successor are revalidated in
3735			// [`Self::poll_stale`], outside this lock.
3736			Poll::Ready(Ok(Drift {
3737				budget: clamp_max_age(max_age, state.max_age_bound()),
3738				edge: state.drift_edge(cap, None, None),
3739				outer: outer.clone(),
3740				successor: successor.clone(),
3741			}))
3742		})
3743	}
3744
3745	/// Whether the drift budget says to skip `group`, against a [`Drift`] already resolved
3746	/// for this poll.
3747	fn poll_stale(&self, group: &group::Consumer, drift: &Drift, waiter: &kio::Waiter) -> Poll<Result<bool>> {
3748		// Revalidate before this track's lock. Both can name another track, including
3749		// one whose own judgment is waiting on this one.
3750		let outer = drift
3751			.outer
3752			.as_ref()
3753			.filter(|live| live.is_live())
3754			.map(|live| (live.sequence, live.timestamp));
3755		let successor = drift
3756			.successor
3757			.as_ref()
3758			.and_then(|successor| successor.poll_start(waiter));
3759		let presentation = drift.edge.presentation;
3760		let cap = drift.edge.cap;
3761		let budget = drift.budget;
3762		self.poll(waiter, move |state| {
3763			let edge = Edge {
3764				presentation,
3765				outer,
3766				cap,
3767				successor,
3768			};
3769			Poll::Ready(Ok(state.is_stale(group.sequence, &edge, budget)))
3770		})
3771	}
3772
3773	fn with_expiry(&self, group: group::Consumer) -> group::Consumer {
3774		let sequence = group.sequence;
3775		group.with_expiry(Arc::new(GroupExpiry {
3776			state: self.state.weak(),
3777			subscription: self.subscription.consume(),
3778			anchor: self.drift_anchor.consume(),
3779			bound: None,
3780			sequence,
3781		}))
3782	}
3783
3784	fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
3785		// An eviction aborts a parked group without touching any cursor this
3786		// subscriber polls, so each entry needs a waiter or this poll would never
3787		// rerun. `poll_closed` observes-or-registers under one lock: `Pending`
3788		// parks the waiter while the group is open (an open group cannot be
3789		// aborted), and `Ready` means closed, where only an abort invalidates the
3790		// entry. Checking `is_aborted` separately from the registration would leave
3791		// a window where an abort lands between the two and wakes nobody.
3792		let watch = |group: &group::Consumer| match group.poll_closed(waiter) {
3793			Poll::Pending => true,
3794			Poll::Ready(()) => !group.is_aborted(),
3795		};
3796
3797		// A raised `start_at` drops parked groups it overtook, and eviction/expiry
3798		// (which aborts a cached group) drops its parked entry. The latter is what
3799		// bounds parking: a subscription capped indefinitely holds only what the
3800		// track's cache policy still retains, not every group it ever observed.
3801		let min_sequence = self.min_sequence;
3802		self.parked
3803			.retain(|sequence, group| *sequence >= min_sequence && watch(group));
3804
3805		// One scan for the whole poll, so walking a backlog off stays linear in its size.
3806		let drift = ready!(self.poll_drift(self.anchor(self.end_sequence), waiter))?;
3807
3808		loop {
3809			// Re-offer the lowest parked group back inside the cap once it rises,
3810			// ahead of the arrival cursor: it is the oldest thing still owed.
3811			let consumer = match self.parked.keys().next().copied() {
3812				Some(sequence) if super::subscription::before_end(sequence, self.end_sequence) => {
3813					let group = self.parked.remove(&sequence).expect("just looked it up");
3814					// A re-offer is a delivery: stamp it like a fresh hand-out.
3815					group.cache_refresh();
3816					group
3817				}
3818				_ => {
3819					let Some((producer, found_index)) =
3820						ready!(self.poll(waiter, |state| state.poll_recv_group(self.index, self.min_sequence))?)
3821					else {
3822						// Parked groups survive a finished track: they become deliverable
3823						// again if the cap rises, so the stream isn't over while any are held.
3824						if self.parked.is_empty() {
3825							return Poll::Ready(Ok(None));
3826						}
3827						return Poll::Pending;
3828					};
3829					let consumer = producer.consume();
3830					// Stamp with the track guard released, so delivery never nests the
3831					// group's state lock under the track's.
3832					consumer.cache_refresh();
3833					self.index = found_index + 1;
3834
3835					// Park a group beyond the cap instead of dropping it, and keep scanning
3836					// so an in-range group that arrived behind it still flows.
3837					if !super::subscription::before_end(consumer.sequence, self.end_sequence) {
3838						// Watch it from the moment it parks: the retain pass above already
3839						// ran, so an entry admitted here would otherwise sit unwatched for
3840						// the rest of this poll, and an abort could wake nobody.
3841						if watch(&consumer) {
3842							self.parked.insert(consumer.sequence, consumer);
3843						}
3844						continue;
3845					}
3846					consumer
3847				}
3848			};
3849
3850			// Drop a group the drift budget has given up on and keep scanning, so one
3851			// poll walks a whole backlog off rather than handing it out group by group.
3852			if ready!(self.poll_stale(&consumer, &drift, waiter))? {
3853				self.stale.add(consumer.content());
3854				continue;
3855			}
3856			return Poll::Ready(Ok(Some(self.with_expiry(consumer))));
3857		}
3858	}
3859
3860	fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
3861		let Some((datagram, found_index)) =
3862			ready!(self.poll(waiter, |state| state.poll_recv_datagram(self.datagram_index))?)
3863		else {
3864			return Poll::Ready(Ok(None));
3865		};
3866
3867		self.datagram_index = found_index + 1;
3868		Poll::Ready(Ok(Some(datagram)))
3869	}
3870
3871	fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
3872		let floor = self.next_sequence.max(self.min_sequence);
3873		let Some(group) = ready!(self.poll_seek_group(floor, self.end_sequence, waiter))? else {
3874			return Poll::Ready(Ok(None));
3875		};
3876		self.next_sequence = group.sequence.saturating_add(1);
3877		// The delivery commits everything the seek stepped over to reach this group.
3878		self.commit_seek_stale(self.next_sequence);
3879		// Delivery is a cache access, same as the arrival-order path.
3880		group.cache_refresh();
3881		Poll::Ready(Ok(Some(group)))
3882	}
3883
3884	/// Seek the lowest servable group in `floor..end` without advancing any cursor,
3885	/// walking off everything the drift budget has convicted on the way.
3886	///
3887	/// The drift anchor is built from the same window the seek reads (`end` folded into
3888	/// the cap), so a candidate is only judged against content that could be served in
3889	/// its place. A nested splice depends on this: its outer boundary arrives only as
3890	/// this call's `end`, and an anchor past it would convict groups the caller still
3891	/// owes its reader.
3892	///
3893	/// Repeated polls return the same group until the caller's own floor passes it,
3894	/// which is the point: the spliced sequence path picks the lowest candidate across
3895	/// segments and must not commit a segment past a group it has not delivered.
3896	fn poll_seek_group(
3897		&mut self,
3898		floor: u64,
3899		end: Option<u64>,
3900		waiter: &kio::Waiter,
3901	) -> Poll<Result<Option<group::Consumer>>> {
3902		let mut floor = floor.max(self.min_sequence);
3903		let end = super::subscription::min_some(end, self.end_sequence);
3904		// One scan for the whole poll, so walking a backlog off stays linear in its size.
3905		let drift = ready!(self.poll_drift(self.anchor(end), waiter))?;
3906
3907		loop {
3908			let Some(producer) = ready!(self.poll(waiter, |state| state.poll_next_in_range(floor, end))?) else {
3909				// Deliberately no flush of `seek_pending` here: only a delivery commit
3910				// may count a conviction. This `None` can be an artifact of a floor
3911				// that will lower again, and a mid-group boundary can serve a
3912				// convicted sequence through another segment even after this cursor
3913				// retires. A conviction never committed is dropped uncounted with the
3914				// cursor, the same tail bound a retired segment already has.
3915				return Poll::Ready(Ok(None));
3916			};
3917			let group = producer.consume();
3918
3919			// Skip a group the budget has given up on and keep scanning, so one poll
3920			// walks a whole backlog off rather than handing it out group by group.
3921			if ready!(self.poll_stale(&group, &drift, waiter))? {
3922				// Not counted yet: a seek advances no cursor, and a conviction is not
3923				// permanent (the budget can widen, the edge can be evicted), so the
3924				// group may still be delivered. Re-snapshot on every re-examination so
3925				// the eventual count reflects the group's latest observed content.
3926				self.seek_pending.insert(group.sequence, group.content());
3927				floor = group.sequence.saturating_add(1);
3928				continue;
3929			}
3930
3931			// A conviction the budget walked back: the group is handed over after all,
3932			// so it must never reach the stale count.
3933			self.seek_pending.remove(&group.sequence);
3934			return Poll::Ready(Ok(Some(self.with_expiry(group))));
3935		}
3936	}
3937
3938	/// Count the seek path's convictions below `committed` into [`Self::stale`],
3939	/// exactly once each.
3940	///
3941	/// A seek is speculative: the spliced path seeks every segment and delivers from
3942	/// one, so a conviction only becomes a real skip once a delivery commits past it.
3943	/// Until then the entry waits in [`Self::seek_pending`], where a delivered group
3944	/// removes itself (see [`Self::poll_seek_group`]). `committed` must be the
3945	/// deliverer's sequence watermark (one past its last delivery), which only rises.
3946	/// The seek's floor is NOT that: it includes `start_at`, which can be lowered
3947	/// again, and a conviction it flushed could then be delivered after all.
3948	fn commit_seek_stale(&mut self, committed: u64) {
3949		let keep = self.seek_pending.split_off(&committed);
3950		for (_, content) in std::mem::replace(&mut self.seek_pending, keep) {
3951			self.stale.add(content);
3952		}
3953	}
3954
3955	/// Drop a conviction for `sequence` without counting it: the sequence was
3956	/// delivered by another cursor over the same content (a splice boundary inside
3957	/// the group leaves a copy in two segments), so its content is not stale.
3958	fn discard_seek_conviction(&mut self, sequence: u64) {
3959		self.seek_pending.remove(&sequence);
3960	}
3961}
3962
3963/// A cloneable handle to a subscriber's delivery preferences.
3964///
3965/// This updates the same subscription as the owning [`Subscriber`] without
3966/// borrowing its read cursor, so callers can change delivery priority, the max age
3967/// budget, or group bounds while another task is waiting for groups.
3968#[derive(Clone)]
3969pub struct Control {
3970	subscription: kio::Producer<Subscription>,
3971}
3972
3973impl Control {
3974	/// This subscriber's current preferences.
3975	pub fn subscription(&self) -> Subscription {
3976		self.subscription.read().clone()
3977	}
3978
3979	/// Replace this subscriber's preferences, updating the producer's aggregate.
3980	///
3981	/// Returns [`Error::Closed`] if the track already ended; the update is
3982	/// meaningless at that point and can usually be ignored.
3983	pub fn update(&self, subscription: Subscription) -> Result<()> {
3984		let mut state = self.subscription.write().map_err(|_| Error::Closed)?;
3985		*state = subscription;
3986		Ok(())
3987	}
3988}
3989
3990impl Subscriber {
3991	/// The track's [`Info`], resolved when the subscription was established.
3992	///
3993	/// Free, unlike [`Consumer::query`]: subscribing already waited for the info
3994	/// (SUBSCRIBE_OK on the wire), so a subscriber always has it.
3995	pub fn info(&self) -> &Info {
3996		&self.info
3997	}
3998
3999	/// The track's name, unique within its broadcast.
4000	pub fn name(&self) -> &str {
4001		&self.name
4002	}
4003
4004	/// The broadcast this track belongs to, as reached through the handle it came from.
4005	/// Its [`path`](broadcast::Info::path) is what a catalog's relative `broadcast`
4006	/// references resolve against.
4007	pub fn broadcast(&self) -> &broadcast::Info {
4008		&self.broadcast
4009	}
4010
4011	/// Attribute the groups the drift budget skipped since the last read.
4012	///
4013	/// Drained rather than metered where the skip happens, for the same reason a
4014	/// delivered group is metered here: a spliced subscriber reads through untagged
4015	/// per-segment cursors, so this wrapper is the one place that counts exactly once.
4016	fn count_stale(&mut self, meter: &stats::Meter) {
4017		// An untagged subscriber leaves the count where it is. A spliced reader polls
4018		// its segments through this same method, and those segments are untagged, so
4019		// draining here would throw the count away before the tagged handle wrapping
4020		// them ever sees it.
4021		if meter.is_tracked() {
4022			meter.stale(self.take_stale());
4023		}
4024	}
4025
4026	/// Take the groups the drift budget skipped since the last call, so a nesting
4027	/// handle (a spliced subscriber reading this one as a segment) can attribute them.
4028	pub(crate) fn take_stale(&mut self) -> stats::Content {
4029		match &mut self.inner {
4030			SubscriberKind::Plain(plain) => plain.take_stale(),
4031			SubscriberKind::Spliced(spliced) => spliced.take_stale(),
4032		}
4033	}
4034
4035	/// Bound the drift anchor from outside, for a reader that caps this subscriber
4036	/// without capping its cursor, and splices it with other tracks.
4037	///
4038	/// A [`super::resume::Subscriber`] segment is deliberately left uncapped
4039	/// ([`Self::set_groups`] would park boundary-crossing groups where its completion can't
4040	/// be seen), so its own cap has to reach the anchor this way or the segment measures
4041	/// drift against groups its reader will never be served. The same goes for the edge:
4042	/// a segment's track never sees the groups of the segments after it. A spliced
4043	/// segment folds the anchor into what it pushes onto its own segments, so it reaches
4044	/// the plain cursors at the leaves however deep the splices nest.
4045	pub(crate) fn set_anchor(&mut self, anchor: Anchor) {
4046		match &mut self.inner {
4047			SubscriberKind::Plain(plain) => {
4048				plain.stale_cap = anchor.cap;
4049				plain.update_drift_anchor(anchor);
4050			}
4051			SubscriberKind::Spliced(spliced) => spliced.set_anchor(anchor),
4052		}
4053	}
4054
4055	/// Count the seek path's convictions below the deliverer's `committed` watermark
4056	/// as stale, exactly once each; see [`PlainSubscriber::commit_seek_stale`].
4057	///
4058	/// The spliced seek calls this on every segment before it delivers, so a losing
4059	/// segment's convictions are counted once the winning delivery moves the cursor
4060	/// past them, and never before.
4061	pub(crate) fn commit_seek_stale(&mut self, committed: u64) {
4062		match &mut self.inner {
4063			SubscriberKind::Plain(plain) => plain.commit_seek_stale(committed),
4064			SubscriberKind::Spliced(spliced) => spliced.commit_seek_stale(committed),
4065		}
4066	}
4067
4068	/// Drop any conviction for `sequence` without counting it; see
4069	/// [`PlainSubscriber::discard_seek_conviction`]. The spliced deliverer calls this
4070	/// for the sequence it just handed out, since a boundary inside that group leaves
4071	/// a convictable copy in the next segment that the delivery splices into.
4072	pub(crate) fn discard_seek_conviction(&mut self, sequence: u64) {
4073		match &mut self.inner {
4074			SubscriberKind::Plain(plain) => plain.discard_seek_conviction(sequence),
4075			SubscriberKind::Spliced(spliced) => spliced.discard_seek_conviction(sequence),
4076		}
4077	}
4078
4079	/// Whether the drift budget says to skip `group`, for a reader holding a group this
4080	/// subscriber handed it earlier (a parked one, re-offered once a cap rose). Counts
4081	/// the skip here so it reaches the stats with the rest. A spliced subscriber asks
4082	/// the segment whose window covers the group, so a nested park is re-checked
4083	/// against the same anchor a fresh read would use.
4084	pub(crate) fn poll_stale(&mut self, group: &group::Consumer, waiter: &kio::Waiter) -> Poll<Result<bool>> {
4085		let plain = match &mut self.inner {
4086			SubscriberKind::Plain(plain) => plain,
4087			SubscriberKind::Spliced(spliced) => return spliced.poll_stale(group, waiter),
4088		};
4089		let drift = ready!(plain.poll_drift(plain.anchor(plain.end_sequence), waiter))?;
4090		let stale = ready!(plain.poll_stale(group, &drift, waiter))?;
4091		if stale {
4092			plain.note_stale(group);
4093		}
4094		Poll::Ready(Ok(stale))
4095	}
4096
4097	/// Create a handle for updating this subscriber's delivery preferences.
4098	pub fn control(&self) -> Control {
4099		Control {
4100			subscription: match &self.inner {
4101				SubscriberKind::Plain(plain) => plain.subscription.clone(),
4102				SubscriberKind::Spliced(spliced) => spliced.prefs(),
4103			},
4104		}
4105	}
4106
4107	/// Poll for the next group in arrival order, without blocking.
4108	///
4109	/// Returns each group it delivers exactly once, in the order it landed on the wire,
4110	/// which may be out of sequence due to network reordering or loss. Use
4111	/// [`Self::ordered`] if you only want groups whose sequence number is higher than any
4112	/// previously returned.
4113	///
4114	/// Groups are semi-reliable, and the [`Subscription::max_age`] budget is the other
4115	/// thing (alongside eviction and a moving start) that decides which of them arrive:
4116	/// one that has drifted further behind the live edge than the budget tolerates is
4117	/// skipped rather than handed over, so a single poll walks off a whole backlog. The
4118	/// default is [`Duration::ZERO`], which takes the live
4119	/// edge and writes the rest off; raise it to read history. [`Self::set_groups`] and
4120	/// [`Subscription::start`] are filters, not exemptions: backfill needs a budget that
4121	/// covers it. [`Consumer::fetch_group`] is the way to ask for one old group outright.
4122	/// The budget remains attached to a returned group: if it stalls while newer data
4123	/// advances, its pending frame read ends with [`Error::Old`].
4124	///
4125	/// Honors the group range set by [`Self::set_groups`]:
4126	/// a group beyond the cap is parked (not dropped) and re-offered once the cap rises
4127	/// (lowest sequence first), without blocking in-range groups that arrive behind it.
4128	/// A parked group that the producer evicts or expires in the meantime is dropped,
4129	/// so parking never outlives the track's cache policy.
4130	///
4131	/// Returns `Poll::Ready(Ok(Some(group)))` when a group is available,
4132	/// `Poll::Ready(Ok(None))` when the track is finished,
4133	/// `Poll::Ready(Err(e))` when the track has been aborted, or
4134	/// `Poll::Pending` when no group is available yet.
4135	pub fn poll_recv_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
4136		let meter = self.stats.meter();
4137		let res = match &mut self.inner {
4138			SubscriberKind::Plain(plain) => plain.poll_recv_group(waiter),
4139			SubscriberKind::Spliced(spliced) => spliced.poll_recv_group(waiter),
4140		};
4141		self.count_stale(&meter);
4142		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
4143	}
4144
4145	/// Receive the next group in arrival order.
4146	///
4147	/// Every group is returned exactly once, in the order it landed on the wire, which may
4148	/// be out of sequence due to network reordering or loss. Use [`Self::ordered`] if you
4149	/// only want groups whose sequence number is higher than any previously returned.
4150	/// See [`Self::poll_recv_group`] for how [`Self::set_groups`] applies.
4151	pub async fn recv_group(&mut self) -> Result<Option<group::Consumer>> {
4152		kio::wait(|waiter| self.poll_recv_group(waiter)).await
4153	}
4154
4155	/// Poll for the next datagram in arrival order, without blocking.
4156	///
4157	/// Datagrams are a separate best-effort channel from groups (see
4158	/// [`Producer::append_datagram`]); they share only the sequence namespace, and
4159	/// neither cursor moves the other. A consumer that falls too far behind silently
4160	/// loses the oldest datagrams.
4161	///
4162	/// Returns `Poll::Ready(Ok(Some(datagram)))` when one is available,
4163	/// `Poll::Ready(Ok(None))` when the track is finished, `Poll::Ready(Err(e))` when the track
4164	/// is aborted, or `Poll::Pending` when none is buffered yet.
4165	pub fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
4166		let meter = self.stats.meter();
4167		let res = match &mut self.inner {
4168			SubscriberKind::Plain(plain) => plain.poll_recv_datagram(waiter),
4169			SubscriberKind::Spliced(spliced) => spliced.poll_recv_datagram(waiter),
4170		};
4171		// Unlike a group (metered lazily as its frames are read), a datagram is
4172		// delivered whole here, so count it as the single-frame group it stands in for.
4173		if let Poll::Ready(Ok(Some(datagram))) = &res {
4174			meter.datagram(datagram.payload.len() as u64);
4175		}
4176		res
4177	}
4178
4179	/// Receive the next datagram in arrival order.
4180	///
4181	/// A best-effort channel parallel to [`Self::recv_group`]; the two share only the sequence
4182	/// namespace. To receive both concurrently from one subscriber, poll
4183	/// [`Self::poll_recv_group`] and [`Self::poll_recv_datagram`] together in a single `poll`
4184	/// closure (sequential `&mut` borrows), rather than awaiting the two `recv` futures at once.
4185	pub async fn recv_datagram(&mut self) -> Result<Option<Datagram>> {
4186		kio::wait(|waiter| self.poll_recv_datagram(waiter)).await
4187	}
4188
4189	/// The sequence cursor behind [`Ordered`], which owns the only public door to it.
4190	fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
4191		let meter = self.stats.meter();
4192		let res = match &mut self.inner {
4193			SubscriberKind::Plain(plain) => plain.poll_next_group(waiter),
4194			SubscriberKind::Spliced(spliced) => spliced.poll_next_group(waiter),
4195		};
4196		self.count_stale(&meter);
4197		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
4198	}
4199
4200	/// Seek the lowest cached group in `floor..end` without advancing any cursor.
4201	/// Crate-visible for [`super::resume::Subscriber`], whose sequence path picks the
4202	/// lowest candidate across its segments and advances only its own floor; see
4203	/// [`PlainSubscriber::poll_seek_group`].
4204	pub(crate) fn poll_seek_group(
4205		&mut self,
4206		floor: u64,
4207		end: Option<u64>,
4208		waiter: &kio::Waiter,
4209	) -> Poll<Result<Option<group::Consumer>>> {
4210		let meter = self.stats.meter();
4211		let res = match &mut self.inner {
4212			SubscriberKind::Plain(plain) => plain.poll_seek_group(floor, end, waiter),
4213			SubscriberKind::Spliced(spliced) => spliced.poll_seek_group(floor, end, waiter),
4214		};
4215		self.count_stale(&meter);
4216		res.map(|res| res.map(|group| group.map(|group| group.with_meter(meter))))
4217	}
4218
4219	/// Read this track's groups in sequence order instead of arrival order.
4220	///
4221	/// Consumes the subscriber, so one handle carries exactly one cursor: an
4222	/// arrival-order [`Subscriber`] or a sequence-order [`Ordered`], never both at once.
4223	/// The two advance independently, and interleaving them produces a group stream that
4224	/// is neither, which is why the choice is a handle rather than a method.
4225	///
4226	/// Datagrams come along: they are a separate cursor either way, so the choice of group
4227	/// order says nothing about them.
4228	pub fn ordered(self) -> Ordered {
4229		Ordered { inner: self }
4230	}
4231
4232	/// Whether `other` was cloned from this subscriber (shares the same underlying state).
4233	pub fn is_clone(&self, other: &Self) -> bool {
4234		match (&self.inner, &other.inner) {
4235			(SubscriberKind::Plain(a), SubscriberKind::Plain(b)) => a.state.same_channel(&b.state),
4236			(SubscriberKind::Spliced(a), SubscriberKind::Spliced(b)) => a.is_clone(b),
4237			_ => false,
4238		}
4239	}
4240
4241	/// Poll for where the source's feed starts, raised to this cursor's floor, once
4242	/// resolved; see [`Consumer::poll_start`]. A feed starting below the floor serves the
4243	/// floor's group too. `None` when the source declares none.
4244	pub(crate) fn poll_start(&mut self, waiter: &kio::Waiter) -> Poll<Option<u64>> {
4245		match &mut self.inner {
4246			SubscriberKind::Plain(plain) => {
4247				let start = ready!(Consumer::poll_state_start(&plain.state, waiter));
4248				Poll::Ready(start.map(|start| start.max(plain.min_sequence)))
4249			}
4250			SubscriberKind::Spliced(spliced) => spliced.poll_start(waiter),
4251		}
4252	}
4253
4254	/// Poll for the track's declared final sequence, without blocking.
4255	pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
4256		match &mut self.inner {
4257			SubscriberKind::Plain(plain) => plain.poll(waiter, |state| state.poll_finished()),
4258			SubscriberKind::Spliced(spliced) => spliced.poll_finished(waiter),
4259		}
4260	}
4261
4262	/// Observe a declared end or a clean local close for an origin's spliced cursor.
4263	pub(crate) fn poll_end(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<u64>>> {
4264		match &mut self.inner {
4265			SubscriberKind::Plain(plain) => plain.poll(waiter, |state| state.poll_end()),
4266			SubscriberKind::Spliced(spliced) => spliced.poll_end(waiter),
4267		}
4268	}
4269
4270	/// Block until the track declares its end, returning the exclusive final sequence
4271	/// (also the total group count), or the cause on an abort.
4272	///
4273	/// Resolves as soon as the boundary is known, which may be ahead of the live edge
4274	/// when the producer finished via [`Producer::finish_at`]. This reports the declared
4275	/// end, not that every group has arrived. A local session close without a declared
4276	/// end returns [`Error::Closed`]. Drive [`Self::recv_group`] (or
4277	/// [`Ordered::next_group`]) until it yields `None` to observe the track fully drained.
4278	pub async fn finished(&mut self) -> Result<u64> {
4279		kio::wait(|waiter| self.poll_finished(waiter)).await
4280	}
4281
4282	/// Limit subsequent reads to these group sequences without rewinding read progress.
4283	///
4284	/// `2..=5` includes groups 2 through 5; `2..5` excludes group 5. An omitted
4285	/// start preserves the current floor, and an omitted end removes the cap.
4286	/// Groups above the end remain buffered and can be read after raising the cap.
4287	/// This changes only local delivery; use [`Subscription::with_groups`] and
4288	/// [`Self::update`] to change the requested groups.
4289	pub fn set_groups(&mut self, groups: impl RangeBounds<u64>) {
4290		let (start, end) = super::subscription::sequence_bounds(groups);
4291		self.raise_start_to(start);
4292		self.end_at(end.map_or(Bound::Unbounded, Bound::Excluded));
4293	}
4294
4295	/// Start this subscriber's read cursor at the given sequence.
4296	///
4297	/// A local filter, not a request: it doesn't tell the publisher anything, so the
4298	/// skipped groups are still delivered and simply not returned. To ask the publisher
4299	/// to start there instead, set [`Subscription::start`] via [`Self::update`].
4300	/// See [Local cursor vs wire preference](Self#local-cursor-vs-wire-preference).
4301	pub(crate) fn start_at(&mut self, sequence: u64) {
4302		match &mut self.inner {
4303			SubscriberKind::Plain(plain) => plain.min_sequence = sequence,
4304			SubscriberKind::Spliced(spliced) => spliced.start_at(sequence),
4305		}
4306	}
4307
4308	/// Raise the read cursor's floor to `sequence`, keeping any higher floor already set.
4309	///
4310	/// The spliced layer positions a segment's inner cursor with this instead of
4311	/// [`Self::set_groups`]: the inner subscription already resolved a start from its own
4312	/// budget and floor, and an assignment would discard it.
4313	pub(crate) fn raise_start_to(&mut self, sequence: u64) {
4314		match &mut self.inner {
4315			SubscriberKind::Plain(plain) => plain.min_sequence = plain.min_sequence.max(sequence),
4316			SubscriberKind::Spliced(spliced) => spliced.raise_start_to(sequence),
4317		}
4318	}
4319
4320	/// Cap this subscriber's read cursor at `end`, or remove the cap with `..`.
4321	///
4322	/// The range says whether its sequence is delivered: `..=5` serves through group 5,
4323	/// `..5` stops before it. `..0` is the empty range: no group is delivered.
4324	/// [`Position::group_end`] translates a [`Subscription::end`].
4325	///
4326	/// A local filter, not a request; [`Subscription::end`] is the wire-level
4327	/// counterpart. See [Local cursor vs wire preference](Self#local-cursor-vs-wire-preference).
4328	///
4329	/// Groups beyond the cap are held rather than skipped past, so a later call to
4330	/// [`Self::set_groups`] with a higher bound (or unbounded) makes them available again.
4331	/// Lowering the cap below the consumer's current cursor parks the consumer until the
4332	/// cap is raised.
4333	pub(crate) fn end_at(&mut self, end: impl Into<Cap>) {
4334		let end = end.into();
4335		match &mut self.inner {
4336			SubscriberKind::Plain(plain) => {
4337				plain.end_sequence = end.exclusive();
4338				// A successor dropped by a lower cap stays dropped until the wrapping
4339				// reader pushes its anchor again, which it does on every poll.
4340				let outer = Anchor {
4341					cap: plain.stale_cap,
4342					..plain.drift_anchor.read().clone()
4343				};
4344				plain.update_drift_anchor(outer);
4345			}
4346			SubscriberKind::Spliced(spliced) => spliced.end_at(end),
4347		}
4348	}
4349
4350	/// This subscriber's current preferences.
4351	pub fn subscription(&self) -> Subscription {
4352		self.control().subscription()
4353	}
4354
4355	/// Replace this subscriber's delivery preferences.
4356	///
4357	/// Stored verbatim; the publisher's max age window is applied to the aggregate, not
4358	/// here (see [`Producer::subscription`]). Returns [`Error::Closed`] if the track
4359	/// already ended; the update is meaningless at that point and can usually be ignored.
4360	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
4361		match &mut self.inner {
4362			SubscriberKind::Plain(plain) => {
4363				let mut state = plain.subscription.write().map_err(|_| Error::Closed)?;
4364				*state = subscription;
4365			}
4366			SubscriberKind::Spliced(spliced) => spliced.update(subscription),
4367		}
4368		Ok(())
4369	}
4370
4371	/// Return the latest sequence number in the track.
4372	pub fn latest(&self) -> Option<u64> {
4373		match &self.inner {
4374			SubscriberKind::Plain(plain) => plain.state.read().max_sequence,
4375			SubscriberKind::Spliced(spliced) => spliced.latest(),
4376		}
4377	}
4378}
4379
4380/// A [`Subscriber`] that reads groups in sequence order.
4381///
4382/// Created by [`Subscriber::ordered`], which consumes the subscriber, so a track is read
4383/// one way or the other and never both. Every group it returns has a higher sequence
4384/// than the last, so a late arrival (network reordering, or a gap the cache filled after
4385/// the fact) is skipped rather than delivered out of turn.
4386///
4387/// # Age and skipping
4388///
4389/// [`Subscription::max_age`] applies as this cursor reads, exactly as it does on the
4390/// arrival cursor: a group is skipped once its *reach*, where its immediate successor
4391/// begins, is that far behind the newest frame on the track. Nothing weaker convicts it,
4392/// because the reach is the only proof that *every* frame it could still hold is past the
4393/// budget: a group's own timestamps say where it starts presenting, not where it stops,
4394/// and an unstamped successor leaves it unbounded and therefore kept. A backlog inside
4395/// the budget is still delivered whole, as a burst in order, which is what a decoder
4396/// reading a gap-free sequence needs.
4397///
4398/// The same budget follows a group already handed out: once newer content pulls that far
4399/// ahead, a read still waiting on it ends with [`Error::Old`] and the cursor moves on. A
4400/// gap costs nothing either way, since the cursor seeks to the lowest cached group in
4401/// range rather than waiting for the sequence it would have read next.
4402///
4403/// The budget is updatable mid-stream through [`Self::control`]. [`Duration::ZERO`] (the
4404/// default) keeps only what nothing newer has superseded, so a consumer that stalls and
4405/// resumes rejoins the live edge instead of replaying at 1x.
4406pub struct Ordered {
4407	inner: Subscriber,
4408}
4409
4410impl Ordered {
4411	/// The track's [`Info`], resolved when the subscription was established.
4412	pub fn info(&self) -> &Info {
4413		self.inner.info()
4414	}
4415
4416	/// The track's name, unique within its broadcast.
4417	pub fn name(&self) -> &str {
4418		self.inner.name()
4419	}
4420
4421	/// The broadcast this track belongs to, as reached through the handle it came from.
4422	pub fn broadcast(&self) -> &broadcast::Info {
4423		self.inner.broadcast()
4424	}
4425
4426	/// Poll for the next group with a higher sequence number than any previously
4427	/// returned, without blocking.
4428	///
4429	/// Honors the group range set by [`Self::set_groups`]:
4430	/// a group past the cap stays in the producer's cache and becomes eligible again if
4431	/// the cap rises or is removed.
4432	///
4433	/// Returns `Poll::Ready(Ok(Some(group)))` when a group is available,
4434	/// `Poll::Ready(Ok(None))` when the track is finished (including an abort after its
4435	/// declared end settled, see [`Producer::abort`]),
4436	/// `Poll::Ready(Err(e))` when the track has been aborted short of its end, or
4437	/// `Poll::Pending` when no group is available yet.
4438	pub fn poll_next_group(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<group::Consumer>>> {
4439		self.inner.poll_next_group(waiter)
4440	}
4441
4442	/// Return the next group with a higher sequence number than any previously returned.
4443	pub async fn next_group(&mut self) -> Result<Option<group::Consumer>> {
4444		kio::wait(|waiter| self.poll_next_group(waiter)).await
4445	}
4446
4447	/// Poll for the next datagram in arrival order, without blocking.
4448	///
4449	/// Datagrams are a separate best-effort channel from groups (see
4450	/// [`Producer::append_datagram`]); they share only the sequence namespace, and neither
4451	/// cursor moves the other. Unordered by construction, so this behaves identically on
4452	/// either handle; it is here so a track carrying both channels needs one subscription
4453	/// rather than two.
4454	pub fn poll_recv_datagram(&mut self, waiter: &kio::Waiter) -> Poll<Result<Option<Datagram>>> {
4455		self.inner.poll_recv_datagram(waiter)
4456	}
4457
4458	/// Receive the next datagram in arrival order.
4459	///
4460	/// To read groups and datagrams concurrently from one handle, poll
4461	/// [`Self::poll_next_group`] and [`Self::poll_recv_datagram`] together in a single
4462	/// `poll` closure (sequential `&mut` borrows), rather than awaiting the two `recv`
4463	/// futures at once.
4464	pub async fn recv_datagram(&mut self) -> Result<Option<Datagram>> {
4465		kio::wait(|waiter| self.poll_recv_datagram(waiter)).await
4466	}
4467
4468	/// Limit subsequent reads to these group sequences without rewinding read progress.
4469	///
4470	/// See [`Subscriber::set_groups`] for inclusive, exclusive, and omitted bounds.
4471	pub fn set_groups(&mut self, groups: impl RangeBounds<u64>) {
4472		self.inner.set_groups(groups);
4473	}
4474
4475	/// Create a handle for updating this subscriber's delivery preferences without
4476	/// borrowing the read cursor.
4477	pub fn control(&self) -> Control {
4478		self.inner.control()
4479	}
4480
4481	/// This subscriber's current preferences.
4482	pub fn subscription(&self) -> Subscription {
4483		self.inner.subscription()
4484	}
4485
4486	/// Replace this subscriber's delivery preferences.
4487	///
4488	/// Returns [`Error::Closed`] if the track already ended.
4489	pub fn update(&mut self, subscription: Subscription) -> Result<()> {
4490		self.inner.update(subscription)
4491	}
4492
4493	/// Poll for the track's declared final sequence, without blocking.
4494	pub fn poll_finished(&mut self, waiter: &kio::Waiter) -> Poll<Result<u64>> {
4495		self.inner.poll_finished(waiter)
4496	}
4497
4498	/// Block until the track declares its end, returning the exclusive final sequence.
4499	///
4500	/// See [`Subscriber::finished`]: this reports the declared end, not that every group
4501	/// has arrived.
4502	pub async fn finished(&mut self) -> Result<u64> {
4503		kio::wait(|waiter| self.poll_finished(waiter)).await
4504	}
4505
4506	/// The latest sequence number in the track.
4507	pub fn latest(&self) -> Option<u64> {
4508		self.inner.latest()
4509	}
4510
4511	/// Whether `other` reads the same underlying track state.
4512	pub fn is_clone(&self, other: &Self) -> bool {
4513		self.inner.is_clone(&other.inner)
4514	}
4515}
4516
4517/// A subscriber asked for a track this broadcast doesn't have yet.
4518///
4519/// Yielded by [`broadcast::Dynamic::requested_track`](crate::broadcast::Dynamic::requested_track),
4520/// or created up front with [`broadcast::Producer::reserve_track`](crate::broadcast::Producer::reserve_track).
4521/// Subscribers block until the request is
4522/// resolved: call [`accept`](Self::accept) to serve it with a [`Producer`], or
4523/// [`reject`](Self::reject) to fail them. Dropping it without either rejects with
4524/// [`Error::Dropped`].
4525///
4526/// Concurrent requests for one name are coalesced, so exactly one of these exists per
4527/// name at a time.
4528pub struct Request {
4529	name: Arc<str>,
4530	// The parent broadcast's info, threaded into the [`Producer`] on accept.
4531	broadcast: Arc<broadcast::Info>,
4532	state: kio::Producer<TrackState>,
4533
4534	// The previous subscription that was combined, used to detect changes.
4535	prev_subscription: Option<Subscription>,
4536
4537	// Shared with the accepted [`Producer`] and every [`Dynamic`]: its `Drop` is the
4538	// teardown, and it stays inert until a producer is minted.
4539	alive: Arc<Alive>,
4540
4541	// A requested track is served on demand, so it counts as fetch-capable from
4542	// birth: a consumer's cache-miss `fetch_group` waits to be served instead of
4543	// racing the producer (e.g. a relay) into creating its own handler. Released
4544	// when the request is accepted or dropped; by then the relay holds its own.
4545	_dynamic: Dynamic,
4546
4547	// Ingress stats scope, threaded into the accepted [`Producer`]. Empty (no-op)
4548	// unless this request was reserved on a tagged broadcast.
4549	stats: stats::Scope,
4550
4551	// The serving session resolves the start of each subscription itself, so the
4552	// accepted track's start is unknown until it says (see [`Self::resolving_start`]).
4553	resolving_start: bool,
4554}
4555
4556impl Request {
4557	pub(crate) fn new(broadcast: Arc<broadcast::Info>, name: impl Into<Arc<str>>) -> Self {
4558		let name = name.into();
4559		let state = TrackState::spawn(broadcast.clone());
4560		let alive = Alive::new(name.clone(), state.clone());
4561		let dynamic = Dynamic::new(name.clone(), state.clone(), alive.clone());
4562		Self {
4563			name,
4564			broadcast,
4565			state,
4566			prev_subscription: None,
4567			alive,
4568			_dynamic: dynamic,
4569			stats: stats::Scope::default(),
4570			resolving_start: false,
4571		}
4572	}
4573
4574	/// Mark the track as served by a session that resolves each subscription's start
4575	/// (lite-06+), so [`Consumer::poll_start`] waits for its declaration instead of
4576	/// reading the requested floor as the start. Applied atomically with
4577	/// [`Self::accept`], before any reader can see the track.
4578	pub(crate) fn resolving_start(mut self) -> Self {
4579		self.resolving_start = true;
4580		self
4581	}
4582
4583	/// Attach an ingress stats scope, applied to the [`Producer`] on accept. Set by
4584	/// a tagged [`broadcast::Producer::reserve_track`].
4585	pub(crate) fn with_stats(mut self, scope: stats::Scope) -> Self {
4586		self.stats = scope;
4587		self
4588	}
4589
4590	/// The requested track name.
4591	pub fn name(&self) -> &str {
4592		&self.name
4593	}
4594
4595	/// A [`Consumer`] for the eventual track, usable before the request is accepted.
4596	pub fn consume(&self) -> Consumer {
4597		Consumer::plain(self.name.clone(), self.state.consume())
4598	}
4599
4600	/// Create a [`Dynamic`] handle that serves on-demand fetches of uncached
4601	/// groups, before [`Self::accept`] is even called. A relay creates one to fetch
4602	/// past groups from upstream while (or instead of) serving a live subscription.
4603	pub fn dynamic(&self) -> Dynamic {
4604		Dynamic::new(self.name.clone(), self.state.clone(), self.alive.clone())
4605	}
4606
4607	/// Poll for the request becoming unused (every consumer dropped), so a relay can
4608	/// stop serving and drop the request.
4609	pub fn poll_unused(&self, waiter: &kio::Waiter) -> Poll<()> {
4610		self.state.poll_unused(waiter).map(|_| ())
4611	}
4612
4613	/// Mark this request as taken by a dynamic handler, which alone decides its answer.
4614	pub(crate) fn claim(self) -> Self {
4615		if let Ok(mut state) = self.state.write() {
4616			state.claimed = true;
4617		}
4618		self
4619	}
4620
4621	/// Reject only while no consumer needs this pending track. Demand and the check
4622	/// share one lock, so demand returning after `poll_unused` wins the race, and the
4623	/// close under that lock stops a later consumer attaching to a dead request.
4624	pub(crate) fn reject_unused(&self, err: Error) -> bool {
4625		match self.state.write_unused() {
4626			kio::Unused::Idle(guard) => {
4627				commit_abort(guard, err);
4628				true
4629			}
4630			kio::Unused::Closed => true,
4631			kio::Unused::Used => false,
4632		}
4633	}
4634
4635	/// Serve the request with the given track, resolving every waiting subscriber.
4636	///
4637	/// The name is taken from [`Self::name`]; `info` supplies the remaining knobs
4638	/// (`None` for the defaults). If the track was already aborted, the returned
4639	/// [`Producer`] is inert: writes fail with the abort error, as if it had been
4640	/// aborted immediately after accepting.
4641	pub fn accept(self, info: impl Into<Option<Info>>) -> Producer {
4642		let info = TrackState::normalize_info(&self.broadcast, info.into().unwrap_or_default());
4643		// A closed state means the track was aborted under us. Mirror `reject` and
4644		// tolerate it: the Producer we hand back simply can't write.
4645		if let Ok(mut state) = self.state.write() {
4646			state.accept(info.clone());
4647			state.start_pending = self.resolving_start;
4648		}
4649		// Accepting the request creates the track producer: count it as one ingress
4650		// subscription (closed when the last handle drops). No-op when untagged.
4651		self.alive.publish(Some(&self.stats));
4652		Producer {
4653			name: self.name,
4654			info,
4655			broadcast: self.broadcast,
4656			state: self.state,
4657			prev_subscription: None,
4658			alive: self.alive,
4659			stats: self.stats,
4660		}
4661	}
4662
4663	/// Reject the request, waking all waiting subscribers with `err`.
4664	pub fn reject(self, err: Error) {
4665		if let Ok(mut state) = self.state.write() {
4666			state.abort = Some(err);
4667		}
4668	}
4669
4670	/// The delivery preferences aggregated across everyone waiting on this request,
4671	/// or `None` if nobody is waiting. Useful for sizing the track before accepting.
4672	pub fn subscription(&self) -> Option<Subscription> {
4673		let state = self.state.read();
4674		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
4675		drop(state);
4676		snapshot_subscription(&subs, bound)
4677	}
4678
4679	/// Block until the aggregate [`subscription`](Self::subscription) changes,
4680	/// yielding `None` once nobody is waiting.
4681	pub async fn subscription_changed(&mut self) -> Option<Subscription> {
4682		kio::wait(|waiter| self.poll_subscription_changed(waiter)).await
4683	}
4684
4685	/// Poll counterpart to [`subscription_changed`](Self::subscription_changed).
4686	pub fn poll_subscription_changed(&mut self, waiter: &kio::Waiter) -> Poll<Option<Subscription>> {
4687		let state = self.state.read();
4688		let (subs, bound) = (state.subscriptions.clone(), state.max_age_bound());
4689		drop(state);
4690
4691		poll_combined_changed(&subs, bound, &mut self.prev_subscription, waiter)
4692	}
4693
4694	pub(super) fn weak(&self) -> TrackWeak {
4695		TrackWeak {
4696			name: self.name.clone(),
4697			state: self.state.weak(),
4698		}
4699	}
4700}
4701
4702#[cfg(test)]
4703use futures::FutureExt;
4704
4705#[cfg(test)]
4706#[allow(missing_docs)] // test-only assertion helpers
4707impl Subscriber {
4708	pub fn assert_group(&mut self) -> group::Consumer {
4709		self.recv_group()
4710			.now_or_never()
4711			.expect("group would have blocked")
4712			.expect("would have errored")
4713			.expect("track was closed")
4714	}
4715
4716	pub fn assert_no_group(&mut self) {
4717		assert!(
4718			self.recv_group().now_or_never().is_none(),
4719			"recv_group would not have blocked"
4720		);
4721	}
4722
4723	pub fn assert_not_closed(&mut self) {
4724		assert!(self.finished().now_or_never().is_none(), "should not be closed");
4725	}
4726
4727	pub fn assert_closed(&mut self) {
4728		assert!(self.finished().now_or_never().is_some(), "should be closed");
4729	}
4730
4731	// TODO assert specific errors after implementing PartialEq
4732	pub fn assert_error(&mut self) {
4733		assert!(
4734			self.finished().now_or_never().expect("should not block").is_err(),
4735			"should be error"
4736		);
4737	}
4738
4739	pub fn assert_is_clone(&self, other: &Self) {
4740		assert!(self.is_clone(other), "should be clone");
4741	}
4742
4743	pub fn assert_not_clone(&self, other: &Self) {
4744		assert!(!self.is_clone(other), "should not be clone");
4745	}
4746}
4747
4748#[cfg(test)]
4749mod test {
4750	use super::*;
4751	use crate::frame;
4752	use crate::model::test_tracing::count_drop_warnings;
4753	use std::time::Duration;
4754
4755	/// Mint a track for tests with a default parent broadcast, since tracks are
4756	/// normally born from a [`broadcast::Producer`].
4757	fn track_producer(name: impl Into<Arc<str>>, info: impl Into<Option<Info>>) -> Producer {
4758		Producer::new(Arc::new(broadcast::Info::default()), name, info)
4759	}
4760
4761	/// A bounded replay window for tests whose subject requires every buffered group.
4762	fn replay() -> Subscription {
4763		Subscription::default().with_max_age(Duration::from_secs(30))
4764	}
4765
4766	/// Helper: count live cached groups in state.
4767	fn live_groups(state: &TrackState) -> usize {
4768		state.lookup.len()
4769	}
4770
4771	/// Helper: get the sequence number of the first live group in arrival order.
4772	fn first_live_sequence(state: &TrackState) -> u64 {
4773		state
4774			.arrival
4775			.iter()
4776			.find(|(sequence, stamp)| state.lookup.get(sequence).is_some_and(|slot| slot.stamp == *stamp))
4777			.map(|(sequence, _)| *sequence)
4778			.unwrap()
4779	}
4780
4781	/// Helper: non-blocking datagram receive that must be ready with a datagram.
4782	fn recv_datagram(dg: &mut Subscriber) -> Datagram {
4783		dg.recv_datagram()
4784			.now_or_never()
4785			.expect("datagram would have blocked")
4786			.expect("would have errored")
4787			.expect("track was closed")
4788	}
4789
4790	/// A declared start (SUBSCRIBE_START) resolves a peek below it as a permanent
4791	/// miss, while a group that is already cached below the start stays readable
4792	/// (a fetch can create one; the declaration only covers the live feed).
4793	#[tokio::test]
4794	async fn peek_resolves_below_the_declared_start() {
4795		let mut producer = track_producer("test", None);
4796		let consumer = producer.consume();
4797
4798		let mut cached = producer.create_group(group::Info { sequence: 1 }).unwrap();
4799		cached.write_frame(Timestamp::ZERO, b"backfill".to_vec()).unwrap();
4800		cached.finish().unwrap();
4801
4802		// Nothing declared yet: a missing group parks.
4803		let waiter = kio::Waiter::noop();
4804		assert!(consumer.poll_peek_group(0, &waiter).is_pending());
4805
4806		// The declaration turns the missing group into a permanent miss, but the
4807		// cached one below it still resolves.
4808		producer.start_at(3).unwrap();
4809		assert!(matches!(consumer.poll_peek_group(0, &waiter), Poll::Ready(None)));
4810		assert!(matches!(consumer.poll_peek_group(1, &waiter), Poll::Ready(Some(_))));
4811		assert!(consumer.poll_peek_group(3, &waiter).is_pending());
4812
4813		// The declaration follows the demand in either direction: forward retires
4814		// the skipped range, backward reopens it.
4815		producer.start_at(4).unwrap();
4816		assert!(matches!(consumer.poll_peek_group(3, &waiter), Poll::Ready(None)));
4817		producer.start_at(0).unwrap();
4818		assert!(consumer.poll_peek_group(0, &waiter).is_pending());
4819	}
4820
4821	#[tokio::test]
4822	async fn append_datagram_shares_group_sequence() {
4823		let mut producer = track_producer("test", None);
4824		let ts = Timestamp::from_millis(10).unwrap();
4825
4826		// Interleave groups and datagrams: they draw from one monotonic counter.
4827		assert_eq!(producer.append_group().unwrap().sequence, 0);
4828		assert_eq!(producer.append_datagram(ts, &b"a"[..]).unwrap(), 1);
4829		assert_eq!(producer.append_group().unwrap().sequence, 2);
4830		assert_eq!(producer.append_datagram(ts, &b"b"[..]).unwrap(), 3);
4831		assert_eq!(producer.latest(), Some(3));
4832	}
4833
4834	#[tokio::test]
4835	async fn append_datagram_roundtrip() {
4836		let mut producer = track_producer("test", None);
4837		let mut dg = producer.subscribe(None);
4838
4839		let ts = Timestamp::from_millis(42).unwrap();
4840		let seq = producer.append_datagram(ts, &b"hello"[..]).unwrap();
4841
4842		let got = recv_datagram(&mut dg);
4843		assert_eq!(got.sequence, seq);
4844		assert_eq!(got.timestamp, ts);
4845		assert_eq!(&got.payload[..], b"hello");
4846	}
4847
4848	#[tokio::test]
4849	async fn insert_datagram_preserves_sequence() {
4850		let mut producer = track_producer("test", None);
4851		let mut dg = producer.subscribe(None);
4852
4853		let ts = Timestamp::from_millis(5).unwrap();
4854		// A relay forwarding an upstream datagram keeps its sequence number.
4855		producer
4856			.insert_datagram(100, ts, bytes::Bytes::from_static(b"x"))
4857			.unwrap();
4858
4859		assert_eq!(recv_datagram(&mut dg).sequence, 100);
4860		// max_sequence advanced, so the next appended group/datagram continues past it.
4861		assert_eq!(producer.append_group().unwrap().sequence, 101);
4862	}
4863
4864	#[tokio::test]
4865	async fn insert_datagram_leaves_a_gap() {
4866		let mut producer = track_producer("test", None);
4867		let mut dg = producer.subscribe(None);
4868		let ts = Timestamp::from_millis(0).unwrap();
4869
4870		producer
4871			.insert_datagram(10, ts, bytes::Bytes::from_static(b"gap"))
4872			.unwrap();
4873		assert_eq!(recv_datagram(&mut dg).sequence, 10);
4874		assert_eq!(producer.append_datagram(ts, &b"next"[..]).unwrap(), 11);
4875		assert_eq!(producer.append_group().unwrap().sequence, 12);
4876	}
4877
4878	#[tokio::test]
4879	async fn insert_datagram_out_of_order_does_not_rewind() {
4880		let mut producer = track_producer("test", None);
4881		let mut dg = producer.subscribe(None);
4882		let ts = Timestamp::from_millis(0).unwrap();
4883
4884		producer
4885			.insert_datagram(10, ts, bytes::Bytes::from_static(b"high"))
4886			.unwrap();
4887		producer
4888			.insert_datagram(5, ts, bytes::Bytes::from_static(b"low"))
4889			.unwrap();
4890
4891		assert_eq!(recv_datagram(&mut dg).sequence, 10);
4892		assert_eq!(recv_datagram(&mut dg).sequence, 5);
4893		assert_eq!(producer.append_datagram(ts, &b"next"[..]).unwrap(), 11);
4894	}
4895
4896	#[tokio::test]
4897	async fn insert_datagram_duplicate_is_best_effort() {
4898		let mut producer = track_producer("test", None);
4899		let mut dg = producer.subscribe(None);
4900		let ts = Timestamp::from_millis(0).unwrap();
4901
4902		producer
4903			.insert_datagram(3, ts, bytes::Bytes::from_static(b"first"))
4904			.unwrap();
4905		producer
4906			.insert_datagram(3, ts, bytes::Bytes::from_static(b"again"))
4907			.unwrap();
4908
4909		assert_eq!(&recv_datagram(&mut dg).payload[..], b"first");
4910		assert_eq!(&recv_datagram(&mut dg).payload[..], b"again");
4911		assert_eq!(producer.append_datagram(ts, &b"next"[..]).unwrap(), 4);
4912	}
4913
4914	#[tokio::test]
4915	async fn insert_datagram_stale_does_not_rewind_after_append() {
4916		let mut producer = track_producer("test", None);
4917		let mut dg = producer.subscribe(None);
4918		let ts = Timestamp::from_millis(0).unwrap();
4919
4920		assert_eq!(producer.append_datagram(ts, &b"0"[..]).unwrap(), 0);
4921		assert_eq!(producer.append_datagram(ts, &b"1"[..]).unwrap(), 1);
4922		producer
4923			.insert_datagram(0, ts, bytes::Bytes::from_static(b"stale"))
4924			.unwrap();
4925
4926		assert_eq!(recv_datagram(&mut dg).sequence, 0);
4927		assert_eq!(recv_datagram(&mut dg).sequence, 1);
4928		assert_eq!(recv_datagram(&mut dg).sequence, 0);
4929		assert_eq!(producer.append_datagram(ts, &b"2"[..]).unwrap(), 2);
4930		assert_eq!(producer.append_group().unwrap().sequence, 3);
4931	}
4932
4933	#[tokio::test]
4934	async fn insert_datagram_cloned_producers_share_counter() {
4935		let mut producer = track_producer("test", None);
4936		let mut other = producer.clone();
4937		let mut dg = producer.subscribe(None);
4938		let ts = Timestamp::from_millis(0).unwrap();
4939
4940		producer
4941			.insert_datagram(4, ts, bytes::Bytes::from_static(b"a"))
4942			.unwrap();
4943		assert_eq!(other.append_datagram(ts, &b"b"[..]).unwrap(), 5);
4944		other.insert_datagram(8, ts, bytes::Bytes::from_static(b"c")).unwrap();
4945		assert_eq!(producer.append_group().unwrap().sequence, 9);
4946
4947		assert_eq!(recv_datagram(&mut dg).sequence, 4);
4948		assert_eq!(recv_datagram(&mut dg).sequence, 5);
4949		assert_eq!(recv_datagram(&mut dg).sequence, 8);
4950	}
4951
4952	#[test]
4953	fn insert_datagram_after_finish_is_closed() {
4954		let mut producer = track_producer("test", None);
4955		let ts = Timestamp::from_millis(0).unwrap();
4956		producer.finish().unwrap();
4957		assert!(matches!(
4958			producer.insert_datagram(0, ts, bytes::Bytes::from_static(b"x")),
4959			Err(Error::Closed)
4960		));
4961		assert!(matches!(producer.append_datagram(ts, &b"x"[..]), Err(Error::Closed)));
4962	}
4963
4964	#[test]
4965	fn insert_datagram_after_abort_fails() {
4966		let producer = track_producer("test", None);
4967		let mut other = producer.clone();
4968		let ts = Timestamp::from_millis(0).unwrap();
4969		producer.abort(Error::Cancel).unwrap();
4970		assert!(other.insert_datagram(0, ts, bytes::Bytes::from_static(b"x")).is_err());
4971	}
4972
4973	#[test]
4974	fn insert_datagram_respects_finish_at() {
4975		let mut producer = track_producer("test", None);
4976		let ts = Timestamp::from_millis(0).unwrap();
4977		producer.finish_at(10).unwrap();
4978		producer
4979			.insert_datagram(5, ts, bytes::Bytes::from_static(b"ok"))
4980			.unwrap();
4981		assert!(matches!(
4982			producer.insert_datagram(10, ts, bytes::Bytes::from_static(b"late")),
4983			Err(Error::Closed)
4984		));
4985		assert_eq!(producer.append_group().unwrap().sequence, 6);
4986	}
4987
4988	/// Datagram sequence advances do not move a route takeover past an open group.
4989	#[test]
4990	fn resume_position_uses_the_latest_group() {
4991		let mut datagram_only = track_producer("datagram-only", None);
4992		let datagram_only_consumer = datagram_only.consume();
4993		datagram_only
4994			.insert_datagram(8, Timestamp::ZERO, bytes::Bytes::from_static(b"x"))
4995			.unwrap();
4996		assert_eq!(
4997			datagram_only_consumer.resume_position(),
4998			None,
4999			"a datagram creates no group position to resume"
5000		);
5001
5002		let mut producer = track_producer("mixed", None);
5003		let consumer = producer.consume();
5004		let mut group = producer.create_group(group::Info { sequence: 3 }).unwrap();
5005		group
5006			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
5007			.unwrap();
5008		producer
5009			.insert_datagram(8, Timestamp::ZERO, bytes::Bytes::from_static(b"x"))
5010			.unwrap();
5011
5012		assert_eq!(
5013			consumer.resume_position(),
5014			Some(Position { group: 3, frame: 1 }),
5015			"the replacement must continue the open group"
5016		);
5017		group.abort(Error::Cancel).unwrap();
5018	}
5019
5020	/// Datagrams and groups are separate channels that share only a sequence namespace,
5021	/// so consuming one must not move the other's cursor.
5022	#[tokio::test]
5023	async fn recv_datagram_leaves_the_ordered_cursor_alone() {
5024		let mut producer = track_producer("test", None);
5025		let mut datagrams = producer.subscribe(None);
5026		let mut subscriber = producer.subscribe(None).ordered();
5027		let ts = Timestamp::from_millis(5).unwrap();
5028
5029		producer
5030			.insert_datagram(5, ts, bytes::Bytes::from_static(b"x"))
5031			.unwrap();
5032		assert_eq!(recv_datagram(&mut datagrams).sequence, 5);
5033
5034		producer.create_group(group::Info { sequence: 3 }).unwrap();
5035		producer.create_group(group::Info { sequence: 6 }).unwrap();
5036
5037		let mut next = || {
5038			subscriber
5039				.next_group()
5040				.now_or_never()
5041				.expect("group would have blocked")
5042				.expect("would have errored")
5043				.expect("track was closed")
5044				.sequence
5045		};
5046		assert_eq!(next(), 3, "the datagram at sequence 5 did not consume group 3");
5047		assert_eq!(next(), 6);
5048	}
5049
5050	#[tokio::test]
5051	async fn datagram_normalized_to_track_timescale() {
5052		let info = Info::default().with_timescale(Timescale::MICRO);
5053		let mut producer = track_producer("test", info);
5054		let mut dg = producer.subscribe(None);
5055
5056		// Supplied at millis; stored/emitted at the track's micro timescale.
5057		producer
5058			.append_datagram(Timestamp::from_millis(2).unwrap(), &b"z"[..])
5059			.unwrap();
5060		let got = recv_datagram(&mut dg);
5061		assert_eq!(got.timestamp.scale(), Timescale::MICRO);
5062		assert_eq!(got.timestamp.value(), 2_000);
5063	}
5064
5065	#[tokio::test]
5066	async fn datagram_rejects_oversized() {
5067		let mut producer = track_producer("test", None);
5068		let big = bytes::Bytes::from(vec![0u8; crate::model::datagram::MAX_DATAGRAM_PAYLOAD + 1]);
5069		let ts = Timestamp::from_millis(0).unwrap();
5070		assert!(matches!(
5071			producer.append_datagram(ts, big.clone()),
5072			Err(Error::FrameTooLarge)
5073		));
5074		assert!(matches!(
5075			producer.insert_datagram(0, ts, big),
5076			Err(Error::FrameTooLarge)
5077		));
5078	}
5079
5080	#[tokio::test]
5081	async fn datagram_fanout_to_subscribers() {
5082		let mut producer = track_producer("test", None);
5083		// Two independent subscribers, each with its own datagram cursor.
5084		let mut a = producer.subscribe(None);
5085		let mut b = producer.subscribe(None);
5086		let ts = Timestamp::from_millis(1).unwrap();
5087
5088		producer.append_datagram(ts, &b"first"[..]).unwrap();
5089		producer.append_datagram(ts, &b"second"[..]).unwrap();
5090
5091		// Both receive every datagram in order, independently.
5092		assert_eq!(&recv_datagram(&mut a).payload[..], b"first");
5093		assert_eq!(&recv_datagram(&mut a).payload[..], b"second");
5094		assert_eq!(&recv_datagram(&mut b).payload[..], b"first");
5095		assert_eq!(&recv_datagram(&mut b).payload[..], b"second");
5096	}
5097
5098	#[test]
5099	fn datagram_buffer_drops_oldest_at_capacity() {
5100		let mut producer = track_producer("test", None);
5101		let mut slow = producer.subscribe(None);
5102		let mut fast = producer.subscribe(None);
5103		let count = MAX_DATAGRAMS * 3;
5104		for sequence in 0..count {
5105			producer.append_datagram(Timestamp::ZERO, b"x".as_slice()).unwrap();
5106			assert_eq!(recv_datagram(&mut fast).sequence, sequence as u64);
5107		}
5108		assert_eq!(producer.state.read().datagrams.len(), MAX_DATAGRAMS);
5109		for sequence in count - MAX_DATAGRAMS..count {
5110			assert_eq!(recv_datagram(&mut slow).sequence, sequence as u64);
5111		}
5112		assert!(slow.poll_recv_datagram(&kio::Waiter::noop()).is_pending());
5113	}
5114
5115	#[tokio::test]
5116	async fn datagram_recv_pends_until_written() {
5117		let mut producer = track_producer("test", None);
5118		let mut dg = producer.subscribe(None);
5119
5120		assert!(
5121			dg.recv_datagram().now_or_never().is_none(),
5122			"should block with no datagrams"
5123		);
5124
5125		producer
5126			.append_datagram(Timestamp::from_millis(0).unwrap(), &b"go"[..])
5127			.unwrap();
5128		assert_eq!(&recv_datagram(&mut dg).payload[..], b"go");
5129	}
5130
5131	/// Exercises the full producer -> publisher-encode -> subscriber-decode -> producer seam
5132	/// (everything but the QUIC datagram send/recv), catching any field-order mismatch between
5133	/// the wire codec and the model.
5134	#[tokio::test]
5135	async fn datagram_wire_roundtrip_between_tracks() {
5136		use crate::coding::{Decode, Encode};
5137		use crate::lite;
5138
5139		let version = lite::Version::Lite05;
5140
5141		// Origin publishes a datagram; the publisher reads it and encodes the wire body.
5142		let mut origin = track_producer("test", None);
5143		let mut origin_dg = origin.subscribe(None);
5144		let ts = Timestamp::from_millis(7).unwrap();
5145		let seq = origin.append_datagram(ts, &b"payload"[..]).unwrap();
5146
5147		let d = recv_datagram(&mut origin_dg);
5148		let body = lite::Datagram {
5149			subscribe: 5,
5150			sequence: d.sequence,
5151			timestamp: d.timestamp.value(),
5152			payload: d.payload.clone(),
5153		}
5154		.encode_bytes(version)
5155		.unwrap();
5156
5157		// Subscriber decodes the body and writes it downstream, preserving the sequence.
5158		let mut slice = &body[..];
5159		let wire = lite::Datagram::decode(&mut slice, version).unwrap();
5160		let mut downstream = track_producer("test", None);
5161		let mut downstream_dg = downstream.subscribe(None);
5162		downstream
5163			.insert_datagram(
5164				wire.sequence,
5165				Timestamp::new(wire.timestamp, Timescale::MILLI).unwrap(),
5166				wire.payload,
5167			)
5168			.unwrap();
5169
5170		let got = recv_datagram(&mut downstream_dg);
5171		assert_eq!(got.sequence, seq);
5172		assert_eq!(got.timestamp, ts);
5173		assert_eq!(&got.payload[..], b"payload");
5174	}
5175
5176	#[tokio::test]
5177	async fn evict_expired_groups() {
5178		let producer = track_producer("test", None);
5179
5180		// Create 3 groups at time 0.
5181		producer.append_group().unwrap(); // seq 0
5182		producer.append_group().unwrap(); // seq 1
5183		producer.append_group().unwrap(); // seq 2
5184
5185		{
5186			let state = producer.state.read();
5187			assert_eq!(live_groups(&state), 3);
5188			assert_eq!(state.offset, 0);
5189		}
5190
5191		// Advance time past the pool's LRU window.
5192		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
5193
5194		// Append a new group to trigger eviction.
5195		producer.append_group().unwrap(); // seq 3
5196
5197		// Groups 0, 1, 2 are expired but seq 3 (the live edge) is kept. Their arrival
5198		// entries no longer resolve, so the leading ones are trimmed and the offset
5199		// advances past them.
5200		{
5201			let state = producer.state.read();
5202			assert_eq!(live_groups(&state), 1);
5203			assert_eq!(first_live_sequence(&state), 3);
5204			assert_eq!(state.offset, 3);
5205			assert!(!state.lookup.contains_key(&0));
5206			assert!(!state.lookup.contains_key(&1));
5207			assert!(!state.lookup.contains_key(&2));
5208			assert!(state.lookup.contains_key(&3));
5209		}
5210	}
5211
5212	/// A group whose frames outlive `max_age` is aged out when the next group starts, but
5213	/// a subscriber that already drained it must still see the clean end of group. Otherwise a
5214	/// track with long groups (a per-minute rollup, say) fails its readers at every boundary.
5215	#[tokio::test]
5216	async fn aging_out_a_finished_group_keeps_the_clean_end() {
5217		let producer = track_producer("test", None);
5218		let mut group = producer.create_group(group::Info { sequence: 0 }).unwrap();
5219		let mut consumer = group.consume();
5220
5221		group
5222			.write_frame(Timestamp::from_millis(0).unwrap(), b"hello".as_slice())
5223			.unwrap();
5224		assert_eq!(consumer.next_frame().await.unwrap().unwrap().size, 5);
5225
5226		// The group stays open well past the LRU window, then the next period starts.
5227		crate::model::clock::advance(cache::DEFAULT_EXPIRY * 2);
5228		group.finish().unwrap();
5229		let _next = producer.create_group(group::Info { sequence: 1 }).unwrap();
5230
5231		assert!(consumer.next_frame().await.unwrap().is_none());
5232	}
5233
5234	/// An actively-read group is not expired out from under its reader: every frame
5235	/// read restarts the retention clock. A group nobody reads still ages out on
5236	/// schedule, so reclamation stays intact.
5237	#[tokio::test]
5238	async fn active_reader_survives_expiry() {
5239		let producer = track_producer("test", None);
5240		let mut subscriber = producer.subscribe(None);
5241
5242		// A finished group with one frame per step of the read loop below.
5243		let mut group = producer.create_group(0u64.into()).unwrap();
5244		for _ in 0..10 {
5245			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5246		}
5247		group.finish().unwrap();
5248		let mut reading = subscriber.assert_group();
5249
5250		// A sibling written at the same time that nobody ever reads.
5251		producer.create_group(1u64.into()).unwrap().finish().unwrap();
5252
5253		// Each step stays well inside the retention window, but the whole read
5254		// spans several windows. New groups keep the expiry scan running.
5255		for seq in 2..12u64 {
5256			crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2);
5257			let frame = reading.next_frame().await;
5258			assert!(
5259				matches!(frame, Ok(Some(_))),
5260				"an actively-read group must not expire mid-read (step {seq})"
5261			);
5262			producer.create_group(seq.into()).unwrap().finish().unwrap();
5263		}
5264
5265		let state = producer.state.read();
5266		assert!(state.lookup.contains_key(&0), "the read group survived");
5267		assert!(!state.lookup.contains_key(&1), "the unread group still expired");
5268	}
5269
5270	/// Whole-frame reads served from the prefetch batch must also keep the group
5271	/// alive: the batch is filled (and stamped) once per `Prefetch::CAP` frames,
5272	/// which bounds frames, not elapsed time, so a slow `read_frame` reader has to
5273	/// re-stamp on a time bound between refills.
5274	#[tokio::test]
5275	async fn slow_prefetch_reader_survives_expiry() {
5276		let producer = track_producer("test", None);
5277		let mut subscriber = producer.subscribe(None);
5278
5279		let mut group = producer.create_group(0u64.into()).unwrap();
5280		for _ in 0..20 {
5281			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5282		}
5283		group.finish().unwrap();
5284		let mut reading = subscriber.assert_group();
5285
5286		// One whole-frame read per half-window: most are served straight from the
5287		// prefetch without locking. New groups keep the expiry scan running.
5288		for seq in 1..20u64 {
5289			crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2);
5290			let frame = reading.read_frame().await;
5291			assert!(
5292				matches!(frame, Ok(Some(_))),
5293				"a slow prefetch reader must not expire mid-read (step {seq})"
5294			);
5295			producer.create_group(seq.into()).unwrap().finish().unwrap();
5296		}
5297	}
5298
5299	/// Receiving a group is itself a cache access: a subscriber that takes
5300	/// delivery just before the group would age out still gets to read it a full
5301	/// window later.
5302	#[tokio::test]
5303	async fn delivery_restarts_the_expiry_clock() {
5304		let producer = track_producer("test", None);
5305		let mut subscriber = producer.subscribe(replay());
5306
5307		let mut group = producer.create_group(0u64.into()).unwrap();
5308		group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5309		group.finish().unwrap();
5310		// A second group so seq 0 leaves the protected live edge.
5311		producer.create_group(1u64.into()).unwrap().finish().unwrap();
5312
5313		// Deliver just inside the window: the delivery stamps the group.
5314		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
5315		let mut reading = subscriber.assert_group();
5316
5317		// Almost another full window passes: far beyond the write, inside the
5318		// delivery stamp. The new group runs the expiry scan.
5319		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
5320		producer.create_group(2u64.into()).unwrap().finish().unwrap();
5321
5322		let frame = reading.read_frame().await.unwrap();
5323		assert!(frame.is_some(), "a just-delivered group must not expire unread");
5324	}
5325
5326	/// Streaming chunks into an in-flight frame is a write access: a straggler
5327	/// group (behind the live edge) trickling a large frame across several
5328	/// retention windows must not be expired mid-write.
5329	#[tokio::test]
5330	async fn streaming_frame_writes_keep_the_group_alive() {
5331		let producer = track_producer("test", None);
5332		let mut straggler = producer.create_group(0u64.into()).unwrap();
5333		// The live edge moves on, so the straggler is demoted and expirable.
5334		producer.create_group(1u64.into()).unwrap().finish().unwrap();
5335
5336		let mut frame = straggler
5337			.create_frame(frame::Info {
5338				size: 10,
5339				timestamp: Timestamp::ZERO,
5340			})
5341			.unwrap();
5342		// One chunk per half-window; the whole frame spans several windows. New
5343		// groups keep the expiry scan running.
5344		for seq in 2..12u64 {
5345			crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2);
5346			frame.write(bytes::Bytes::from_static(b"x")).unwrap();
5347			producer.create_group(seq.into()).unwrap().finish().unwrap();
5348		}
5349		frame.finish().unwrap();
5350		straggler.finish().unwrap();
5351
5352		let state = producer.state.read();
5353		assert!(
5354			state.lookup.contains_key(&0),
5355			"a group streaming a frame survives expiry"
5356		);
5357	}
5358
5359	/// The wire ingest coalesces its chunk wakes to the poll boundary, so a payload
5360	/// whose tail arrives all at once completes without a single `frame_notify`.
5361	/// Committing is itself a write access: a group that just finished a frame must
5362	/// not be expired by the next track write on its stale frame-open stamp.
5363	#[tokio::test]
5364	async fn coalesced_frame_completion_keeps_the_group_alive() {
5365		let producer = track_producer("test", None);
5366		let mut straggler = producer.create_group(0u64.into()).unwrap();
5367		// The live edge moves on, so the straggler is demoted and expirable.
5368		producer.create_group(1u64.into()).unwrap().finish().unwrap();
5369
5370		let mut frame = straggler
5371			.create_frame_owned(
5372				frame::Info {
5373					size: 3,
5374					timestamp: Timestamp::ZERO,
5375				},
5376				&Default::default(),
5377			)
5378			.unwrap();
5379
5380		// The sender stalls past the retention window, then the whole payload lands in
5381		// one poll turn: the loop never returns `Pending`, so `notify` is never reached
5382		// and `finish` is the only write the charge sees.
5383		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
5384		frame.write(bytes::Bytes::from_static(b"abc")).unwrap();
5385		frame.finish().unwrap();
5386		straggler.finish().unwrap();
5387
5388		// A new group runs the expiry scan.
5389		producer.create_group(2u64.into()).unwrap().finish().unwrap();
5390
5391		let state = producer.state.read();
5392		assert!(
5393			state.lookup.contains_key(&0),
5394			"a group whose frame just completed must not expire"
5395		);
5396	}
5397
5398	/// Re-offering a parked group (once the cap rises) is a delivery: it restarts
5399	/// the expiry clock so the subscriber gets to read what it was just handed.
5400	#[tokio::test]
5401	async fn parked_reoffer_restarts_the_expiry_clock() {
5402		let producer = track_producer("test", None);
5403		let mut subscriber = producer.subscribe(None);
5404		subscriber.set_groups(..1);
5405
5406		for seq in 0..2u64 {
5407			let mut group = producer.create_group(seq.into()).unwrap();
5408			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5409			group.finish().unwrap();
5410		}
5411
5412		// Group 0 is in range; group 1 is beyond the cap and parks.
5413		assert_eq!(subscriber.assert_group().sequence, 0);
5414		subscriber.assert_no_group();
5415
5416		// Just inside the window, the cap rises and the re-offer stamps group 1.
5417		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
5418		subscriber.set_groups(..2);
5419		let mut reading = subscriber.assert_group();
5420		assert_eq!(reading.sequence, 1);
5421
5422		// Almost another full window passes: far beyond the write, inside the
5423		// re-offer stamp. The new group runs the expiry scan.
5424		crate::model::clock::advance(cache::DEFAULT_EXPIRY - Duration::from_secs(1));
5425		producer.create_group(2u64.into()).unwrap().finish().unwrap();
5426
5427		let frame = reading.read_frame().await.unwrap();
5428		assert!(frame.is_some(), "a just-re-offered group must not expire unread");
5429	}
5430
5431	#[tokio::test]
5432	async fn evict_keeps_max_sequence() {
5433		let producer = track_producer("test", None);
5434		producer.append_group().unwrap(); // seq 0
5435
5436		// Advance time past the LRU window.
5437		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
5438
5439		// Append another group; seq 0 is expired and evicted.
5440		producer.append_group().unwrap(); // seq 1
5441
5442		{
5443			let state = producer.state.read();
5444			assert_eq!(live_groups(&state), 1);
5445			assert_eq!(first_live_sequence(&state), 1);
5446			assert_eq!(state.offset, 1);
5447		}
5448	}
5449
5450	#[tokio::test]
5451	async fn no_eviction_when_fresh() {
5452		let producer = track_producer("test", None);
5453		producer.append_group().unwrap(); // seq 0
5454		producer.append_group().unwrap(); // seq 1
5455		producer.append_group().unwrap(); // seq 2
5456
5457		{
5458			let state = producer.state.read();
5459			assert_eq!(live_groups(&state), 3);
5460			assert_eq!(state.offset, 0);
5461		}
5462	}
5463
5464	#[tokio::test]
5465	async fn consumer_skips_evicted_groups() {
5466		let producer = track_producer("test", None);
5467		producer.append_group().unwrap(); // seq 0
5468
5469		let mut consumer = producer.subscribe(None);
5470
5471		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
5472		producer.append_group().unwrap(); // seq 1
5473
5474		// Group 0 was evicted. Consumer should get group 1.
5475		let group = consumer.assert_group();
5476		assert_eq!(group.sequence, 1);
5477	}
5478
5479	/// Mint a track under an origin whose pool has the given wall-clock LRU window.
5480	fn track_producer_expiring(name: impl Into<Arc<str>>, expiry: impl Into<Option<Duration>>) -> Producer {
5481		track_producer_pooled(name, cache::Pool::new(cache::Config::default().with_expiry(expiry)))
5482	}
5483
5484	/// Mint a track under an origin caching into `pool`.
5485	fn track_producer_pooled(name: impl Into<Arc<str>>, pool: cache::Pool) -> Producer {
5486		Producer::new(
5487			Arc::new(broadcast::Info {
5488				pool,
5489				..Default::default()
5490			}),
5491			name,
5492			None,
5493		)
5494	}
5495
5496	/// The write path is not the only thing that runs expiry: a pool sweep reclaims a
5497	/// track's idle groups even when the track never writes again, which is the only
5498	/// bound on a publisher that stalls with a group still open.
5499	#[tokio::test]
5500	async fn pool_sweep_expires_without_a_write() {
5501		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
5502		let producer = track_producer_pooled("test", pool.clone());
5503		let mut stalled = producer.append_group().unwrap(); // seq 0, left open
5504		stalled.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5505		producer.append_group().unwrap(); // seq 1, the live edge
5506
5507		crate::model::clock::advance(Duration::from_secs(2));
5508		pool.sweep();
5509
5510		assert!(
5511			!producer.state.read().lookup.contains_key(&0),
5512			"the sweep reclaimed an idle open group with no write behind it"
5513		);
5514	}
5515
5516	#[test]
5517	fn cache_gc_dates_activity_before_expiring_it() {
5518		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
5519		let now = crate::model::clock::now();
5520		assert_eq!(pool.gc(now), Some(now + Duration::from_millis(500)));
5521		let producer = track_producer_pooled("test", pool.clone());
5522		let mut group = producer.append_group().unwrap();
5523		group.write_frame(Timestamp::ZERO, b"first".as_slice()).unwrap();
5524		producer.append_group().unwrap();
5525		// Activity written while cleanup was idle gets the new supplied time.
5526		let later = now + Duration::from_secs(60);
5527		pool.gc(later);
5528		assert!(producer.state.read().lookup.contains_key(&0));
5529		pool.gc(later + Duration::from_secs(2));
5530		assert!(!producer.state.read().lookup.contains_key(&0));
5531	}
5532
5533	#[test]
5534	fn cache_gc_reaches_old_entries_behind_a_fresh_front() {
5535		let expiry = Duration::from_secs(1);
5536		let pool = cache::Pool::new(cache::Config::default().with_expiry(expiry));
5537		let producer = track_producer_pooled("test", pool.clone());
5538		let now = crate::model::clock::now();
5539		let groups: Vec<_> = (0..EVICT_SCAN * 3).map(|_| producer.append_group().unwrap()).collect();
5540		producer.append_group().unwrap();
5541		pool.gc(now);
5542		// Keep more than a write scan's worth of leading entries fresh.
5543		for group in &groups[..EVICT_SCAN * 2] {
5544			group.cache_refresh();
5545		}
5546		pool.gc(now + expiry * 2);
5547		let state = producer.state.read();
5548		for sequence in 0..EVICT_SCAN * 2 {
5549			assert!(state.lookup.contains_key(&(sequence as u64)), "fresh front survives");
5550		}
5551		for sequence in EVICT_SCAN * 2..EVICT_SCAN * 3 {
5552			assert!(!state.lookup.contains_key(&(sequence as u64)), "old tail is reclaimed");
5553		}
5554	}
5555
5556	/// One sweep drains a whole idle backlog, not a rotating window of it: a quiet
5557	/// track has no writes left to revisit the rest of the queue with, so a bounded
5558	/// pass would leave the oldest groups parked for a backlog's length in windows.
5559	#[tokio::test]
5560	async fn pool_sweep_drains_a_deep_backlog() {
5561		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
5562		let producer = track_producer_pooled("test", pool.clone());
5563
5564		// Comfortably more than one write-driven scan window (EVICT_SCAN).
5565		let backlog = 4 * EVICT_SCAN;
5566		for _ in 0..backlog {
5567			let mut group = producer.append_group().unwrap();
5568			group.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5569		}
5570		producer.append_group().unwrap(); // the live edge, always protected
5571
5572		crate::model::clock::advance(Duration::from_secs(2));
5573		pool.sweep();
5574
5575		let state = producer.state.read();
5576		let stale = (0..backlog as u64).filter(|seq| state.lookup.contains_key(seq)).count();
5577		assert_eq!(stale, 0, "one sweep reclaimed the whole idle backlog");
5578	}
5579
5580	#[tokio::test]
5581	async fn pool_expiry_controls_eviction() {
5582		// A shorter LRU window on the pool evicts sooner than the default.
5583		let producer = track_producer_expiring("test", Duration::from_secs(1));
5584		producer.append_group().unwrap(); // seq 0
5585
5586		// Past the pool's window but well within cache::DEFAULT_EXPIRY.
5587		crate::model::clock::advance(Duration::from_secs(2));
5588		producer.append_group().unwrap(); // seq 1
5589
5590		// Seq 0 is gone because the pool only keeps idle groups for 1s.
5591		let state = producer.state.read();
5592		assert_eq!(live_groups(&state), 1);
5593		assert_eq!(first_live_sequence(&state), 1);
5594	}
5595
5596	#[tokio::test]
5597	async fn small_frame_write_expires_idle_siblings() {
5598		let producer = track_producer_expiring("test", Duration::from_secs(1));
5599		producer.append_group().unwrap().finish().unwrap(); // seq 0
5600		let mut live = producer.append_group().unwrap(); // seq 1
5601
5602		crate::model::clock::advance(Duration::from_secs(2));
5603		live.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5604
5605		let expired = !producer.state.read().lookup.contains_key(&0);
5606		assert!(expired, "a small frame write runs expiry");
5607	}
5608
5609	#[tokio::test]
5610	async fn fresh_expiry_scan_does_not_wake_track_consumers() {
5611		use std::sync::atomic::{AtomicBool, Ordering};
5612
5613		let producer = track_producer_expiring("test", cache::DEFAULT_EXPIRY);
5614		producer.append_group().unwrap().finish().unwrap();
5615		let mut live = producer.append_group().unwrap();
5616		let mut consumer = producer.subscribe(None);
5617		assert_eq!(consumer.assert_group().sequence, 0);
5618		assert_eq!(consumer.assert_group().sequence, 1);
5619
5620		let woken = Arc::new(AtomicBool::new(false));
5621		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
5622		assert!(consumer.poll_recv_group(&waiter).is_pending());
5623
5624		live.write_frame(Timestamp::ZERO, b"x".as_slice()).unwrap();
5625		assert!(
5626			!woken.load(Ordering::SeqCst),
5627			"a no-op expiry scan must not wake track consumers"
5628		);
5629	}
5630
5631	#[tokio::test]
5632	async fn streaming_frame_write_expires_idle_siblings() {
5633		let producer = track_producer_expiring("test", Duration::from_secs(1));
5634		producer.append_group().unwrap().finish().unwrap(); // seq 0
5635		let mut live = producer.append_group().unwrap(); // seq 1
5636		let mut frame = live
5637			.create_frame(frame::Info {
5638				size: 1,
5639				timestamp: Timestamp::ZERO,
5640			})
5641			.unwrap();
5642
5643		crate::model::clock::advance(Duration::from_secs(2));
5644		frame.write(b"x".as_slice()).unwrap();
5645
5646		let expired = !producer.state.read().lookup.contains_key(&0);
5647		assert!(expired, "a streamed chunk runs expiry");
5648	}
5649
5650	#[tokio::test]
5651	async fn appended_datagram_expires_idle_groups() {
5652		let mut producer = track_producer_expiring("test", Duration::from_secs(1));
5653		producer.append_group().unwrap().finish().unwrap(); // seq 0
5654		producer.append_group().unwrap().finish().unwrap(); // seq 1
5655
5656		crate::model::clock::advance(Duration::from_secs(2));
5657		producer.append_datagram(Timestamp::ZERO, b"x".as_slice()).unwrap();
5658
5659		let expired = !producer.state.read().lookup.contains_key(&0);
5660		assert!(expired, "an appended datagram runs expiry");
5661	}
5662
5663	#[tokio::test]
5664	async fn forwarded_datagram_expires_idle_groups() {
5665		let mut producer = track_producer_expiring("test", Duration::from_secs(1));
5666		producer.append_group().unwrap().finish().unwrap(); // seq 0
5667		producer.append_group().unwrap().finish().unwrap(); // seq 1
5668
5669		crate::model::clock::advance(Duration::from_secs(2));
5670		producer
5671			.insert_datagram(2, Timestamp::ZERO, bytes::Bytes::from_static(b"x"))
5672			.unwrap();
5673
5674		let expired = !producer.state.read().lookup.contains_key(&0);
5675		assert!(expired, "a forwarded datagram runs expiry");
5676	}
5677
5678	/// A track's `max_age` is a media-timestamp budget: it does not drive wall-clock
5679	/// eviction, so a stall (no accesses, no timestamp progress) shorter than the
5680	/// pool's LRU window can't age content out no matter how small the window is.
5681	#[tokio::test]
5682	async fn max_age_does_not_drive_wall_eviction() {
5683		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(1)));
5684		producer.append_group().unwrap(); // seq 0
5685
5686		// Far past max_age in wall time, but inside the pool's LRU window.
5687		crate::model::clock::advance(Duration::from_secs(10));
5688		producer.append_group().unwrap(); // seq 1
5689
5690		let state = producer.state.read();
5691		assert_eq!(live_groups(&state), 2, "max_age is media time, not a wall clock");
5692	}
5693
5694	/// Disabling the pool's expiry keeps idle groups until byte pressure reclaims them.
5695	#[tokio::test]
5696	async fn disabled_pool_expiry_never_reclaims() {
5697		let producer = track_producer_expiring("test", None);
5698		producer.append_group().unwrap(); // seq 0
5699
5700		crate::model::clock::advance(Duration::from_secs(3600));
5701		producer.append_group().unwrap(); // seq 1
5702
5703		let state = producer.state.read();
5704		assert_eq!(live_groups(&state), 2);
5705	}
5706
5707	#[test]
5708	fn max_age_clamped_to_cache() {
5709		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2)));
5710
5711		// A max age budget beyond the cache is capped in the aggregate; a group can't be
5712		// waited for longer than the publisher keeps it. The subscriber's own preference
5713		// is stored verbatim, so what it asked for stays readable.
5714		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
5715		assert_eq!(subscriber.subscription().max_age, Duration::from_secs(10));
5716		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5717
5718		// A budget within the cache is left alone, and ZERO (skip immediately) stays ZERO.
5719		subscriber
5720			.update(Subscription::default().with_max_age(Duration::from_millis(500)))
5721			.unwrap();
5722		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_millis(500));
5723
5724		subscriber
5725			.update(Subscription::default().with_max_age(Duration::ZERO))
5726			.unwrap();
5727		assert_eq!(producer.subscription().unwrap().max_age, Duration::ZERO);
5728	}
5729
5730	/// Mint a track under an origin whose retention ceiling is `cap`, so the
5731	/// track's own window is clamped down to it on bind.
5732	fn track_producer_capped(name: impl Into<Arc<str>>, info: Info, cap: Duration) -> Producer {
5733		Producer::new(
5734			Arc::new(broadcast::Info {
5735				cache_duration: cap,
5736				..Default::default()
5737			}),
5738			name,
5739			info,
5740		)
5741	}
5742
5743	#[test]
5744	fn origin_cache_duration_clamps_max_age() {
5745		// A publisher asking to keep groups for a minute is capped to the origin's 1s
5746		// ceiling; a publisher already below the ceiling is left alone (it's a min).
5747		let capped = track_producer_capped(
5748			"test",
5749			Info::default().with_max_age(Duration::from_secs(60)),
5750			Duration::from_secs(1),
5751		);
5752		assert_eq!(capped.state.read().max_age_bound(), Some(Duration::from_secs(1)));
5753		assert_eq!(capped.subscribe(None).info().max_age, Duration::from_secs(1));
5754
5755		let under = track_producer_capped(
5756			"test",
5757			Info::default().with_max_age(Duration::from_millis(500)),
5758			Duration::from_secs(1),
5759		);
5760		assert_eq!(under.state.read().max_age_bound(), Some(Duration::from_millis(500)));
5761	}
5762
5763	/// The origin ceiling clamps the media-timestamp budget only; wall-clock
5764	/// reclamation belongs to the pool's LRU window, not the ceiling.
5765	#[tokio::test]
5766	async fn origin_cache_duration_does_not_wall_evict() {
5767		let producer = track_producer_capped(
5768			"test",
5769			Info::default().with_max_age(Duration::from_secs(60)),
5770			Duration::from_secs(1),
5771		);
5772		producer.append_group().unwrap(); // seq 0
5773
5774		// Far past the ceiling in wall time, but inside the pool's LRU window.
5775		crate::model::clock::advance(Duration::from_secs(2));
5776		producer.append_group().unwrap(); // seq 1
5777
5778		let state = producer.state.read();
5779		assert_eq!(live_groups(&state), 2);
5780	}
5781
5782	#[test]
5783	fn max_age_clamped_via_every_update_path() {
5784		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2)));
5785		let over = Subscription::default().with_max_age(Duration::from_secs(10));
5786
5787		// The clamp lives in the aggregation, so it applies no matter which entry point
5788		// wrote the raw preference. Previously only `Subscriber::update` clamped.
5789		let mut subscriber = producer.subscribe(over.clone());
5790		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5791
5792		subscriber.control().update(over.clone()).unwrap();
5793		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5794
5795		subscriber.update(over).unwrap();
5796		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5797	}
5798
5799	#[test]
5800	fn max_age_aggregate_clamps_across_subscribers() {
5801		let producer = track_producer("test", Info::default().with_max_age(Duration::from_secs(2)));
5802
5803		// The aggregate takes the max, then clamps once. Equivalent to clamping each
5804		// subscriber first, since `min` distributes over `max`.
5805		let _a = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
5806		let _b = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
5807
5808		assert_eq!(producer.subscription().unwrap().max_age, Duration::from_secs(2));
5809	}
5810
5811	#[test]
5812	fn churned_subscribers_do_not_accumulate() {
5813		let producer = track_producer("test", None);
5814		let consumer = producer.consume();
5815		let _steady = producer.subscribe(None);
5816
5817		// Nobody polls the aggregate here, so registration alone has to bound the list.
5818		for _ in 0..100 {
5819			drop(producer.subscribe(None));
5820			drop(consumer.subscribe(None));
5821		}
5822
5823		// Registration sweeps only when the list is about to grow, so it holds a small
5824		// multiple of the peak live count (2) rather than all 200 departures.
5825		let subs = producer.state.read().subscriptions.clone();
5826		let len = subs.read().len();
5827		assert!(len <= 8, "departed subscribers accumulated: {len}");
5828	}
5829
5830	#[test]
5831	fn aggregate_poll_prunes_after_a_peak() {
5832		let mut producer = track_producer("test", None);
5833		let waiter = kio::Waiter::noop();
5834		let _steady = producer.subscribe(None);
5835		assert!(producer.poll_subscription_changed(&waiter).is_ready());
5836
5837		// A departed burst leaves room for many entries, and identical preferences never
5838		// change the aggregate, so each wake has to prune what it walks.
5839		drop((0..1000).map(|_| producer.subscribe(None)).collect::<Vec<_>>());
5840		for _ in 0..100 {
5841			drop(producer.subscribe(None));
5842			assert!(producer.poll_subscription_changed(&waiter).is_pending());
5843
5844			let subs = producer.state.read().subscriptions.clone();
5845			assert_eq!(subs.read().len(), 1, "the poll walked past departed subscribers");
5846		}
5847	}
5848
5849	/// Append a finished group presenting at `millis`, so the track carries a media
5850	/// timeline for the drift budget to measure against.
5851	fn append_at(producer: &mut Producer, millis: u64) -> u64 {
5852		let mut group = producer.append_group().unwrap();
5853		group
5854			.write_frame(Timestamp::from_millis(millis).unwrap(), bytes::Bytes::from_static(b"x"))
5855			.unwrap();
5856		group.finish().unwrap();
5857		group.sequence
5858	}
5859
5860	/// Every group the subscriber can read right now, in delivery order.
5861	fn drain(subscriber: &mut Subscriber) -> Vec<u64> {
5862		let mut sequences = Vec::new();
5863		while let Some(Ok(Some(group))) = subscriber.recv_group().now_or_never() {
5864			sequences.push(group.sequence);
5865		}
5866		sequences
5867	}
5868
5869	#[test]
5870	fn real_time_skips_a_backlog_to_the_live_edge() {
5871		let mut producer = track_producer("test", None);
5872		for second in 0..5 {
5873			append_at(&mut producer, second * 1000);
5874		}
5875
5876		// The default budget is REAL_TIME: a subscriber joining a track that already
5877		// holds five seconds of history takes the live edge, not the history. This is
5878		// the ceiling a takeover backlog runs into.
5879		let mut subscriber = producer.subscribe(None);
5880		assert_eq!(drain(&mut subscriber), vec![4]);
5881
5882		// And it stays caught up: the next group is live when it lands.
5883		append_at(&mut producer, 5000);
5884		assert_eq!(drain(&mut subscriber), vec![5]);
5885	}
5886
5887	#[test]
5888	fn real_time_skips_a_backlog_after_catching_up() {
5889		let mut producer = track_producer("test", None);
5890		append_at(&mut producer, 0);
5891		let mut subscriber = producer.subscribe(None);
5892		assert_eq!(drain(&mut subscriber), vec![0]);
5893
5894		// Catch-up is a subscriber state, not a startup-only choice. A reader can pause
5895		// between groups and still needs to shed the backlog that accumulated meanwhile.
5896		for second in 1..6 {
5897			append_at(&mut producer, second * 1000);
5898		}
5899		assert_eq!(drain(&mut subscriber), vec![5]);
5900	}
5901
5902	#[test]
5903	fn a_newer_edge_changes_an_active_catch_up() {
5904		let mut producer = track_producer("test", None);
5905		for second in 0..5 {
5906			append_at(&mut producer, second * 1000);
5907		}
5908		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(1)));
5909		assert_eq!(
5910			subscriber
5911				.recv_group()
5912				.now_or_never()
5913				.unwrap()
5914				.unwrap()
5915				.unwrap()
5916				.sequence,
5917			3
5918		);
5919
5920		// Group 4 was the edge at the first delivery. Advancing twice puts it outside
5921		// the budget before the subscriber asks for another group.
5922		append_at(&mut producer, 5000);
5923		append_at(&mut producer, 10000);
5924		assert_eq!(drain(&mut subscriber), vec![5, 6]);
5925	}
5926
5927	#[test]
5928	fn a_growing_edge_changes_an_active_catch_up() {
5929		let mut producer = track_producer("test", None);
5930		append_at(&mut producer, 0);
5931		append_at(&mut producer, 1000);
5932		let mut edge = producer.append_group().unwrap();
5933		edge.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"a"))
5934			.unwrap();
5935
5936		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(2)));
5937		assert_eq!(
5938			subscriber
5939				.recv_group()
5940				.now_or_never()
5941				.unwrap()
5942				.unwrap()
5943				.unwrap()
5944				.sequence,
5945			0
5946		);
5947
5948		// The edge is still the same group, but its newest presentation moved far
5949		// enough that group 1 has fallen out of range.
5950		edge.write_frame(Timestamp::from_millis(5000).unwrap(), bytes::Bytes::from_static(b"b"))
5951			.unwrap();
5952		assert_eq!(drain(&mut subscriber), vec![2]);
5953	}
5954
5955	#[test]
5956	fn a_budget_admits_groups_within_it() {
5957		let mut producer = track_producer("test", None);
5958		for second in 0..5 {
5959			append_at(&mut producer, second * 1000);
5960		}
5961
5962		// Two seconds of tolerance keeps the groups presenting within 2s of the live
5963		// edge (2s, 3s, 4s) and drops the two below it. Group 1 reaches exactly 2s behind
5964		// the edge, and the reach bound is exclusive, so every frame it could hold is
5965		// already past the budget.
5966		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(2)));
5967		assert_eq!(drain(&mut subscriber), vec![2, 3, 4]);
5968	}
5969
5970	#[test]
5971	fn a_budget_reaches_back_over_the_cache() {
5972		let mut producer = track_producer("test", None);
5973		for second in 0..5 {
5974			append_at(&mut producer, second * 1000);
5975		}
5976
5977		// The default zero budget calls every non-latest group stale, so a subscription
5978		// that says nothing joins at the live edge.
5979		let mut live = producer.subscribe(None);
5980		assert_eq!(drain(&mut live), vec![4]);
5981
5982		// Two seconds of tolerance covers the groups presenting within 2s of the live
5983		// edge, so the same join is handed the head of what it can still use. One bound
5984		// decides both what is sent and what is expired, so a subscriber is never sent
5985		// history it would discard on arrival.
5986		let budget = Subscription::default().with_max_age(Duration::from_secs(2));
5987		let mut subscriber = producer.subscribe(budget);
5988		assert_eq!(drain(&mut subscriber), vec![2, 3, 4]);
5989	}
5990
5991	#[test]
5992	fn a_named_start_is_a_floor_not_a_request() {
5993		let mut producer = track_producer("test", None);
5994		for second in 0..5 {
5995			append_at(&mut producer, second * 1000);
5996		}
5997
5998		// The budget is the only thing that asks for data; a named start only bounds how
5999		// far back it may reach. Naming group 1 at real time still delivers the live edge
6000		// alone, since the zero budget calls everything older stale.
6001		let named = Subscription::default().with_start(Position::group(1));
6002		let mut subscriber = producer.subscribe(named);
6003		assert_eq!(drain(&mut subscriber), vec![4]);
6004
6005		// A budget reaching further back than the floor is cut off at it.
6006		let floored = Subscription::default()
6007			.with_start(Position::group(3))
6008			.with_max_age(Duration::from_secs(10));
6009		let mut subscriber = producer.subscribe(floored);
6010		assert_eq!(drain(&mut subscriber), vec![3, 4]);
6011
6012		// A floor below what the budget admits changes nothing.
6013		let slack = Subscription::default()
6014			.with_start(Position::group(1))
6015			.with_max_age(Duration::from_secs(2));
6016		let mut subscriber = producer.subscribe(slack);
6017		assert_eq!(drain(&mut subscriber), vec![2, 3, 4]);
6018	}
6019
6020	#[test]
6021	fn a_floor_above_the_live_edge_waits_there() {
6022		let mut producer = track_producer("test", None);
6023		for second in 0..3 {
6024			append_at(&mut producer, second * 1000);
6025		}
6026
6027		// A resumed subscription names where it left off, which may not exist yet. The
6028		// cursor sits at the floor rather than sliding back to what is cached.
6029		let resumed = Subscription::default()
6030			.with_start(Position::group(7))
6031			.with_max_age(Duration::from_secs(10));
6032		let mut subscriber = producer.subscribe(resumed);
6033		assert_eq!(drain(&mut subscriber), Vec::<u64>::new());
6034		append_at(&mut producer, 3000); // sequence 3: still below the floor
6035		assert_eq!(drain(&mut subscriber), Vec::<u64>::new());
6036		for second in 4..8 {
6037			append_at(&mut producer, second * 1000);
6038		}
6039		assert_eq!(drain(&mut subscriber), vec![7]);
6040	}
6041
6042	#[test]
6043	fn a_late_lower_group_within_the_budget_is_delivered() {
6044		let producer = track_producer("test", None);
6045		for (sequence, millis) in [(5, 0), (6, 1000), (7, 2000)] {
6046			let mut group = producer.create_group(group::Info { sequence }).unwrap();
6047			group
6048				.write_frame(Timestamp::from_millis(millis).unwrap(), bytes::Bytes::from_static(b"x"))
6049				.unwrap();
6050			group.finish().unwrap();
6051		}
6052
6053		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(5)));
6054		assert_eq!(drain(&mut subscriber), vec![5, 6, 7]);
6055
6056		// Arriving below everything already delivered is not what makes content stale:
6057		// the budget is the only gate, and this straggler's timestamp is within it. A
6058		// consumer that needs sequence order reorders (or drops) it itself.
6059		let mut late = producer.create_group(group::Info { sequence: 4 }).unwrap();
6060		late.write_frame(Timestamp::from_millis(500).unwrap(), bytes::Bytes::from_static(b"late"))
6061			.unwrap();
6062		late.finish().unwrap();
6063		assert_eq!(drain(&mut subscriber), vec![4]);
6064	}
6065
6066	#[test]
6067	fn drift_is_measured_in_presentation_time_not_arrival_time() {
6068		let mut producer = track_producer("test", None);
6069		// A relay ingesting a backlog creates every group at once, so arrival time says
6070		// they are all equally fresh. Their timestamps say otherwise, which is the whole
6071		// point of measuring in presentation time.
6072		for second in 0..4 {
6073			append_at(&mut producer, second * 1000);
6074		}
6075
6076		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(1500)));
6077		assert_eq!(drain(&mut subscriber), vec![1, 2, 3]);
6078	}
6079
6080	#[tokio::test]
6081	async fn a_stamped_successor_expires_an_unstamped_group() {
6082		let mut producer = track_producer("test", None);
6083		let mut subscriber = producer.subscribe(None);
6084		producer.append_group().unwrap(); // seq 0 stalls before its first frame
6085
6086		append_at(&mut producer, 1000); // seq 1 proves the live feed moved on
6087
6088		// The candidate needs no timestamp of its own: its reach is where its stamped
6089		// successor begins, which the zero budget already puts out of range.
6090		assert_eq!(drain(&mut subscriber), vec![1]);
6091	}
6092
6093	#[tokio::test]
6094	async fn a_handed_out_group_expires_while_its_first_frame_is_stalled() {
6095		let mut producer = track_producer("test", None);
6096		let mut subscriber = producer.subscribe(None);
6097		producer.append_group().unwrap();
6098
6099		let mut stalled = subscriber.recv_group().await.unwrap().expect("stalled group");
6100		let pending = tokio::spawn(async move { stalled.read_frame().await });
6101		tokio::task::yield_now().await;
6102		assert!(
6103			!pending.is_finished(),
6104			"the empty live edge still waits for its first frame"
6105		);
6106
6107		crate::model::clock::advance(Duration::from_secs(1));
6108		append_at(&mut producer, 1000);
6109
6110		// It ends rather than fails: the reader took every frame the group ever had
6111		// (none), so nothing was truncated. What it was waiting for was the producer,
6112		// and a group abandoned where its reader stands looks exactly like one that
6113		// ended there.
6114		let result = pending.await.unwrap();
6115		assert!(matches!(result, Ok(None)), "the held group ends: {result:?}");
6116	}
6117
6118	/// A rewound successor can keep a drained group within budget until its abort
6119	/// moves the reach to the next cached group, without changing the track itself.
6120	#[tokio::test]
6121	async fn aborted_stamped_successor_wakes_a_parked_read() {
6122		tokio::time::pause();
6123		let mut producer = track_producer("test", None);
6124		let mut head = producer.append_group().unwrap();
6125		head.write_frame(Timestamp::ZERO, b"head".as_slice()).unwrap();
6126		let mut successor = producer.append_group().unwrap();
6127		successor
6128			.write_frame(Timestamp::from_millis(30_000).unwrap(), b"next".as_slice())
6129			.unwrap();
6130		append_at(&mut producer, 1000);
6131		append_at(&mut producer, 20_000);
6132		let mut sub = producer.subscribe(None);
6133		let mut reading = sub.recv_group().await.unwrap().unwrap();
6134		assert_eq!(reading.sequence, 0, "the rewound successor extends the reach");
6135		assert!(reading.read_frame().await.unwrap().is_some());
6136
6137		let woken = Arc::new(std::sync::atomic::AtomicBool::new(false));
6138		let waker = futures::task::waker(Arc::new(FlagWake(woken.clone())));
6139		let mut cx = std::task::Context::from_waker(&waker);
6140		let mut next = std::pin::pin!(reading.read_frame());
6141		assert!(next.as_mut().poll(&mut cx).is_pending());
6142		successor.abort(Error::Cancel).unwrap();
6143		assert!(
6144			woken.load(Ordering::SeqCst),
6145			"the stamped successor's abort lost its wakeup"
6146		);
6147		let result = next.as_mut().poll(&mut cx);
6148		assert!(matches!(result, Poll::Ready(Ok(None))), "the head is stale: {result:?}");
6149	}
6150
6151	/// A first timestamp on a *newer* group can convict a held one, so the held reader has
6152	/// to be woken by it. The conviction needs a group beyond the held one's successor:
6153	/// a group is bounded by where its successor begins, so the successor itself never
6154	/// proves it stale.
6155	#[tokio::test]
6156	async fn a_handed_out_group_wakes_when_a_newer_group_gets_its_first_timestamp() {
6157		let mut producer = track_producer("test", None);
6158		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6159		let mut old = producer.append_group().unwrap();
6160		old.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"old"))
6161			.unwrap();
6162
6163		// Group 1 bounds group 0's reach at 1s.
6164		append_at(&mut producer, 1000);
6165
6166		let mut held = subscriber.recv_group().await.unwrap().expect("old group");
6167		assert!(held.read_frame().await.unwrap().is_some());
6168
6169		let mut live = producer.append_group().unwrap();
6170		let pending = tokio::spawn(async move { held.read_frame().await });
6171		tokio::task::yield_now().await;
6172		assert!(!pending.is_finished(), "the newer group has no timestamp yet");
6173
6174		// 2s edge against group 0's 1s reach: a full second past the budget.
6175		live.write_frame(
6176			Timestamp::from_millis(2000).unwrap(),
6177			bytes::Bytes::from_static(b"live"),
6178		)
6179		.unwrap();
6180		tokio::task::yield_now().await;
6181
6182		assert!(pending.is_finished(), "the new presentation edge wakes the held reader");
6183		// Drained, so the budget ends the group rather than truncating it.
6184		let result = pending.await.unwrap();
6185		assert!(matches!(result, Ok(None)), "the held group ends: {result:?}");
6186	}
6187
6188	/// A parked read registers only where a first timestamp can change its verdict: its
6189	/// immediate successor and the groups past the edge. Walking every group between them
6190	/// costs each parked publisher stream the whole backlog, enough for a 2s audio backlog
6191	/// at 400 groups/s to pin a publisher's runtime and starve its connection.
6192	#[test]
6193	fn a_parked_read_ignores_first_frames_between_its_successor_and_the_edge() {
6194		let mut producer = track_producer("test", None);
6195		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
6196		let mut head = producer.append_group().unwrap();
6197		head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
6198			.unwrap();
6199		append_at(&mut producer, 1000); // the successor bounds the head's reach
6200		let mut between = producer.append_group().unwrap();
6201		append_at(&mut producer, 3000); // the edge
6202
6203		let mut held = subscriber
6204			.recv_group()
6205			.now_or_never()
6206			.unwrap()
6207			.unwrap()
6208			.expect("head group");
6209		assert_eq!(held.sequence, 0);
6210		assert!(held.read_frame().now_or_never().unwrap().unwrap().is_some());
6211
6212		let woken = Arc::new(std::sync::atomic::AtomicBool::new(false));
6213		let waker = futures::task::waker(Arc::new(FlagWake(woken.clone())));
6214		let mut cx = std::task::Context::from_waker(&waker);
6215		let mut next = std::pin::pin!(held.read_frame());
6216		assert!(next.as_mut().poll(&mut cx).is_pending());
6217
6218		between
6219			.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"x"))
6220			.unwrap();
6221		assert!(
6222			!woken.load(Ordering::SeqCst),
6223			"a first frame below the edge moves neither the reach nor the edge"
6224		);
6225
6226		// Control: a first frame past the edge is still observed.
6227		let mut beyond = producer.append_group().unwrap();
6228		assert!(next.as_mut().poll(&mut cx).is_pending());
6229		woken.store(false, Ordering::SeqCst);
6230		beyond
6231			.write_frame(Timestamp::from_millis(4000).unwrap(), bytes::Bytes::from_static(b"x"))
6232			.unwrap();
6233		assert!(woken.load(Ordering::SeqCst), "a new edge wakes the parked read");
6234	}
6235
6236	/// An aborted successor hands the reach to the next group, which sits below the edge
6237	/// and so is watched only because the read re-selects its successor.
6238	#[test]
6239	fn a_parked_read_watches_the_replacement_for_an_aborted_successor() {
6240		let mut producer = track_producer("test", None);
6241		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6242		let mut head = producer.append_group().unwrap();
6243		head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
6244			.unwrap();
6245		let successor = producer.append_group().unwrap();
6246		let mut replacement = producer.append_group().unwrap();
6247		append_at(&mut producer, 20_000); // the edge
6248
6249		let mut held = subscriber
6250			.recv_group()
6251			.now_or_never()
6252			.unwrap()
6253			.unwrap()
6254			.expect("head group");
6255		assert_eq!(held.sequence, 0, "an unstamped successor leaves the reach unbounded");
6256		assert!(held.read_frame().now_or_never().unwrap().unwrap().is_some());
6257
6258		let woken = Arc::new(std::sync::atomic::AtomicBool::new(false));
6259		let waker = futures::task::waker(Arc::new(FlagWake(woken.clone())));
6260		let mut cx = std::task::Context::from_waker(&waker);
6261		let mut next = std::pin::pin!(held.read_frame());
6262		assert!(next.as_mut().poll(&mut cx).is_pending());
6263
6264		successor.abort(Error::Cancel).unwrap();
6265		assert!(
6266			woken.load(Ordering::SeqCst),
6267			"the successor's abort wakes the parked read"
6268		);
6269		woken.store(false, Ordering::SeqCst);
6270		assert!(
6271			next.as_mut().poll(&mut cx).is_pending(),
6272			"the unstamped replacement leaves the reach unbounded"
6273		);
6274
6275		replacement
6276			.write_frame(Timestamp::from_millis(1000).unwrap(), bytes::Bytes::from_static(b"x"))
6277			.unwrap();
6278		assert!(
6279			woken.load(Ordering::SeqCst),
6280			"the replacement's first frame bounds the reach"
6281		);
6282		let result = next.as_mut().poll(&mut cx);
6283		assert!(matches!(result, Poll::Ready(Ok(None))), "the head is stale: {result:?}");
6284	}
6285
6286	/// An aborted edge hands the edge back to a lower group, which sits below the old edge
6287	/// and so is watched only because the read observes the edge's abort. Neither the
6288	/// abort nor that group's first frame touches the track.
6289	#[test]
6290	fn a_parked_read_watches_its_edge_abort() {
6291		let mut producer = track_producer("test", None);
6292		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(5)));
6293		let mut head = producer.append_group().unwrap();
6294		head.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
6295			.unwrap();
6296		append_at(&mut producer, 1000); // the successor bounds the head's reach
6297		let mut between = producer.append_group().unwrap();
6298		let mut edge = producer.append_group().unwrap();
6299		edge.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"x"))
6300			.unwrap();
6301		edge.finish().unwrap();
6302
6303		let mut held = subscriber
6304			.recv_group()
6305			.now_or_never()
6306			.unwrap()
6307			.unwrap()
6308			.expect("head group");
6309		assert_eq!(held.sequence, 0);
6310		assert!(held.read_frame().now_or_never().unwrap().unwrap().is_some());
6311
6312		let woken = Arc::new(std::sync::atomic::AtomicBool::new(false));
6313		let waker = futures::task::waker(Arc::new(FlagWake(woken.clone())));
6314		let mut cx = std::task::Context::from_waker(&waker);
6315		let mut next = std::pin::pin!(held.read_frame());
6316		assert!(next.as_mut().poll(&mut cx).is_pending());
6317
6318		edge.abort(Error::Cancel).unwrap();
6319		between
6320			.write_frame(Timestamp::from_millis(10_000).unwrap(), bytes::Bytes::from_static(b"x"))
6321			.unwrap();
6322		assert!(
6323			woken.load(Ordering::SeqCst),
6324			"the edge's abort, then a new edge below it, lost the wakeup"
6325		);
6326		let result = next.as_mut().poll(&mut cx);
6327		assert!(matches!(result, Poll::Ready(Ok(None))), "the head is stale: {result:?}");
6328	}
6329
6330	/// The ordinary live case, at the default real-time budget: 2s GOPs produced one at
6331	/// a time and read as they arrive. The budget must take the live edge without
6332	/// shortening the group the reader is already on, so every frame of every group is
6333	/// delivered and each group *ends* rather than fails at its boundary.
6334	///
6335	/// Two things would break this. Measuring drift from a group's first frame rather
6336	/// than its reader's position convicts every group the moment its successor opens,
6337	/// since a live reader is always a little behind the edge. And the reader is parked
6338	/// at its group's end when that happens, because the FIN and the next group's first
6339	/// frame are separate events and the wire does not order them.
6340	#[tokio::test]
6341	async fn real_time_reads_a_live_stream_without_truncating_it() {
6342		let producer = track_producer("test", None);
6343		let mut subscriber = producer.subscribe(None);
6344
6345		let gop = |n: u64| {
6346			[
6347				Timestamp::from_millis(n * 2000).unwrap(),
6348				Timestamp::from_millis(n * 2000 + 1900).unwrap(),
6349			]
6350		};
6351		let write = |group: &mut group::Producer, timestamp| {
6352			group.write_frame(timestamp, bytes::Bytes::from_static(b"x")).unwrap();
6353		};
6354
6355		let mut open = producer.append_group().unwrap();
6356		write(&mut open, gop(0)[0]);
6357		write(&mut open, gop(0)[1]);
6358		let mut reading = subscriber.recv_group().await.unwrap().expect("the live group");
6359
6360		let mut read = Vec::new();
6361		for n in 1..5u64 {
6362			let sequence = reading.sequence;
6363			let mut frames = 0;
6364			while let Some(res) = reading.read_frame().now_or_never() {
6365				match res.expect("no truncation while draining") {
6366					Some(_) => frames += 1,
6367					None => panic!("group {sequence} ended early"),
6368				}
6369			}
6370			read.push((sequence, frames));
6371
6372			let next = {
6373				// Parked at the end of the current group: every frame is read and no FIN
6374				// has landed.
6375				let mut end = std::pin::pin!(reading.read_frame());
6376				assert!(futures::poll!(end.as_mut()).is_pending(), "parked on the FIN");
6377
6378				// The next keyframe opens its group. The verdict is taken here, in the
6379				// window before the previous group's FIN arrives.
6380				let mut opened = producer.append_group().unwrap();
6381				write(&mut opened, gop(n)[0]);
6382				let verdict = futures::poll!(end.as_mut());
6383
6384				open.finish().unwrap();
6385				let res = match verdict {
6386					Poll::Ready(res) => res,
6387					Poll::Pending => end.await,
6388				};
6389				assert!(
6390					matches!(res, Ok(None)),
6391					"group {sequence} ends at the boundary rather than failing: {res:?}"
6392				);
6393				opened
6394			};
6395			let mut next = next;
6396			write(&mut next, gop(n)[1]);
6397
6398			reading = subscriber.recv_group().await.unwrap().expect("the next live group");
6399			open = next;
6400		}
6401
6402		assert_eq!(read, vec![(0, 2), (1, 2), (2, 2), (3, 2)], "every frame of every group");
6403	}
6404
6405	/// A budget is spent from where the reader stands, not from where its group opened.
6406	///
6407	/// A 2s GOP with a 1s budget: the reader has drained to 1900ms when the next group
6408	/// opens at 2000ms, so it is 100ms behind the live edge and well inside what it
6409	/// asked for. A straggling frame of the old group arriving after the new one opened
6410	/// (which is ordinary, the two are separate streams) must still reach it.
6411	///
6412	/// Measuring from the group's first frame instead makes the drift 2000ms, so a
6413	/// budget shorter than one GOP would drop the tail of every GOP.
6414	#[tokio::test]
6415	async fn a_budget_is_measured_from_the_readers_position() {
6416		let producer = track_producer("test", None);
6417		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(1)));
6418
6419		let mut open = producer.append_group().unwrap();
6420		open.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"key"))
6421			.unwrap();
6422		open.write_frame(
6423			Timestamp::from_millis(1900).unwrap(),
6424			bytes::Bytes::from_static(b"tail"),
6425		)
6426		.unwrap();
6427
6428		let mut reading = subscriber.recv_group().await.unwrap().expect("the live group");
6429		assert!(reading.read_frame().await.unwrap().is_some());
6430		assert!(reading.read_frame().await.unwrap().is_some());
6431
6432		let mut end = std::pin::pin!(reading.read_frame());
6433		assert!(futures::poll!(end.as_mut()).is_pending(), "parked at 1900ms");
6434
6435		// The next GOP opens. The reader is 100ms behind it, inside its 1s budget.
6436		let mut next = producer.append_group().unwrap();
6437		next.write_frame(Timestamp::from_millis(2000).unwrap(), bytes::Bytes::from_static(b"key"))
6438			.unwrap();
6439		assert!(
6440			futures::poll!(end.as_mut()).is_pending(),
6441			"a reader inside its budget is not expired by the next group opening"
6442		);
6443
6444		// A straggler from the old group, still within the budget.
6445		open.write_frame(
6446			Timestamp::from_millis(1950).unwrap(),
6447			bytes::Bytes::from_static(b"late"),
6448		)
6449		.unwrap();
6450		let late = end.await.expect("the straggler is not truncated");
6451		assert_eq!(
6452			late.map(|frame| frame.timestamp),
6453			Some(Timestamp::from_millis(1950).unwrap())
6454		);
6455	}
6456
6457	/// A group the budget ends rather than fails stays ended. `expired` alone would
6458	/// turn the clean answer into [`Error::Old`] on the next poll, and a caller is
6459	/// allowed to probe again past the end of a group.
6460	#[tokio::test]
6461	async fn an_ended_group_stays_ended_when_probed_again() {
6462		let mut producer = track_producer("test", None);
6463		let mut subscriber = producer.subscribe(None);
6464		let mut open = producer.append_group().unwrap();
6465		open.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"a"))
6466			.unwrap();
6467
6468		let mut group = subscriber.recv_group().await.unwrap().expect("group");
6469		assert!(group.read_frame().await.unwrap().is_some());
6470
6471		let probes = tokio::spawn(async move {
6472			let first = group.read_frame().await;
6473			let second = group.read_frame().await;
6474			let finished = group.finished().await;
6475			(first, second, finished)
6476		});
6477		tokio::task::yield_now().await;
6478
6479		append_at(&mut producer, 1000);
6480
6481		let (first, second, finished) = probes.await.unwrap();
6482		assert!(matches!(first, Ok(None)), "the group ends: {first:?}");
6483		assert!(matches!(second, Ok(None)), "and stays ended: {second:?}");
6484		assert!(matches!(finished, Ok(1)), "reporting what it delivered: {finished:?}");
6485	}
6486
6487	#[tokio::test]
6488	async fn a_drained_group_finishes_cleanly_after_the_live_edge_advances() {
6489		let mut producer = track_producer("test", None);
6490		let mut subscriber = producer.subscribe(None);
6491		append_at(&mut producer, 0);
6492
6493		let mut group = subscriber.recv_group().await.unwrap().expect("first group");
6494		assert!(group.read_frame().await.unwrap().is_some());
6495
6496		append_at(&mut producer, 1000);
6497
6498		assert!(group.read_frame().await.unwrap().is_none());
6499		assert!(!group.latency_expired());
6500	}
6501
6502	#[tokio::test]
6503	async fn a_handed_out_partial_frame_expires_while_its_payload_is_stalled() {
6504		let mut producer = track_producer("test", None);
6505		let mut subscriber = producer.subscribe(None);
6506		let mut source = producer.append_group().unwrap();
6507		let mut writing = source
6508			.create_frame(frame::Info {
6509				size: 6,
6510				timestamp: Timestamp::ZERO,
6511			})
6512			.unwrap();
6513		writing.write(bytes::Bytes::from_static(b"old")).unwrap();
6514
6515		let mut group = subscriber.recv_group().await.unwrap().expect("partial group");
6516		let mut frame = group.next_frame().await.unwrap().expect("partial frame");
6517		assert_eq!(
6518			frame.read_chunk().await.unwrap(),
6519			Some(bytes::Bytes::from_static(b"old"))
6520		);
6521		let pending = tokio::spawn(async move { frame.read_chunk().await });
6522		tokio::task::yield_now().await;
6523		assert!(!pending.is_finished(), "the partial payload is still stalled");
6524
6525		crate::model::clock::advance(Duration::from_secs(1));
6526		append_at(&mut producer, 1000);
6527
6528		let result = pending.await.unwrap();
6529		assert!(
6530			matches!(result, Err(Error::Old)),
6531			"the in-flight frame expires: {result:?}"
6532		);
6533		writing.abort(Error::Cancel).unwrap();
6534	}
6535
6536	#[test]
6537	fn max_age_bounds_the_budget() {
6538		// The publisher only keeps a group around for 500ms, so a subscriber asking to
6539		// wait ten seconds for one still gives up at 500ms: the same clamp the aggregate
6540		// applies, on the subscriber's own side of it.
6541		let mut producer = track_producer("test", Info::default().with_max_age(Duration::from_millis(500)));
6542		append_at(&mut producer, 0);
6543		append_at(&mut producer, 1000);
6544		append_at(&mut producer, 2000);
6545
6546		// Group 0 reaches 1s, a full second behind the 2s edge, so the clamped 500ms
6547		// budget drops it. An unclamped ten seconds would have kept it.
6548		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(10)));
6549		assert_eq!(drain(&mut subscriber), vec![1, 2]);
6550	}
6551
6552	#[test]
6553	fn an_explicit_start_gets_no_exemption_from_the_budget() {
6554		let mut producer = track_producer("test", None);
6555		for second in 0..4 {
6556			append_at(&mut producer, second * 1000);
6557		}
6558
6559		// Asking to start at the beginning is a filter, not a request for reliability.
6560		// Backfill needs a budget that covers it; without one the live edge still wins.
6561		let mut subscriber = producer.subscribe(Subscription::default().with_start(Position::group(0)));
6562		subscriber.start_at(0);
6563		assert_eq!(drain(&mut subscriber), vec![3]);
6564
6565		let mut patient = producer.subscribe(
6566			Subscription::default()
6567				.with_start(Position::group(0))
6568				.with_max_age(Duration::from_secs(10)),
6569		);
6570		patient.start_at(0);
6571		assert_eq!(drain(&mut patient), vec![0, 1, 2, 3]);
6572	}
6573
6574	/// A group's age is where its content *ends*, not where it began. A long group whose
6575	/// tail is level with the live edge still owes the reader every frame in it, so
6576	/// judging it by its first timestamp would discard exactly the group being filled.
6577	#[test]
6578	fn a_long_group_is_not_stale_while_its_tail_reaches_the_edge() {
6579		let mut producer = track_producer("test", None);
6580
6581		// Group 0 spans 0..2000ms; group 1 starts at 2000ms, where group 0 ends.
6582		let mut long = producer.append_group().unwrap();
6583		for ms in [0u64, 500, 1000, 1500, 2000] {
6584			long.write_frame(Timestamp::from_millis(ms).unwrap(), bytes::Bytes::from_static(b"x"))
6585				.unwrap();
6586		}
6587		long.finish().unwrap();
6588		append_at(&mut producer, 2000);
6589
6590		// A budget far shorter than the group's own span still keeps it.
6591		let mut sub = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6592		assert_eq!(drain(&mut sub), vec![0, 1]);
6593	}
6594
6595	/// The same group, once its tail has fallen behind, is stale like any other.
6596	/// The same long group, once its *successor* has itself fallen behind. A frame's
6597	/// duration is not on the wire, so group 0's own last timestamp proves nothing about
6598	/// where it ends; only group 1's start bounds it. Group 0 is convicted once that bound
6599	/// is further behind the edge than the budget allows.
6600	#[test]
6601	fn a_long_group_is_stale_once_its_successor_falls_behind() {
6602		let mut producer = track_producer("test", None);
6603
6604		let mut long = producer.append_group().unwrap();
6605		for ms in [0u64, 500, 1000] {
6606			long.write_frame(Timestamp::from_millis(ms).unwrap(), bytes::Bytes::from_static(b"x"))
6607				.unwrap();
6608		}
6609		long.finish().unwrap();
6610		// Group 0 reaches at most 3s (where group 1 starts), a full second behind the 4s
6611		// edge and so past the budget. Group 1 reaches the edge itself and is kept.
6612		append_at(&mut producer, 3000);
6613		append_at(&mut producer, 4000);
6614
6615		let mut sub = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6616		assert_eq!(drain(&mut sub), vec![1, 2]);
6617	}
6618
6619	/// An unstamped immediate successor leaves a group's reach unbounded: a later
6620	/// stamped group proves nothing about where the successor will begin, and
6621	/// shrinking the bound is the unsafe direction.
6622	#[tokio::test]
6623	async fn an_unstamped_immediate_successor_leaves_reach_unbounded() {
6624		let mut producer = track_producer("test", None);
6625		let mut subscriber = producer.subscribe(None);
6626
6627		append_at(&mut producer, 0); // seq 0
6628		producer.append_group().unwrap(); // seq 1 stalls before its first frame
6629		append_at(&mut producer, 10_000); // seq 2
6630
6631		// Group 1's reach is group 2's start, a full edge behind: stale at zero budget.
6632		// Group 0's reach is unknown until group 1 presents its first frame, so it is
6633		// kept rather than convicted on a bound that could shrink the wrong way.
6634		assert_eq!(drain(&mut subscriber), vec![0, 2]);
6635	}
6636
6637	/// Reach is the *immediate* successor's start, never a minimum across later groups.
6638	/// Timestamps need not rise with sequence: a rewind can put a much earlier timestamp on
6639	/// a much later group, and that group proves nothing about where the candidate's own
6640	/// successor begins. Taking the minimum would shrink the bound and discard content that
6641	/// is still well inside the budget.
6642	#[test]
6643	fn reach_follows_the_immediate_successor_not_a_later_rewind() {
6644		let mut producer = track_producer("test", None);
6645
6646		// Group 0 is bounded by group 1 at 10s. Groups 2 and 3 rewind to 1s and 2s.
6647		append_at(&mut producer, 0);
6648		append_at(&mut producer, 10_000);
6649		append_at(&mut producer, 1_000);
6650		append_at(&mut producer, 2_000);
6651
6652		// Group 0 reaches 10s, so nothing here proves it is past a 500ms budget: it could
6653		// hold frames through nearly 10s. A minimum over later groups would put its reach
6654		// at 1s and drop it.
6655		let mut sub = producer.subscribe(Subscription::default().with_max_age(Duration::from_millis(500)));
6656		assert!(
6657			drain(&mut sub).contains(&0),
6658			"group 0 is bounded by its successor at 10s, not by a later rewind"
6659		);
6660	}
6661
6662	/// Datagrams are unordered by construction, so the sequence cursor carries them too:
6663	/// a track using both channels needs one subscription, not two.
6664	#[tokio::test]
6665	async fn ordered_carries_datagrams() {
6666		let mut producer = track_producer("test", None);
6667		let mut sub = producer.subscribe(None).ordered();
6668
6669		producer
6670			.insert_datagram(5, Timestamp::from_millis(5).unwrap(), bytes::Bytes::from_static(b"x"))
6671			.unwrap();
6672		producer.create_group(group::Info { sequence: 3 }).unwrap();
6673
6674		let datagram = sub
6675			.recv_datagram()
6676			.now_or_never()
6677			.expect("datagram would have blocked")
6678			.expect("would have errored")
6679			.expect("track was closed");
6680		assert_eq!(datagram.sequence, 5);
6681
6682		// The datagram did not consume the group cursor.
6683		let group = sub
6684			.next_group()
6685			.now_or_never()
6686			.expect("group would have blocked")
6687			.expect("would have errored")
6688			.expect("track was closed");
6689		assert_eq!(group.sequence, 3);
6690	}
6691
6692	/// Every group the ordered cursor can read right now, in sequence order.
6693	fn drain_ordered(subscriber: &mut Ordered) -> Vec<u64> {
6694		let mut sequences = Vec::new();
6695		while let Some(Ok(Some(group))) = subscriber.next_group().now_or_never() {
6696			sequences.push(group.sequence);
6697		}
6698		sequences
6699	}
6700
6701	/// A cached backlog is not free to deliver: a consumer that stalls and resumes would
6702	/// otherwise replay it at 1x and stay behind forever, since nothing it reads ever
6703	/// blocks. The budget applies as the cursor reads, exactly as on the arrival cursor.
6704	#[test]
6705	fn next_group_sheds_a_stale_backlog() {
6706		let mut producer = track_producer("test", None);
6707		for second in 0..4 {
6708			append_at(&mut producer, second * 1000);
6709		}
6710
6711		let mut subscriber = producer.subscribe(None).ordered();
6712		assert_eq!(drain_ordered(&mut subscriber), vec![3]);
6713
6714		// The arrival cursor, which the relay forwarders use, sheds the same backlog.
6715		let mut arrival = producer.subscribe(None);
6716		assert_eq!(drain(&mut arrival), vec![3]);
6717	}
6718
6719	/// Only what is provably too old goes. A group is convicted by its *reach* (where its
6720	/// successor begins), so a budget spanning part of the backlog keeps every group that
6721	/// could still present something inside it, and the burst is delivered gap-free.
6722	#[test]
6723	fn next_group_keeps_a_backlog_inside_the_budget() {
6724		let mut producer = track_producer("test", None);
6725		for second in 0..4 {
6726			append_at(&mut producer, second * 1000);
6727		}
6728
6729		// Group 0 reaches 1s, a full 2s behind the 3s edge, so it is gone. Group 1
6730		// reaches 2s and could still present up to it: inside a 1.5s budget.
6731		let mut subscriber = producer
6732			.subscribe(Subscription::default().with_max_age(Duration::from_millis(1500)))
6733			.ordered();
6734		assert_eq!(drain_ordered(&mut subscriber), vec![1, 2, 3]);
6735
6736		// A budget covering the whole history still bursts it in full.
6737		let mut replay = producer.subscribe(replay()).ordered();
6738		assert_eq!(drain_ordered(&mut replay), vec![0, 1, 2, 3]);
6739	}
6740
6741	/// A group whose immediate successor has not presented a frame has no proven reach,
6742	/// so nothing says its frames are too old and the ordered cursor keeps it.
6743	#[test]
6744	fn next_group_keeps_a_group_with_no_proven_reach() {
6745		let mut producer = track_producer("test", None);
6746		append_at(&mut producer, 0); // seq 0
6747		producer.append_group().unwrap(); // seq 1 stalls before its first frame
6748		append_at(&mut producer, 10_000); // seq 2
6749
6750		// Group 1 reaches 10s, a full edge behind: convicted. Group 0 is bounded only by
6751		// group 1, which has yet to say where it begins.
6752		let mut subscriber = producer.subscribe(None).ordered();
6753		assert_eq!(drain_ordered(&mut subscriber), vec![0, 2]);
6754	}
6755
6756	#[tokio::test]
6757	async fn real_time_skips_older_sequences_with_equal_ages() {
6758		let mut producer = track_producer("test", None);
6759		append_at(&mut producer, 0);
6760		append_at(&mut producer, 0);
6761
6762		let mut subscriber = producer.subscribe(None);
6763		assert_eq!(drain(&mut subscriber), vec![1]);
6764	}
6765
6766	#[test]
6767	fn fetch_ignores_the_budget() {
6768		let mut producer = track_producer("test", None);
6769		for second in 0..4 {
6770			append_at(&mut producer, second * 1000);
6771		}
6772
6773		// A fetch names one old group explicitly, so there is no live edge to drift
6774		// from: the budget bounds a subscription, not a request for a specific group.
6775		let consumer = producer.consume();
6776		let group = consumer.fetch_group(0, None).now_or_never().unwrap().unwrap();
6777		assert_eq!(group.sequence, 0);
6778	}
6779
6780	/// A one-shot fetch populates the shared cache but never the live arrival cursor,
6781	/// so it cannot make content available to a subscription look stale.
6782	#[tokio::test]
6783	async fn fetched_group_is_not_a_live_drift_edge() {
6784		let mut producer = track_producer("test", None);
6785		let dynamic = producer.dynamic();
6786		let consumer = producer.consume();
6787		append_at(&mut producer, 0);
6788
6789		let pending = consumer.fetch_group(100, None);
6790		let req = dynamic
6791			.requested_group()
6792			.now_or_never()
6793			.expect("fetch request is ready")
6794			.unwrap();
6795		let mut fetched = req.accept(None).unwrap();
6796		fetched
6797			.write_frame(
6798				Timestamp::from_millis(100_000).unwrap(),
6799				bytes::Bytes::from_static(b"fetched"),
6800			)
6801			.unwrap();
6802		fetched.finish().unwrap();
6803		pending.await.unwrap();
6804
6805		let mut groups = producer.subscribe(None);
6806		assert_eq!(groups.assert_group().sequence, 0);
6807		groups.assert_no_group();
6808	}
6809
6810	/// The live edge is resolved once per poll and applied to every group that poll
6811	/// walks off, so it has to be revalidated before it convicts anything: an eviction
6812	/// in between would otherwise discard a group on the strength of content that is no
6813	/// longer there to jump to.
6814	#[test]
6815	fn an_evicted_live_edge_convicts_nothing() {
6816		let mut producer = track_producer("test", None);
6817		append_at(&mut producer, 0);
6818		let edge = append_at(&mut producer, 30_000);
6819
6820		let state = producer.state.read();
6821		let drift = Drift {
6822			budget: Duration::ZERO,
6823			edge: state.drift_edge(None, None, None),
6824			outer: None,
6825			successor: None,
6826		};
6827		assert!(
6828			state.is_stale(0, &drift.edge, drift.budget),
6829			"stale against a live edge"
6830		);
6831		drop(state);
6832
6833		// The edge dies between resolving it and judging the candidate.
6834		let slot = producer.modify().unwrap().lookup.remove(&edge).unwrap();
6835		let _ = slot.group.abort(Error::Evicted);
6836
6837		let state = producer.state.read();
6838		assert!(
6839			!state.is_stale(0, &drift.edge, drift.budget),
6840			"a vanished edge is no reason to drop what is left"
6841		);
6842	}
6843
6844	/// The same holds for an edge a splice pushed from another segment: a handed-out
6845	/// group judges it long after the splice resolved it, so it is revalidated on its own
6846	/// track before it convicts anything.
6847	#[tokio::test]
6848	async fn an_evicted_outer_edge_convicts_nothing() {
6849		let mut producer = track_producer("a", None);
6850		let mut subscriber = producer.subscribe(Subscription::default().with_max_age(Duration::from_secs(1)));
6851		let mut open = producer.append_group().unwrap();
6852		open.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"a"))
6853			.unwrap();
6854		let mut group = subscriber.recv_group().await.unwrap().expect("group");
6855		assert!(group.read_frame().await.unwrap().is_some());
6856		// A successor bounds the open group's reach, without being late against it.
6857		append_at(&mut producer, 10);
6858
6859		let mut next = track_producer("b", None);
6860		append_at(&mut next, 30_000);
6861		let edge = append_at(&mut next, 30_010);
6862		subscriber.set_anchor(Anchor {
6863			cap: None,
6864			edge: next.consume().live_edge(None),
6865			successor: None,
6866		});
6867
6868		let mut control = group.clone();
6869		assert!(
6870			matches!(control.read_frame().now_or_never(), Some(Ok(None))),
6871			"the open group ends against the outer edge"
6872		);
6873
6874		// The outer edge dies before the group is judged again.
6875		let slot = next.modify().unwrap().lookup.remove(&edge).unwrap();
6876		let _ = slot.group.abort(Error::Evicted);
6877
6878		assert!(
6879			group.read_frame().now_or_never().is_none(),
6880			"a vanished outer edge is no reason to drop what is left"
6881		);
6882		assert!(!group.latency_expired());
6883		open.finish().unwrap();
6884	}
6885
6886	#[tokio::test]
6887	async fn a_lower_sequence_is_never_the_live_edge() {
6888		let producer = track_producer("test", None);
6889		// A high timestamp on a lower sequence is not a live edge: backfill served on
6890		// demand sits there, and so does the tail of a timeline the publisher rewound.
6891		// Only groups above the candidate anchor the measure, so neither can convict
6892		// the group that follows it.
6893		let mut straggler = producer.create_group(0u64.into()).unwrap();
6894		straggler
6895			.write_frame(Timestamp::from_millis(60_000).unwrap(), bytes::Bytes::from_static(b"x"))
6896			.unwrap();
6897		straggler.finish().unwrap();
6898
6899		let mut rewound = producer.create_group(1u64.into()).unwrap();
6900		rewound
6901			.write_frame(Timestamp::from_millis(0).unwrap(), bytes::Bytes::from_static(b"x"))
6902			.unwrap();
6903		rewound.finish().unwrap();
6904
6905		let mut subscriber = producer.subscribe(replay());
6906		assert_eq!(drain(&mut subscriber), vec![0, 1]);
6907	}
6908
6909	#[test]
6910	fn a_requested_end_does_not_cap_the_live_edge() {
6911		let mut producer = track_producer("test", None);
6912		for second in 0..4 {
6913			append_at(&mut producer, second * 1000);
6914		}
6915
6916		// `Subscription::end` is a request to the publisher, folded in with every other
6917		// subscriber's, and it does not filter this handle: the groups above it arrive
6918		// anyway. Capping the live edge with it would pin the edge below them, leaving
6919		// everything past it with nothing newer to be late against.
6920		let mut subscriber = producer.subscribe(Subscription::default().with_end(Position::after_group(1)));
6921		assert_eq!(drain(&mut subscriber), vec![3]);
6922	}
6923
6924	#[test]
6925	fn a_capped_subscriber_measures_drift_against_its_cap() {
6926		let mut producer = track_producer("test", None);
6927		append_at(&mut producer, 0);
6928		append_at(&mut producer, 1000);
6929
6930		// Capped at group 0: the route running on past the cap is data this subscriber
6931		// can never be served, so it isn't a live edge to be late against. This is what
6932		// keeps a spliced segment from dropping the groups either side of a takeover
6933		// boundary.
6934		let mut subscriber = producer.subscribe(Subscription::default().with_end(Position::after_group(0)));
6935		subscriber.set_groups(..1);
6936		assert_eq!(drain(&mut subscriber), vec![0]);
6937
6938		// Raising the cap re-offers the parked group, now measured against the wider
6939		// edge it just admitted.
6940		subscriber.set_groups(..);
6941		assert_eq!(drain(&mut subscriber), vec![1]);
6942	}
6943
6944	#[test]
6945	fn subscriber_control_updates_while_read_future_is_pending() {
6946		let producer = track_producer("test", None);
6947		let mut subscriber = producer.subscribe(None);
6948		let control = subscriber.control();
6949
6950		let mut recv = Box::pin(subscriber.recv_group());
6951		assert!(recv.as_mut().now_or_never().is_none());
6952
6953		control.update(Subscription::default().with_priority(7)).unwrap();
6954
6955		let aggregate = producer.subscription().expect("expected an active subscription");
6956		assert_eq!(aggregate.priority, 7);
6957	}
6958
6959	#[test]
6960	fn dropped_subscriber_leaves_no_ghost_in_aggregate() {
6961		// Regression (#2351): a departed subscriber must not keep contributing its
6962		// last subscription to the aggregate. When it did, a relay's linger loop
6963		// never observed the track going idle, and an identical viewer reconnecting
6964		// within the linger window was reset when the stale timer fired.
6965		let mut producer = track_producer("test", None);
6966		let a = producer.subscribe(Subscription::default().with_priority(5));
6967
6968		// Prime the change cursor: the aggregate currently has one subscriber.
6969		let waiter = kio::Waiter::noop();
6970		assert!(
6971			matches!(producer.poll_subscription_changed(&waiter), Poll::Ready(Ok(Some(_)))),
6972			"one live subscriber should aggregate to Some",
6973		);
6974
6975		// The only subscriber leaves.
6976		drop(a);
6977
6978		// The aggregate must report the drop to None, not the ghost's last value.
6979		assert!(
6980			matches!(producer.poll_subscription_changed(&waiter), Poll::Ready(Ok(None))),
6981			"a dropped subscriber must not linger in the aggregate",
6982		);
6983
6984		// And the snapshot used by the linger loop must agree.
6985		assert!(
6986			producer.subscription().is_none(),
6987			"snapshot must exclude a dropped subscriber",
6988		);
6989	}
6990
6991	#[test]
6992	fn dropped_subscriber_wakes_the_aggregate() {
6993		// The value being right isn't enough: nothing re-polls the aggregate on its
6994		// own, so the drop has to wake the waiter. A subscriber contributing demand
6995		// takes `kio::Consumer::poll`'s Ready path, which registers no waiter, so
6996		// the departure needs the closed waiter armed explicitly. Without it a relay
6997		// never learns the last viewer left and holds the upstream subscription (and
6998		// the upstream's viewer count) open forever.
6999		use std::sync::atomic::{AtomicBool, Ordering};
7000
7001		let mut producer = track_producer("test", None);
7002		let a = producer.subscribe(Subscription::default().with_priority(5));
7003
7004		let woken = Arc::new(AtomicBool::new(false));
7005		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
7006
7007		// Prime the cursor, then confirm the next poll parks.
7008		assert!(matches!(
7009			producer.poll_subscription_changed(&waiter),
7010			Poll::Ready(Ok(Some(_)))
7011		));
7012		assert!(
7013			producer.poll_subscription_changed(&waiter).is_pending(),
7014			"the aggregate is unchanged, so this poll must park",
7015		);
7016		assert!(!woken.load(Ordering::SeqCst), "nothing happened yet");
7017
7018		drop(a);
7019		assert!(
7020			woken.load(Ordering::SeqCst),
7021			"the last subscriber leaving must wake the aggregate watcher",
7022		);
7023	}
7024
7025	#[test]
7026	fn widest_subscriber_update_wakes_the_aggregate() {
7027		// The value counterpart of the drop above. A subscriber that widens the
7028		// fold takes the Ready path too, so its next update registered no waiter.
7029		// A relay whose upstream cap came from a downstream reader then never
7030		// learned that the reader lifted it, and the groups parked upstream never
7031		// resumed: every session stayed up and nothing flowed.
7032		use std::sync::atomic::{AtomicBool, Ordering};
7033
7034		let mut producer = track_producer("test", None);
7035		let _narrow = producer.subscribe(Subscription::default().with_end(Position::after_group(3)));
7036		let mut wide = producer.subscribe(Subscription::default());
7037
7038		let woken = Arc::new(AtomicBool::new(false));
7039		let waiter = kio::Waiter::new(futures::task::waker(Arc::new(FlagWake(woken.clone()))));
7040
7041		assert!(matches!(
7042			producer.poll_subscription_changed(&waiter),
7043			Poll::Ready(Ok(Some(_)))
7044		));
7045		assert!(producer.poll_subscription_changed(&waiter).is_pending());
7046		assert!(!woken.load(Ordering::SeqCst), "nothing happened yet");
7047
7048		wide.update(Subscription::default().with_end(Position::after_group(5)))
7049			.unwrap();
7050		assert!(
7051			woken.load(Ordering::SeqCst),
7052			"the widest subscriber changing must wake the aggregate watcher",
7053		);
7054		match producer.poll_subscription_changed(&waiter) {
7055			Poll::Ready(Ok(Some(sub))) => assert_eq!(sub.end, Position::after_group(5)),
7056			other => panic!("expected the narrowed aggregate, got {other:?}"),
7057		}
7058	}
7059
7060	/// An [`ArcWake`] that just records that it was woken.
7061	struct FlagWake(Arc<std::sync::atomic::AtomicBool>);
7062
7063	impl futures::task::ArcWake for FlagWake {
7064		fn wake_by_ref(arc_self: &Arc<Self>) {
7065			arc_self.0.store(true, std::sync::atomic::Ordering::SeqCst);
7066		}
7067	}
7068
7069	#[tokio::test]
7070	async fn out_of_order_max_sequence_at_front() {
7071		let producer = track_producer("test", None);
7072
7073		// Arrive out of order: seq 5 first, then 3, then 4.
7074		producer.create_group(group::Info { sequence: 5 }).unwrap();
7075		producer.create_group(group::Info { sequence: 3 }).unwrap();
7076		producer.create_group(group::Info { sequence: 4 }).unwrap();
7077
7078		// max_sequence = 5, which is at the front of the VecDeque.
7079		{
7080			let state = producer.state.read();
7081			assert_eq!(state.max_sequence, Some(5));
7082		}
7083
7084		// Expire all three groups.
7085		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
7086
7087		// Append seq 6 (becomes new max_sequence).
7088		producer.append_group().unwrap(); // seq 6
7089
7090		// Seq 3, 4, 5 are all expired. Seq 5 was the old max_sequence but now 6 is.
7091		// All old groups are evicted.
7092		{
7093			let state = producer.state.read();
7094			assert_eq!(live_groups(&state), 1);
7095			assert_eq!(first_live_sequence(&state), 6);
7096			assert!(!state.lookup.contains_key(&3));
7097			assert!(!state.lookup.contains_key(&4));
7098			assert!(!state.lookup.contains_key(&5));
7099			assert!(state.lookup.contains_key(&6));
7100		}
7101	}
7102
7103	#[tokio::test]
7104	async fn max_sequence_at_front_blocks_trim() {
7105		let producer = track_producer("test", None);
7106
7107		// Arrive: seq 5, then seq 3.
7108		producer.create_group(group::Info { sequence: 5 }).unwrap();
7109
7110		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
7111
7112		// Seq 3 arrives late; max_sequence is still 5 (at front).
7113		producer.create_group(group::Info { sequence: 3 }).unwrap();
7114
7115		// Seq 5 is max_sequence (protected). Seq 3 is not expired (just created).
7116		// Nothing should be evicted.
7117		{
7118			let state = producer.state.read();
7119			assert_eq!(live_groups(&state), 2);
7120			assert_eq!(state.offset, 0);
7121		}
7122
7123		// Expire seq 3 as well.
7124		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
7125
7126		// Seq 2 arrives late, triggering eviction.
7127		producer.create_group(group::Info { sequence: 2 }).unwrap();
7128
7129		// Seq 5 is the live edge (protected) and still resolves at the front of
7130		// `arrival`, so nothing is trimmed and the offset stays. Seq 3 expired out of
7131		// `lookup`, leaving a hole its arrival entry no longer resolves; seq 2 is
7132		// fresh and kept.
7133		{
7134			let state = producer.state.read();
7135			assert_eq!(live_groups(&state), 2);
7136			assert_eq!(state.offset, 0);
7137			assert!(state.lookup.contains_key(&5));
7138			assert!(!state.lookup.contains_key(&3));
7139			assert!(state.lookup.contains_key(&2));
7140		}
7141
7142		// Consumer should still be able to read through the hole.
7143		let mut consumer = producer.subscribe(None);
7144		let group = consumer.assert_group();
7145		// consume() starts at index 0; the first arrival entry that still resolves is seq 5.
7146		assert_eq!(group.sequence, 5);
7147	}
7148
7149	#[tokio::test]
7150	async fn abort_drops_open_groups() {
7151		let producer = track_producer("test", None);
7152		producer.append_group().unwrap();
7153		producer.append_group().unwrap();
7154
7155		let mut consumer = producer.subscribe(None);
7156		assert_eq!(live_groups(&producer.state.read()), 2);
7157
7158		producer.clone().abort(Error::Cancel).unwrap();
7159
7160		// Nobody will finish them, so they leave the cache rather than park a reader.
7161		assert!(
7162			producer.state.read().lookup.is_empty(),
7163			"open groups are dropped on abort"
7164		);
7165		let result = consumer.recv_group().now_or_never().expect("should not block");
7166		assert!(matches!(result, Err(Error::Cancel)));
7167	}
7168
7169	#[tokio::test]
7170	async fn drop_unfinished_clears_cached_groups() {
7171		let producer = track_producer("test", None);
7172		let writer = producer.clone();
7173		writer.append_group().unwrap();
7174
7175		// A stale consumer keeps the channel (and thus the cache) alive.
7176		let mut consumer = producer.subscribe(None);
7177		assert_eq!(live_groups(&producer.state.read()), 1);
7178
7179		// Drop every producer without finishing: the cache is released.
7180		drop(writer);
7181		drop(producer);
7182
7183		let result = consumer.recv_group().now_or_never().expect("should not block");
7184		assert!(matches!(result, Err(Error::Dropped)));
7185	}
7186
7187	#[tokio::test]
7188	async fn drop_after_abort_does_not_warn() {
7189		// abort() closes the channel after recording `abort`. Drop must treat the
7190		// read-only guard returned by write() as clean or it emits a false WARN.
7191		let warns = count_drop_warnings("track::Producer dropped without finish", || {
7192			let producer = track_producer("test", None);
7193			let keep = producer.clone();
7194			let writer = producer.clone();
7195			let group = writer.append_group().unwrap();
7196			group.finish().unwrap();
7197			let _consumer = producer.subscribe(None);
7198			writer.abort(Error::Cancel).unwrap();
7199			drop(keep);
7200		});
7201		assert_eq!(warns, 0, "abort-then-drop must not emit unfinished-producer WARN");
7202	}
7203
7204	#[tokio::test]
7205	async fn drop_unfinished_warns() {
7206		let warns = count_drop_warnings("track::Producer dropped without finish", || {
7207			let producer = track_producer("test", None);
7208			let writer = producer.clone();
7209			writer.append_group().unwrap();
7210			let _consumer = producer.subscribe(None);
7211			drop(writer);
7212			drop(producer);
7213		});
7214		assert!(warns >= 1, "unfinished drop must emit unfinished-producer WARN");
7215	}
7216
7217	#[tokio::test]
7218	async fn drop_finished_keeps_cached_groups() {
7219		let producer = track_producer("test", None);
7220		producer.append_group().unwrap();
7221		producer.finish().unwrap();
7222
7223		let mut consumer = producer.subscribe(None);
7224		drop(producer);
7225
7226		// A cleanly finished track keeps its cache so the consumer can still drain.
7227		assert_eq!(consumer.assert_group().sequence, 0);
7228		let done = consumer.recv_group().now_or_never().expect("should not block").unwrap();
7229		assert!(done.is_none(), "consumer should drain then see clean finish");
7230	}
7231
7232	/// A boundary declared ahead of the live edge, then the last producer dropping, means
7233	/// the missing groups will never come: the reader ends cleanly rather than with
7234	/// `Dropped`, as it would for a track that never declared its end.
7235	#[tokio::test]
7236	async fn drop_short_of_the_boundary_ends_cleanly() {
7237		let mut producer = track_producer("test", None);
7238		producer.append_group().unwrap();
7239		producer.finish_at(3).unwrap();
7240
7241		let mut consumer = producer.subscribe(None);
7242		assert_eq!(consumer.assert_group().sequence, 0);
7243		assert!(
7244			consumer.recv_group().now_or_never().is_none(),
7245			"groups 1 and 2 are owed"
7246		);
7247
7248		drop(producer);
7249		let done = consumer.recv_group().now_or_never().expect("should not block").unwrap();
7250		assert!(
7251			done.is_none(),
7252			"the track ends at its boundary without the missing groups"
7253		);
7254	}
7255
7256	/// The sequence cursor ends the same way. A gap below the boundary is skipped,
7257	/// a later cached group is still delivered, and the read finishes cleanly.
7258	#[tokio::test]
7259	async fn ordered_ends_cleanly_when_sealed_short_of_the_boundary() {
7260		let mut producer = track_producer("test", None);
7261		producer.create_group(group::Info { sequence: 0 }).unwrap();
7262		producer.create_group(group::Info { sequence: 2 }).unwrap();
7263		producer.finish_at(4).unwrap();
7264
7265		let mut ordered = producer.subscribe(None).ordered();
7266		drop(producer);
7267
7268		let first = ordered.next_group().now_or_never().expect("group 0").unwrap().unwrap();
7269		assert_eq!(first.sequence, 0);
7270		let second = ordered.next_group().now_or_never().expect("group 2").unwrap().unwrap();
7271		assert_eq!(second.sequence, 2);
7272		let done = ordered.next_group().now_or_never().expect("end").unwrap();
7273		assert!(done.is_none(), "missing groups below the boundary are not an error");
7274	}
7275
7276	#[tokio::test]
7277	async fn cached_groups_preserve_arrival_order() {
7278		let producer = track_producer("test", None);
7279		producer.create_group(group::Info { sequence: 5 }).unwrap();
7280		producer.create_group(group::Info { sequence: 3 }).unwrap();
7281
7282		let groups = producer.consume().cached_groups();
7283		let sequences: Vec<u64> = groups.iter().map(|(group, _)| group.sequence).collect();
7284		assert_eq!(
7285			sequences,
7286			vec![5, 3],
7287			"warm snapshot must follow arrival, not sequence order"
7288		);
7289	}
7290
7291	#[test]
7292	fn append_finish_cannot_be_rewritten() {
7293		let producer = track_producer("test", None);
7294
7295		// Finishing an empty track is valid (fin = 0, total groups = 0).
7296		assert!(producer.finish().is_ok());
7297		assert!(producer.finish().is_err());
7298		assert!(producer.append_group().is_err());
7299	}
7300
7301	#[test]
7302	fn finish_after_groups() {
7303		let producer = track_producer("test", None);
7304
7305		producer.append_group().unwrap();
7306		assert!(producer.finish().is_ok());
7307		assert!(producer.finish().is_err());
7308		assert!(producer.append_group().is_err());
7309	}
7310
7311	#[test]
7312	fn finish_at_rejects_a_boundary_at_or_below_the_live_edge() {
7313		let mut producer = track_producer("test", None);
7314		producer.create_group(group::Info { sequence: 5 }).unwrap();
7315
7316		// The boundary is exclusive, so it must be strictly above the highest produced
7317		// group. 5 or below would orphan groups that already exist.
7318		assert!(producer.finish_at(4).is_err());
7319		assert!(producer.finish_at(5).is_err());
7320		assert!(producer.finish_at(6).is_ok());
7321
7322		{
7323			let state = producer.state.read();
7324			assert_eq!(state.final_sequence, Some(6));
7325		}
7326
7327		// Re-finishing is rejected, and no group at or above the boundary can be created.
7328		assert!(producer.finish_at(6).is_err());
7329		assert!(producer.create_group(group::Info { sequence: 4 }).is_ok());
7330		assert!(producer.create_group(group::Info { sequence: 6 }).is_err());
7331	}
7332
7333	#[test]
7334	fn final_sequence_reports_the_declared_boundary() {
7335		let mut producer = track_producer("test", None);
7336		assert_eq!(producer.final_sequence(), None);
7337
7338		producer.create_group(group::Info { sequence: 5 }).unwrap();
7339		assert_eq!(producer.final_sequence(), None, "a group does not declare a boundary");
7340
7341		producer.finish_at(9).unwrap();
7342		assert_eq!(producer.final_sequence(), Some(9));
7343
7344		// finish() would try to declare a second boundary, so callers check first.
7345		assert!(producer.finish().is_err());
7346	}
7347
7348	#[test]
7349	fn final_sequence_reports_the_live_edge_after_finish() {
7350		let producer = track_producer("test", None);
7351		producer.create_group(group::Info { sequence: 5 }).unwrap();
7352		producer.finish().unwrap();
7353		assert_eq!(producer.final_sequence(), Some(6));
7354	}
7355
7356	#[tokio::test]
7357	async fn finish_at_declares_a_future_boundary() {
7358		let mut producer = track_producer("test", None);
7359		producer.create_group(group::Info { sequence: 5 }).unwrap();
7360
7361		// Learn the track ends at group 6 (exclusive 7) while the live edge is still 5.
7362		producer.finish_at(7).unwrap();
7363
7364		let mut consumer = producer.subscribe(None);
7365		assert_eq!(consumer.assert_group().sequence, 5);
7366
7367		// The boundary is known immediately, but the track isn't done: group 6 is still
7368		// outstanding, so the consumer parks rather than seeing end-of-stream.
7369		let boundary = consumer
7370			.finished()
7371			.now_or_never()
7372			.expect("boundary is known immediately")
7373			.expect("would have errored");
7374		assert_eq!(boundary, 7);
7375		assert!(
7376			consumer.recv_group().now_or_never().is_none(),
7377			"should wait for the outstanding group"
7378		);
7379
7380		// The trailing group arrives (below the boundary), then the track completes.
7381		producer.create_group(group::Info { sequence: 6 }).unwrap();
7382		assert_eq!(consumer.assert_group().sequence, 6);
7383		let done = consumer
7384			.recv_group()
7385			.now_or_never()
7386			.expect("should not block")
7387			.expect("would have errored");
7388		assert!(done.is_none(), "track completes once the boundary is reached");
7389	}
7390
7391	/// An abort before the declared end settled wins over it: the boundary was reached,
7392	/// but a group below it was still open, so the track was cut off rather than ended.
7393	#[tokio::test]
7394	async fn abort_before_the_end_settles_wins() {
7395		let mut producer = track_producer("test", None);
7396		let mut consumer = producer.subscribe(None);
7397
7398		let head = producer.create_group(group::Info { sequence: 0 }).unwrap();
7399		head.finish().unwrap();
7400		let _tail = producer.create_group(group::Info { sequence: 1 }).unwrap();
7401		producer.finish_at(2).unwrap();
7402		assert_eq!(consumer.assert_group().sequence, 0);
7403
7404		producer.abort(Error::Timeout).unwrap();
7405		let res = consumer.recv_group().now_or_never().expect("should not block");
7406		assert!(matches!(res, Err(Error::Timeout)));
7407	}
7408
7409	#[tokio::test]
7410	async fn local_close_keeps_finished_groups_without_declaring_an_end() {
7411		let producer = track_producer("local", None);
7412		let mut consumer = producer.consume().subscribe(None).await.unwrap();
7413		producer.append_group().unwrap().finish().unwrap();
7414		producer.clone().close().unwrap();
7415		assert!(producer.final_sequence().is_none());
7416		assert!(matches!(consumer.finished().await, Err(Error::Closed)));
7417		assert!(consumer.recv_group().await.unwrap().is_some());
7418		assert!(consumer.recv_group().await.unwrap().is_none());
7419	}
7420
7421	#[tokio::test]
7422	async fn local_close_cannot_mask_an_abort_before_declared_end_settles() {
7423		let mut producer = track_producer("local", None);
7424		let mut consumer = producer.consume().subscribe(None).await.unwrap();
7425		let _group = producer.append_group().unwrap();
7426		producer.finish_at(2).unwrap();
7427		producer.clone().abort(Error::Timeout).unwrap();
7428		assert!(producer.close().is_err());
7429		assert!(matches!(consumer.recv_group().await, Err(Error::Timeout)));
7430	}
7431
7432	/// A local close is a dead route to the origin, not finished content, so a
7433	/// replacement route can take over where it left off.
7434	#[test]
7435	fn local_close_is_not_completion_and_keeps_the_resume_position() {
7436		let producer = track_producer("local", None);
7437		let consumer = producer.consume();
7438		producer.append_group().unwrap().finish().unwrap();
7439		let mut group = producer.append_group().unwrap();
7440		group
7441			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"a"))
7442			.unwrap();
7443		group
7444			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"b"))
7445			.unwrap();
7446		producer.close().unwrap();
7447		assert!(matches!(
7448			consumer.poll_complete(&kio::Waiter::noop()),
7449			Poll::Ready(Err(_))
7450		));
7451		assert_eq!(consumer.resume_position(), Some(Position { group: 1, frame: 2 }));
7452	}
7453
7454	/// An abort short of the end keeps the groups that finished for a reader that has not
7455	/// pulled them yet: it gets them, then the abort. The open group nobody will finish
7456	/// is gone, on both cursors.
7457	#[tokio::test]
7458	async fn abort_keeps_finished_groups_for_a_slow_reader() {
7459		let producer = track_producer("test", None);
7460		let mut arrival = producer.subscribe(None);
7461		let mut ordered = producer.subscribe(None).ordered();
7462
7463		for sequence in 0..2 {
7464			producer
7465				.create_group(group::Info { sequence })
7466				.unwrap()
7467				.finish()
7468				.unwrap();
7469		}
7470		let _open = producer.create_group(group::Info { sequence: 2 }).unwrap();
7471		producer.abort(Error::Timeout).unwrap();
7472
7473		assert_eq!(arrival.assert_group().sequence, 0);
7474		assert_eq!(arrival.assert_group().sequence, 1);
7475		let res = arrival.recv_group().now_or_never().expect("should not block");
7476		assert!(matches!(res, Err(Error::Timeout)), "expected the abort");
7477
7478		assert_eq!(drain_ordered(&mut ordered), [0, 1]);
7479		let res = ordered
7480			.next_group()
7481			.now_or_never()
7482			.expect("should not block")
7483			.map(|group| group.map(|group| group.sequence));
7484		assert!(matches!(res, Err(Error::Timeout)), "expected the abort, got {res:?}");
7485	}
7486
7487	/// The last producer dropping without a finish keeps the finished groups the same way.
7488	#[tokio::test]
7489	async fn dropped_producer_keeps_finished_groups_for_a_slow_reader() {
7490		let producer = track_producer("test", None);
7491		let mut consumer = producer.subscribe(None);
7492		producer
7493			.create_group(group::Info { sequence: 0 })
7494			.unwrap()
7495			.finish()
7496			.unwrap();
7497		drop(producer);
7498
7499		assert_eq!(consumer.assert_group().sequence, 0);
7500		let res = consumer.recv_group().now_or_never().expect("should not block");
7501		assert!(matches!(res, Err(Error::Dropped)), "expected the drop");
7502	}
7503
7504	/// A closed track's groups, its latest included, expire once idle, so a stale
7505	/// consumer cannot pin them. A live track's latest stays protected.
7506	#[tokio::test]
7507	async fn closed_track_expires_its_latest_group() {
7508		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
7509
7510		let live = track_producer_pooled("live", pool.clone());
7511		live.append_group().unwrap().finish().unwrap();
7512
7513		let aborted = track_producer_pooled("aborted", pool.clone());
7514		aborted.append_group().unwrap().finish().unwrap();
7515		let stale_aborted = aborted.consume();
7516		aborted.abort(Error::Timeout).unwrap();
7517
7518		let finished = track_producer_pooled("finished", pool.clone());
7519		finished.append_group().unwrap().finish().unwrap();
7520		finished.finish().unwrap();
7521		let stale_finished = finished.consume();
7522		drop(finished);
7523
7524		// The first pass dates the activity it has not seen yet; the next one expires it.
7525		for _ in 0..2 {
7526			crate::model::clock::advance(Duration::from_secs(2));
7527			pool.sweep();
7528		}
7529
7530		assert!(live.consume().peek_group(0).is_some(), "a live track keeps its latest");
7531		assert!(
7532			stale_aborted.peek_group(0).is_none(),
7533			"an aborted track's latest expired"
7534		);
7535		assert!(
7536			stale_finished.peek_group(0).is_none(),
7537			"a sealed track's latest expired"
7538		);
7539	}
7540
7541	/// An ended track that shares a group with a live one leaves it while it is the live
7542	/// track's latest, and expires it like any other idle group once it is not. A group
7543	/// only ended tracks share expires even while stale consumers keep them all.
7544	#[tokio::test]
7545	async fn closed_track_expires_a_shared_group_unless_live_edge() {
7546		let pool = cache::Pool::new(cache::Config::default().with_expiry(Duration::from_secs(1)));
7547		// The first pass dates the activity it has not seen yet; the next one expires it.
7548		let sweep = || {
7549			for _ in 0..2 {
7550				crate::model::clock::advance(Duration::from_secs(2));
7551				pool.sweep();
7552			}
7553		};
7554		let adopt = |name: &str, group: &group::Producer| {
7555			let mut track = track_producer_pooled(name, pool.clone());
7556			track.adopt_group(group.clone(), true).unwrap();
7557			track.finish().unwrap();
7558			let stale = track.consume();
7559			drop(track);
7560			stale
7561		};
7562
7563		let live = track_producer_pooled("live", pool.clone());
7564		let group = live.append_group().unwrap();
7565		group.finish().unwrap();
7566		let stale = adopt("ended", &group);
7567		drop(group);
7568
7569		sweep();
7570		assert!(live.consume().peek_group(0).is_some(), "the live edge stays");
7571		assert!(
7572			stale.peek_group(0).is_some(),
7573			"and so does the ended track's share of it"
7574		);
7575
7576		live.append_group().unwrap().finish().unwrap();
7577		sweep();
7578		assert!(stale.peek_group(0).is_none(), "once demoted, it expires");
7579
7580		let group = live.append_group().unwrap();
7581		group.finish().unwrap();
7582		let (first, second) = (adopt("first", &group), adopt("second", &group));
7583		drop(group);
7584		live.finish().unwrap();
7585		drop(live);
7586
7587		sweep();
7588		assert!(first.peek_group(2).is_none(), "ended tracks alone don't keep it");
7589		assert!(second.peek_group(2).is_none(), "for each other");
7590	}
7591
7592	/// An abort after every group below the declared end finished leaves the end standing.
7593	#[tokio::test]
7594	async fn abort_after_the_end_settles_ends_clean() {
7595		let mut producer = track_producer("test", None);
7596		let mut consumer = producer.subscribe(None);
7597
7598		for sequence in 0..2 {
7599			let group = producer.create_group(group::Info { sequence }).unwrap();
7600			group.finish().unwrap();
7601		}
7602		producer.finish_at(2).unwrap();
7603		assert_eq!(consumer.assert_group().sequence, 0);
7604		assert_eq!(consumer.assert_group().sequence, 1);
7605
7606		producer.abort(Error::Timeout).unwrap();
7607		let res = consumer.recv_group().now_or_never().expect("should not block");
7608		assert!(matches!(res, Ok(None)));
7609	}
7610
7611	/// The settled end stands on the ordered cursor too: a reader that starts after the
7612	/// abort gets every group below the end, then the clean end, not the abort.
7613	#[tokio::test]
7614	async fn abort_after_the_end_settles_ends_clean_for_an_ordered_reader() {
7615		let mut producer = track_producer("test", None);
7616		let mut consumer = producer.subscribe(None).ordered();
7617
7618		for sequence in 0..2 {
7619			let group = producer.create_group(group::Info { sequence }).unwrap();
7620			group.finish().unwrap();
7621		}
7622		producer.finish_at(2).unwrap();
7623		producer.abort(Error::Timeout).unwrap();
7624
7625		assert_eq!(drain_ordered(&mut consumer), [0, 1]);
7626		let end = consumer
7627			.next_group()
7628			.now_or_never()
7629			.expect("should not block")
7630			.map(|group| group.map(|group| group.sequence));
7631		assert!(matches!(end, Ok(None)), "expected the clean end, got {end:?}");
7632	}
7633
7634	/// The settled end stands on the ordered cursor even where it has nothing to read:
7635	/// capped below the end, and with a gap below the cap. Parking is not an option on a
7636	/// closed track (the channel would turn it into the abort), so these end clean.
7637	#[tokio::test]
7638	async fn abort_after_the_end_settles_ends_clean_below_the_cap() {
7639		let mut producer = track_producer("test", None);
7640		let mut consumer = producer.subscribe(None).ordered();
7641		for sequence in 0..2 {
7642			producer
7643				.create_group(group::Info { sequence })
7644				.unwrap()
7645				.finish()
7646				.unwrap();
7647		}
7648		producer.finish_at(2).unwrap();
7649		producer.abort(Error::Timeout).unwrap();
7650
7651		consumer.set_groups(..1);
7652		assert_eq!(drain_ordered(&mut consumer), [0]);
7653		let end = consumer
7654			.next_group()
7655			.now_or_never()
7656			.expect("should not block")
7657			.map(|group| group.map(|group| group.sequence));
7658		assert!(
7659			matches!(end, Ok(None)),
7660			"capped at the end: expected the clean end, got {end:?}"
7661		);
7662
7663		let mut producer = track_producer("test", None);
7664		let mut consumer = producer.subscribe(None).ordered();
7665		for sequence in [0, 2] {
7666			producer
7667				.create_group(group::Info { sequence })
7668				.unwrap()
7669				.finish()
7670				.unwrap();
7671		}
7672		producer.finish_at(3).unwrap();
7673		producer.abort(Error::Timeout).unwrap();
7674
7675		consumer.set_groups(..2);
7676		assert_eq!(drain_ordered(&mut consumer), [0]);
7677		let end = consumer
7678			.next_group()
7679			.now_or_never()
7680			.expect("should not block")
7681			.map(|group| group.map(|group| group.sequence));
7682		assert!(
7683			matches!(end, Ok(None)),
7684			"gap below the cap: expected the clean end, got {end:?}"
7685		);
7686	}
7687
7688	#[tokio::test]
7689	async fn recv_group_finishes_without_waiting_for_gaps() {
7690		let producer = track_producer("test", None);
7691		producer.create_group(group::Info { sequence: 1 }).unwrap();
7692		producer.finish().unwrap();
7693
7694		let mut consumer = producer.subscribe(None);
7695		assert_eq!(consumer.assert_group().sequence, 1);
7696
7697		let done = consumer
7698			.recv_group()
7699			.now_or_never()
7700			.expect("should not block")
7701			.expect("would have errored");
7702		assert!(done.is_none(), "track should finish without waiting for gaps");
7703	}
7704
7705	#[tokio::test]
7706	async fn next_group_skips_late_arrivals() {
7707		let producer = track_producer("test", None);
7708		let mut consumer = producer.subscribe(None).ordered();
7709
7710		// Seq 5 arrives first.
7711		producer.create_group(group::Info { sequence: 5 }).unwrap();
7712		let group = consumer
7713			.next_group()
7714			.now_or_never()
7715			.expect("should not block")
7716			.expect("would have errored")
7717			.expect("track should not be closed");
7718		assert_eq!(group.sequence, 5);
7719
7720		// Seq 3 arrives late, skipped because 3 <= 5.
7721		producer.create_group(group::Info { sequence: 3 }).unwrap();
7722		// Seq 4 arrives late and is also skipped.
7723		producer.create_group(group::Info { sequence: 4 }).unwrap();
7724		// Seq 7 arrives and is returned.
7725		producer.create_group(group::Info { sequence: 7 }).unwrap();
7726
7727		let group = consumer
7728			.next_group()
7729			.now_or_never()
7730			.expect("should not block")
7731			.expect("would have errored")
7732			.expect("track should not be closed");
7733		assert_eq!(group.sequence, 7);
7734
7735		// No more groups. This would block.
7736		assert!(
7737			consumer.next_group().now_or_never().is_none(),
7738			"should block waiting for a higher sequence"
7739		);
7740	}
7741
7742	#[tokio::test]
7743	async fn next_group_returns_arrivals_in_order() {
7744		let producer = track_producer("test", None);
7745		let mut consumer = producer.subscribe(replay()).ordered();
7746
7747		// Seq 3 arrives first, then seq 5. Both should be returned in arrival order.
7748		producer.create_group(group::Info { sequence: 3 }).unwrap();
7749		producer.create_group(group::Info { sequence: 5 }).unwrap();
7750
7751		let group = consumer
7752			.next_group()
7753			.now_or_never()
7754			.expect("should not block")
7755			.expect("would have errored")
7756			.expect("track should not be closed");
7757		assert_eq!(group.sequence, 3);
7758
7759		let group = consumer
7760			.next_group()
7761			.now_or_never()
7762			.expect("should not block")
7763			.expect("would have errored")
7764			.expect("track should not be closed");
7765		assert_eq!(group.sequence, 5);
7766	}
7767
7768	#[tokio::test]
7769	async fn ordered_and_arrival_cursors_are_independent() {
7770		let producer = track_producer("test", None);
7771		let mut ordered = producer.subscribe(replay()).ordered();
7772		let mut arrival = producer.subscribe(replay());
7773
7774		// Out-of-order arrivals: seq 5 first, then seq 3.
7775		producer.create_group(group::Info { sequence: 5 }).unwrap();
7776		producer.create_group(group::Info { sequence: 3 }).unwrap();
7777
7778		// The ordered handle returns the smallest sequence first, regardless of
7779		// arrival order.
7780		let group = ordered
7781			.next_group()
7782			.now_or_never()
7783			.expect("should not block")
7784			.expect("would have errored")
7785			.expect("track should not be closed");
7786		assert_eq!(group.sequence, 3);
7787
7788		// The plain handle walks arrivals, so it still starts at seq 5.
7789		assert_eq!(arrival.assert_group().sequence, 5);
7790	}
7791
7792	#[tokio::test]
7793	async fn end_at_caps_next_group() {
7794		let producer = track_producer("test", None);
7795		let mut consumer = producer.subscribe(replay()).ordered();
7796
7797		for s in 0..6 {
7798			producer.create_group(group::Info { sequence: s }).unwrap();
7799		}
7800
7801		consumer.set_groups(..3);
7802
7803		// Groups 0, 1, 2 are within the cap.
7804		assert_eq!(
7805			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7806			0
7807		);
7808		assert_eq!(
7809			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7810			1
7811		);
7812		assert_eq!(
7813			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7814			2
7815		);
7816
7817		// Group 3 is beyond the cap: next_group parks even though cached groups exist.
7818		assert!(
7819			consumer.next_group().now_or_never().is_none(),
7820			"capped consumer must block instead of returning out-of-range groups"
7821		);
7822	}
7823
7824	#[tokio::test]
7825	async fn end_at_release_drains_cached_groups() {
7826		let producer = track_producer("test", None);
7827		let mut consumer = producer.subscribe(replay()).ordered();
7828
7829		for s in 0..6 {
7830			producer.create_group(group::Info { sequence: s }).unwrap();
7831		}
7832
7833		consumer.set_groups(..2);
7834		assert_eq!(
7835			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7836			0
7837		);
7838		assert_eq!(
7839			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7840			1
7841		);
7842		assert!(consumer.next_group().now_or_never().is_none(), "capped at 2");
7843
7844		// Raise the cap; previously-blocked cached groups become available again.
7845		consumer.set_groups(..5);
7846		assert_eq!(
7847			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7848			2
7849		);
7850		assert_eq!(
7851			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7852			3
7853		);
7854		assert_eq!(
7855			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7856			4
7857		);
7858		assert!(consumer.next_group().now_or_never().is_none(), "capped at 5");
7859
7860		// Remove the cap; everything remaining flows.
7861		consumer.set_groups(..);
7862		assert_eq!(
7863			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7864			5
7865		);
7866		assert!(consumer.next_group().now_or_never().is_none(), "no more groups");
7867	}
7868
7869	#[tokio::test]
7870	async fn end_at_lower_than_cursor_parks_consumer() {
7871		let producer = track_producer("test", None);
7872		let mut consumer = producer.subscribe(replay()).ordered();
7873
7874		for s in 0..3 {
7875			producer.create_group(group::Info { sequence: s }).unwrap();
7876		}
7877
7878		// Drain everything with no cap.
7879		assert_eq!(
7880			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7881			0
7882		);
7883		assert_eq!(
7884			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7885			1
7886		);
7887		assert_eq!(
7888			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7889			2
7890		);
7891
7892		// Lower the cap below the cursor. New groups beyond the cap are blocked.
7893		consumer.set_groups(..2);
7894		producer.create_group(group::Info { sequence: 3 }).unwrap();
7895		producer.create_group(group::Info { sequence: 4 }).unwrap();
7896		assert!(
7897			consumer.next_group().now_or_never().is_none(),
7898			"cap is below cursor; nothing returnable until cap rises"
7899		);
7900
7901		// Restoring the cap to no-limit (or any value >= cursor) releases them.
7902		consumer.set_groups(..);
7903		assert_eq!(
7904			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7905			3
7906		);
7907		assert_eq!(
7908			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7909			4
7910		);
7911	}
7912
7913	#[tokio::test]
7914	async fn end_at_toggling_around_late_arrivals() {
7915		let producer = track_producer("test", None);
7916		let mut consumer = producer.subscribe(replay()).ordered();
7917
7918		consumer.set_groups(..6);
7919
7920		// Out-of-order arrivals all within the cap.
7921		producer.create_group(group::Info { sequence: 2 }).unwrap();
7922		producer.create_group(group::Info { sequence: 5 }).unwrap();
7923		producer.create_group(group::Info { sequence: 3 }).unwrap();
7924		// One beyond the cap; should be held even though it arrived in the middle.
7925		producer.create_group(group::Info { sequence: 8 }).unwrap();
7926		producer.create_group(group::Info { sequence: 4 }).unwrap();
7927
7928		// next_group walks in sequence order through everything <= cap.
7929		assert_eq!(
7930			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7931			2
7932		);
7933		assert_eq!(
7934			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7935			3
7936		);
7937		assert_eq!(
7938			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7939			4
7940		);
7941		assert_eq!(
7942			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7943			5
7944		);
7945		// Now blocked: 8 is still beyond the cap.
7946		assert!(consumer.next_group().now_or_never().is_none());
7947
7948		// Raise the cap; cached seq 8 is finally served.
7949		consumer.set_groups(..11);
7950		assert_eq!(
7951			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7952			8
7953		);
7954	}
7955
7956	/// `recv_group` (arrival order) honors the `end_at` cap by parking, like
7957	/// `next_group`: beyond-cap groups are held, not dropped, and a raised cap
7958	/// re-offers them, even after the track finishes.
7959	#[tokio::test]
7960	async fn end_at_parks_recv_group() {
7961		let producer = track_producer("test", None);
7962		let mut consumer = producer.subscribe(replay());
7963
7964		for s in 0..3 {
7965			producer.create_group(group::Info { sequence: s }).unwrap();
7966		}
7967
7968		consumer.set_groups(..2);
7969		assert_eq!(
7970			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7971			0
7972		);
7973		assert_eq!(
7974			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7975			1
7976		);
7977		assert!(consumer.recv_group().now_or_never().is_none(), "capped at 2");
7978
7979		// A finished track keeps the parked group claimable: the cap may rise.
7980		producer.finish().unwrap();
7981		assert!(
7982			consumer.recv_group().now_or_never().is_none(),
7983			"still parked after finish"
7984		);
7985
7986		consumer.set_groups(..);
7987		assert_eq!(
7988			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
7989			2
7990		);
7991		assert!(
7992			matches!(consumer.recv_group().now_or_never(), Some(Ok(None))),
7993			"finished once the parked group drains"
7994		);
7995	}
7996
7997	/// A group beyond the cap must not block in-range groups that arrive behind
7998	/// it: a relay can ingest a burst micro-reordered (newest first).
7999	#[tokio::test]
8000	async fn recv_group_serves_arrivals_behind_the_cap() {
8001		let producer = track_producer("test", None);
8002		let mut consumer = producer.subscribe(replay());
8003
8004		consumer.set_groups(..2);
8005
8006		// Reordered burst: the beyond-cap group arrives first.
8007		producer.create_group(group::Info { sequence: 2 }).unwrap();
8008		producer.create_group(group::Info { sequence: 0 }).unwrap();
8009		producer.create_group(group::Info { sequence: 1 }).unwrap();
8010
8011		assert_eq!(
8012			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8013			0
8014		);
8015		assert_eq!(
8016			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8017			1
8018		);
8019		assert!(consumer.recv_group().now_or_never().is_none(), "capped at 2");
8020
8021		consumer.set_groups(..3);
8022		assert_eq!(
8023			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8024			2
8025		);
8026	}
8027
8028	#[tokio::test]
8029	async fn group_ranges_preserve_the_floor_when_the_cap_changes() {
8030		let producer = track_producer("test", None);
8031		let mut consumer = producer.subscribe(None);
8032		consumer.set_groups(2..=2);
8033		producer.create_group(group::Info { sequence: 2 }).unwrap();
8034		assert_eq!(consumer.recv_group().await.unwrap().unwrap().sequence, 2);
8035		consumer.set_groups(..4);
8036		producer.create_group(group::Info { sequence: 1 }).unwrap();
8037		producer.create_group(group::Info { sequence: 3 }).unwrap();
8038		assert_eq!(consumer.recv_group().await.unwrap().unwrap().sequence, 3);
8039		consumer.set_groups(0..=4);
8040		producer.create_group(group::Info { sequence: 0 }).unwrap();
8041		producer.create_group(group::Info { sequence: 4 }).unwrap();
8042		assert_eq!(consumer.recv_group().await.unwrap().unwrap().sequence, 4);
8043	}
8044
8045	/// A raised `start_at` drops parked groups it overtook instead of re-offering
8046	/// them once the cap rises.
8047	#[tokio::test]
8048	async fn start_at_drops_parked_recv_groups() {
8049		let producer = track_producer("test", None);
8050		let mut consumer = producer.subscribe(None);
8051
8052		consumer.set_groups(..1);
8053		producer.create_group(group::Info { sequence: 1 }).unwrap();
8054		assert!(
8055			consumer.recv_group().now_or_never().is_none(),
8056			"group 1 parked at the cap"
8057		);
8058
8059		consumer.start_at(2);
8060		consumer.set_groups(..);
8061		producer.create_group(group::Info { sequence: 2 }).unwrap();
8062		assert_eq!(
8063			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8064			2,
8065			"the overtaken parked group is dropped, not re-offered"
8066		);
8067	}
8068
8069	/// A parked group the producer aborts (eviction/expiry) is dropped: it is
8070	/// neither delivered once the cap rises nor allowed to hold the stream open
8071	/// after the track finishes. This is what bounds parking by the cache policy.
8072	#[tokio::test]
8073	async fn evicted_parked_recv_groups_are_dropped() {
8074		let producer = track_producer("test", None);
8075		let mut consumer = producer.subscribe(None);
8076
8077		producer.create_group(group::Info { sequence: 0 }).unwrap();
8078		assert_eq!(
8079			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8080			0
8081		);
8082
8083		consumer.set_groups(..1);
8084		let straggler = producer.create_group(group::Info { sequence: 1 }).unwrap();
8085		assert!(
8086			consumer.recv_group().now_or_never().is_none(),
8087			"group 1 parked at the cap"
8088		);
8089
8090		// The cache evicts the parked group (abort-as-tombstone), then the track ends.
8091		straggler.abort(Error::Old).unwrap();
8092		producer.finish().unwrap();
8093
8094		consumer.set_groups(..);
8095		assert!(
8096			matches!(consumer.recv_group().now_or_never(), Some(Ok(None))),
8097			"a dead parked group must not be delivered or hold the stream open"
8098		);
8099	}
8100
8101	/// Eviction aborts a parked group behind a sleeping subscriber's back. Nothing
8102	/// else will poll it (the track already finished), so the entry has to carry a
8103	/// waiter or the subscription sleeps forever holding its stream open.
8104	#[tokio::test]
8105	async fn evicted_parked_group_wakes_the_clean_end() {
8106		use std::sync::atomic::{AtomicUsize, Ordering};
8107		use std::task::{Context, Wake};
8108
8109		/// A waker that counts its wakes, for asserting a pending poll left a live
8110		/// registration behind.
8111		struct CountWaker(AtomicUsize);
8112		impl Wake for CountWaker {
8113			fn wake(self: std::sync::Arc<Self>) {
8114				self.0.fetch_add(1, Ordering::SeqCst);
8115			}
8116		}
8117
8118		let producer = track_producer("test", None);
8119		let mut consumer = producer.subscribe(None);
8120
8121		producer.create_group(group::Info { sequence: 0 }).unwrap();
8122		assert_eq!(
8123			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8124			0
8125		);
8126
8127		consumer.set_groups(..1);
8128		let straggler = producer.create_group(group::Info { sequence: 1 }).unwrap();
8129		assert!(consumer.recv_group().now_or_never().is_none(), "parked at the cap");
8130		producer.finish().unwrap();
8131
8132		let counter = std::sync::Arc::new(CountWaker(AtomicUsize::new(0)));
8133		let waker = std::task::Waker::from(counter.clone());
8134		let mut cx = Context::from_waker(&waker);
8135		let mut fut = std::pin::pin!(consumer.recv_group());
8136		assert!(
8137			fut.as_mut().poll(&mut cx).is_pending(),
8138			"the parked group holds it open"
8139		);
8140
8141		straggler.abort(Error::Old).unwrap();
8142		assert!(counter.0.load(Ordering::SeqCst) > 0, "the eviction wakeup was lost");
8143		assert!(matches!(fut.as_mut().poll(&mut cx), Poll::Ready(Ok(None))));
8144	}
8145
8146	/// An exclusive cap at 0 is the empty range: no group is delivered, even group 0.
8147	#[tokio::test]
8148	async fn end_at_zero_is_the_empty_range() {
8149		let producer = track_producer("test", None);
8150		let mut consumer = producer.subscribe(replay());
8151		producer.create_group(group::Info { sequence: 0 }).unwrap();
8152		producer.create_group(group::Info { sequence: 1 }).unwrap();
8153
8154		consumer.set_groups(..0);
8155		assert!(
8156			consumer.recv_group().now_or_never().is_none(),
8157			"empty cap delivers nothing"
8158		);
8159
8160		consumer.set_groups(..1);
8161		assert_eq!(
8162			consumer.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8163			0
8164		);
8165		assert!(consumer.recv_group().now_or_never().is_none(), "group 1 stays parked");
8166	}
8167
8168	/// A requested empty range plus a local empty cap still parks while another
8169	/// subscriber keeps aggregate demand unbounded.
8170	#[tokio::test]
8171	async fn empty_local_cap_holds_while_another_subscriber_requests_everything() {
8172		let producer = track_producer("test", None);
8173		let mut everything = producer.subscribe(replay());
8174		let mut empty = producer.subscribe(Subscription::default().with_end(Position::group(0)));
8175		empty.set_groups((Bound::Unbounded, Position::group(0).group_end()));
8176
8177		for s in 0..3 {
8178			producer.create_group(group::Info { sequence: s }).unwrap();
8179		}
8180
8181		assert_eq!(
8182			everything
8183				.recv_group()
8184				.now_or_never()
8185				.unwrap()
8186				.unwrap()
8187				.unwrap()
8188				.sequence,
8189			0
8190		);
8191		assert!(
8192			empty.recv_group().now_or_never().is_none(),
8193			"local empty cap must not ride the unbounded aggregate"
8194		);
8195
8196		empty.set_groups(..2);
8197		assert_eq!(empty.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence, 0);
8198		assert_eq!(empty.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence, 1);
8199		assert!(empty.recv_group().now_or_never().is_none(), "still capped at 2");
8200
8201		empty.set_groups(..);
8202		assert_eq!(empty.recv_group().now_or_never().unwrap().unwrap().unwrap().sequence, 2);
8203	}
8204
8205	/// A frame-limited exclusive end includes the last group and stops before that frame.
8206	#[tokio::test]
8207	async fn end_at_frame_limited_last_group() {
8208		let producer = track_producer("test", None);
8209		let mut consumer = producer.subscribe(replay()).ordered();
8210		let end = Position::after(1, 1).unwrap();
8211		consumer.set_groups((Bound::Unbounded, end.group_end()));
8212
8213		for s in 0..3u64 {
8214			let mut group = producer.create_group(group::Info { sequence: s }).unwrap();
8215			for i in 0..3u8 {
8216				group.write_frame(Timestamp::ZERO, vec![i]).unwrap();
8217			}
8218			group.finish().unwrap();
8219		}
8220
8221		assert_eq!(
8222			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8223			0
8224		);
8225		let mut last = consumer.next_group().now_or_never().unwrap().unwrap().unwrap();
8226		assert_eq!(last.sequence, 1);
8227		last.set_frames(..end.frame);
8228		assert_eq!(
8229			last.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
8230			0
8231		);
8232		assert_eq!(
8233			last.read_frame().now_or_never().unwrap().unwrap().unwrap().payload[0],
8234			1
8235		);
8236		assert!(
8237			last.read_frame().now_or_never().unwrap().unwrap().is_none(),
8238			"frame cap is exclusive"
8239		);
8240		assert!(
8241			consumer.next_group().now_or_never().is_none(),
8242			"group 2 is past the exclusive group cap"
8243		);
8244	}
8245
8246	/// An inclusive bound at the last group withholds nothing.
8247	#[tokio::test]
8248	async fn end_at_maximum_group_is_unbounded() {
8249		let producer = track_producer("test", None);
8250		let mut consumer = producer.subscribe(replay()).ordered();
8251		consumer.set_groups(..=u64::MAX);
8252
8253		producer.create_group(group::Info { sequence: 0 }).unwrap();
8254		producer.create_group(group::Info { sequence: u64::MAX }).unwrap();
8255
8256		assert_eq!(
8257			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8258			0
8259		);
8260		assert_eq!(
8261			consumer.next_group().now_or_never().unwrap().unwrap().unwrap().sequence,
8262			u64::MAX
8263		);
8264	}
8265
8266	#[test]
8267	fn write_frame_rejects_an_oversized_frame_before_appending_its_group() {
8268		let mut producer = track_producer("test", None);
8269		let frame = bytes::Bytes::from(vec![0; group::MAX_CACHE_BYTES as usize + 1]);
8270
8271		assert!(matches!(
8272			producer.write_frame(Timestamp::ZERO, frame),
8273			Err(Error::FrameTooLarge)
8274		));
8275		assert_eq!(producer.latest(), None, "the rejected frame did not publish a group");
8276	}
8277
8278	#[test]
8279	fn append_group_returns_bounds_exceeded_on_sequence_overflow() {
8280		let producer = track_producer("test", None);
8281		{
8282			let mut state = producer.state.write().ok().unwrap();
8283			state.max_sequence = Some(u64::MAX);
8284		}
8285
8286		assert!(matches!(producer.append_group(), Err(Error::BoundsExceeded(_))));
8287	}
8288
8289	#[tokio::test]
8290	async fn fetch_cache_hit() {
8291		let producer = track_producer("test", None);
8292
8293		// Produce a cached group.
8294		let mut group = producer.append_group().unwrap(); // seq 0
8295		group
8296			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hello"))
8297			.unwrap();
8298		group.finish().unwrap();
8299
8300		// A cached group resolves immediately and never queues a request. `peek_group`
8301		// also returns it synchronously.
8302		let dynamic = producer.dynamic();
8303		let consumer = producer.consume();
8304		assert!(consumer.peek_group(0).is_some());
8305		let mut g = consumer.fetch_group(0, None).await.unwrap();
8306		assert_eq!(g.sequence, 0);
8307		assert_eq!(&g.read_frame().await.unwrap().unwrap().payload[..], b"hello");
8308
8309		// Nothing was queued for the dynamic handler to serve.
8310		assert!(dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending());
8311	}
8312
8313	#[tokio::test]
8314	async fn fetch_miss_signals_dynamic() {
8315		let producer = track_producer("test", None);
8316		let dynamic = producer.dynamic();
8317		let consumer = producer.consume();
8318
8319		// A cache miss isn't in `peek_group`, but a dynamic handler exists, so
8320		// `fetch_group` stays pending and queues a request. `*pending` derefs the
8321		// wrapper to the inner `Fetching` (a `kio::Pollable`).
8322		assert!(consumer.peek_group(5).is_none());
8323		let pending = consumer.fetch_group(5, group::Fetch::default().with_priority(7));
8324		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
8325
8326		let req = dynamic
8327			.requested_group()
8328			.now_or_never()
8329			.expect("should not block")
8330			.unwrap();
8331		assert_eq!(req.sequence(), 5);
8332		assert_eq!(req.priority(), 7);
8333
8334		// Serve it by accepting the request; the fetch then resolves.
8335		let mut group = req.accept(None).unwrap();
8336		group
8337			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi"))
8338			.unwrap();
8339		group.finish().unwrap();
8340
8341		let mut g = pending.await.unwrap();
8342		assert_eq!(g.sequence, 5);
8343		assert_eq!(&g.read_frame().await.unwrap().unwrap().payload[..], b"hi");
8344	}
8345
8346	#[tokio::test]
8347	async fn fetch_miss_rejects() {
8348		let producer = track_producer("test", None);
8349		let dynamic = producer.dynamic();
8350		let consumer = producer.consume();
8351
8352		let pending = consumer.fetch_group(5, None);
8353		let req = dynamic
8354			.requested_group()
8355			.now_or_never()
8356			.expect("should not block")
8357			.unwrap();
8358
8359		req.reject(Error::Cancel);
8360		assert!(matches!(pending.await, Err(Error::Cancel)));
8361		let fetch = producer.state.read().fetch.clone();
8362		assert!(fetch.read().is_empty());
8363	}
8364
8365	#[tokio::test]
8366	async fn fetch_miss_drop_rejects() {
8367		let producer = track_producer("test", None);
8368		let dynamic = producer.dynamic();
8369		let consumer = producer.consume();
8370
8371		let pending = consumer.fetch_group(5, None);
8372		let req = dynamic
8373			.requested_group()
8374			.now_or_never()
8375			.expect("should not block")
8376			.unwrap();
8377
8378		drop(req);
8379		assert!(matches!(pending.await, Err(Error::Dropped)));
8380	}
8381
8382	/// A request stays wanted while any joined fetch waits, and once the last leaves it
8383	/// is withdrawn: a later fetch starts a fresh request instead of joining it.
8384	#[tokio::test]
8385	async fn fetch_request_unused_once_every_fetch_leaves() {
8386		let producer = track_producer("test", None);
8387		let dynamic = producer.dynamic();
8388		let consumer = producer.consume();
8389
8390		let first = consumer.fetch_group(5, None);
8391		let req = dynamic
8392			.requested_group()
8393			.now_or_never()
8394			.expect("should not block")
8395			.unwrap();
8396		let second = consumer.fetch_group(5, None);
8397
8398		drop(first);
8399		assert!(req.poll_unused(&kio::Waiter::noop()).is_pending());
8400		drop(second);
8401		assert!(req.poll_unused(&kio::Waiter::noop()).is_ready());
8402
8403		let retry = consumer.fetch_group(5, None);
8404		let fresh = dynamic
8405			.requested_group()
8406			.now_or_never()
8407			.expect("the retry queues a fresh request")
8408			.unwrap();
8409		drop(req);
8410		assert!(fresh.poll_unused(&kio::Waiter::noop()).is_pending());
8411		fresh.accept(None).unwrap().finish().unwrap();
8412		assert_eq!(retry.await.unwrap().sequence, 5);
8413	}
8414
8415	/// Dropping an auto trait from a published type is a semver break, so the group
8416	/// consumer a cached fetch holds must not cost `Fetching` its unwind safety.
8417	#[test]
8418	fn fetching_is_unwind_safe() {
8419		fn assert_unwind_safe<T: std::panic::UnwindSafe + std::panic::RefUnwindSafe>() {}
8420		assert_unwind_safe::<Fetching>();
8421		assert_unwind_safe::<group::Consumer>();
8422	}
8423
8424	/// A fetch that hits the cache holds the group until polled, so a handler that aborts
8425	/// the group once nobody wants it (an abandoned upstream fetch) leaves it alone.
8426	#[tokio::test]
8427	async fn fetch_hit_keeps_the_group_wanted() {
8428		let producer = track_producer("test", None);
8429		let dynamic = producer.dynamic();
8430		let consumer = producer.consume();
8431
8432		let first = consumer.fetch_group(5, None);
8433		let req = dynamic
8434			.requested_group()
8435			.now_or_never()
8436			.expect("should not block")
8437			.unwrap();
8438		let mut group = req.accept(None).unwrap();
8439		group
8440			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
8441			.unwrap();
8442		drop(first.await.unwrap());
8443
8444		let hit = consumer.fetch_group(5, None);
8445		assert!(!group.abort_unused(Error::Cancel), "the pending hit wants the group");
8446		let mut fetched = hit.await.unwrap();
8447		assert_eq!(&fetched.read_frame().await.unwrap().unwrap().payload[..], b"head");
8448	}
8449
8450	/// An accepted group starts where its request did before anyone can see it, so a
8451	/// wider fetch arriving before the handler writes misses instead of being handed a
8452	/// reader that would skip the head it asked for.
8453	#[tokio::test]
8454	async fn accepted_group_starts_at_the_request() {
8455		let producer = track_producer("test", None);
8456		let dynamic = producer.dynamic();
8457		let consumer = producer.consume();
8458
8459		let _tail = consumer.fetch_group(5, group::Fetch::default().with_frame_start(3));
8460		let req = dynamic
8461			.requested_group()
8462			.now_or_never()
8463			.expect("should not block")
8464			.unwrap();
8465		let _group = req.accept(None).unwrap();
8466
8467		let whole = consumer.fetch_group(5, None);
8468		let req = dynamic
8469			.requested_group()
8470			.now_or_never()
8471			.expect("the wider fetch misses and queues its own request")
8472			.unwrap();
8473		assert_eq!(req.frame_start(), 0);
8474		assert!(kio::Pollable::poll(&*whole, &kio::Waiter::noop()).is_pending());
8475	}
8476
8477	/// Once a group nobody wanted is aborted, a later fetch misses rather than reading
8478	/// the abort, and queues a fresh request.
8479	#[tokio::test]
8480	async fn fetch_misses_an_abandoned_group() {
8481		let producer = track_producer("test", None);
8482		let dynamic = producer.dynamic();
8483		let consumer = producer.consume();
8484
8485		let first = consumer.fetch_group(5, None);
8486		let req = dynamic
8487			.requested_group()
8488			.now_or_never()
8489			.expect("should not block")
8490			.unwrap();
8491		let group = req.accept(None).unwrap();
8492		drop(first.await.unwrap());
8493		assert!(group.abort_unused(Error::Cancel));
8494
8495		let retry = consumer.fetch_group(5, None);
8496		let req = dynamic
8497			.requested_group()
8498			.now_or_never()
8499			.expect("the retry queues a fresh request")
8500			.unwrap();
8501		req.accept(None).unwrap().finish().unwrap();
8502		assert!(retry.await.unwrap().read_frame().await.unwrap().is_none());
8503	}
8504
8505	#[tokio::test]
8506	async fn fetch_reject_does_not_poison_retry() {
8507		let producer = track_producer("test", None);
8508		let dynamic = producer.dynamic();
8509		let consumer = producer.consume();
8510
8511		let pending = consumer.fetch_group(5, None);
8512		let req = dynamic
8513			.requested_group()
8514			.now_or_never()
8515			.expect("should not block")
8516			.unwrap();
8517		req.reject(Error::Cancel);
8518		assert!(matches!(pending.await, Err(Error::Cancel)));
8519
8520		let retry = consumer.fetch_group(5, None);
8521		let req = dynamic
8522			.requested_group()
8523			.now_or_never()
8524			.expect("should not block")
8525			.unwrap();
8526		let mut group = req.accept(None).unwrap();
8527		group
8528			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"retry"))
8529			.unwrap();
8530		group.finish().unwrap();
8531
8532		let mut group = retry.await.unwrap();
8533		assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"retry");
8534	}
8535
8536	/// A group cached from a frame-bounded subscription starts partway in. Serving it to
8537	/// someone who asked for the whole group would silently hand back a tail, so it is a
8538	/// miss and the fetch goes upstream instead.
8539	#[tokio::test]
8540	async fn fetch_ignores_a_group_that_starts_too_late() {
8541		let producer = track_producer("test", None);
8542		let dynamic = producer.dynamic();
8543		let consumer = producer.consume();
8544
8545		// The live subscription resumed mid-group, so only the tail is cached.
8546		let mut group = producer.create_group(group::Info { sequence: 0 }).unwrap();
8547		group.start_at(3).unwrap();
8548		group
8549			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"tail"))
8550			.unwrap();
8551		group.finish().unwrap();
8552
8553		// A fetch for the tail is covered and resolves from the cache.
8554		let fetch = consumer.fetch_group(0, group::Fetch::default().with_frame_start(3));
8555		let cached = fetch.now_or_never().expect("covered by the cache").unwrap();
8556		assert_eq!(cached.index(), 3);
8557
8558		// A fetch for the whole group is not, so it queues for a handler rather than
8559		// resolving to the tail.
8560		let mut fetch = std::pin::pin!(consumer.fetch_group(0, None));
8561		assert!(
8562			futures::poll!(fetch.as_mut()).is_pending(),
8563			"must not answer from the tail"
8564		);
8565
8566		let request = dynamic.requested_group().await.unwrap();
8567		assert_eq!((request.sequence(), request.frame_start()), (0, 0));
8568
8569		// Serving it replaces the too-narrow entry rather than colliding with it.
8570		let mut whole = request.accept(None).unwrap();
8571		whole
8572			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
8573			.unwrap();
8574		whole.finish().unwrap();
8575
8576		let mut served = fetch.await.unwrap();
8577		assert_eq!(served.index(), 0);
8578		assert_eq!(
8579			served.read_frame().await.unwrap().unwrap().payload,
8580			bytes::Bytes::from_static(b"head")
8581		);
8582	}
8583
8584	/// Joining a queued fetch widens its range, and a caller that arrives once the range
8585	/// is already on the wire fails cleanly rather than being handed the narrower group.
8586	#[tokio::test]
8587	async fn fetch_widens_or_fails_cleanly() {
8588		let producer = track_producer("test", None);
8589		let dynamic = producer.dynamic();
8590		let consumer = producer.consume();
8591
8592		let _narrow = consumer.fetch_group(0, group::Fetch::default().with_frame_start(5));
8593		let mut narrow = std::pin::pin!(_narrow);
8594		assert!(futures::poll!(narrow.as_mut()).is_pending());
8595
8596		// Still queued: widening is honored, because nothing has read the range yet.
8597		let _wider = consumer.fetch_group(0, group::Fetch::default().with_frame_start(2));
8598		let mut wider = std::pin::pin!(_wider);
8599		assert!(futures::poll!(wider.as_mut()).is_pending());
8600
8601		let request = dynamic.requested_group().await.unwrap();
8602		assert_eq!(request.frame_start(), 2, "widened while queued");
8603
8604		// Handed off now: the handler holds its own copy of the range, so a later wider
8605		// caller cannot move what is already being served.
8606		let _widest = consumer.fetch_group(0, group::Fetch::default().with_frame_start(0));
8607		let mut widest = std::pin::pin!(_widest);
8608		assert!(futures::poll!(widest.as_mut()).is_pending());
8609		assert_eq!(request.frame_start(), 2, "the in-flight range is already on the wire");
8610
8611		// A handler may keep tracking the joined demand past the accept, as the lite
8612		// subscriber does, which holds the result channel open.
8613		let _joined = request.result.clone();
8614		let mut group = request.accept(None).unwrap();
8615		// A handler numbers the frames from where it was asked to start, as `serve_fetch`
8616		// does; that offset is what makes the cached group too narrow for `widest`.
8617		group.start_at(2).unwrap();
8618		group
8619			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"from2"))
8620			.unwrap();
8621
8622		// The two callers it covers resolve; the one it doesn't fails cleanly rather
8623		// than being handed a group that starts above what it asked for, even while
8624		// the group is still being written.
8625		assert_eq!(narrow.await.unwrap().index(), 5);
8626		assert_eq!(wider.await.unwrap().index(), 2);
8627		assert!(matches!(widest.await, Err(Error::NotFound)));
8628		group.finish().unwrap();
8629	}
8630
8631	#[tokio::test]
8632	async fn fetch_coalesces_concurrent() {
8633		let producer = track_producer("test", None);
8634		let dynamic = producer.dynamic();
8635		let consumer = producer.consume();
8636
8637		// Two fetches for the same uncached group produce ONE handler request,
8638		// carrying the higher of the two priorities.
8639		let first = consumer.fetch_group(5, group::Fetch::default().with_priority(1));
8640		let second = consumer.fetch_group(5, group::Fetch::default().with_priority(7));
8641		assert!(kio::Pollable::poll(&*first, &kio::Waiter::noop()).is_pending());
8642
8643		let req = dynamic
8644			.requested_group()
8645			.now_or_never()
8646			.expect("should not block")
8647			.unwrap();
8648		assert_eq!(req.sequence(), 5);
8649		assert_eq!(req.priority(), 7);
8650		assert!(
8651			dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending(),
8652			"the second fetch queued a duplicate request"
8653		);
8654
8655		// A fetch arriving while the request is already in flight joins it too.
8656		let third = consumer.fetch_group(5, None);
8657
8658		// One accept resolves all of them.
8659		let mut group = req.accept(None).unwrap();
8660		group
8661			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"hi"))
8662			.unwrap();
8663		group.finish().unwrap();
8664
8665		assert_eq!(first.await.unwrap().sequence, 5);
8666		assert_eq!(second.await.unwrap().sequence, 5);
8667		assert_eq!(third.await.unwrap().sequence, 5);
8668	}
8669
8670	#[tokio::test]
8671	async fn fetch_coalesced_reject_fails_all() {
8672		let producer = track_producer("test", None);
8673		let dynamic = producer.dynamic();
8674		let consumer = producer.consume();
8675
8676		let first = consumer.fetch_group(5, None);
8677		let second = consumer.fetch_group(5, None);
8678		let req = dynamic
8679			.requested_group()
8680			.now_or_never()
8681			.expect("should not block")
8682			.unwrap();
8683		req.reject(Error::Cancel);
8684
8685		assert!(matches!(first.await, Err(Error::Cancel)));
8686		assert!(matches!(second.await, Err(Error::Cancel)));
8687
8688		// The rejected attempt is gone: a retry starts a fresh one.
8689		let retry = consumer.fetch_group(5, None);
8690		assert!(kio::Pollable::poll(&*retry, &kio::Waiter::noop()).is_pending());
8691		let req = dynamic
8692			.requested_group()
8693			.now_or_never()
8694			.expect("should not block")
8695			.unwrap();
8696		assert_eq!(req.sequence(), 5);
8697	}
8698
8699	#[tokio::test]
8700	async fn fetch_queued_fails_when_handlers_leave() {
8701		let producer = track_producer("test", None);
8702		let dynamic = producer.dynamic();
8703		let consumer = producer.consume();
8704
8705		// Queued but never popped: the last handler leaving fails it fast.
8706		let pending = consumer.fetch_group(5, None);
8707		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
8708		drop(dynamic);
8709		assert!(matches!(pending.await, Err(Error::NotFound)));
8710
8711		// And the attempt didn't leak.
8712		let fetch = producer.state.read().fetch.clone();
8713		assert!(fetch.read().is_empty());
8714	}
8715
8716	#[tokio::test]
8717	async fn fetch_miss_no_dynamic_not_found() {
8718		// A track with no `Dynamic` can't serve old content, so a cache miss
8719		// resolves to NotFound instead of blocking forever.
8720		let producer = track_producer("test", None);
8721		producer.append_group().unwrap(); // seq 0, but we miss on seq 5
8722		let consumer = producer.consume();
8723		assert!(matches!(consumer.fetch_group(5, None).await, Err(Error::NotFound)));
8724	}
8725
8726	#[tokio::test]
8727	async fn fetch_past_final_not_found() {
8728		let producer = track_producer("test", None);
8729		producer.append_group().unwrap(); // seq 0
8730		producer.finish().unwrap(); // final_sequence = 1
8731
8732		// A group at or past the final sequence can never exist, even with a handler,
8733		// so it resolves to NotFound.
8734		let dynamic = producer.dynamic();
8735		let consumer = producer.consume();
8736		assert!(matches!(consumer.fetch_group(5, None).await, Err(Error::NotFound)));
8737
8738		// And it doesn't signal the dynamic handler.
8739		assert!(dynamic.poll_requested_group(&kio::Waiter::noop()).is_pending());
8740	}
8741
8742	/// Mint a track whose groups charge into a bounded [`cache::Pool`].
8743	fn pooled_producer(capacity: u64) -> (Producer, cache::Pool) {
8744		let config = cache::Config::default()
8745			.with_capacity(capacity)
8746			.with_expiry(cache::DEFAULT_EXPIRY);
8747		let pool = cache::Pool::new(config);
8748		let broadcast = broadcast::Info {
8749			pool: pool.clone(),
8750			..Default::default()
8751		};
8752		let producer = Producer::new(Arc::new(broadcast), "test", None);
8753		(producer, pool)
8754	}
8755
8756	fn finished_group(producer: &mut Producer, size: usize) -> u64 {
8757		let mut group = producer.append_group().unwrap();
8758		group
8759			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; size]))
8760			.unwrap();
8761		group.finish().unwrap();
8762		group.sequence
8763	}
8764
8765	/// While the pool is over capacity, every append accrues debt and pays it by
8766	/// evicting this track's own oldest groups, so the newest content survives.
8767	#[tokio::test]
8768	async fn debt_evicts_oldest_group() {
8769		// Fits one 10k group; each additional group pushes the pool over budget.
8770		let (mut producer, pool) = pooled_producer(10_000);
8771
8772		finished_group(&mut producer, 10_000); // seq 0
8773		finished_group(&mut producer, 10_000); // seq 1: over budget, debt starts accruing
8774		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
8775
8776		let consumer = producer.consume();
8777		assert!(consumer.peek_group(0).is_none(), "oldest group is evicted");
8778		assert!(consumer.peek_group(2).is_some(), "latest group survives");
8779		// Steady state carries the protected live edge plus the just-demoted group
8780		// (debt is charged before the demotion, so eviction lags one append).
8781		assert!(
8782			pool.used() <= 2 * (10_000 + cache::ENTRY_OVERHEAD),
8783			"usage hovers near capacity: {}",
8784			pool.used()
8785		);
8786
8787		// A fresh subscriber skips the evicted groups entirely.
8788		let mut subscriber = producer.subscribe(replay());
8789		assert!(subscriber.assert_group().sequence > 0, "evicted group is not delivered");
8790	}
8791
8792	/// The latest group is never in the eviction order, so it survives any budget.
8793	#[tokio::test]
8794	async fn latest_group_never_evicted() {
8795		// Far too small for even one group: the latest survives anyway.
8796		let (mut producer, pool) = pooled_producer(100);
8797		finished_group(&mut producer, 1000); // seq 0
8798		assert!(pool.used() > 100, "the latest may exceed the budget");
8799
8800		// Later writes evict the demoted seq 0; each new latest is untouchable in turn.
8801		finished_group(&mut producer, 1000); // seq 1: demotes seq 0
8802		finished_group(&mut producer, 1000); // seq 2: pays by evicting seq 0
8803
8804		let consumer = producer.consume();
8805		assert!(consumer.peek_group(0).is_none());
8806		let mut group = consumer.peek_group(2).expect("latest survives");
8807		assert_eq!(group.read_frame().await.unwrap().unwrap().payload.len(), 1000);
8808	}
8809
8810	/// A FETCH cache hit refreshes the group's access time: anything accessed more
8811	/// recently than the pool-wide average is protected, so the eviction walk skips
8812	/// it and evicts a never-read group instead, even one that arrived later.
8813	#[tokio::test]
8814	async fn fetch_refresh_survives_eviction() {
8815		let (mut producer, _pool) = pooled_producer(10_000);
8816		let consumer = producer.consume();
8817
8818		finished_group(&mut producer, 3_000); // seq 0
8819		crate::model::clock::advance(Duration::from_secs(1));
8820		finished_group(&mut producer, 3_000); // seq 1
8821		crate::model::clock::advance(Duration::from_secs(1));
8822		finished_group(&mut producer, 3_000); // seq 2
8823		crate::model::clock::advance(Duration::from_millis(500));
8824
8825		// FETCH seq 0: the cache hit lifts its access time above the average.
8826		let mut fetched = consumer.fetch_group(0, None).await.unwrap();
8827		assert_eq!(fetched.read_frame().await.unwrap().unwrap().payload.len(), 3_000);
8828		crate::model::clock::advance(Duration::from_millis(500));
8829
8830		// Pressure: seq 0 is first in eviction order but freshly accessed, so it
8831		// rotates to the back and the never-read seq 1 dies instead.
8832		finished_group(&mut producer, 3_000); // seq 3
8833		crate::model::clock::advance(Duration::from_secs(1));
8834		finished_group(&mut producer, 3_000); // seq 4
8835
8836		assert!(consumer.peek_group(0).is_some(), "refreshed group survives");
8837		assert!(consumer.peek_group(1).is_none(), "unread group is evicted instead");
8838	}
8839
8840	/// A consumer holding an evicted group surfaces the eviction, not a hang or a
8841	/// truncated clean end.
8842	#[tokio::test]
8843	async fn eviction_aborts_readers() {
8844		let (mut producer, _pool) = pooled_producer(10_000);
8845		let mut subscriber = producer.subscribe(None);
8846
8847		finished_group(&mut producer, 10_000); // seq 0
8848		let mut group0 = subscriber.assert_group();
8849
8850		finished_group(&mut producer, 10_000); // seq 1: demotes seq 0
8851		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
8852
8853		let read = group0.read_frame().await;
8854		assert!(matches!(read, Err(Error::Evicted)), "expected Evicted, got {read:?}");
8855	}
8856
8857	/// A write smaller than the next victim carries debt instead of evicting: a
8858	/// large group dies only once enough debt accumulates, never to pay off a
8859	/// far smaller write.
8860	#[tokio::test]
8861	async fn small_writes_carry_debt() {
8862		// Payloads dwarf the fixed per-group charge, so the budget arithmetic below is
8863		// about bytes written rather than bookkeeping.
8864		let unit = 100 * cache::ENTRY_OVERHEAD;
8865		let (mut producer, pool) = pooled_producer(22 * unit);
8866		let consumer = producer.consume();
8867
8868		finished_group(&mut producer, 20 * unit as usize); // seq 0, the large victim-to-be
8869
8870		// The first few small writes owe far less than seq 0's size: the debt
8871		// carries over instead of evicting it.
8872		for _ in 0..3 {
8873			finished_group(&mut producer, unit as usize);
8874		}
8875		assert!(consumer.peek_group(0).is_some(), "debt smaller than the victim carries");
8876
8877		// Enough small writes accumulate the debt to finally evict it.
8878		for _ in 0..20 {
8879			finished_group(&mut producer, unit as usize);
8880		}
8881		assert!(
8882			consumer.peek_group(0).is_none(),
8883			"accumulated debt evicts the large group"
8884		);
8885		// Steady state hovers within about one group of capacity: a victim smaller
8886		// than the outstanding debt is never evicted, so the excess stays bounded.
8887		assert!(pool.used() <= 24 * unit, "usage hovers near capacity: {}", pool.used());
8888	}
8889
8890	/// One write pays at most twice what it produced, so a capacity shrink (or one
8891	/// track's burst) drains gradually instead of one writer dumping its whole
8892	/// backlog in a single call.
8893	#[tokio::test]
8894	async fn payment_capped_per_write() {
8895		let (mut producer, pool) = pooled_producer(1 << 40);
8896		for _ in 0..10 {
8897			finished_group(&mut producer, 1_000);
8898		}
8899
8900		// The governor slashes the target; nothing is reclaimed synchronously.
8901		pool.resize(100);
8902		let before = pool.used();
8903
8904		// One 1k write may evict at most ~2k of backlog, not all ten groups.
8905		finished_group(&mut producer, 1_000);
8906
8907		let consumer = producer.consume();
8908		assert!(consumer.peek_group(0).is_none(), "the oldest groups are evicted");
8909		assert!(consumer.peek_group(1).is_none());
8910		assert!(consumer.peek_group(2).is_some(), "the backlog drains gradually");
8911		assert!(pool.used() > before - 4_000, "one write must not dump the backlog");
8912	}
8913
8914	/// Accepting a track after pre-accept backfill must keep the same write
8915	/// counter: the counter is owned by the track state, so replacing the info
8916	/// can't strand the bytes already-created groups keep charging.
8917	#[tokio::test]
8918	async fn accept_preserves_write_accounting() {
8919		let config = cache::Config::default()
8920			.with_capacity(12_000)
8921			.with_expiry(cache::DEFAULT_EXPIRY);
8922		let pool = cache::Pool::new(config);
8923		let broadcast = broadcast::Info {
8924			pool: pool.clone(),
8925			..Default::default()
8926		};
8927		let request = Request::new(Arc::new(broadcast), "test");
8928		let dynamic = request.dynamic();
8929		let consumer = request.consume();
8930
8931		// Serve a backfill before the track is accepted, then grow it.
8932		let pending = consumer.fetch_group(0, None);
8933		let req = dynamic
8934			.requested_group()
8935			.now_or_never()
8936			.expect("should not block")
8937			.unwrap();
8938		let mut backfill = req.accept(None).unwrap();
8939		pending.await.unwrap();
8940		backfill
8941			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 30_000]))
8942			.unwrap();
8943
8944		// Accept with a fresh Info: the pre-accept group's writes must still be
8945		// drained by this track's future charges.
8946		let producer = request.accept(None);
8947		producer.append_group().unwrap().finish().unwrap();
8948		producer.append_group().unwrap().finish().unwrap();
8949
8950		assert!(
8951			producer.consume().peek_group(0).is_none(),
8952			"pre-accept backfill growth is reclaimed after accept"
8953		);
8954		assert!(pool.used() <= 13_000, "usage converges: {}", pool.used());
8955	}
8956
8957	/// A cache only a fetch handler fills has no live feed, so its newest fetched object
8958	/// is not the track's largest. Accepting the request feeds it live until the producer goes.
8959	#[tokio::test]
8960	async fn a_fetch_only_cache_is_not_live() {
8961		let request = Request::new(Arc::new(broadcast::Info::default()), "test");
8962		let dynamic = request.dynamic();
8963		let consumer = request.consume();
8964
8965		let pending = consumer.fetch_group(2, None);
8966		let req = dynamic
8967			.requested_group()
8968			.now_or_never()
8969			.expect("should not block")
8970			.unwrap();
8971		let mut fetched = req.accept(None).unwrap();
8972		fetched
8973			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"2-0"))
8974			.unwrap();
8975		fetched.finish().unwrap();
8976		pending.await.unwrap();
8977		assert!(!consumer.is_live(), "a fetch-only cache is not live");
8978
8979		let producer = request.accept(None);
8980		assert!(consumer.is_live(), "an accepted track is live");
8981
8982		// The fetch handler keeps the track alive like a producer clone, so both go.
8983		drop(dynamic);
8984		drop(producer);
8985		assert!(!consumer.is_live(), "a track whose producer went is not live");
8986	}
8987
8988	/// Re-serving a sequence many times must not accumulate eviction hints: stale
8989	/// hints die on stamp mismatch and compaction reclaims them.
8990	#[tokio::test]
8991	async fn recreated_sequence_bounds_eviction_hints() {
8992		let (producer, _pool) = pooled_producer(1 << 40);
8993		producer.create_group(5u64.into()).unwrap().finish().unwrap();
8994
8995		for _ in 0..200 {
8996			let group = producer.create_group(1u64.into()).unwrap();
8997			group.abort(Error::Cancel).unwrap();
8998		}
8999
9000		let state = producer.state.read();
9001		assert!(
9002			state.evict.len() <= 2 * state.lookup.len() + EVICT_SLACK,
9003			"stale hints are compacted: {} entries for {} slots",
9004			state.evict.len(),
9005			state.lookup.len()
9006		);
9007	}
9008
9009	/// A frame write within the same coarse tick still outranks merely-inserted
9010	/// content, so the freshly-written group survives and the empty one pays.
9011	#[tokio::test]
9012	async fn same_tick_write_outranks_inserted() {
9013		// Payloads dwarf the fixed per-group charge, so the budget arithmetic below is
9014		// about bytes written rather than bookkeeping.
9015		let unit = 100 * cache::ENTRY_OVERHEAD;
9016		// No time advances: every stamp lands in the same tick.
9017		let (mut producer, _pool) = pooled_producer(10 * unit);
9018
9019		producer.append_group().unwrap().finish().unwrap(); // seq 0: empty
9020		finished_group(&mut producer, 3 * unit as usize); // seq 1: written
9021		finished_group(&mut producer, 3 * unit as usize); // seq 2
9022		finished_group(&mut producer, 3 * unit as usize); // seq 3
9023		finished_group(&mut producer, 3 * unit as usize); // seq 4: over budget, pays
9024
9025		let consumer = producer.consume();
9026		assert!(consumer.peek_group(0).is_none(), "insert-only content pays first");
9027		assert!(consumer.peek_group(1).is_some(), "same-tick written content survives");
9028	}
9029
9030	/// A track that only appends frames to an open group, never inserting another
9031	/// group, still settles its eviction debt once enough bytes accumulate.
9032	#[tokio::test]
9033	async fn frame_only_writer_pays() {
9034		let (producer, pool) = pooled_producer(2_000);
9035		let mut demoted = producer.append_group().unwrap(); // seq 0
9036		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
9037
9038		// One large frame crosses the charge threshold: the write itself pays,
9039		// with no further group insert on this track.
9040		demoted
9041			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 300_000]))
9042			.unwrap();
9043
9044		assert!(
9045			pool.used() <= 5_000,
9046			"the frame write settled the debt: {}",
9047			pool.used()
9048		);
9049		assert!(matches!(demoted.finish(), Err(Error::Evicted)));
9050	}
9051
9052	/// One `Info` describing several tracks must not join their eviction accounting:
9053	/// each track opens its own account against the pool.
9054	#[tokio::test]
9055	async fn each_track_owns_its_account() {
9056		let broadcast = Arc::new(broadcast::Info::default());
9057		let info = Info::default();
9058		let a = Producer::new(broadcast.clone(), "a", info.clone());
9059		let b = Producer::new(broadcast, "b", info);
9060
9061		let a = a.state.read().cache.clone();
9062		let b = b.state.read().cache.clone();
9063		assert!(!Arc::ptr_eq(&a, &b), "each track owns its account");
9064	}
9065
9066	/// A `Dynamic` still serving fetches keeps the track alive, so the publisher
9067	/// letting go isn't an abrupt teardown: the handler can still serve the cache.
9068	#[tokio::test]
9069	async fn a_dynamic_defers_teardown() {
9070		let (mut producer, pool) = pooled_producer(1 << 40);
9071		let dynamic = producer.dynamic();
9072		finished_group(&mut producer, 100);
9073
9074		drop(producer);
9075		assert!(pool.used() > 0, "the handler still serves the cache");
9076
9077		drop(dynamic);
9078		assert_eq!(pool.used(), 0, "the last handle tears it down");
9079	}
9080
9081	/// A finished track releases everything once every handle is gone.
9082	///
9083	/// Its groups hold the cache account, and the account links back here, so that link
9084	/// has to be weak: anything stronger makes the state (and every cached frame in it)
9085	/// immortal, even with no producer or consumer left.
9086	#[tokio::test]
9087	async fn finished_track_frees_its_cache() {
9088		let (mut producer, pool) = pooled_producer(1 << 40);
9089		finished_group(&mut producer, 100);
9090		producer.finish().unwrap();
9091
9092		let state = producer.state.downgrade();
9093		drop(producer);
9094
9095		assert!(state.upgrade().is_none(), "the track state is freed");
9096		assert_eq!(pool.used(), 0, "so are its cached bytes");
9097	}
9098
9099	/// A group settling its eviction debt upgrades the account's weak handle, which
9100	/// counts as a producer on the track state. Teardown must not mistake that for a
9101	/// surviving publisher, or an abrupt drop silently behaves like a clean finish.
9102	#[tokio::test]
9103	async fn teardown_ignores_a_settling_group() {
9104		let (producer, pool) = pooled_producer(1 << 40);
9105		// Open, so only the abrupt teardown releases it.
9106		let mut group = producer.append_group().unwrap();
9107		group
9108			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
9109			.unwrap();
9110		drop(group);
9111
9112		// Stand in for a concurrent `cache::Track::settle`, mid-upgrade.
9113		let settling = producer.state.downgrade().upgrade().expect("open");
9114		drop(producer);
9115
9116		assert_eq!(pool.used(), 0, "the abrupt teardown still released the cache");
9117		drop(settling);
9118	}
9119
9120	/// A subscriber holding one cached group must not pin the whole track: a group
9121	/// carries the track's properties by value, not a handle back to its state.
9122	#[tokio::test]
9123	async fn cached_group_outlives_its_track() {
9124		let (mut producer, pool) = pooled_producer(1 << 40);
9125		let sequence = finished_group(&mut producer, 100);
9126		let group = producer.consume().peek_group(sequence).expect("cached");
9127		producer.finish().unwrap();
9128
9129		let state = producer.state.downgrade();
9130		drop(producer);
9131		assert!(state.upgrade().is_none(), "the track state is freed");
9132		assert!(pool.used() > 0, "the retained group keeps its own bytes");
9133
9134		drop(group);
9135		assert_eq!(pool.used(), 0, "which it releases when dropped");
9136	}
9137
9138	/// A backfill served before the track was accepted settles its own debt: the
9139	/// account exists from the moment the state does, so acceptance replacing the
9140	/// `Info` can't leave already-created groups writing for free.
9141	#[tokio::test]
9142	async fn pre_accept_backfill_settles_late_writes() {
9143		let config = cache::Config::default()
9144			.with_capacity(2_000)
9145			.with_expiry(cache::DEFAULT_EXPIRY);
9146		let pool = cache::Pool::new(config);
9147		let broadcast = broadcast::Info {
9148			pool: pool.clone(),
9149			..Default::default()
9150		};
9151		let request = Request::new(Arc::new(broadcast), "test");
9152		let dynamic = request.dynamic();
9153		let consumer = request.consume();
9154
9155		// Serve backfill seq 0 before the track is accepted.
9156		let pending = consumer.fetch_group(0, None);
9157		let req = dynamic
9158			.requested_group()
9159			.now_or_never()
9160			.expect("should not block")
9161			.unwrap();
9162		let mut backfill = req.accept(None).unwrap();
9163		pending.await.unwrap();
9164
9165		// Accept, then demote the backfill with a live group.
9166		let producer = request.accept(None);
9167		producer.append_group().unwrap().finish().unwrap();
9168
9169		// No further insert: the late write into the demoted backfill is the only
9170		// thing that can pay the debt it just took on.
9171		backfill
9172			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 300_000]))
9173			.unwrap();
9174
9175		assert!(
9176			pool.used() <= 5_000,
9177			"the frame write settled the debt: {}",
9178			pool.used()
9179		);
9180	}
9181
9182	/// A late frame write restarts the LRU clock (the window measures time since
9183	/// last written or fetched), so an actively-growing group is not expired as
9184	/// idle mid-write.
9185	#[tokio::test]
9186	async fn write_restarts_retention_clock() {
9187		let (producer, _pool) = pooled_producer(1 << 40);
9188		let mut straggler = producer.append_group().unwrap(); // seq 0
9189		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
9190
9191		// Idle past the window, then the straggler receives a late frame.
9192		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
9193		straggler
9194			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
9195			.unwrap();
9196		producer.append_group().unwrap().finish().unwrap(); // seq 2 runs expiry
9197
9198		let consumer = producer.consume();
9199		assert!(consumer.peek_group(0).is_some(), "the write restarted the clock");
9200
9201		// Once the writes stop, the group ages out normally.
9202		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
9203		producer.append_group().unwrap().finish().unwrap(); // seq 3 runs expiry
9204		assert!(consumer.peek_group(0).is_none(), "idle content still expires");
9205	}
9206
9207	/// Continuously refreshed entries at the front of the eviction order must not
9208	/// starve expiry of entries behind them: the scan cursor rotates.
9209	#[tokio::test]
9210	async fn refreshed_front_does_not_starve_expiry() {
9211		let (producer, _pool) = pooled_producer(1 << 40);
9212		let dynamic = producer.dynamic();
9213		let consumer = producer.consume();
9214
9215		producer.create_group(10u64.into()).unwrap().finish().unwrap();
9216		for sequence in 1..=5u64 {
9217			let pending = consumer.fetch_group(sequence, None);
9218			let req = dynamic
9219				.requested_group()
9220				.now_or_never()
9221				.expect("should not block")
9222				.unwrap();
9223			let mut group = req.accept(None).unwrap();
9224			group
9225				.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
9226				.unwrap();
9227			group.finish().unwrap();
9228			pending.await.unwrap();
9229		}
9230
9231		// Age everything out, then refresh the first four backfills so they sit
9232		// fresh at the front of the eviction order, hiding the expired fifth.
9233		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
9234		for sequence in 1..=4u64 {
9235			consumer.fetch_group(sequence, None).await.unwrap();
9236		}
9237
9238		// The rotating cursor reaches the fifth entry within a few writes.
9239		for _ in 0..3 {
9240			producer.append_group().unwrap().finish().unwrap();
9241		}
9242		assert!(consumer.peek_group(5).is_none(), "expired backfill is reclaimed");
9243		assert!(consumer.peek_group(1).is_some(), "refreshed backfill survives");
9244	}
9245
9246	/// A publisher re-creating an aborted sequence is delivered exactly once, at
9247	/// its actual arrival position: the historical arrival entry is dead.
9248	#[tokio::test]
9249	async fn recreated_sequence_delivered_once() {
9250		let (producer, _pool) = pooled_producer(1 << 40);
9251
9252		producer.create_group(0u64.into()).unwrap().finish().unwrap();
9253		let aborted = producer.create_group(1u64.into()).unwrap();
9254		aborted.abort(Error::Cancel).unwrap();
9255		producer.create_group(2u64.into()).unwrap().finish().unwrap();
9256		producer.create_group(1u64.into()).unwrap().finish().unwrap();
9257
9258		let mut subscriber = producer.subscribe(replay());
9259		assert_eq!(subscriber.assert_group().sequence, 0);
9260		assert_eq!(subscriber.assert_group().sequence, 2);
9261		assert_eq!(
9262			subscriber.assert_group().sequence,
9263			1,
9264			"replacement arrives at its own position"
9265		);
9266		subscriber.assert_no_group();
9267	}
9268
9269	/// Datagrams share `max_sequence` but must not break group demotion: the live
9270	/// edge is tracked per group, so interleaving datagrams can't strand groups
9271	/// outside the eviction order and bypass the budget.
9272	#[tokio::test]
9273	async fn datagrams_do_not_block_eviction() {
9274		let (mut producer, pool) = pooled_producer(1_000);
9275		for _ in 0..10 {
9276			finished_group(&mut producer, 1_000);
9277			producer.append_datagram(Timestamp::ZERO, &b"beat"[..]).unwrap();
9278		}
9279
9280		let consumer = producer.consume();
9281		assert!(consumer.peek_group(0).is_none(), "old groups still evict");
9282		assert!(
9283			pool.used() < 4 * 1_256,
9284			"interleaved datagrams must not bypass the budget: {}",
9285			pool.used()
9286		);
9287	}
9288
9289	/// An aborted group releases its access sample along with its bytes, from any
9290	/// handle: ghost samples must not linger in the pool mean where they'd hold it
9291	/// in the past and over-protect every live group.
9292	#[tokio::test]
9293	async fn aborted_group_leaves_no_ghost_sample() {
9294		let (producer, pool) = pooled_producer(1 << 40);
9295		let group0 = producer.append_group().unwrap();
9296		producer.append_group().unwrap(); // demotes seq 0 into the mean
9297
9298		assert!(pool.average().is_some(), "demoted group is sampled");
9299		group0.abort(Error::Cancel).unwrap();
9300		assert_eq!(pool.average(), None, "the abort must remove the sample");
9301	}
9302
9303	/// Empty groups still carry fixed overhead; they must repay the budget when
9304	/// evicted rather than being unevictable freeloaders.
9305	#[tokio::test]
9306	async fn empty_groups_repay_overhead() {
9307		let (producer, pool) = pooled_producer(1_000);
9308		for _ in 0..100 {
9309			let group = producer.append_group().unwrap();
9310			group.finish().unwrap();
9311		}
9312
9313		assert!(
9314			pool.used() <= 3_000,
9315			"empty-group overhead must stay near the budget: {}",
9316			pool.used()
9317		);
9318	}
9319
9320	/// Late growth on an already-demoted group is billed: the gross-write counter
9321	/// feeds debt on the next append, so a straggler can't grow unbounded.
9322	#[tokio::test]
9323	async fn growth_on_demoted_group_is_billed() {
9324		let (producer, pool) = pooled_producer(2_000);
9325		let mut straggler = producer.append_group().unwrap(); // seq 0
9326		producer.append_group().unwrap().finish().unwrap(); // seq 1 demotes seq 0
9327
9328		// The demoted group balloons: no eviction yet (nothing ran), but billed.
9329		straggler
9330			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 10_000]))
9331			.unwrap();
9332
9333		// The next append observes the growth and evicts the straggler.
9334		producer.append_group().unwrap().finish().unwrap(); // seq 2
9335
9336		let consumer = producer.consume();
9337		assert!(consumer.peek_group(0).is_none(), "the ballooned group is evicted");
9338		assert!(pool.used() <= 3_000, "growth is reclaimed: {}", pool.used());
9339	}
9340
9341	/// A stale arrival entry whose sequence was later re-served by fetched backfill
9342	/// must not leak the replacement into arrival-order subscriptions.
9343	#[tokio::test]
9344	async fn refilled_sequence_stays_out_of_subscriptions() {
9345		let (producer, _pool) = pooled_producer(1 << 40);
9346		let dynamic = producer.dynamic();
9347		let consumer = producer.consume();
9348
9349		producer.create_group(0u64.into()).unwrap().finish().unwrap();
9350		let aborted = producer.create_group(1u64.into()).unwrap();
9351		aborted.abort(Error::Cancel).unwrap();
9352		producer.create_group(2u64.into()).unwrap().finish().unwrap();
9353
9354		// Re-serve seq 1 as backfill; its slot replaces the aborted one, and the
9355		// old arrival entry for seq 1 now resolves to it.
9356		let pending = consumer.fetch_group(1, None);
9357		let req = dynamic
9358			.requested_group()
9359			.now_or_never()
9360			.expect("should not block")
9361			.unwrap();
9362		let mut group = req.accept(None).unwrap();
9363		group
9364			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill"))
9365			.unwrap();
9366		group.finish().unwrap();
9367		pending.await.unwrap();
9368
9369		// The backfill serves by sequence, but never in arrival order.
9370		assert!(consumer.peek_group(1).is_some());
9371		let mut subscriber = producer.subscribe(replay());
9372		assert_eq!(subscriber.assert_group().sequence, 0);
9373		assert_eq!(subscriber.assert_group().sequence, 2);
9374		subscriber.assert_no_group();
9375	}
9376
9377	/// An expired backfill can't hide behind a refreshed one: the eviction-order
9378	/// expiry scans a bounded prefix instead of stopping at the first fresh entry.
9379	#[tokio::test]
9380	async fn expired_backfill_behind_refreshed_reclaimed() {
9381		let (producer, _pool) = pooled_producer(1 << 40);
9382		let dynamic = producer.dynamic();
9383		let consumer = producer.consume();
9384
9385		producer.create_group(5u64.into()).unwrap().finish().unwrap();
9386		for sequence in [2u64, 3u64] {
9387			let pending = consumer.fetch_group(sequence, None);
9388			let req = dynamic
9389				.requested_group()
9390				.now_or_never()
9391				.expect("should not block")
9392				.unwrap();
9393			let mut group = req.accept(None).unwrap();
9394			group
9395				.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 100]))
9396				.unwrap();
9397			group.finish().unwrap();
9398			pending.await.unwrap();
9399		}
9400
9401		// Keep seq 2 fresh while seq 3 (behind it in eviction order) expires.
9402		crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2 + Duration::from_secs(1));
9403		consumer.fetch_group(2, None).await.unwrap();
9404		crate::model::clock::advance(cache::DEFAULT_EXPIRY / 2 + Duration::from_secs(1));
9405		producer.create_group(6u64.into()).unwrap().finish().unwrap();
9406
9407		let consumer = producer.consume();
9408		assert!(consumer.peek_group(2).is_some(), "refreshed backfill survives");
9409		assert!(consumer.peek_group(3).is_none(), "expired backfill is reclaimed");
9410	}
9411
9412	/// A FETCH hit within the same coarse clock tick still protects the group: the
9413	/// refresh stamps one tick ahead, so it reads strictly newer than the mean.
9414	#[tokio::test]
9415	async fn same_tick_fetch_protects() {
9416		// No time advances at all: every timestamp lands in the same tick.
9417		let (mut producer, _pool) = pooled_producer(10_000);
9418		let consumer = producer.consume();
9419
9420		finished_group(&mut producer, 3_000); // seq 0
9421		finished_group(&mut producer, 3_000); // seq 1
9422		finished_group(&mut producer, 3_000); // seq 2
9423
9424		consumer.fetch_group(0, None).await.unwrap();
9425
9426		finished_group(&mut producer, 3_000); // seq 3
9427		finished_group(&mut producer, 3_000); // seq 4
9428
9429		assert!(consumer.peek_group(0).is_some(), "same-tick refresh protects");
9430		assert!(consumer.peek_group(1).is_none(), "the unread group dies instead");
9431	}
9432
9433	/// A refetched group that reclaims max_sequence is the live edge again: it must
9434	/// not re-enter the eviction order, or memory pressure could evict the newest
9435	/// content.
9436	#[tokio::test]
9437	async fn refetched_latest_stays_protected() {
9438		let (producer, _pool) = pooled_producer(10_000);
9439		let dynamic = producer.dynamic();
9440		let consumer = producer.consume();
9441
9442		let straggler = producer.append_group().unwrap(); // seq 0
9443
9444		// The publisher aborts its own latest group; the sequence stays at the live edge.
9445		let latest = producer.append_group().unwrap(); // seq 1
9446		latest.abort(Error::Cancel).unwrap();
9447
9448		// Re-fetch it: the replacement takes over max_sequence.
9449		let pending = consumer.fetch_group(1, None);
9450		let req = dynamic
9451			.requested_group()
9452			.now_or_never()
9453			.expect("should not block")
9454			.unwrap();
9455		let mut group = req.accept(None).unwrap();
9456		group
9457			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000]))
9458			.unwrap();
9459		group.finish().unwrap();
9460		pending.await.unwrap();
9461
9462		// The refetched latest is protected by omission: it has no entry in the
9463		// eviction order, so no amount of debt can select it.
9464		{
9465			let state = producer.state.read();
9466			assert!(state.lookup.contains_key(&1), "refetched group is cached");
9467			assert!(
9468				state.evict.iter().all(|(sequence, _)| *sequence != 1),
9469				"the live edge must not be an eviction candidate"
9470			);
9471		}
9472		drop(straggler);
9473	}
9474
9475	/// An evicted group is a cache miss, so a fetch re-fetches it and the accepted
9476	/// replacement serves the sequence again (not `Error::Duplicate`).
9477	#[tokio::test]
9478	async fn eviction_allows_refetch() {
9479		let (mut producer, _pool) = pooled_producer(10_000);
9480		let dynamic = producer.dynamic();
9481
9482		finished_group(&mut producer, 10_000); // seq 0
9483		finished_group(&mut producer, 10_000); // seq 1: demotes seq 0
9484		finished_group(&mut producer, 10_000); // seq 2: pays by evicting seq 0
9485
9486		let consumer = producer.consume();
9487		assert!(consumer.peek_group(0).is_none());
9488		let pending = consumer.fetch_group(0, None);
9489
9490		let req = dynamic
9491			.requested_group()
9492			.now_or_never()
9493			.expect("should not block")
9494			.unwrap();
9495		assert_eq!(req.sequence(), 0);
9496
9497		let mut group = req.accept(None).unwrap();
9498		group
9499			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"refetched"))
9500			.unwrap();
9501		group.finish().unwrap();
9502
9503		let mut group = pending.await.unwrap();
9504		assert_eq!(&group.read_frame().await.unwrap().unwrap().payload[..], b"refetched");
9505	}
9506
9507	/// An aborted group is dead whatever frame it starts at: the sequence is claimable
9508	/// again, and a cache lookup misses rather than handing back a slot that can no
9509	/// longer serve it.
9510	///
9511	/// The abort and the group's first frame decide this together, so they are read
9512	/// under one guard. Read separately, the abort can land between them and the slot
9513	/// answers as a live duplicate on the strength of an offset it only still has
9514	/// because it died. That interleaving is what the single guard rules out; this
9515	/// pins the committed semantics it has to preserve.
9516	#[test]
9517	fn an_aborted_group_releases_its_sequence() {
9518		let producer = track_producer("test", None);
9519		let consumer = producer.consume();
9520
9521		let mut group = producer.create_group(group::Info { sequence: 3 }).unwrap();
9522		group
9523			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"head"))
9524			.unwrap();
9525
9526		// While it lives the slot answers, so the sequence is taken.
9527		assert!(matches!(
9528			producer.create_group(group::Info { sequence: 3 }),
9529			Err(Error::Duplicate)
9530		));
9531		assert!(consumer.peek_group(3).is_some());
9532
9533		group.abort(Error::Cancel).unwrap();
9534
9535		assert!(consumer.peek_group(3).is_none(), "an aborted slot is a cache miss");
9536		producer
9537			.create_group(group::Info { sequence: 3 })
9538			.expect("an aborted slot releases its sequence")
9539			.finish()
9540			.unwrap();
9541	}
9542
9543	/// A fetched (backfill) group is served by sequence but never replayed to
9544	/// arrival-order subscribers.
9545	#[tokio::test]
9546	async fn fetched_backfill_not_subscribed() {
9547		let (producer, _pool) = pooled_producer(1 << 40);
9548		let dynamic = producer.dynamic();
9549		let consumer = producer.consume();
9550
9551		// The publisher starts at seq 5; earlier groups exist only upstream.
9552		producer.create_group(5u64.into()).unwrap().finish().unwrap();
9553		producer.create_group(6u64.into()).unwrap().finish().unwrap();
9554
9555		// Fetch the gap: it lands in the cache and resolves the fetch...
9556		let pending = consumer.fetch_group(2, None);
9557		let req = dynamic
9558			.requested_group()
9559			.now_or_never()
9560			.expect("should not block")
9561			.unwrap();
9562		let mut group = req.accept(None).unwrap();
9563		group
9564			.write_frame(Timestamp::ZERO, bytes::Bytes::from_static(b"backfill"))
9565			.unwrap();
9566		group.finish().unwrap();
9567		let mut fetched = pending.await.unwrap();
9568		assert_eq!(&fetched.read_frame().await.unwrap().unwrap().payload[..], b"backfill");
9569		assert!(consumer.peek_group(2).is_some(), "backfill is cached for later fetches");
9570
9571		// ...but an arrival-order subscriber only sees the live groups.
9572		let mut subscriber = producer.subscribe(replay());
9573		assert_eq!(subscriber.assert_group().sequence, 5);
9574		assert_eq!(subscriber.assert_group().sequence, 6);
9575		subscriber.assert_no_group();
9576	}
9577
9578	/// Fetched backfill isn't in arrival order, so it ages out through the eviction
9579	/// order instead of lingering until the track closes.
9580	#[tokio::test]
9581	async fn expired_backfill_reclaimed() {
9582		let (producer, pool) = pooled_producer(1 << 40);
9583		let dynamic = producer.dynamic();
9584		let consumer = producer.consume();
9585
9586		producer.create_group(5u64.into()).unwrap().finish().unwrap();
9587
9588		// Serve a backfill fetch for an old sequence.
9589		let pending = consumer.fetch_group(2, None);
9590		let req = dynamic
9591			.requested_group()
9592			.now_or_never()
9593			.expect("should not block")
9594			.unwrap();
9595		let mut group = req.accept(None).unwrap();
9596		group
9597			.write_frame(Timestamp::ZERO, bytes::Bytes::from(vec![0u8; 1000]))
9598			.unwrap();
9599		group.finish().unwrap();
9600		pending.await.unwrap();
9601		let used = pool.used();
9602
9603		// Age past the pool's LRU window; the next write reclaims the backfill.
9604		crate::model::clock::advance(cache::DEFAULT_EXPIRY + Duration::from_secs(1));
9605		producer.create_group(6u64.into()).unwrap().finish().unwrap();
9606
9607		assert!(consumer.peek_group(2).is_none(), "expired backfill is reclaimed");
9608		assert!(pool.used() < used, "its bytes are released");
9609	}
9610
9611	#[tokio::test]
9612	async fn fetch_aborts_with_track() {
9613		let producer = track_producer("test", None);
9614		let dynamic = producer.dynamic();
9615		let consumer = producer.consume();
9616
9617		let pending = consumer.fetch_group(3, None);
9618		assert!(kio::Pollable::poll(&*pending, &kio::Waiter::noop()).is_pending());
9619
9620		producer.abort(Error::Cancel).unwrap();
9621		assert!(pending.await.is_err());
9622		drop(dynamic);
9623	}
9624}