Skip to main content

moq_net/model/
track.rs

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