Skip to main content

moqtap_proxy/
egress.rs

1//! The per-stream deferred-write deque — the engine behind `Delay` and
2//! `Hold`.
3//!
4//! A [`PendingQueue`] is a **local variable** in the pipe function that already
5//! owns both halves of the stream. No task, no channel, no `Semaphore`: the
6//! reader keeps `&mut send`, so a mirrored peer reset still leaves the
7//! destination *after* the bytes already written, which is what
8//! `proxy_reset.rs::upstream_reset_reaches_the_client_as_a_reset_with_the_same_code`
9//! (*data, **then** reset*) asserts. A writer-task design reverses that
10//! ordering and cannot be made to pass it.
11//!
12//! The queue is empty and non-allocating unless a timing action is used —
13//! `VecDeque::new` does not allocate — so a session that only passes,
14//! replaces or drops never touches this module's hot path and never links
15//! [`crate::release_timer`] into any executed code.
16//!
17//! # The shape the pipe loop takes
18//!
19//! ```ignore
20//! let can_read     = pending.accepts_more();   // byte + unit budget, a bool
21//! let head_release = pending.head_release();   // Option<Release>, owned clone
22//!
23//! tokio::select! {
24//!     // `can_read` chooses *inside* observe_source: true polls
25//!     // `recv.read`, false polls `recv.received_reset()`, which consumes
26//!     // no bytes. The source is never unobserved — see `observe_source`.
27//!     source = observe_source(&mut recv, &mut buf, can_read, watch) => { /* enqueue-or-write-now */ }
28//!     _ = egress::wait_release(head_release.clone(), &ctx.cancel), if head_release.is_some() => {
29//!         let now = Instant::now();
30//!         while let Some(unit) = pending.pop_next_due(now) {
31//!             pending.record_release(&unit, now);
32//!             egress::write_unit(unit, &mut send).await?;
33//!         }
34//!     }
35//!     _ = ctx.cancel.cancelled() => { /* framer.finish() flush, then drain, then return */ }
36//! }
37//! ```
38//!
39//! Both branch *expressions* are free of any borrow of `pending`: one is a
40//! `bool`, the other an owned [`Release`] (one `Arc` clone plus one token
41//! clone). That is a **rule, not a compile error** — a branch future holding
42//! `&mut pending` whose winning body then calls `pending.pop_next_due()`
43//! does in fact compile, because `select!` drops the branch-future tuple
44//! before the handler runs. Keeping the expressions borrow-free is what lets
45//! [`wait_release`] be a free function shared by the release branch and by
46//! [`drain_honouring_release_times`], and lets a later edit add a fourth
47//! branch without re-arranging the other three.
48//!
49//! **Why [`tokio_util::sync::CancellationToken`] and not a `Notify` or a
50//! waker registration**: it is level-triggered. The `select!` may create and
51//! drop a fresh `cancelled()` future on every iteration with no lost-wakeup
52//! window and no re-registration, whereas a `Notify`-based wheel would need
53//! a global-mutex round trip per read chunk to re-register.
54//! `if head_release.is_some()` matters for the same reason from the other
55//! side: tokio does not evaluate a branch's async expression when its
56//! precondition is false, so an empty queue costs one `Option::is_some()`.
57//!
58//! # Cancellation
59//!
60//! Every wait in this module is a `select!` **branch**, never an arm body:
61//! an arm body is not preemptible, so an inline sleep there starves the
62//! cancel branch and a `Hold` on a gate nobody releases would pin session
63//! teardown for up to [`EgressConfig::max_hold`] (30 s by default). That
64//! applies to the two release-honouring drains as much as to the pipe loop,
65//! which is why [`drain_honouring_release_times`] is written as a `biased`
66//! race against cancellation with
67//! [`PendingQueue::drain_ignoring_release_times`] as its fallback.
68//!
69//! # Ordering is the queue's; readiness is the unit's
70//!
71//! The two are **separate properties**, and conflating them is what the
72//! first shape of this module got wrong.
73//!
74//! * *Ordering* belongs to the deque. [`PendingQueue::pop_next_due`] only
75//!   ever looks at the **front**, so nothing can be written before
76//!   everything ahead of it has been. That invariant needs no arithmetic
77//!   at all, and it is absolute: anything else silently corrupts MoQT
78//!   stream semantics.
79//! * *Readiness* belongs to the unit. A [`Pending`] is due when
80//!   [`Pending::due_at`] has passed **or** its own `Hold` gate is open —
81//!   see [`Pending::is_due`]. Nothing that happens to a unit ahead of it
82//!   may rewrite that.
83//!
84//! So [`PendingQueue::push`] does **not** clamp `due_at` against the tail.
85//! An earlier shape did, and it turned a `Hold`'s
86//! [`EgressConfig::max_hold`] ceiling into a *deadline conferred on every
87//! successor*: releasing the gate woke only the unit carrying it, and
88//! objects queued behind — including the stream's FIN, which waits for the
89//! queue to drain — sat until `max_hold` (30 s by default). Worse, a
90//! `Delay`'s own deadline was overwritten by the ceiling of a `Hold` in
91//! front of it, so the deadline `Action::Delay` documents ("`arrived_at +
92//! by`, a deadline, not a spacing") was not the one the engine honoured.
93//!
94//! What the clamp *was* for is still wanted, and it survives as a separate
95//! field: [`Pending::expected_at`] is `max(due_at, tail.expected_at, now)`,
96//! the queue's estimate of when the unit will actually reach the wire given
97//! everything ahead of it. It is what [`Push::release_at`] reports as
98//! `Effect::Queued { release_at }` and what [`PendingQueue::record_release`]
99//! measures lateness against, so a unit queued behind a 300 ms delay is not
100//! logged as a 300 ms timer error. It never gates anything.
101//!
102//! # Bound
103//!
104//! [`PendingQueue::accepts_more`] is `false` once the queue holds
105//! [`EgressConfig::max_pending_bytes`] (1 MiB default) **or**
106//! [`MAX_PENDING_UNITS`] units. The pipe loop stops calling `recv.read` on
107//! `false`, so the queue cannot grow past that plus one in-flight read
108//! chunk. **It does not stop observing the source**: it swaps the read for
109//! `RecvStream::received_reset`, which consumes nothing, so the bound here
110//! is unchanged and a peer's `RESET_STREAM` is still seen at once. The
111//! unit cap is not redundant with the byte cap: an elided unit
112//! holds an ordering slot and carries zero bytes, so a hook that elides
113//! every object behind a never-released `Hold` would otherwise grow the
114//! deque without bound at zero bytes. Crossing either cap is reported once
115//! per stream — see [`Push::entered_backpressure`].
116
117use std::collections::VecDeque;
118use std::future::Future;
119use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
120use std::sync::{Arc, OnceLock};
121use std::time::{Duration, Instant};
122
123use bytes::Bytes;
124use tokio::sync::Notify;
125use tokio_util::sync::CancellationToken;
126
127use moqtap_client::transport::{SendStream, TransportError};
128
129use crate::action::{EgressConfig, Gate};
130use crate::error::ProxyError;
131use crate::instrument::Recorder;
132use crate::release_timer::{self, Deadline};
133use crate::shape::{Acquire, Class, Expiry, QueueDepth, Scheduler, ShapeRecorder};
134use crate::types::ProxySide;
135
136/// Hard cap on queued units, independent of the byte budget.
137///
138/// A [`Pending`] is on the order of 64 bytes, so this is roughly the same
139/// order of bookkeeping as the default 1 MiB byte budget, and it is far
140/// more objects than any realistic hold window. It exists because
141/// [`Item::Elided`] units carry zero bytes: without it, eliding every
142/// object behind a never-released `Hold` grows the deque without bound
143/// while `queued_bytes()` stays at zero.
144pub(crate) const MAX_PENDING_UNITS: usize = 8192;
145
146/// What writing to a destination can fail with.
147///
148/// Deliberately narrower than [`ProxyError`]: this module hands bytes to a
149/// transport and abandons streams, and those are the only two ways it can
150/// fail. Naming them separately is what lets a sink be implemented without
151/// naming a crate-wide error enum whose listener, TLS, certificate, qlog and
152/// draft-admission cases a write can never produce — and the recording sink
153/// below is the second implementor, so "without naming it" is not
154/// hypothetical.
155///
156/// Nothing a caller sees changes: the conversion below is applied by `?` at
157/// every boundary this module is called across, and it produces the same
158/// [`ProxyError`] the failure produced before.
159///
160/// One case, and the enum is still the right shape. Every failure reaching
161/// here today comes back from the transport, which is a fact about the two
162/// operations rather than about the one production implementor — so a second
163/// case would be a second *kind of failure*, not a second sink, and would want
164/// a name of its own. The recording sink below models a refused write as a
165/// refused write for the same reason: a test sink inventing a failure the
166/// transport cannot produce would be testing a path nothing takes.
167#[derive(Debug, thiserror::Error)]
168pub(crate) enum EgressError {
169    /// The transport refused a write or a reset.
170    #[error("transport error: {0}")]
171    Transport(#[from] TransportError),
172}
173
174impl From<EgressError> for ProxyError {
175    fn from(error: EgressError) -> Self {
176        let EgressError::Transport(source) = error;
177        ProxyError::Transport(source)
178    }
179}
180
181// ── The destination ─────────────────────────────────────────────────
182
183/// The write half this module drives.
184///
185/// A trait rather than a bare `&mut SendStream` for exactly one reason:
186/// the ordering, clamping, terminal and cancellation behaviour below is
187/// then testable in-process against a recording sink, with no QUIC
188/// handshake and no timing. The only production implementor is
189/// [`SendStream`].
190///
191/// `write_all` returns `impl Future + Send` rather than being an
192/// `async fn`: the forwarding tasks are spawned onto a multi-threaded
193/// runtime, so every future this module builds has to be `Send`, and an
194/// `async fn` in a trait carries no such bound.
195pub(crate) trait EgressSink {
196    /// Hand `buf` to the transport.
197    fn write_all(&mut self, buf: &[u8]) -> impl Future<Output = Result<(), EgressError>> + Send;
198    /// Abandon the stream with `code`, discarding anything still in the
199    /// local send buffer.
200    fn reset(&mut self, code: u64) -> Result<(), EgressError>;
201}
202
203impl EgressSink for SendStream {
204    // `async fn` here still satisfies the trait's `impl Future + Send`:
205    // the compiler checks the Send-ness of the generated future against
206    // the bound, which is the whole reason the bound is on the trait.
207    // Inherent methods win method resolution, so the call below is the
208    // transport's own `write_all`, not this trait's.
209    async fn write_all(&mut self, buf: &[u8]) -> Result<(), EgressError> {
210        SendStream::write_all(self, buf).await.map_err(EgressError::from)
211    }
212
213    fn reset(&mut self, code: u64) -> Result<(), EgressError> {
214        SendStream::reset(self, code).map_err(EgressError::from)
215    }
216}
217
218// ── Release handles ─────────────────────────────────────────────────
219
220/// What the head of a queue is waiting for. Owned, cheap to clone.
221///
222/// Cloning is one `Arc` clone plus one or two token clones. Nothing here
223/// borrows the queue, which is what keeps [`wait_release`] a free function
224/// usable from both the pipe loop's release branch and the drains.
225#[derive(Clone, Debug)]
226pub(crate) struct Release {
227    /// The wheel's handle for `release_at`. Registration happened once, in
228    /// [`PendingQueue::arm_head`]; awaiting this registers nothing.
229    deadline: Deadline,
230    /// A `Hold`'s gate, when the head carries one.
231    gate: Option<Gate>,
232}
233
234// Read only by this module's own tests today — `session.rs` awaits a
235// `Release` through `wait_release` and never inspects it, and `exec.rs`
236// never sees one. Kept because it is part of this type's deliberate read
237// surface and because the fast-path test asserts on the deadline's token
238// directly; the allow is on the one item rather than on the file, so
239// anything that becomes dead later is still a build failure.
240#[allow(dead_code)]
241impl Release {
242    /// The wheel handle, for callers that want to observe it directly.
243    pub(crate) fn deadline(&self) -> &Deadline {
244        &self.deadline
245    }
246}
247
248/// Resolve when the head unit may be written, or when the session is torn
249/// down — whichever comes first.
250///
251/// Races three level-triggered tokens: the wheel's deadline, the unit's
252/// `Hold` gate (a released gate makes a unit due early — see
253/// [`Pending::is_due`]) and session cancellation. It **registers nothing**:
254/// every one of the three is a `CancellationToken`, so creating and
255/// dropping the futures on each `select!` iteration is free of lost-wakeup
256/// races.
257///
258/// `None` parks until cancellation. Callers guard the branch with
259/// `if head_release.is_some()`, so that path is a safety net rather than a
260/// normal outcome: it must not resolve immediately, or a caller that forgot
261/// the guard would spin the loop hot on an empty queue.
262pub(crate) async fn wait_release(release: Option<Release>, cancel: &CancellationToken) {
263    let Some(release) = release else {
264        cancel.cancelled().await;
265        return;
266    };
267    match release.gate {
268        Some(gate) => {
269            tokio::select! {
270                () = release.deadline.token().cancelled() => {}
271                () = gate.wait() => {}
272                () = cancel.cancelled() => {}
273            }
274        }
275        None => {
276            tokio::select! {
277                () = release.deadline.token().cancelled() => {}
278                () = cancel.cancelled() => {}
279            }
280        }
281    }
282}
283
284// ── Queued units ────────────────────────────────────────────────────
285
286/// A positional stream ending.
287///
288/// Enqueued *behind* the current queue contents, so everything decided
289/// before it is written first. Nothing may be queued behind one.
290#[derive(Clone, Debug, PartialEq, Eq)]
291pub(crate) enum Terminal {
292    /// Write `prefix`, then `RESET_STREAM` with `code` —
293    /// [`crate::action::Action::Truncate`].
294    ///
295    /// The peer observes **at most** `prefix.len()` further bytes and may
296    /// observe none: quinn clears the receive assembler when the reset is
297    /// processed, and `reset()` discards the local send buffer too.
298    Truncate {
299        /// The prefix of the unit to write before resetting.
300        prefix: Bytes,
301        /// The application error code for the reset.
302        code: u64,
303    },
304    /// `RESET_STREAM` with `code` and nothing further —
305    /// [`crate::action::Action::ResetStream`].
306    Reset {
307        /// The application error code for the reset.
308        code: u64,
309    },
310}
311
312impl Terminal {
313    /// Bytes this terminal still owes the wire.
314    fn len(&self) -> usize {
315        match self {
316            Terminal::Truncate { prefix, .. } => prefix.len(),
317            Terminal::Reset { .. } => 0,
318        }
319    }
320}
321
322/// What a queued unit does when it is written.
323#[derive(Clone, Debug, PartialEq, Eq)]
324pub(crate) enum Item {
325    /// Write these bytes. Already whatever the action made of them —
326    /// verbatim for `Pass`, spliced for `ReplacePayload`, substituted for
327    /// `Replace`.
328    Write(Bytes),
329    /// Write nothing.
330    ///
331    /// An elided unit still takes a slot when the queue is non-empty: it
332    /// was decided at a point in the stream, and dropping it out of band
333    /// would let a later unit be written before an earlier one is.
334    Elided,
335    /// End the stream. See [`Terminal`].
336    Terminal(Terminal),
337}
338
339/// What the shaper attached to one queued unit, on a shaped stream.
340///
341/// `None` on every unit of every unshaped stream, which is what makes "a
342/// session with no `ShapeProfile` adds nothing to this path" a fact the type
343/// carries rather than a claim a reviewer checks.
344#[derive(Clone, Copy, Debug)]
345struct ShapeTag {
346    /// The row this unit's bytes are charged to, and the bucket it draws
347    /// from. Resolved per unit at admission — never per stream, because
348    /// `object_id` and `every_nth` are legal class selectors and a
349    /// stream-sticky class would evaluate them on object 0 alone.
350    class: Class,
351    /// The instant this unit goes out **anyway**: `arrived_at` plus the
352    /// profile's `max_hold`, or the engine's when the profile inherits it.
353    ///
354    /// Under the default `Expiry::Deliver` that is a clamp, not a drop — so
355    /// a starved class is late, never silently lossy, and "delivers zero
356    /// bytes" is always a statement about a sampling window.
357    expires_at: Instant,
358    /// Whether this unit has already been counted against
359    /// `starved_behind_other_class`. Once per unit, not once per wake.
360    starved_noted: bool,
361}
362
363/// One unit of traffic waiting for its release time.
364#[derive(Clone, Debug)]
365pub(crate) struct Pending {
366    /// The earliest instant **this** unit may be written, and the only instant
367    /// that gates it. Set once, by whoever built the unit, and never rewritten
368    /// by the queue — see the module doc's *Ordering is the queue's; readiness
369    /// is the unit's*.
370    due_at: Instant,
371    /// When the queue *expects* to write it, given everything ahead of it:
372    /// `max(due_at, tail.expected_at, now)` at push time.
373    ///
374    /// Reporting and lateness only. It gates nothing, and it is
375    /// deliberately an over-estimate for a unit queued behind a `Hold`,
376    /// whose gate may open long before its [`EgressConfig::max_hold`]
377    /// ceiling.
378    expected_at: Instant,
379    /// A `Hold`'s gate. Releasing it makes the unit due before `due_at`,
380    /// which for a held unit is the [`EgressConfig::max_hold`] ceiling.
381    gate: Option<Gate>,
382    /// What writing it does.
383    item: Item,
384    /// The shaper's tag, on a shaped stream only. Attached by
385    /// [`PendingQueue::push`] from the class the pipe loop most recently
386    /// resolved, so `exec`'s pushes carry it without `exec` naming a class.
387    shape: Option<ShapeTag>,
388}
389
390impl Pending {
391    /// Bytes to write no earlier than `due_at`.
392    pub(crate) fn bytes(raw: Bytes, due_at: Instant) -> Self {
393        Self { due_at, expected_at: due_at, gate: None, item: Item::Write(raw), shape: None }
394    }
395
396    /// An ordering placeholder that writes nothing.
397    pub(crate) fn elided(due_at: Instant) -> Self {
398        Self { due_at, expected_at: due_at, gate: None, item: Item::Elided, shape: None }
399    }
400
401    /// A positional terminal, due as soon as the queue ahead of it drains.
402    pub(crate) fn terminal(terminal: Terminal) -> Self {
403        let now = Instant::now();
404        Self {
405            due_at: now,
406            expected_at: now,
407            gate: None,
408            item: Item::Terminal(terminal),
409            shape: None,
410        }
411    }
412
413    /// Attach a `Hold`'s gate. `due_at` stays the `max_hold` ceiling.
414    #[must_use]
415    pub(crate) fn with_gate(mut self, gate: Gate) -> Self {
416        self.gate = Some(gate);
417        self
418    }
419
420    // The observers below are read by this module's tests and by nothing
421    // else: the queue answers `due_at` through `head_release()`, and a
422    // terminal announces itself through `Written::Terminated` rather than
423    // being asked. They stay as the deliberate read surface of a unit,
424    // with the allow scoped to them rather than to the file.
425    /// When this unit becomes due on its own account.
426    #[allow(dead_code)]
427    pub(crate) fn due_at(&self) -> Instant {
428        self.due_at
429    }
430
431    /// When the queue expected to write it. Never a gate; see the field.
432    #[allow(dead_code)]
433    pub(crate) fn expected_at(&self) -> Instant {
434        self.expected_at
435    }
436
437    /// What it does. Terminals end the stream.
438    #[allow(dead_code)]
439    pub(crate) fn item(&self) -> &Item {
440        &self.item
441    }
442
443    /// Whether this unit ends the stream.
444    #[allow(dead_code)]
445    pub(crate) fn is_terminal(&self) -> bool {
446        matches!(self.item, Item::Terminal(_))
447    }
448
449    /// Bytes this unit is holding on the queue's behalf.
450    pub(crate) fn len(&self) -> usize {
451        match &self.item {
452            Item::Write(raw) => raw.len(),
453            Item::Elided => 0,
454            Item::Terminal(t) => t.len(),
455        }
456    }
457
458    /// Whether this unit may be written at `now`, **on its own account**.
459    ///
460    /// Readiness only: whether anything is still queued ahead of it is the
461    /// deque's business, and [`PendingQueue::pop_next_due`] answers that by
462    /// looking at the front and nowhere else.
463    ///
464    /// A **released gate makes it due**, regardless of `due_at`. Without
465    /// that clause, releasing a `Hold` early wakes the release branch to
466    /// find nothing due and the loop spins hot until
467    /// [`EgressConfig::max_hold`] — one of the two ways this loop can spin
468    /// hot, and the one that is invisible from the call site.
469    ///
470    /// `expected_at` is deliberately **not** consulted: it is the queue's
471    /// estimate, and for a unit behind a `Hold` it is that hold's ceiling.
472    /// Gating on it is what stranded whole streams behind a released gate.
473    fn is_due(&self, now: Instant) -> bool {
474        self.due_at <= now || self.gate.as_ref().is_some_and(Gate::is_released)
475    }
476}
477
478/// What handing one unit to the transport did.
479#[derive(Clone, Copy, Debug, PartialEq, Eq)]
480pub(crate) enum Written {
481    /// This many bytes were handed to the transport.
482    Bytes(usize),
483    /// Nothing was written — an elided unit holding its ordering slot.
484    Nothing,
485    /// A terminal fired. The destination is reset; nothing further may be
486    /// written to it.
487    Terminated {
488        /// Bytes written ahead of the reset, on this unit.
489        forwarded: usize,
490        /// The code the stream was reset with.
491        code: u64,
492    },
493}
494
495/// Write one popped unit.
496///
497/// A failing prefix write on a [`Terminal::Truncate`] is propagated rather
498/// than swallowed: the caller mirrors it with `propagate_stop`, and the
499/// reset it would have sent is moot once the peer has stopped us. A failing
500/// `reset` is *not* propagated — it means the stream was already finished
501/// or reset, so there is nothing left to report and nothing left to do.
502pub(crate) async fn write_unit<S: EgressSink>(
503    unit: Pending,
504    send: &mut S,
505) -> Result<Written, EgressError> {
506    match unit.item {
507        Item::Write(raw) => {
508            send.write_all(&raw).await?;
509            Ok(Written::Bytes(raw.len()))
510        }
511        Item::Elided => Ok(Written::Nothing),
512        Item::Terminal(Terminal::Truncate { prefix, code }) => {
513            if !prefix.is_empty() {
514                send.write_all(&prefix).await?;
515            }
516            let _ = send.reset(code);
517            Ok(Written::Terminated { forwarded: prefix.len(), code })
518        }
519        Item::Terminal(Terminal::Reset { code }) => {
520            let _ = send.reset(code);
521            Ok(Written::Terminated { forwarded: 0, code })
522        }
523    }
524}
525
526// ── Delay and hold arithmetic ───────────────────────────────────────
527
528/// A resolved release deadline, and whether [`EgressConfig::max_hold`] cut
529/// it short.
530#[derive(Clone, Copy, Debug, PartialEq, Eq)]
531pub(crate) struct Deferral {
532    /// The deadline to put on the [`Pending`], before the queue's own
533    /// monotonic clamp.
534    pub(crate) release_at: Instant,
535    /// What was asked for.
536    pub(crate) requested: Duration,
537    /// What was applied.
538    pub(crate) applied: Duration,
539}
540
541impl Deferral {
542    /// Whether `max_hold` cut the request short — emit
543    /// `Impairment { HoldClamped { requested, applied } }`, once per
544    /// clamped unit.
545    pub(crate) fn was_clamped(&self) -> bool {
546        self.applied < self.requested
547    }
548}
549
550/// Resolve `Delay { by }` against [`EgressConfig::max_hold`].
551///
552/// A **deadline**, not a spacing: `arrived_at + by`. Two units arriving
553/// together with the same `by` are released together, not staggered.
554pub(crate) fn defer_by(arrived_at: Instant, by: Duration, config: &EgressConfig) -> Deferral {
555    let applied = by.min(config.max_hold);
556    Deferral { release_at: arrived_at + applied, requested: by, applied }
557}
558
559/// The ceiling on an [`crate::action::Action::Hold`]: a gate nobody
560/// releases is still written at `arrived_at + max_hold`, so a hook cannot
561/// pin a stream open forever.
562pub(crate) fn hold_ceiling(arrived_at: Instant, config: &EgressConfig) -> Instant {
563    arrived_at + config.max_hold
564}
565
566// ── The queue ───────────────────────────────────────────────────────
567
568/// What [`PendingQueue::push`] did.
569#[derive(Clone, Copy, Debug, PartialEq, Eq)]
570pub(crate) struct Push {
571    /// [`Pending::expected_at`] — the queue's estimate of when this unit
572    /// reaches the wire, which is the value to report as
573    /// `Effect::Queued { release_at }`. It accounts for everything already
574    /// queued, so it may be later than the action asked for, and for a unit
575    /// behind a `Hold` it is that hold's `max_hold` ceiling: an upper
576    /// bound, since a gate may open at any time before it.
577    ///
578    /// **Not** the unit's own deadline. That is [`Pending::due_at`], it is
579    /// the only thing that gates the unit, and `push` never rewrites it.
580    pub(crate) release_at: Instant,
581    /// `true` exactly once per stream: on the push after which
582    /// [`PendingQueue::accepts_more`] first turns `false`. Emit
583    /// `Impairment { EgressQueueFull { stream_id } }` on it, and only on
584    /// it — a queue that drains and fills again does not report twice.
585    pub(crate) entered_backpressure: bool,
586}
587
588/// How a drain ended.
589#[derive(Clone, Copy, Debug, PartialEq, Eq)]
590pub(crate) enum DrainOutcome {
591    /// Every queued unit was written.
592    Complete,
593    /// Cancellation won mid-drain. The remainder was flushed ignoring
594    /// release times; [`PendingQueue::unconfirmed_bytes`] afterwards is
595    /// what that fallback either could not write **or** wrote into a
596    /// transport that is being closed out from under it, and is what
597    /// `Impairment { QueuedBytesAtTeardown }` carries.
598    CancelledMidDrain,
599    /// A queued terminal fired. The destination is reset and nothing
600    /// further may be written to it.
601    Terminated {
602        /// Bytes written on the terminal unit itself, ahead of the reset.
603        forwarded: usize,
604        /// The code the stream was reset with.
605        code: u64,
606    },
607    /// A write failed. The failing unit and everything behind it are still
608    /// queued. Only the best-effort drain returns this; the
609    /// release-honouring drain propagates the error instead.
610    WriteFailed,
611    /// The teardown flush declined to write anything, because the session's
612    /// [`EgressGauge`] is discarding: a requested close ran out of drain
613    /// time, so what is left is reported rather than handed to a connection
614    /// that is about to be closed. The queue is untouched, so
615    /// [`PendingQueue::unconfirmed_bytes`] is exactly what was abandoned.
616    Discarded,
617}
618
619/// How many bytes one session's egress queues are holding, right now.
620///
621/// One gauge per session, shared by every [`PendingQueue`] the session
622/// builds. It exists for a single caller — a requested close, which has to
623/// know when there is nothing left to flush so it can stop waiting early
624/// — and it answers that question in the one unit that is worth waiting on.
625///
626/// # Why a byte gauge rather than a stream count
627///
628/// A session's live streams are already enumerated by
629/// [`StreamRegistry`](crate::control::StreamRegistry), and waiting for that
630/// to empty was the obvious alternative. It is the wrong signal twice over:
631/// a stream with an empty queue keeps its registration until its source
632/// FINs, so the wait would run to the deadline on a session that had
633/// nothing to flush at all; and a stream that ends while its queue is full
634/// drops its registration with the bytes still unwritten, so the wait would
635/// finish claiming a drain that lost data.
636///
637/// # Two-valued, and the second value is the whole bound
638///
639/// [`Self::wait_idle`] is the wait. [`Self::begin_discarding`] is what
640/// happens when the wait runs out: it puts every queue in the session into
641/// a mode where [`PendingQueue::drain_ignoring_release_times`] writes
642/// nothing at all.
643///
644/// That looks backwards next to the rest of this module, whose teardown rule is
645/// *delivered late beats lost silently*. The difference is that an ordinary
646/// teardown is not a deadline anybody set — the flush is the best remaining
647/// chance those bytes have. A close that has already been given its drain
648/// window and spent it is a deadline somebody set, and flushing past it makes
649/// the outcome *unreportable*: a `write_all` into quinn returns `Ok` as soon as
650/// the bytes are buffered and `Connection::close` discards that buffer, so the
651/// bytes are neither confirmably delivered nor confirmably lost, and no count
652/// of them adds up. Declining to write keeps the arithmetic exact — what the
653/// peer received plus what
654/// [`ImpairmentKind::QueuedBytesAtTeardown`](crate::event::ImpairmentKind::QueuedBytesAtTeardown)
655/// names equals what was queued when the close was requested — which is the
656/// property a gate can actually be written against.
657#[derive(Debug)]
658pub(crate) struct EgressGauge {
659    /// Bytes queued across every live [`PendingQueue`] of this session.
660    queued: AtomicUsize,
661    /// Pulsed whenever `queued` reaches zero.
662    idle: Notify,
663    /// Whether teardown flushes are declined — see the type's own doc.
664    discarding: AtomicBool,
665}
666
667impl EgressGauge {
668    /// A gauge reading zero, flushing normally.
669    pub(crate) fn new() -> Arc<Self> {
670        Arc::new(Self {
671            queued: AtomicUsize::new(0),
672            idle: Notify::new(),
673            discarding: AtomicBool::new(false),
674        })
675    }
676
677    /// Bytes queued across the session at this instant.
678    pub(crate) fn queued(&self) -> usize {
679        self.queued.load(Ordering::Acquire)
680    }
681
682    /// Whether teardown flushes are being declined.
683    pub(crate) fn is_discarding(&self) -> bool {
684        self.discarding.load(Ordering::Acquire)
685    }
686
687    /// Decline every later teardown flush on this session.
688    ///
689    /// One-way: nothing turns it off again, because the only caller is a
690    /// close whose drain window has expired and a session in that state is
691    /// on its way down.
692    pub(crate) fn begin_discarding(&self) {
693        self.discarding.store(true, Ordering::Release);
694    }
695
696    /// Charge `bytes` to the session total.
697    fn add(&self, bytes: usize) {
698        if bytes > 0 {
699            self.queued.fetch_add(bytes, Ordering::AcqRel);
700        }
701    }
702
703    /// Credit `bytes` back, waking a waiter when the session reaches zero.
704    fn sub(&self, bytes: usize) {
705        if bytes == 0 {
706            return;
707        }
708        // `saturating` in effect: a queue never credits back more than it
709        // charged, but an underflow here would wrap to a total that never
710        // reaches zero and would turn every later close into a full-length
711        // wait, so the arithmetic is written not to be able to. The loop is
712        // spelled out because the closure form is `try_update` from Rust 1.99
713        // and `fetch_update`, deprecated there, is the only name under the
714        // 1.88 MSRV.
715        let mut before = self.queued.load(Ordering::Acquire);
716        while let Err(seen) = self.queued.compare_exchange_weak(
717            before,
718            before.saturating_sub(bytes),
719            Ordering::AcqRel,
720            Ordering::Acquire,
721        ) {
722            before = seen;
723        }
724        if before.saturating_sub(bytes) == 0 {
725            self.idle.notify_waiters();
726        }
727    }
728
729    /// Wait for the session's queues to empty, for at most `timeout`.
730    ///
731    /// Answers the bytes still queued when it returns: `0` when everything
732    /// drained, and the residue when the deadline won. That residue is the
733    /// figure a caller acts on — it is what will be abandoned — and it is
734    /// deliberately a byte count and not "did it time out", because the
735    /// two differ: a queue that empties on the last poll before the
736    /// deadline drained, and one that reports zero after the deadline
737    /// drained too.
738    ///
739    /// The registration is armed **before** the count is re-read, so a
740    /// queue that empties between the two is not a lost wakeup that costs
741    /// the caller its whole timeout.
742    pub(crate) async fn wait_idle(&self, timeout: Duration) -> usize {
743        let deadline = tokio::time::sleep(timeout);
744        tokio::pin!(deadline);
745        loop {
746            if self.queued() == 0 {
747                return 0;
748            }
749            let notified = self.idle.notified();
750            tokio::pin!(notified);
751            notified.as_mut().enable();
752            if self.queued() == 0 {
753                return 0;
754            }
755            tokio::select! {
756                () = &mut notified => {}
757                () = &mut deadline => return self.queued(),
758            }
759        }
760    }
761}
762
763/// A per-stream FIFO of units waiting for their release times.
764///
765/// Live as a local variable in `pipe_data_framed` / `pipe_control_mutating`
766/// for the lifetime of one stream direction.
767#[derive(Debug)]
768pub(crate) struct PendingQueue {
769    q: VecDeque<Pending>,
770    /// The wheel handle for `q.front()`. Invariant:
771    /// `head_deadline.is_some() == !q.is_empty()`.
772    head_deadline: Option<Deadline>,
773    queued_bytes: usize,
774    /// Bytes a teardown drain handed to the transport, which nothing can
775    /// confirm ever left it. See [`Self::unconfirmed_bytes`].
776    flushed_unconfirmed: usize,
777    /// The session-wide byte gauge this queue reports into, or `None` for a
778    /// queue built outside a session — every `#[cfg(test)]` queue in this
779    /// module, which has no session to report to.
780    ///
781    /// Mirrored rather than derived: nothing can walk a session's queues,
782    /// because each one is a local variable in the pipe function that owns
783    /// its stream. Every write to `queued_bytes` goes through
784    /// [`Self::charge`] or [`Self::credit`] so the mirror cannot drift from
785    /// the value it mirrors.
786    gauge: Option<Arc<EgressGauge>>,
787    config: EgressConfig,
788    counters: Arc<Recorder>,
789    /// Report-once latch for [`Push::entered_backpressure`].
790    backpressure_reported: bool,
791    /// The shaping profile's per-stream depth, when this stream's session
792    /// has one **and** its overflow policy is `Block`.
793    ///
794    /// `None` on every unshaped stream and under every non-blocking
795    /// overflow: `DropTail` needs the read branch to stay enabled or
796    /// nothing arrives to be dropped, and `ResetStream` is decided on the
797    /// arriving unit. See [`Self::accepts_more`].
798    shape_depth: Option<QueueDepth>,
799    /// The session's shaper, on a shaped **data** stream and nowhere else.
800    /// `None` on every unshaped stream and on both control pipes, which is what
801    /// makes *the control pipes are never shaped* structural: a control queue
802    /// has no scheduler to ask, so no control frame can reach a bucket even by
803    /// accident.
804    shaper: Option<Arc<Scheduler>>,
805    /// Where a shaped release records what it did. Carried beside the
806    /// scheduler rather than reached through it because the recorder is
807    /// always constructed and the scheduler is not.
808    shape_stats: Option<Arc<ShapeRecorder>>,
809    /// The side this stream's traffic **arrived** on, for the per-leg and
810    /// per-direction figures the recorder keeps.
811    ///
812    /// `Option`, and deliberately not a defaulted side: one recorder serves
813    /// both legs of a session, so a queue that guessed would charge real
814    /// bytes to the wrong side and the figure would still look plausible.
815    /// `Some` exactly when `shape_stats` is — the same builder sets both —
816    /// so every reader below takes them together.
817    ///
818    /// A side rather than a direction because the recorder needs the leg
819    /// too, and this queue is the only thing that knows which stream it is
820    /// serving. It stays the *arrival* side even for the release figures the
821    /// proxy charges to the leg a unit leaves by: the turn-around belongs to
822    /// the recorder, which is one place, rather than to every queue, which
823    /// is one per stream.
824    shape_side: Option<ProxySide>,
825    /// `max_hold` for this stream's shaped units: the profile's, or the
826    /// engine's when the profile inherits it.
827    shape_hold: Duration,
828    /// The class the pipe loop resolved for the unit it is currently
829    /// handling. Read — not taken — by [`Self::push`], so every push made
830    /// while one framed unit is being executed carries the same class, and
831    /// the next unit overwrites it.
832    unit_class: Class,
833    /// The gate the head is parked on because a [`Discipline`] is holding
834    /// its class back. Merged into [`Self::head_release`] so `wait_release`
835    /// races it exactly as it races a `Hold`'s gate.
836    ///
837    /// [`Discipline`]: crate::shape::Discipline
838    shape_gate: Option<Gate>,
839    /// Whether the shaper is currently holding the head back.
840    ///
841    /// It decides which gate [`Self::head_release`] reports, and that is a
842    /// **spin guard**, not bookkeeping. A `Hold` whose gate has been released
843    /// makes its unit due (`Pending::is_due`) and the gate stays released
844    /// forever; if the queue kept reporting it while the shaper was refusing
845    /// the same unit, `wait_release` would resolve on every `select!`
846    /// iteration and the pipe loop would spin hot until the bucket refilled.
847    ///
848    /// Once a unit is due, the only things that may wake its stream are the
849    /// deadline the scheduler armed and the gate a discipline handed back.
850    shape_parked: bool,
851    /// The class this queue has declared demand for with the scheduler.
852    /// Exactly one declaration is outstanding at a time, and [`Drop`] is what
853    /// guarantees it is withdrawn.
854    shape_demand: Option<Class>,
855    /// Edge latch for `tokens_exhausted_episodes` — armed when a release is
856    /// refused for want of tokens, re-armed when one is granted.
857    tokens_dry: bool,
858    /// The index the next `starved_behind_other_class` sweep starts from, so
859    /// each queued unit is examined once over the queue's whole life rather
860    /// than once per wake.
861    starved_from: usize,
862    /// What the last [`Self::pop_next_due`] wants reported, for a caller that
863    /// has an `exec::Reporter` and an event sink. Taken, never accumulated:
864    /// at most one unit is released per call.
865    shape_report: Option<ShapeReport>,
866}
867
868/// What a shaped release wants the pipe loop to report.
869///
870/// The queue owns the decision and the caller owns the reporting: `egress`
871/// has no `Reporter`, no `ProxyObserver` and no session id, and giving it
872/// one would put the whole event surface behind a type whose job is a deque.
873#[derive(Clone, Copy, Debug, PartialEq, Eq)]
874pub(crate) enum ShapeReport {
875    /// A unit went out at its `max_hold` clamp rather than when its bucket
876    /// would have allowed it. Report `Impairment{HoldClamped}`, whose
877    /// cardinality is already *once per clamped unit*.
878    Clamped {
879        /// What the bucket asked for, or `None` when it named no instant at
880        /// all — a zero rate, or a unit larger than `burst_bytes`, where the
881        /// wait is genuinely unbounded.
882        ///
883        /// `None` rather than a sentinel duration: a finite figure here
884        /// would be a fabrication, and the largest representable one renders
885        /// as an eighteen-quintillion-second request that reads as an
886        /// encoding fault rather than as "unbounded".
887        requested: Option<Duration>,
888        /// What was applied: the clamp.
889        applied: Duration,
890    },
891    /// A unit outlived its clamp under `Expiry::ResetStream`, and the queue
892    /// has replaced everything with the reset. Report exactly one
893    /// `Shaped{Expired}`.
894    Expired,
895    /// The head could not be granted because its class's `burst_bytes` is
896    /// smaller than the head itself, so that class's configured rate can
897    /// never pace anything. Report `Impairment{ShapeBurstBelowUnit}`.
898    ///
899    /// The queue produces this on **every** refusal of such a unit and does
900    /// not deduplicate: the burst is a property of the session's profile,
901    /// not of this stream, so the report-once state belongs to the scheduler
902    /// and the caller claims it there through
903    /// `Scheduler::claim_burst_report`. A per-queue latch would report once
904    /// per stream for a fault that is the same fault on every stream.
905    ///
906    /// Carries the class rather than its name so this type stays `Copy`;
907    /// resolving a name here would allocate on a release path for an event
908    /// that is emitted once.
909    BurstBelowUnit {
910        /// The class whose rate is not being applied.
911        class: Class,
912        /// Its bucket cap, as configured.
913        burst_bytes: u64,
914        /// The wire size of the unit the cap could not cover.
915        unit_bytes: u64,
916    },
917}
918
919impl PendingQueue {
920    /// An empty queue. Allocates nothing until the first [`Self::push`].
921    pub(crate) fn new(config: EgressConfig, counters: Arc<Recorder>) -> Self {
922        Self {
923            q: VecDeque::new(),
924            head_deadline: None,
925            queued_bytes: 0,
926            flushed_unconfirmed: 0,
927            gauge: None,
928            shape_hold: config.max_hold,
929            config,
930            counters,
931            backpressure_reported: false,
932            shape_depth: None,
933            shaper: None,
934            shape_stats: None,
935            shape_side: None,
936            unit_class: Class::Unshapeable,
937            shape_gate: None,
938            shape_parked: false,
939            shape_demand: None,
940            tokens_dry: false,
941            starved_from: 1,
942            shape_report: None,
943        }
944    }
945
946    /// Report this queue's depth into its session's [`EgressGauge`].
947    ///
948    /// A builder for the same reason [`Self::with_shape_depth`] is: the
949    /// tests in this module build queues that belong to no session, and a
950    /// required parameter would make each of them invent one.
951    /// Installed by every pipe function, on every stream, shaped or not —
952    /// unlike a scheduler, which only the framed data pipe installs. A gauge
953    /// that only some queues reported into would answer *the session has
954    /// nothing left to flush* while a control stream still held a deferred
955    /// SUBSCRIBE_OK.
956    pub(crate) fn with_gauge(mut self, gauge: Arc<EgressGauge>) -> Self {
957        // A fresh queue is empty, so there is nothing to charge here. If
958        // that ever stops being true this has to charge `queued_bytes`.
959        debug_assert_eq!(self.queued_bytes, 0, "a queue takes its gauge before it takes bytes");
960        self.gauge = Some(gauge);
961        self
962    }
963
964    /// Add `bytes` to this queue's depth and to its session's gauge.
965    fn charge(&mut self, bytes: usize) {
966        self.queued_bytes = self.queued_bytes.saturating_add(bytes);
967        if let Some(gauge) = &self.gauge {
968            gauge.add(bytes);
969        }
970    }
971
972    /// Take `bytes` off this queue's depth and off its session's gauge.
973    fn credit(&mut self, bytes: usize) {
974        let bytes = bytes.min(self.queued_bytes);
975        self.queued_bytes -= bytes;
976        if let Some(gauge) = &self.gauge {
977            gauge.sub(bytes);
978        }
979    }
980
981    /// Whether this queue's session has stopped flushing at teardown.
982    ///
983    /// `false` for a queue with no gauge, which is every queue built
984    /// outside a session: nothing can have asked such a queue to stop.
985    fn discarding(&self) -> bool {
986        self.gauge.as_ref().is_some_and(|g| g.is_discarding())
987    }
988
989    /// Carry a shaping profile's per-stream depth into [`Self::accepts_more`].
990    ///
991    /// A builder rather than a fourth parameter on [`Self::new`], because
992    /// every caller but one passes `None` and the shape of the exception is
993    /// the point: exactly one construction site — the framed data pipe —
994    /// has a profile to install, and the ~25 unit tests below construct
995    /// queues that never had one.
996    ///
997    /// **Installed only under `Overflow::Block`**, which the caller
998    /// decides; this type deliberately does not name `Overflow`. What it
999    /// does is enforce a depth, and a depth that reaches it is one the
1000    /// session has already decided should stop reads.
1001    ///
1002    /// Plumbed *into* `accepts_more` rather than checked beside it because
1003    /// the once-per-stream backpressure latch is computed inside
1004    /// [`Self::push`] from `accepts_more()`: a stream full by
1005    /// `QueueConfig::depth_objects` but under
1006    /// [`EgressConfig::max_pending_bytes`] would otherwise never trip it,
1007    /// and `Impairment{EgressQueueFull}` would have no producer on a shaped
1008    /// stream at all.
1009    pub(crate) fn with_shape_depth(mut self, depth: Option<QueueDepth>) -> Self {
1010        self.shape_depth = depth;
1011        self
1012    }
1013
1014    /// Install the session's shaper, making this a **paced** queue.
1015    /// A builder for the same reason [`Self::with_shape_depth`] is: exactly one
1016    /// construction site — the framed data pipe — has a shaper, and the ~25
1017    /// unit tests below build queues that never had one. Both control pipes and
1018    /// `pipe_data_passthrough` call [`Self::new`] and stop there, which is what
1019    /// makes *the control pipes are never shaped* a property of the type graph
1020    /// rather than of a review.
1021    ///
1022    /// What it changes, and only this: [`Self::pop_next_due`] asks
1023    /// `Scheduler::acquire` before it yields a shaped unit, and [`Self::push`]
1024    /// tags what it queues. Everything else — ordering, `due_at`, `Hold`
1025    /// gates, the two drains, `head_release`'s signature — is untouched.
1026    ///
1027    /// `side` travels with the recorder rather than beside it because the
1028    /// recorder is session-scoped and this queue is not: one `ShapeRecorder`
1029    /// is shared by the forwarding tasks of both legs, and the figures it
1030    /// keeps are per leg and per direction. Naming it here — at the one site
1031    /// that has both a profile and a stream — is what makes a downlink stall
1032    /// unattributable to the uplink.
1033    ///
1034    /// It is the side this stream's traffic **arrives** on, which is the
1035    /// only side a forwarding task is ever handed. What a release figure
1036    /// does with it — charge the leg the unit leaves by — is the recorder's
1037    /// business, not this queue's.
1038    pub(crate) fn with_shaper(
1039        mut self,
1040        shaper: Option<Arc<Scheduler>>,
1041        stats: Arc<ShapeRecorder>,
1042        side: ProxySide,
1043    ) -> Self {
1044        if let Some(shaper) = shaper {
1045            self.shape_hold = shaper.max_hold().unwrap_or(self.config.max_hold);
1046            self.shaper = Some(shaper);
1047            self.shape_stats = Some(stats);
1048            self.shape_side = Some(side);
1049        }
1050        self
1051    }
1052
1053    /// Whether this queue paces what it holds.
1054    ///
1055    /// Read by `exec::Engine::queue_is_busy`, which is what routes a shaped
1056    /// stream's units *through* the queue instead of writing them inline: a
1057    /// unit written straight to the transport never reaches
1058    /// [`Self::pop_next_due`], so on a shaped stream the fast path is exactly
1059    /// the path that cannot be paced.
1060    pub(crate) fn is_shaped(&self) -> bool {
1061        self.shaper.is_some()
1062    }
1063
1064    /// Name the class of the unit the pipe loop is about to queue.
1065    ///
1066    /// Set once per framed unit, immediately after classification, and read
1067    /// by every [`Self::push`] until the next unit overwrites it. That is why
1068    /// `exec` can queue a `Delay`'d object without ever naming a class: the
1069    /// tag is the loop's, the push is `exec`'s, and neither has to know about
1070    /// the other.
1071    ///
1072    /// A no-op on an unshaped queue, where nothing reads it.
1073    pub(crate) fn tag_unit(&mut self, class: Class) {
1074        self.unit_class = class;
1075    }
1076
1077    /// Whatever the last [`Self::pop_next_due`] wants the caller to report.
1078    pub(crate) fn take_shape_report(&mut self) -> Option<ShapeReport> {
1079        self.shape_report.take()
1080    }
1081
1082    /// Whether anything is waiting.
1083    pub(crate) fn is_empty(&self) -> bool {
1084        self.q.is_empty()
1085    }
1086
1087    /// How many units are waiting.
1088    ///
1089    /// Read by this module's tests and by `exec.rs`'s, which assert
1090    /// `DeferredEffects::len() == PendingQueue::len()`, and on the data
1091    /// path by shaping admission, which needs the object count the
1092    /// arriving unit would be the `n + 1`-th of. The pipe loops themselves
1093    /// ask [`Self::accepts_more`] and [`Self::is_empty`].
1094    pub(crate) fn len(&self) -> usize {
1095        self.q.len()
1096    }
1097
1098    /// Bytes the queue is holding, right now, still unwritten.
1099    ///
1100    /// Not the teardown report — see [`Self::unconfirmed_bytes`]. This is
1101    /// the live figure the byte budget and the tests are written against.
1102    pub(crate) fn queued_bytes(&self) -> usize {
1103        self.queued_bytes
1104    }
1105
1106    /// Bytes this queue cannot vouch for: everything a teardown drain
1107    /// handed to the transport, **plus** everything still queued.
1108    ///
1109    /// What `Impairment { QueuedBytesAtTeardown }` carries, and the reason
1110    /// it is not simply [`Self::queued_bytes`]. A cancelled session's pipe
1111    /// task races `run_with_transport`'s `client.close()` / `relay.close()`:
1112    /// [`Self::drain_ignoring_release_times`] can hand a 256 KiB
1113    /// object to quinn, have `write_all` return `Ok` because quinn buffered
1114    /// it, and have the connection torn down before a byte of it is on the
1115    /// wire. Reporting only the residue reports **zero** there, and the
1116    /// object is gone with no event at all — the one failure class this
1117    /// engine forbids outright: traffic is never silently gone.
1118    ///
1119    /// So a teardown flush counts as *unconfirmed*, not as delivered. The
1120    /// event says exactly this already: "they were flushed best-effort;
1121    /// `bytes` may not have reached the peer". A peer that did receive them
1122    /// gets a spurious impairment, which is the right side to err on: an
1123    /// over-report is a diagnosable nuisance, a silent loss is not.
1124    ///
1125    /// Zero on every path that is not a teardown, so a stream that drains
1126    /// at its release times reports nothing.
1127    pub(crate) fn unconfirmed_bytes(&self) -> usize {
1128        self.flushed_unconfirmed.saturating_add(self.queued_bytes)
1129    }
1130
1131    /// The engine knobs this queue was built with.
1132    pub(crate) fn config(&self) -> &EgressConfig {
1133        &self.config
1134    }
1135
1136    /// Whether the source may still be **read from**.
1137    ///
1138    /// `false` once the queue holds [`EgressConfig::max_pending_bytes`] or
1139    /// [`MAX_PENDING_UNITS`] units — the point at which `Delay` stops being
1140    /// a latency shift and becomes backpressure — **or** once it reaches a
1141    /// shaping profile's own depth, when one was installed by
1142    /// [`Self::with_shape_depth`]. A `bool`, deliberately: it is hoisted
1143    /// out of the `select!` and holds no borrow.
1144    ///
1145    /// **Read from, not observed.** This gates `recv.read`, which is the
1146    /// only call that consumes bytes and therefore the only one that grants
1147    /// the peer flow-control credit. It does *not* gate whether the pipe
1148    /// loop watches its source: `false` swaps the read for
1149    /// `RecvStream::received_reset`, so a peer's `RESET_STREAM` is mirrored
1150    /// at once however full this queue is. Gating the observation on this
1151    /// was the defect — a dry bucket under `Overflow::Block` held the read
1152    /// shut for `max_hold` (30 s by default) and the reset went unseen for
1153    /// the whole of it.
1154    ///
1155    /// Both terms are **per stream**, and both imply a non-empty deque, so
1156    /// `head_release()` stays `Some` and the release branch stays live. A
1157    /// shared cross-class budget must never reach this function: an empty
1158    /// own queue with a shared budget exhausted disables the read branch
1159    /// *and* the release branch, and the stream deadlocks until session
1160    /// teardown.
1161    pub(crate) fn accepts_more(&self) -> bool {
1162        let within_engine_budget =
1163            self.queued_bytes < self.config.max_pending_bytes && self.q.len() < MAX_PENDING_UNITS;
1164        let within_shape_depth = match self.shape_depth {
1165            Some(depth) => self.queued_bytes < depth.bytes && self.q.len() < depth.objects,
1166            None => true,
1167        };
1168        within_engine_budget && within_shape_depth
1169    }
1170
1171    /// What the head is waiting for, as an owned clone — see [`Release`].
1172    ///
1173    /// **`&self`, and it debits nothing.** It reports the deadline the
1174    /// scheduler last computed and the gate it last parked on; the decision
1175    /// itself belongs to [`Self::pop_next_due`], which is `&mut self` and
1176    /// runs exactly once per released unit. This function is called once
1177    /// per `select!` iteration from a hoisted local, so a debit here would
1178    /// be charged per read-wake rather than per byte written.
1179    ///
1180    /// **The FIN drain *is* on `pop_next_due`'s path.**
1181    /// [`drain_honouring_release_times`] —
1182    /// the read arm's `None` branch — loops `pop_next_due`, debits the
1183    /// bucket, and applies the clamp and the expiry, which is why it takes
1184    /// an `on_shape` callback and reports them. The control pipes really
1185    /// are exempt, but by *construction* rather than by call graph: they
1186    /// install no [`Scheduler`] at all, so
1187    /// `shaping_grants_head` short-circuits on `shaper: None` and no
1188    /// control frame can reach a bucket even by accident. The only drain
1189    /// that is genuinely off the seam is
1190    /// [`Self::drain_ignoring_release_times`], which pops the front
1191    /// directly and consults nothing.
1192    pub(crate) fn head_release(&self) -> Option<Release> {
1193        let head = self.q.front()?;
1194        debug_assert!(
1195            self.head_deadline.is_some(),
1196            "a non-empty queue always has an armed head deadline",
1197        );
1198        Some(Release {
1199            deadline: self.head_deadline.clone()?,
1200            // Exactly one of the two, never both, and the choice is the spin
1201            // guard `shape_parked` documents: while the shaper is holding a
1202            // unit the shaper's gate is the only one that can usefully wake
1203            // it, because the unit's own `Hold` gate has already fired and
1204            // would resolve `wait_release` on every iteration forever.
1205            gate: if self.shape_parked { self.shape_gate.clone() } else { head.gate.clone() },
1206        })
1207    }
1208
1209    /// When the queue expects to have written the last unit in it, or
1210    /// `None` if it is empty.
1211    ///
1212    /// `None` rather than `Instant::now()`: the estimate exists only to say
1213    /// what a unit waits behind, and on an empty queue there is nothing
1214    /// ahead. Folding `now` in there would quietly shift every unit's
1215    /// reported release time forward to its push instant.
1216    fn tail_expected_at(&self) -> Option<Instant> {
1217        self.q.back().map(|u| u.expected_at)
1218    }
1219
1220    /// Register the head with the release wheel.
1221    ///
1222    /// Armed at the head's **`due_at`**, never its `expected_at`: the head
1223    /// has nothing ahead of it, so its own deadline is when it may go, and
1224    /// arming on the estimate would re-introduce the successor stall the
1225    /// module doc describes (a unit clamped behind a `Hold` would sleep to
1226    /// that hold's ceiling even after it became the head).
1227    ///
1228    /// The **only** caller of [`release_timer::arm_at`] besides
1229    /// [`Self::push`]'s use of it through here. `arm_at` starts nothing when
1230    /// the head is already due, so a queue of undelayed units never
1231    /// constructs the wheel.
1232    fn arm_head(&mut self) {
1233        self.head_deadline = self.q.front().map(|u| release_timer::arm_at(u.due_at));
1234    }
1235
1236    /// Re-arm the head at an instant the *scheduler* named, rather than at
1237    /// the head's own `due_at`.
1238    /// The head is due on its own account — that is how it reached the shaper —
1239    /// so re-arming at `due_at` would give an already-fired deadline and spin
1240    /// the release branch hot until the bucket refilled. This is what makes
1241    /// `head_release()` *the deadline the scheduler last computed*: it reads
1242    /// back exactly what was armed here.
1243    fn arm_head_at(&mut self, at: Instant) {
1244        self.head_deadline = Some(release_timer::arm_at(at));
1245    }
1246
1247    /// Queue a unit behind everything already waiting.
1248    ///
1249    /// **`due_at` is not touched.** The unit keeps the deadline it was
1250    /// built with, because that deadline is its own: a `Delay`'s
1251    /// `arrived_at + by`, a `Hold`'s `max_hold` ceiling, or `now` for a
1252    /// unit queued purely for ordering. Wire order is held by the deque —
1253    /// [`Self::pop_next_due`] only ever considers the *front* — so nothing
1254    /// has to be added to a successor's deadline to keep it behind its
1255    /// predecessor, and adding something is exactly what stranded a stream
1256    /// behind a released `Hold` (module doc).
1257    /// What *is* computed here is [`Pending::expected_at`], the queue's
1258    /// estimate of when this unit will reach the wire: `max(due_at,
1259    /// tail.expected_at, now)` on a non-empty queue, and `due_at` on an empty
1260    /// one. It is what [`Push::release_at`] returns, so `Effect::Queued {
1261    /// release_at }` still says *no earlier than everything ahead of you*, and
1262    /// it is what [`Self::record_release`] measures lateness against, so a
1263    /// `Pass` queued behind a 300 ms `Delay` is not logged as a 300 ms release
1264    /// error.
1265    ///
1266    /// A unit that is due now on an *empty* queue should not be pushed at
1267    /// all — the pipe loop writes it inline in the read arm, which is
1268    /// today's code and today's cost.
1269    pub(crate) fn push(&mut self, mut unit: Pending) -> Push {
1270        if let Some(tail) = self.tail_expected_at() {
1271            unit.expected_at = unit.due_at.max(tail).max(Instant::now());
1272        }
1273        // Tagged here, not at the call site, so that every producer of a
1274        // queued unit — this file's own terminals, `exec`'s `Delay`/`Hold`
1275        // pushes, and the pipe loop's own header and passthrough bytes — is
1276        // charged to the class the loop resolved, without any of them naming
1277        // one. The clock read is behind the shaping test, so an unshaped push
1278        // costs exactly what it did before.
1279        if self.shaper.is_some() {
1280            unit.shape = Some(ShapeTag {
1281                class: self.unit_class,
1282                expires_at: Instant::now() + self.shape_hold,
1283                starved_noted: false,
1284            });
1285            self.note_unshapeable_seen(&unit);
1286        }
1287        let release_at = unit.expected_at;
1288        let becomes_head = self.q.is_empty();
1289        self.charge(unit.len());
1290        self.q.push_back(unit);
1291        self.counters.note_egress_item_queued();
1292        if becomes_head {
1293            self.arm_head();
1294        }
1295        self.sync_shape_demand();
1296        let entered_backpressure = !self.accepts_more() && !self.backpressure_reported;
1297        if entered_backpressure {
1298            self.backpressure_reported = true;
1299        }
1300        Push { release_at, entered_backpressure }
1301    }
1302
1303    /// Pop the head if it is due at `now`, re-arming the new head.
1304    ///
1305    /// **The front and only the front.** That single fact is the whole
1306    /// ordering guarantee: a unit whose own deadline passed long ago is
1307    /// still not popped while anything is queued in front of it.
1308    ///
1309    /// Call it in a `while let` loop: `pop_next_due` yields one unit at a
1310    /// time and allocates nothing, where a `Vec`-returning `pop_due` would
1311    /// allocate on every release wake. Looping matters as much as it reads:
1312    /// one wake on a `Hold`'s gate has to drain the held unit *and* every
1313    /// unit behind it that is due on its own account, or releasing a gate
1314    /// resumes one object and strands the rest.
1315    ///
1316    /// # The release seam
1317    ///
1318    /// On a shaped queue — and only there — a unit that is due on its own
1319    /// account is then put to `Scheduler::acquire`, which debits its class's
1320    /// token bucket and applies the [`Discipline`]. This function is where
1321    /// that happens because it is `&mut self`, it is the sole ordering
1322    /// authority, and it is called **exactly once per released unit**;
1323    /// `head_release` is none of those things.
1324    ///
1325    /// A refusal is not an error and costs nothing: the head deadline is
1326    /// re-armed at whatever the scheduler named (clamped by the unit's own
1327    /// `max_hold`), the gate a discipline handed back is stored for
1328    /// `head_release`, and `None` is returned exactly as a not-yet-due head
1329    /// returns it. The caller's loop is unchanged.
1330    ///
1331    /// [`Discipline`]: crate::shape::Discipline
1332    pub(crate) fn pop_next_due(&mut self, now: Instant) -> Option<Pending> {
1333        if !self.q.front().is_some_and(|u| u.is_due(now)) {
1334            return None;
1335        }
1336        // A fresh decision every call: a gate stored by a previous refusal
1337        // says nothing about this one, and leaving it set would let
1338        // `head_release` hand out a gate nobody will release.
1339        self.shape_gate = None;
1340        self.shape_parked = false;
1341        if !self.shaping_grants_head(now) {
1342            return None;
1343        }
1344        let unit = self.q.pop_front()?;
1345        self.credit(unit.len());
1346        self.starved_from = self.starved_from.saturating_sub(1).max(1);
1347        self.note_delivered(&unit);
1348        self.arm_head();
1349        self.sync_shape_demand();
1350        Some(unit)
1351    }
1352
1353    /// Whether the shaper lets the head go at `now`, arming whatever it must
1354    /// wait for when it does not.
1355    ///
1356    /// `true` on every unshaped queue and for every unit the shaper does not
1357    /// own — a terminal, an elided ordering slot, a header — so the only
1358    /// thing a bucket can ever hold back is bytes.
1359    fn shaping_grants_head(&mut self, now: Instant) -> bool {
1360        let Some(shaper) = self.shaper.clone() else {
1361            return true;
1362        };
1363        let Some(head) = self.q.front() else {
1364            return true;
1365        };
1366        let Some(tag) = head.shape else {
1367            return true;
1368        };
1369        // Terminals bypass the pacer. A queued reset or truncate is the end
1370        // of the stream, not media: gating it would make teardown ordering
1371        // depend on a token bucket, which is exactly what turns the
1372        // data-then-reset tests into coin flips.
1373        if matches!(head.item, Item::Terminal(_)) {
1374            return true;
1375        }
1376        let bytes = head.len() as u64;
1377
1378        // The clamp comes first, and it is what makes starvation late rather
1379        // than lossy: a unit that has outlived `max_hold` is decided by
1380        // `Expiry`, never by a bucket that has already refused it.
1381        if now >= tag.expires_at {
1382            return self.expire_head(shaper.on_expiry());
1383        }
1384
1385        match shaper.acquire(tag.class, bytes, now) {
1386            Acquire::Now => {
1387                self.tokens_dry = false;
1388                true
1389            }
1390            Acquire::Later(at) => {
1391                self.note_tokens_dry(tag.class);
1392                self.park_head(at.min(tag.expires_at), tag, None);
1393                false
1394            }
1395            // No refill instant exists, so the clamp *is* the deadline. Arming
1396            // a fabricated far-future instant instead would arm a timer that
1397            // never fires, and the stream would look identical to one that
1398            // had simply hung.
1399            Acquire::Never => {
1400                self.note_tokens_dry(tag.class);
1401                self.park_head(tag.expires_at, tag, None);
1402                false
1403            }
1404            // The clamp is the deadline here too — nothing about *time* makes
1405            // a unit larger than the whole bucket fit — but this refusal is a
1406            // mis-sized burst rather than a rate doing its job, and the two
1407            // are otherwise identical from outside: same clamp, same counter,
1408            // same `HoldClamped`. The report is the only thing that separates
1409            // them, so it is set here and deduplicated by the scheduler.
1410            Acquire::LargerThanBurst { burst_bytes, unit_bytes } => {
1411                self.note_tokens_dry(tag.class);
1412                self.shape_report =
1413                    Some(ShapeReport::BurstBelowUnit { class: tag.class, burst_bytes, unit_bytes });
1414                self.park_head(tag.expires_at, tag, None);
1415                false
1416            }
1417            Acquire::Starved(gate) => {
1418                self.park_head(tag.expires_at, tag, Some(gate));
1419                false
1420            }
1421        }
1422    }
1423
1424    /// Charge one **episode** of this class's own bucket being dry, on the
1425    /// edge only.
1426    ///
1427    /// Shared by the three refusals that are the bucket's rather than a
1428    /// discipline's, so the edge latch cannot be re-armed on one of them and
1429    /// forgotten on another. Re-armed by a grant, in `shaping_grants_head`.
1430    fn note_tokens_dry(&mut self, class: Class) {
1431        if self.tokens_dry {
1432            return;
1433        }
1434        self.tokens_dry = true;
1435        if let Some(stats) = &self.shape_stats {
1436            stats.note_tokens_exhausted(class);
1437        }
1438    }
1439
1440    /// Park the head until `at` — or until `gate` opens, when a discipline is
1441    /// what is holding it — and charge everything queued behind a unit of
1442    /// another class.
1443    fn park_head(&mut self, at: Instant, tag: ShapeTag, gate: Option<Gate>) {
1444        let by_discipline = gate.is_some();
1445        self.shape_gate = gate;
1446        self.shape_parked = true;
1447        self.arm_head_at(at);
1448        self.sync_shape_demand();
1449        if by_discipline {
1450            // The head itself waited on **another class**, not on its own
1451            // bucket. Same counter as a same-stream head of another class,
1452            // because to a caller they are one question — *was I held
1453            // up by my rate, or by somebody else?* — and the answer is the same
1454            // in both. What it is emphatically not is
1455            // `tokens_exhausted_episodes`: the bucket was never asked.
1456            self.note_head_starved();
1457        }
1458        self.note_starved_behind(tag.class);
1459    }
1460
1461    /// Charge the head once against `starved_behind_other_class`.
1462    fn note_head_starved(&mut self) {
1463        let Some(stats) = self.shape_stats.clone() else { return };
1464        let Some(tag) = self.q.front_mut().and_then(|u| u.shape.as_mut()) else { return };
1465        if tag.starved_noted {
1466            return;
1467        }
1468        tag.starved_noted = true;
1469        stats.note_starved(tag.class);
1470    }
1471
1472    /// Charge `starved_behind_other_class` for every queued unit that is
1473    /// waiting behind a unit of a *different* class.
1474    ///
1475    /// Once per unit over the queue's whole life, not once per wake: the
1476    /// sweep resumes from where the last one stopped and the cursor follows
1477    /// the front as units are popped, so the total work is linear in units
1478    /// queued rather than quadratic in wakes.
1479    ///
1480    /// Deliberately separate from `tokens_exhausted_episodes`. Head-gating
1481    /// makes configured shaping and head-of-line blocking look identical from
1482    /// outside, and this is the number that tells them apart.
1483    fn note_starved_behind(&mut self, head_class: Class) {
1484        let Some(stats) = self.shape_stats.clone() else { return };
1485        let len = self.q.len();
1486        for index in self.starved_from.max(1)..len {
1487            let Some(unit) = self.q.get_mut(index) else { continue };
1488            let Some(tag) = unit.shape.as_mut() else { continue };
1489            if tag.starved_noted || tag.class == head_class {
1490                continue;
1491            }
1492            tag.starved_noted = true;
1493            stats.note_starved(tag.class);
1494        }
1495        self.starved_from = len.max(1);
1496    }
1497
1498    /// The head outlived its clamp. Deliver it, or abandon the stream.
1499    ///
1500    /// Returns whether the caller may pop the head. Under
1501    /// [`Expiry::Deliver`] it may — that is the whole of "clamps and
1502    /// delivers", and it is why `objects_expired` is zero by default.
1503    fn expire_head(&mut self, expiry: Expiry) -> bool {
1504        match expiry {
1505            Expiry::Deliver => {
1506                // What the bucket *would* have asked for is gone by now — the
1507                // deadline it named is in the past, or it named none — so the
1508                // report states the clamp that was applied and leaves the
1509                // request absent rather than inventing a figure for it.
1510                self.shape_report =
1511                    Some(ShapeReport::Clamped { requested: None, applied: self.shape_hold });
1512                true
1513            }
1514            Expiry::ResetStream { code } => {
1515                if let (Some(stats), Some(side)) = (&self.shape_stats, self.shape_side) {
1516                    stats.note_expired(side);
1517                    stats.note_stream_reset_by_shaping(side);
1518                }
1519                self.shape_report = Some(ShapeReport::Expired);
1520                // Everything queued is replaced by the reset: the stream is
1521                // being given up on, and writing part of it first would leave
1522                // the peer a prefix it cannot distinguish from a truncation.
1523                self.clear();
1524                self.q.push_back(Pending::terminal(Terminal::Reset { code }));
1525                self.arm_head();
1526                true
1527            }
1528        }
1529    }
1530
1531    /// Account for a queued unit **no rule could see**: the left-hand side of
1532    /// the conservation identity, for bytes the classifier never met.
1533    /// A stream header, an oversized object's passthrough chunk and a bypassed
1534    /// stream's tail carry no `ObjectMeta`, so `note_object_seen` is never
1535    /// reached for them and `bytes_shaped` would not count them. They are still
1536    /// bytes the shaper handled — it queued them, it ordered them, and it wrote
1537    /// them — so leaving them out would make `bytes_shaped` mean "bytes a rule
1538    /// saw" while
1539    /// [`ShapeStats::bytes_shaped`](crate::shape::ShapeStats::bytes_shaped)
1540    /// promises *every byte it accounted for*, and the `unshapeable` row would
1541    /// have nothing to be the other half of.
1542    ///
1543    /// **Here and not at the call site**, which is what makes the identity
1544    /// structural: `push` is the one place an unshapeable unit enters, this
1545    /// reads the same `unit.len()` [`Self::note_delivered`] will charge when
1546    /// it leaves, and both are gated on the same `ShapeTag`. `session.rs`
1547    /// cannot get it wrong because `session.rs` is not asked — the object
1548    /// arm and the unshown arm share one `exec` entry point, and only the
1549    /// tag tells them apart.
1550    ///
1551    /// Zero-length units — an elided ordering slot, a queued reset — add
1552    /// nothing on either side.
1553    fn note_unshapeable_seen(&self, unit: &Pending) {
1554        if self.unit_class != Class::Unshapeable {
1555            return;
1556        }
1557        let (Some(stats), Some(side)) = (&self.shape_stats, self.shape_side) else {
1558            return;
1559        };
1560        let bytes = unit.len();
1561        if bytes > 0 {
1562            stats.note_unshapeable_seen(side, bytes as u64);
1563        }
1564    }
1565
1566    /// Charge one released unit to its class.
1567    ///
1568    /// [`Class::Unshapeable`] is charged like any other row, and only
1569    /// because [`Self::note_unshapeable_seen`] now counts the same bytes
1570    /// into `bytes_shaped` on the way in: both terms move together or
1571    /// neither does, and this is the unit in which they moved.
1572    /// The consequence worth stating: unshapeable bytes are **not paced**.
1573    /// `shaping_grants_head` never asks a bucket about them — they carry no
1574    /// class a bucket is configured for — so an object too large for the framer
1575    /// to buffer bypasses every rate, and a class rate can be exceeded by
1576    /// exactly one oversized object. That is what "unshapeable" means; the row
1577    /// named for it is where the figure is reported, and it is deliberately a
1578    /// *separate* row from `default_class` so that *no rule claimed this unit*
1579    /// and "no rule could have" stay two answers.
1580    ///
1581    /// The row alone does not say *whose* rate went, which is the question an
1582    /// author with a 500 kbps video cap and a large initial segment is
1583    /// actually asking, so the pipe loop pairs it with
1584    /// `Impairment{ShapeUnpacedObject}` naming the class the stream's
1585    /// classified units are charged to.
1586    fn note_delivered(&self, unit: &Pending) {
1587        let (Some(stats), Some(side), Some(tag)) = (&self.shape_stats, self.shape_side, unit.shape)
1588        else {
1589            return;
1590        };
1591        let bytes = unit.len();
1592        if bytes > 0 {
1593            stats.note_delivered(side, tag.class, bytes as u64);
1594        }
1595    }
1596
1597    /// Tell the scheduler which class this queue is holding an unreleased
1598    /// head of, if any.
1599    ///
1600    /// **Demand, not depth.** One declaration per queue, replaced whenever
1601    /// the head's class changes and withdrawn when the queue empties — which
1602    /// includes [`Self::clear`], the teardown drain, and [`Drop`]. A
1603    /// declaration that outlived its stream would starve a lower-priority
1604    /// class for the rest of the session, and the failure would look like a
1605    /// hang rather than like a leak.
1606    fn sync_shape_demand(&mut self) {
1607        let Some(shaper) = &self.shaper else { return };
1608        let want = self.q.front().and_then(|u| u.shape).map(|t| t.class);
1609        if want == self.shape_demand {
1610            return;
1611        }
1612        if let Some(old) = self.shape_demand.take() {
1613            shaper.withdraw_demand(old);
1614        }
1615        if let Some(new) = want {
1616            shaper.declare_demand(new);
1617            self.shape_demand = Some(new);
1618        }
1619    }
1620
1621    /// Sample one deferred release's lateness onto the session counters.
1622    ///
1623    /// Call it **only** from the release branch, once per unit
1624    /// `pop_next_due` yields, before writing. Units written inline in the
1625    /// read arm are not releases and would dilute the distribution with
1626    /// zeros; drains ignore release times by design and recording their
1627    /// lateness would report teardown as a timing failure. Neither
1628    /// drain in this file calls it.
1629    /// Measured against [`Pending::expected_at`], not [`Pending::due_at`]: the
1630    /// question is *did the engine release when it said it would*, and what it
1631    /// said was `Effect::Queued { release_at }`. Sampling `due_at` instead
1632    /// would log the queueing delay of every unit behind a `Delay` as a timer
1633    /// error.
1634    ///
1635    /// `saturating_duration_since` because a unit released early — a
1636    /// `Hold`'s gate opening long before its ceiling, which is the normal
1637    /// case — is a zero, not a negative.
1638    pub(crate) fn record_release(&self, unit: &Pending, now: Instant) {
1639        self.counters.record_release(now.saturating_duration_since(unit.expected_at));
1640    }
1641
1642    /// Forget everything queued. Used after a terminal fires — the
1643    /// destination is reset, so nothing behind it can be written.
1644    ///
1645    /// Dropping the [`Release`]s abandons their wheel slots, which the
1646    /// wheel prunes at their own instants or at a sweep, without waking
1647    /// anything.
1648    pub(crate) fn clear(&mut self) {
1649        self.q.clear();
1650        self.credit(self.queued_bytes);
1651        self.head_deadline = None;
1652        self.starved_from = 1;
1653        // The shaping gate goes with the units it was holding, and the
1654        // demand goes with the head that declared it: a queue that is being
1655        // emptied has nothing to send, and leaving the declaration standing
1656        // would starve a lower class for the rest of the session.
1657        self.shape_gate = None;
1658        self.shape_parked = false;
1659        self.sync_shape_demand();
1660    }
1661
1662    /// Write everything queued **now**, in order, ignoring release times.
1663    ///
1664    /// The teardown path: delivered late beats lost silently. Best effort
1665    /// by construction — a write failure stops the drain and leaves the
1666    /// failing unit and everything behind it queued, so
1667    /// [`Self::queued_bytes`] reports exactly what did not reach the
1668    /// transport.
1669    ///
1670    /// Every byte it *does* hand over is added to
1671    /// [`Self::unconfirmed_bytes`], because a `write_all` that returns `Ok`
1672    /// into a connection another task is about to `close()` is not a
1673    /// delivery. That is the whole of the teardown-race loss: read
1674    /// [`Self::unconfirmed_bytes`] after this, never [`Self::queued_bytes`],
1675    /// on any path that runs because the session is going down.
1676    ///
1677    /// **Consults no bucket.** This is the teardown drain, and teardown
1678    /// bypasses the pacer entirely: it pops the front directly
1679    /// rather than through [`Self::pop_next_due`], so a token bucket can
1680    /// never gate a mirrored reset. That is what keeps the eleven
1681    /// data-then-teardown ordering tests independent of any configured rate.
1682    ///
1683    /// **And therefore reports no shaping, deliberately.** Every other
1684    /// release path takes a [`ShapeReport`] out of the queue and hands it to
1685    /// a reporter, because a clamp or an expiry that nothing says happened is
1686    /// indistinguishable from a profile that did nothing. This one is the
1687    /// exception, and it is an exception because it *applies* no shaping:
1688    /// [`Self::pop_next_due`] is the only producer of a `ShapeReport` and
1689    /// this function never calls it, so there is nothing to drain rather
1690    /// than something dropped. The slot is empty on entry as well — both
1691    /// callers that can reach it through a shaped queue
1692    /// ([`drain_honouring_release_times`]'s cancel arm and
1693    /// `release_due_units`) drain the report before the pop that produced it
1694    /// is written.
1695    ///
1696    /// **Known residue, stated rather than fixed:** popping the front
1697    /// directly also skips the `shape_parked` / `shape_gate` bookkeeping
1698    /// [`Self::pop_next_due`] maintains, so after this drain a
1699    /// [`Self::head_release`] would describe a park belonging to a unit that
1700    /// is gone. Nothing reads it — every caller returns immediately, the
1701    /// queue is empty or cleared, and `head_release` answers `None` on an
1702    /// empty deque — so there is no failure case here to fix. It is
1703    /// written down because "unreachable today" is a property of the
1704    /// callers, not of this function.
1705    pub(crate) async fn drain_ignoring_release_times<S: EgressSink>(
1706        &mut self,
1707        send: &mut S,
1708    ) -> DrainOutcome {
1709        // A close that was given a drain window and spent it has already
1710        // decided these bytes are not going out. Writing them anyway would move
1711        // them from *queued, and reported as abandoned* into *handed to a
1712        // connection that is closing*, which is the one state nothing
1713        // downstream can count: see [`EgressGauge`].
1714        if self.discarding() {
1715            return DrainOutcome::Discarded;
1716        }
1717        while let Some(unit) = self.q.front().cloned() {
1718            match write_unit(unit, send).await {
1719                Ok(Written::Terminated { forwarded, code }) => {
1720                    // Everything behind a terminal is discarded by design,
1721                    // not lost: the destination is reset. Only the prefix
1722                    // this unit actually handed over is unconfirmed.
1723                    self.flushed_unconfirmed = self.flushed_unconfirmed.saturating_add(forwarded);
1724                    self.clear();
1725                    return DrainOutcome::Terminated { forwarded, code };
1726                }
1727                Ok(written) => {
1728                    if let Written::Bytes(n) = written {
1729                        self.flushed_unconfirmed = self.flushed_unconfirmed.saturating_add(n);
1730                    }
1731                    let unit = self.q.pop_front().expect("front was just observed");
1732                    self.credit(unit.len());
1733                    self.starved_from = self.starved_from.saturating_sub(1).max(1);
1734                    // Charged to its class like any other release: a flush that
1735                    // `write_all` accepted is the only definition of *released
1736                    // to the destination stream* this side of the transport,
1737                    // and what it could not vouch for is separately reported as
1738                    // `QueuedBytesAtTeardown`.
1739                    self.note_delivered(&unit);
1740                    self.arm_head();
1741                    self.sync_shape_demand();
1742                }
1743                Err(_) => return DrainOutcome::WriteFailed,
1744            }
1745        }
1746        DrainOutcome::Complete
1747    }
1748}
1749
1750/// Withdraw whatever demand this queue still holds.
1751///
1752/// `Drop` rather than a call on each teardown path, and for the same reason
1753/// `StreamGuard` is: a forwarding task can end in at least five different
1754/// ways, any hand-written list of them is only as complete as its reader,
1755/// and `Drop` additionally covers a `?` return, a panicking task, and
1756/// `JoinSet::shutdown` dropping the future wholesale. A leaked declaration
1757/// is not a loud failure — it is a *different* stream stalling to
1758/// `max_hold`, on a class the reader was not looking at.
1759impl Drop for PendingQueue {
1760    fn drop(&mut self) {
1761        if let (Some(shaper), Some(class)) = (&self.shaper, self.shape_demand.take()) {
1762            shaper.withdraw_demand(class);
1763        }
1764        // The session gauge counts bytes that are still *somewhere*, and a
1765        // queue going away takes its remainder with it. Without this a
1766        // stream torn down with a full queue would leave the session's
1767        // total permanently above zero, and every later close would spend
1768        // its whole drain window waiting for a queue that no longer exists.
1769        self.credit(self.queued_bytes);
1770    }
1771}
1772
1773/// Write everything queued **at its release time**, racing cancellation.
1774///
1775/// The shape both release-honouring drains take — the FIN drain in the read
1776/// arm's `None` branch and the one a terminal runs before it resets. Both
1777/// sit inside `select!` arm bodies, which are not preemptible, so writing
1778/// either as a plain `while let` loop would let a `Hold` on a gate nobody
1779/// releases pin session teardown for up to [`EgressConfig::max_hold`] —
1780/// precisely the failure the deque was chosen over a writer task to avoid.
1781///
1782/// The inner `select!` is `biased` so cancellation wins deterministically
1783/// when both are ready. Unbiased, a cancelled session could keep picking
1784/// the release branch, find nothing due, and spin.
1785///
1786/// Release lateness is **not** sampled here: this drain honours release
1787/// times only until cancellation, and recording a teardown flush as a
1788/// timing sample would report teardown as a timing failure.
1789///
1790/// # Shaping **is** reported here
1791///
1792/// `on_shape` is called once per [`ShapeReport`] the drain's own
1793/// [`PendingQueue::pop_next_due`] produces, and it exists because this loop
1794/// is a full release seam and not a teardown flush: on a shaped queue every
1795/// pop below debits a token bucket and every clamp or expiry is decided
1796/// here. The FIN path — header, a few objects, FIN, the ordinary MoQT
1797/// subgroup shape — reaches the wire through *this* function and not
1798/// through `release_due_units`, so a drain that swallowed its reports would
1799/// apply the whole profile to the normal case and say nothing about it.
1800/// That is the silent no-op this signature exists to prevent, and the reason
1801/// the parameter is not optional: a caller cannot forget what it has to name.
1802///
1803/// Passing a no-op closure is correct only where no report can exist —
1804/// every `#[cfg(test)]` caller in this module builds an unshaped queue, and
1805/// an unshaped queue's `pop_next_due` never reaches a scheduler.
1806pub(crate) async fn drain_honouring_release_times<S, F>(
1807    pending: &mut PendingQueue,
1808    send: &mut S,
1809    cancel: &CancellationToken,
1810    mut on_shape: F,
1811) -> Result<DrainOutcome, EgressError>
1812where
1813    S: EgressSink,
1814    F: FnMut(ShapeReport),
1815{
1816    while let Some(release) = pending.head_release() {
1817        tokio::select! {
1818            biased;
1819            () = cancel.cancelled() => {
1820                return Ok(match pending.drain_ignoring_release_times(send).await {
1821                    DrainOutcome::Terminated { forwarded, code } => {
1822                        DrainOutcome::Terminated { forwarded, code }
1823                    }
1824                    _ => DrainOutcome::CancelledMidDrain,
1825                });
1826            }
1827            () = wait_release(Some(release), cancel) => {
1828                let now = Instant::now();
1829                while let Some(unit) = pending.pop_next_due(now) {
1830                    // Before the write, exactly as `release_due_units` does:
1831                    // a terminal write returns out of this loop, and an
1832                    // expiry's report belongs to the very unit whose write
1833                    // takes that return.
1834                    if let Some(report) = pending.take_shape_report() {
1835                        on_shape(report);
1836                    }
1837                    if let Written::Terminated { forwarded, code } = write_unit(unit, send).await? {
1838                        pending.clear();
1839                        return Ok(DrainOutcome::Terminated { forwarded, code });
1840                    }
1841                }
1842                // A refusal reports too. The clamp is decided inside
1843                // `shaping_grants_head`, which runs whether or not the head
1844                // is yielded, so reading the report only after a successful
1845                // pop would lose the one case that matters.
1846                if let Some(report) = pending.take_shape_report() {
1847                    on_shape(report);
1848                }
1849            }
1850        }
1851    }
1852    Ok(DrainOutcome::Complete)
1853}
1854
1855// ── Session close ───────────────────────────────────────────────────
1856
1857/// The default `(code, reason)` `run_with_transport` closes with when no
1858/// hook asked for anything else — today's hard-coded pair.
1859const DEFAULT_CLOSE: (u32, &[u8]) = (0, b"proxy session ended");
1860
1861/// Where [`crate::action::Action::CloseSession`] lands.
1862///
1863/// A `OnceLock<(u32, Bytes)>` plus the session's `CancellationToken`, and
1864/// deliberately **no `Transport` handles**: the transports own the tasks
1865/// that own the hooks that reach this, so holding them here would close a
1866/// reference cycle. The close itself happens where it already happens, in
1867/// `run_with_transport`, which reads [`Self::close_args`] instead of
1868/// hard-coding `(0, b"proxy session ended")`.
1869///
1870/// `CloseSession` is honoured at **every** site that returns an `Action`,
1871/// including `Site::StreamEnd` on both data and control streams: a close is
1872/// session-scoped, so no site can be the wrong one for it.
1873#[derive(Clone, Debug)]
1874pub(crate) struct SessionCloser {
1875    inner: Arc<CloserInner>,
1876}
1877
1878#[derive(Debug)]
1879struct CloserInner {
1880    request: OnceLock<(u32, Bytes, CloseOrigin)>,
1881    cancel: CancellationToken,
1882}
1883
1884/// Who asked for a session close.
1885///
1886/// Carried alongside the code and the reason because
1887/// [`ProxyEvent::SessionEnded`](crate::event::ProxyEvent::SessionEnded)
1888/// names the cause in prose, and the two callers are not interchangeable to
1889/// anyone reading that: a hook's `Action::CloseSession` is the run
1890/// under test deciding something, while
1891/// [`ProxyControl::close_session`](crate::control::ProxyControl::close_session)
1892/// is the operator outside it pulling the plug. The recorded pair alone
1893/// cannot tell them apart — both arrive as a `u32` and some bytes — and
1894/// before this every control-plane close was reported to observers as a
1895/// hook's, which is a sentence about the run that was simply untrue.
1896#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1897pub(crate) enum CloseOrigin {
1898    /// A hook returned [`crate::action::Action::CloseSession`].
1899    Hook,
1900    /// [`ProxyControl::close_session`](crate::control::ProxyControl::close_session).
1901    ControlPlane,
1902}
1903
1904impl SessionCloser {
1905    /// A closer bound to the session's cancellation token.
1906    pub(crate) fn new(cancel: CancellationToken) -> Self {
1907        Self { inner: Arc::new(CloserInner { request: OnceLock::new(), cancel }) }
1908    }
1909
1910    /// Record a hook's close request. The session keeps running until
1911    /// [`Self::cancel`].
1912    ///
1913    /// The first request wins and returns `true`. A later one changes
1914    /// nothing and returns `false` — refuse it with
1915    /// [`Refusal::SessionAlreadyClosing`](crate::capability::Refusal::SessionAlreadyClosing)
1916    /// rather than letting the second reason overwrite the first.
1917    ///
1918    /// Recording and cancelling are two calls so that `exec::execute` can
1919    /// report the close between them. Cancelling wakes every task in the
1920    /// session, and their teardown is reported from other worker threads: a
1921    /// stream-end decision on each stream the close cuts, a
1922    /// `ControlStreamTruncated`. Cancelling as it records would let those
1923    /// reports reach the observer ahead of the `ActionApplied` that caused
1924    /// them whenever the deciding thread is descheduled in between.
1925    pub(crate) fn request(&self, code: u32, reason: Bytes) -> bool {
1926        self.inner.request.set((code, reason, CloseOrigin::Hook)).is_ok()
1927    }
1928
1929    /// End the session: cancel its token. Idempotent.
1930    pub(crate) fn cancel(&self) {
1931        self.inner.cancel.cancel();
1932    }
1933
1934    /// Record a close request **without** cancelling the session.
1935    ///
1936    /// The first request wins and returns `true`, exactly as
1937    /// [`Self::request`] does; the only difference is the origin it is
1938    /// recorded under.
1939    ///
1940    /// A control-plane close has two steps — fix the code and reason, then
1941    /// give the egress queues a bounded window to flush — so its caller
1942    /// cancels only after the drain. A cancel at this point would end the
1943    /// session before the drain it just asked for had begun, and the drain
1944    /// window would be unobservable.
1945    ///
1946    /// Nothing else changes: `run_with_transport` still reads
1947    /// [`Self::close_args`] at teardown, so a session recorded this way
1948    /// closes with the recorded pair whenever it does close — whether that
1949    /// is the caller cancelling after its drain, or the peer going away
1950    /// first.
1951    pub(crate) fn record(&self, code: u32, reason: Bytes) -> bool {
1952        self.inner.request.set((code, reason, CloseOrigin::ControlPlane)).is_ok()
1953    }
1954
1955    /// Whether a close has been requested.
1956    ///
1957    /// `session.rs` reads [`Self::requested`] instead, because it wants the
1958    /// code and the reason as well; this stays as the cheap yes-or-no
1959    /// predicate for callers that need nothing more.
1960    #[allow(dead_code)]
1961    pub(crate) fn is_closing(&self) -> bool {
1962        self.inner.request.get().is_some()
1963    }
1964
1965    /// What was requested and by whom, if anything.
1966    ///
1967    /// The origin rides along rather than being a second accessor because
1968    /// the two are set together, under one `OnceLock`, and a caller that
1969    /// read them in two calls would be writing a race it does not have.
1970    pub(crate) fn requested(&self) -> Option<(u32, Bytes, CloseOrigin)> {
1971        self.inner.request.get().cloned()
1972    }
1973
1974    /// The `(code, reason)` to hand `Transport::close`, falling back to the
1975    /// pair the proxy has always used.
1976    /// The origin is deliberately dropped here: it is a fact about this
1977    /// process, and what goes on the wire is what the peer was told. A
1978    /// `CONNECTION_CLOSE` carrying *the control plane asked* would be this
1979    /// proxy leaking its own topology into somebody else's session.
1980    pub(crate) fn close_args(&self) -> (u32, Bytes) {
1981        self.inner
1982            .request
1983            .get()
1984            .map(|(code, reason, _)| (*code, reason.clone()))
1985            .unwrap_or_else(|| (DEFAULT_CLOSE.0, Bytes::from_static(DEFAULT_CLOSE.1)))
1986    }
1987}
1988
1989#[cfg(test)]
1990mod tests {
1991    use super::*;
1992
1993    /// An [`EgressSink`] that records instead of sending.
1994    #[derive(Debug, Default)]
1995    struct RecordingSink {
1996        writes: Vec<Bytes>,
1997        reset_code: Option<u64>,
1998        /// Fail every write once this many have succeeded.
1999        fail_after: Option<usize>,
2000    }
2001
2002    impl RecordingSink {
2003        fn written(&self) -> Bytes {
2004            let mut out = Vec::new();
2005            for w in &self.writes {
2006                out.extend_from_slice(w);
2007            }
2008            Bytes::from(out)
2009        }
2010    }
2011
2012    impl EgressSink for RecordingSink {
2013        fn write_all(
2014            &mut self,
2015            buf: &[u8],
2016        ) -> impl Future<Output = Result<(), EgressError>> + Send {
2017            let out = if self.fail_after.is_some_and(|n| self.writes.len() >= n) {
2018                Err(EgressError::Transport(TransportError::Write(
2019                    "recording sink is full".to_owned(),
2020                )))
2021            } else {
2022                self.writes.push(Bytes::copy_from_slice(buf));
2023                Ok(())
2024            };
2025            async move { out }
2026        }
2027
2028        fn reset(&mut self, code: u64) -> Result<(), EgressError> {
2029            self.reset_code = Some(code);
2030            Ok(())
2031        }
2032    }
2033
2034    /// The `on_shape` every drain in this module passes.
2035    ///
2036    /// Sound only because every queue built here is **unshaped** — `queue()`
2037    /// and `queue_with()` call `PendingQueue::new` and never
2038    /// `with_shaper`, and `pop_next_due` reaches no scheduler without one —
2039    /// so no `ShapeReport` can exist to swallow. A `unreachable!` rather than
2040    /// an empty body, so a later edit that installs a shaper here finds out
2041    /// instead of inheriting that silent no-op a second time.
2042    fn no_shape_reports(report: ShapeReport) {
2043        unreachable!("an unshaped queue produces no shaping report, got {report:?}");
2044    }
2045
2046    fn queue() -> (PendingQueue, Arc<Recorder>) {
2047        let counters = Arc::new(Recorder::new());
2048        (PendingQueue::new(EgressConfig::default(), Arc::clone(&counters)), counters)
2049    }
2050
2051    fn queue_with(config: EgressConfig) -> (PendingQueue, Arc<Recorder>) {
2052        let counters = Arc::new(Recorder::new());
2053        (PendingQueue::new(config, Arc::clone(&counters)), counters)
2054    }
2055
2056    // ── shaped queues ───────────────────────────────────────────────
2057    //
2058    // Every queue above is unshaped, which is deliberate: the ~25 tests in
2059    // this module encode invariants that must hold whether or not a profile
2060    // is configured, and a shaper installed on all of them would stop being
2061    // separable from them. The handful below install one.
2062
2063    use crate::shape::{
2064        BucketConfig, ClassRule, DirectionStats, Discipline, Expiry, Matcher, Overflow,
2065        QueueConfig, ShapeProfile, ShapeRecorder,
2066    };
2067
2068    /// One named bucket. Struct literals rather than field assignment:
2069    /// `#[non_exhaustive]` does not apply inside the defining crate, and
2070    /// `..Default::default()` is what clippy's `field_reassign_with_default`
2071    /// asks for.
2072    fn test_bucket(rate: Option<u64>, burst: u64) -> BucketConfig {
2073        BucketConfig {
2074            name: "b".to_string(),
2075            rate_bps: rate,
2076            burst_bytes: burst,
2077            ..BucketConfig::default()
2078        }
2079    }
2080
2081    /// One class over bucket `b`, claiming every unit.
2082    fn test_class(name: &str, priority: u8) -> ClassRule {
2083        ClassRule {
2084            name: name.to_string(),
2085            bucket: "b".to_string(),
2086            matcher: Matcher::default(),
2087            priority,
2088            weight: 1,
2089        }
2090    }
2091
2092    /// A one-class profile over one bucket, with everything a shaped queue
2093    /// reads spelled out.
2094    fn profile(rate: Option<u64>, burst: u64, max_hold: Duration, expiry: Expiry) -> ShapeProfile {
2095        let queue = QueueConfig {
2096            max_hold: Some(max_hold),
2097            overflow: Overflow::Block,
2098            on_expiry: expiry,
2099            ..QueueConfig::default()
2100        };
2101        ShapeProfile::try_new(
2102            vec![test_bucket(rate, burst)],
2103            vec![test_class("only", 0)],
2104            queue,
2105            Discipline::Fifo,
2106        )
2107        .expect("the fixture names its own bucket")
2108    }
2109
2110    /// A queue paced by `profile`, plus the recorder it writes to.
2111    ///
2112    /// One leg, because one leg is all these tests are about; the leg the
2113    /// totals actually land on is
2114    /// [`two_legs_sharing_one_recorder_charge_their_own_side`].
2115    fn shaped(profile: ShapeProfile) -> (PendingQueue, Arc<ShapeRecorder>, Arc<Scheduler>) {
2116        let counters = Arc::new(Recorder::new());
2117        let stats = Arc::new(ShapeRecorder::for_profile(Some(&profile)));
2118        let shaper = Arc::new(Scheduler::new(profile));
2119        let mut q = PendingQueue::new(EgressConfig::default(), counters).with_shaper(
2120            Some(Arc::clone(&shaper)),
2121            Arc::clone(&stats),
2122            ProxySide::ClientToProxy,
2123        );
2124        q.tag_unit(Class::Rule(0));
2125        (q, stats, shaper)
2126    }
2127
2128    fn payload(n: usize) -> Bytes {
2129        Bytes::from(vec![0xAB; n])
2130    }
2131
2132    /// The seam. A shaped head that is due on its own account is still held
2133    /// back by its bucket, and the deadline `head_release` then reports is
2134    /// the one the **scheduler** computed — not the head's own `due_at`,
2135    /// which is already in the past.
2136    ///
2137    /// That last clause is the whole reason `arm_head_at` exists: re-arming
2138    /// at `due_at` would hand back an already-fired deadline and spin the
2139    /// release branch hot until the bucket refilled.
2140    #[test]
2141    fn a_shaped_head_waits_for_its_bucket_and_reports_the_scheduler_s_deadline() {
2142        // 1000 bytes/s, 1000-byte burst: the first 1000-byte unit fits and
2143        // the second needs a full second.
2144        let (mut q, _stats, _s) =
2145            shaped(profile(Some(1_000), 1_000, Duration::from_secs(60), Expiry::Deliver));
2146        let now = Instant::now();
2147        q.push(Pending::bytes(payload(1_000), now));
2148        q.push(Pending::bytes(payload(1_000), now));
2149
2150        assert!(q.pop_next_due(now).is_some(), "the burst covers the first unit");
2151        assert!(
2152            q.pop_next_due(now).is_none(),
2153            "the burst is spent, so the second unit waits on a refill"
2154        );
2155        // The head is due on its own account — it was pushed at `now` — so a
2156        // deadline that had not been re-armed would already have fired.
2157        let release = q.head_release().expect("a non-empty queue always has one");
2158        assert!(
2159            !release.deadline().token().is_cancelled(),
2160            "the head must be armed at the refill instant, not at its own past due_at"
2161        );
2162        assert!(
2163            q.pop_next_due(now + Duration::from_secs(1)).is_some(),
2164            "a second's refill covers it"
2165        );
2166        assert!(q.is_empty());
2167    }
2168
2169    /// The spin guard.
2170    ///
2171    /// A `Hold` whose gate has been released makes its unit due, and the gate
2172    /// stays released forever. If the queue kept reporting that gate while
2173    /// the shaper was refusing the same unit, `wait_release` would resolve on
2174    /// every `select!` iteration and the pipe loop would spin hot for the
2175    /// whole of `max_hold`.
2176    #[test]
2177    fn a_shaper_holding_a_gated_unit_does_not_report_the_gate_that_freed_it() {
2178        let (mut q, _stats, _s) =
2179            shaped(profile(Some(0), 0, Duration::from_secs(60), Expiry::Deliver));
2180        let now = Instant::now();
2181        let gate = Gate::new();
2182        // A `Hold`'s shape: due at the ceiling, released early by its gate.
2183        q.push(Pending::bytes(payload(16), now + Duration::from_secs(30)).with_gate(gate.clone()));
2184
2185        assert!(q.pop_next_due(now).is_none(), "an unreleased gate leaves it not due");
2186        assert!(
2187            q.head_release().expect("queued").gate.is_some_and(|g| !g.is_released()),
2188            "before the shaper has an opinion, the unit's own gate is what it waits on"
2189        );
2190
2191        gate.release();
2192        assert!(q.pop_next_due(now).is_none(), "released and due, but the bucket refuses");
2193        let release = q.head_release().expect("still queued");
2194        assert!(
2195            !release.gate.is_some_and(|g| g.is_released()),
2196            "a released Hold gate must not be re-reported while the shaper holds the unit: \
2197             wait_release would resolve on every iteration and spin the pipe loop"
2198        );
2199    }
2200
2201    /// An **unshaped** queue is untouched by any of this: no scheduler, no
2202    /// tag, no clock read beyond what it already did.
2203    ///
2204    /// The control for every test above it. Without this row a bug that
2205    /// paced everything unconditionally would still leave the shaped tests
2206    /// green.
2207    #[test]
2208    fn an_unshaped_queue_pops_a_due_head_with_no_shaper_at_all() {
2209        let (mut q, _c) = queue();
2210        assert!(!q.is_shaped());
2211        let now = Instant::now();
2212        q.push(Pending::bytes(payload(1_000_000), now));
2213        assert!(q.pop_next_due(now).is_some(), "an unshaped queue has nothing to ask");
2214    }
2215
2216    /// A queued terminal bypasses the pacer, on a bucket that grants nothing
2217    /// at all.
2218    ///
2219    /// This is the teardown-bypasses-the-pacer rule at the one place it is
2220    /// not obvious: a `Truncate` or a hook's `ResetStream` is a *queued*
2221    /// unit, so it reaches `pop_next_due` like any other. Gating it would
2222    /// make the eleven data-then-teardown ordering tests depend on a token
2223    /// bucket.
2224    ///
2225    /// # Why the `Truncate` arm is the one that matters
2226    ///
2227    /// A bare `Terminal::Reset` carries **zero bytes**, and a zero-byte
2228    /// charge is granted by any bucket — `tokens >= 0` holds even at rate
2229    /// zero — so a `Reset`-only fixture passes with the guard deleted and
2230    /// proves nothing. A `Truncate` owes its prefix to the wire, and that
2231    /// prefix is exactly what a dry bucket would sit on. Both are asserted,
2232    /// in that order, so the row says which of the two the guard is for.
2233    #[test]
2234    fn a_terminal_is_never_gated_by_a_bucket() {
2235        let (mut q, _stats, _s) =
2236            shaped(profile(Some(0), 0, Duration::from_secs(60), Expiry::Deliver));
2237
2238        q.push(Pending::terminal(Terminal::Reset { code: 7 }));
2239        // `Pending::terminal` stamps its own `due_at` from the clock, so the
2240        // pop instant has to be taken after the push or the unit is simply
2241        // not due yet and this would pass without asking the shaper anything.
2242        let unit = q.pop_next_due(Instant::now()).expect("a reset consults no bucket");
2243        assert!(unit.is_terminal());
2244
2245        q.push(Pending::terminal(Terminal::Truncate { prefix: payload(64), code: 9 }));
2246        let unit = q
2247            .pop_next_due(Instant::now())
2248            .expect("a truncate owes its prefix to the wire, not to a bucket");
2249        assert_eq!(unit.len(), 64, "and the prefix goes with it");
2250    }
2251
2252    /// Under the default `Expiry::Deliver` a class that can never be
2253    /// granted from tokens **still delivers**, at `max_hold`, and says so.
2254    ///
2255    /// The two halves are one test because the report is what keeps the
2256    /// clamp from being a silent lateness, and the delivery is what keeps a
2257    /// starved class from being a silent loss.
2258    ///
2259    /// The report is asserted as a **whole variant**, `requested` included,
2260    /// and not as `matches!(.., Clamped { applied, .. })`: a zero-rate
2261    /// bucket names no refill instant, so the request this clamp cut short
2262    /// is unbounded and the only honest value for it is absent. Written as
2263    /// a wildcard it would pass against a `Duration::MAX` sentinel, which
2264    /// `an_unbounded_shaping_wait_reports_no_request_at_all` exists to rule
2265    /// out.
2266    #[test]
2267    fn a_zero_rate_class_still_delivers_at_the_clamp_and_reports_it() {
2268        const HOLD: Duration = Duration::from_millis(80);
2269        let (mut q, stats, _s) = shaped(profile(Some(0), 0, HOLD, Expiry::Deliver));
2270        let now = Instant::now();
2271        q.push(Pending::bytes(payload(16), now));
2272
2273        assert!(q.pop_next_due(now).is_none(), "a zero-rate bucket grants nothing");
2274        assert_eq!(
2275            stats.snapshot().classes[0].tokens_exhausted_episodes,
2276            1,
2277            "and says its own bucket was dry, once per episode"
2278        );
2279        assert_eq!(q.take_shape_report(), None, "nothing is clamped before the clamp");
2280
2281        let unit = q
2282            .pop_next_due(now + HOLD + Duration::from_millis(1))
2283            .expect("Expiry::Deliver clamps and delivers");
2284        assert_eq!(unit.len(), 16);
2285        assert_eq!(
2286            q.take_shape_report(),
2287            Some(ShapeReport::Clamped { requested: None, applied: HOLD }),
2288            "a clamped release reports the clamp it applied, and reports the \
2289             request it cut short as absent because a zero-rate bucket named none"
2290        );
2291        assert_eq!(stats.snapshot().classes[0].bytes_delivered, 16);
2292        assert_eq!(stats.snapshot().objects_expired, 0, "Deliver never expires an object");
2293    }
2294
2295    /// A shaping clamp reports **no request at all**, on both of the two
2296    /// ways a bucket can decline to name a refill instant.
2297    /// The two legs are the two refusals that name no instant: a rate of zero,
2298    /// and a unit larger than the burst the bucket can ever hold. Both are
2299    /// released by the clamp and by nothing else, and neither has a duration to
2300    /// quote — the wait was unbounded. Leg (b) additionally has a **positive**
2301    /// rate, so it rules out *the report is absent because the class is
2302    /// switched off*.
2303    ///
2304    /// Asserted as a whole-variant equality, which is the point: a
2305    /// `Duration::MAX` sentinel here would satisfy every inequality an author
2306    /// would naturally write against the field — `requested > applied` —
2307    /// exactly as a real request does.
2308    /// The **pre-clamp** report is asserted per leg for the same reason, and it
2309    /// is where the two legs stop looking alike: a zero-rate class is silent
2310    /// until its clamp fires, and a class whose burst cannot cover one of its
2311    /// own units says so at the first refusal. That asymmetry is the only thing
2312    /// separating *this rate is being applied and it is low* from *this rate is
2313    /// not being applied at all*, since both produce this same `Clamped`
2314    /// afterwards.
2315    #[test]
2316    fn an_unbounded_shaping_wait_reports_no_request_at_all() {
2317        const HOLD: Duration = Duration::from_millis(80);
2318        /// Bigger than the burst leg (b) configures, so the bucket can never
2319        /// hold enough for it however long it accrues.
2320        const OVERSIZED: usize = 4_096;
2321
2322        let legs = [
2323            ("a zero rate", profile(Some(0), 0, HOLD, Expiry::Deliver), 16, None),
2324            (
2325                "a unit above burst_bytes",
2326                profile(Some(64_000), 64, HOLD, Expiry::Deliver),
2327                OVERSIZED,
2328                Some(ShapeReport::BurstBelowUnit {
2329                    class: Class::Rule(0),
2330                    burst_bytes: 64,
2331                    unit_bytes: OVERSIZED as u64,
2332                }),
2333            ),
2334        ];
2335
2336        for (label, profile, bytes, pre_clamp) in legs {
2337            let (mut q, _stats, _s) = shaped(profile);
2338            let now = Instant::now();
2339            q.push(Pending::bytes(payload(bytes), now));
2340
2341            assert!(q.pop_next_due(now).is_none(), "{label}: nothing may be granted up front");
2342            assert_eq!(
2343                q.take_shape_report(),
2344                pre_clamp,
2345                "{label}: nothing is *clamped* before the clamp, and only the \
2346                 mis-sized burst says anything at all before it"
2347            );
2348
2349            let unit = q
2350                .pop_next_due(now + HOLD + Duration::from_millis(1))
2351                .unwrap_or_else(|| panic!("{label}: Expiry::Deliver clamps and delivers"));
2352            assert_eq!(unit.len(), bytes, "{label}: the whole unit goes out");
2353            assert_eq!(
2354                q.take_shape_report(),
2355                Some(ShapeReport::Clamped { requested: None, applied: HOLD }),
2356                "{label}: the bucket named no instant, so there is no request to \
2357                 quote and the report must say so rather than name a sentinel"
2358            );
2359        }
2360    }
2361
2362    /// `Expiry::ResetStream` abandons the whole destination stream: the
2363    /// queue is replaced by the reset, the object is counted as expired and
2364    /// the stream as reset by shaping.
2365    ///
2366    /// Everything queued goes with it, and that is deliberate — writing part
2367    /// of a stream the shaper has given up on leaves the peer a prefix it
2368    /// cannot tell from a truncation.
2369    #[test]
2370    fn expiry_reset_stream_replaces_the_queue_with_its_reset() {
2371        const HOLD: Duration = Duration::from_millis(50);
2372        let (mut q, stats, _s) =
2373            shaped(profile(Some(0), 0, HOLD, Expiry::ResetStream { code: 0x2A }));
2374        let now = Instant::now();
2375        q.push(Pending::bytes(payload(16), now));
2376        q.push(Pending::bytes(payload(16), now));
2377
2378        let unit = q
2379            .pop_next_due(now + HOLD + Duration::from_millis(1))
2380            .expect("an expired head under ResetStream yields the reset");
2381        assert_eq!(unit.item(), &Item::Terminal(Terminal::Reset { code: 0x2A }));
2382        assert_eq!(q.take_shape_report(), Some(ShapeReport::Expired));
2383        assert!(q.is_empty(), "everything behind it went with the stream");
2384
2385        let snap = stats.snapshot();
2386        assert_eq!(snap.objects_expired, 1);
2387        assert_eq!(snap.streams_reset_by_shaping, 1);
2388    }
2389
2390    /// Two queues, one recorder, one leg each: what a queue charges lands on
2391    /// the leg it was built for.
2392    ///
2393    /// This is the seam between the split totals and the thing that knows
2394    /// which side a stream is on. The recorder is session-scoped and shared
2395    /// by every forwarding task, so it cannot work the direction out for
2396    /// itself; the queue is per stream direction and can. A recorder that
2397    /// could split but that nothing ever told which leg to charge would
2398    /// report one side doing all the work and the other doing none, which
2399    /// is indistinguishable from a session that really was one-way.
2400    ///
2401    /// The two legs are deliberately asymmetric in **opposite** senses —
2402    /// the uplink carries fewer bytes and takes the expiry, the downlink
2403    /// carries six times the bytes and expires nothing — so swapping the
2404    /// rows fails on every field rather than cancelling out.
2405    ///
2406    /// `objects_seen` is zero on both legs, and it is asserted rather than
2407    /// elided: it is charged where a unit is classified, which is the pipe
2408    /// loop's job and not this queue's, so a queue that started moving it
2409    /// would be counting the same object twice.
2410    #[test]
2411    fn two_legs_sharing_one_recorder_charge_their_own_side() {
2412        const HOLD: Duration = Duration::from_millis(50);
2413        // A bucket that grants nothing, so nothing leaves except through
2414        // the clamp, and the clamp is what the expiry arm decides.
2415        let p = profile(Some(0), 0, HOLD, Expiry::ResetStream { code: 0x2A });
2416        let counters = Arc::new(Recorder::new());
2417        let stats = Arc::new(ShapeRecorder::for_profile(Some(&p)));
2418        let shaper = Arc::new(Scheduler::new(p));
2419
2420        let mut up = PendingQueue::new(EgressConfig::default(), Arc::clone(&counters)).with_shaper(
2421            Some(Arc::clone(&shaper)),
2422            Arc::clone(&stats),
2423            ProxySide::ClientToProxy,
2424        );
2425        let mut down = PendingQueue::new(EgressConfig::default(), counters).with_shaper(
2426            Some(shaper),
2427            Arc::clone(&stats),
2428            ProxySide::RelayToProxy,
2429        );
2430
2431        let now = Instant::now();
2432        // Untagged, so these are the units no rule could see — the ones
2433        // charged to `bytes_shaped` as they are queued. Different sizes on
2434        // the two legs, so a leg that counted pushes rather than bytes is
2435        // separable from one that adds.
2436        up.push(Pending::bytes(payload(16), now));
2437        down.push(Pending::bytes(payload(48), now));
2438        down.push(Pending::bytes(payload(48), now));
2439
2440        // The uplink head outlives its clamp and the stream is abandoned.
2441        assert!(
2442            up.pop_next_due(now + HOLD + Duration::from_millis(1)).is_some(),
2443            "an expired head under ResetStream yields the reset"
2444        );
2445        // The downlink's head is inside its clamp and goes out normally —
2446        // unshapeable bytes charge no bucket — so nothing on that leg
2447        // expires and nothing on it is reset.
2448        assert!(down.pop_next_due(now).is_some(), "the downlink head is not past its clamp");
2449
2450        let snap = stats.snapshot();
2451        assert_eq!(
2452            snap.uplink,
2453            DirectionStats {
2454                objects_seen: 0,
2455                bytes_shaped: 16,
2456                objects_expired: 1,
2457                streams_reset_by_shaping: 1,
2458                streams_with_mixed_classes: 0,
2459            },
2460            "the uplink queue's bytes and its expiry are the uplink's"
2461        );
2462        assert_eq!(
2463            snap.downlink,
2464            DirectionStats {
2465                objects_seen: 0,
2466                bytes_shaped: 96,
2467                objects_expired: 0,
2468                streams_reset_by_shaping: 0,
2469                streams_with_mixed_classes: 0,
2470            },
2471            "the downlink queued more and gave nothing up: a stall on one leg \
2472             must not be reported on the other"
2473        );
2474        assert_eq!(snap.bytes_shaped, 112, "the aggregate is the two legs and nothing else");
2475        assert_eq!(snap.objects_expired, 1);
2476        assert_eq!(snap.streams_reset_by_shaping, 1);
2477    }
2478
2479    /// A unit queued behind a unit of a **different** class is counted once,
2480    /// against its own class, and never against `tokens_exhausted_episodes`
2481    /// — which belongs to the class whose bucket was actually dry.
2482    ///
2483    /// Two causes of waiting, two counters. Conflating them is what makes
2484    /// head-of-line blocking read as configured shaping.
2485    #[test]
2486    fn a_unit_behind_another_class_is_counted_once_and_separately() {
2487        // Two classes, one bucket that grants nothing, so the head parks.
2488        let queue =
2489            QueueConfig { max_hold: Some(Duration::from_secs(60)), ..QueueConfig::default() };
2490        let p = ShapeProfile::try_new(
2491            vec![test_bucket(Some(0), 0)],
2492            vec![test_class("head", 0), test_class("behind", 0)],
2493            queue,
2494            Discipline::Fifo,
2495        )
2496        .expect("two uniquely named classes over one bucket");
2497
2498        let counters = Arc::new(Recorder::new());
2499        let stats = Arc::new(ShapeRecorder::for_profile(Some(&p)));
2500        let mut q = PendingQueue::new(EgressConfig::default(), counters).with_shaper(
2501            Some(Arc::new(Scheduler::new(p))),
2502            Arc::clone(&stats),
2503            ProxySide::ClientToProxy,
2504        );
2505
2506        let now = Instant::now();
2507        q.tag_unit(Class::Rule(0));
2508        q.push(Pending::bytes(payload(16), now));
2509        q.tag_unit(Class::Rule(1));
2510        q.push(Pending::bytes(payload(16), now));
2511        q.push(Pending::bytes(payload(16), now));
2512
2513        // Three refusals; the sweep must still charge each waiting unit once.
2514        for _ in 0..3 {
2515            assert!(q.pop_next_due(now).is_none());
2516        }
2517        let snap = stats.snapshot();
2518        assert_eq!(
2519            snap.classes[1].starved_behind_other_class, 2,
2520            "both units behind the other class's head are counted, once each"
2521        );
2522        assert_eq!(
2523            snap.classes[0].starved_behind_other_class, 0,
2524            "the head is not waiting behind anybody"
2525        );
2526        assert_eq!(
2527            snap.classes[0].tokens_exhausted_episodes, 1,
2528            "the dry bucket is the head's own, and it is one episode"
2529        );
2530    }
2531
2532    /// The demand a queue declares is withdrawn when it is dropped.
2533    ///
2534    /// Not a nicety: a declaration that outlives its stream starves every
2535    /// lower-priority class for the rest of the session, and the symptom is
2536    /// a *different* stream stalling to `max_hold`.
2537    #[test]
2538    fn dropping_a_queue_withdraws_the_demand_it_declared() {
2539        let queue =
2540            QueueConfig { max_hold: Some(Duration::from_secs(60)), ..QueueConfig::default() };
2541        let p = ShapeProfile::try_new(
2542            vec![test_bucket(None, 0)],
2543            vec![test_class("hi", 9), test_class("lo", 0)],
2544            queue,
2545            Discipline::StrictPriority,
2546        )
2547        .expect("two uniquely named classes over one bucket");
2548
2549        let counters = Arc::new(Recorder::new());
2550        let stats = Arc::new(ShapeRecorder::for_profile(Some(&p)));
2551        let shaper = Arc::new(Scheduler::new(p));
2552
2553        let now = Instant::now();
2554        {
2555            let mut hi = PendingQueue::new(EgressConfig::default(), Arc::clone(&counters))
2556                .with_shaper(
2557                    Some(Arc::clone(&shaper)),
2558                    Arc::clone(&stats),
2559                    ProxySide::ClientToProxy,
2560                );
2561            hi.tag_unit(Class::Rule(0));
2562            // Due a minute out, so it stays queued and keeps declaring.
2563            hi.push(Pending::bytes(payload(16), now + Duration::from_secs(60)));
2564            assert!(
2565                matches!(shaper.acquire(Class::Rule(1), 16, now), Acquire::Starved(_)),
2566                "the high class is holding the bucket while its queue is alive"
2567            );
2568        }
2569        assert!(
2570            matches!(shaper.acquire(Class::Rule(1), 16, now), Acquire::Now),
2571            "the high class's stream is gone, so nothing is holding the low one back"
2572        );
2573    }
2574
2575    #[test]
2576    fn an_empty_queue_waits_for_nothing_and_accepts_more() {
2577        let (q, _c) = queue();
2578        assert!(q.is_empty());
2579        assert_eq!(q.len(), 0);
2580        assert_eq!(q.queued_bytes(), 0);
2581        assert!(q.accepts_more());
2582        assert!(q.head_release().is_none());
2583    }
2584
2585    #[test]
2586    fn a_later_unit_cannot_overtake_an_earlier_one() {
2587        let (mut q, _c) = queue();
2588        let now = Instant::now();
2589        let head =
2590            q.push(Pending::bytes(Bytes::from_static(b"0"), now + Duration::from_millis(300)));
2591        // A `Pass` — no delay at all — pushed behind it.
2592        let behind = q.push(Pending::bytes(Bytes::from_static(b"1"), now));
2593        assert_eq!(
2594            behind.release_at, head.release_at,
2595            "the reported release is the queue's estimate, and it accounts for the head",
2596        );
2597        // And a third with a smaller delay than the head's.
2598        let third =
2599            q.push(Pending::bytes(Bytes::from_static(b"2"), now + Duration::from_millis(10)));
2600        assert_eq!(third.release_at, head.release_at);
2601        assert_eq!(q.len(), 3);
2602        assert_eq!(q.queued_bytes(), 3);
2603    }
2604
2605    /// The estimate is reporting; `due_at` is readiness. Conflating the two
2606    /// would strand whole streams behind a released `Hold`.
2607    #[test]
2608    fn the_queues_estimate_never_rewrites_a_units_own_deadline() {
2609        let (mut q, _c) = queue();
2610        let now = Instant::now();
2611        q.push(Pending::bytes(Bytes::from_static(b"0"), now + Duration::from_millis(300)));
2612        q.push(Pending::bytes(Bytes::from_static(b"1"), now));
2613        q.push(Pending::bytes(Bytes::from_static(b"2"), now + Duration::from_millis(10)));
2614
2615        // Popped at a time past everything, so the deque hands all three
2616        // back and each can be asked what it was actually built with.
2617        let far = now + Duration::from_secs(1);
2618        let units: Vec<Pending> = std::iter::from_fn(|| q.pop_next_due(far)).collect();
2619        assert_eq!(units.len(), 3);
2620        assert_eq!(units[0].due_at(), now + Duration::from_millis(300));
2621        assert_eq!(units[1].due_at(), now, "a `Pass` behind a delay keeps its own `now`");
2622        assert_eq!(units[2].due_at(), now + Duration::from_millis(10));
2623        // The estimate is the clamped one, on every unit.
2624        for unit in &units {
2625            assert_eq!(unit.expected_at(), units[0].due_at());
2626        }
2627    }
2628
2629    #[test]
2630    fn pushing_bumps_the_egress_counter_once_per_unit() {
2631        let (mut q, counters) = queue();
2632        let now = Instant::now();
2633        for _ in 0..4 {
2634            q.push(Pending::bytes(Bytes::from_static(b"x"), now));
2635        }
2636        assert_eq!(counters.snapshot().egress_items_queued, 4);
2637    }
2638
2639    #[test]
2640    fn a_unit_that_is_due_pops_and_one_that_is_not_does_not() {
2641        let (mut q, _c) = queue();
2642        let now = Instant::now();
2643        q.push(Pending::bytes(Bytes::from_static(b"soon"), now));
2644        q.push(Pending::bytes(Bytes::from_static(b"later"), now + Duration::from_secs(60)));
2645        let first = q.pop_next_due(now).expect("head is due");
2646        assert_eq!(first.len(), 4);
2647        assert!(q.pop_next_due(now).is_none(), "the tail is a minute out");
2648        assert_eq!(q.queued_bytes(), 5);
2649        assert!(q.head_release().is_some());
2650    }
2651
2652    #[test]
2653    fn a_released_gate_makes_a_unit_due_before_its_ceiling() {
2654        let (mut q, _c) = queue();
2655        let now = Instant::now();
2656        let gate = Gate::new();
2657        // The `max_hold` ceiling: 30 s out.
2658        q.push(
2659            Pending::bytes(Bytes::from_static(b"held"), hold_ceiling(now, q.config()))
2660                .with_gate(gate.clone()),
2661        );
2662        assert!(q.pop_next_due(Instant::now()).is_none());
2663        gate.release();
2664        let unit = q
2665            .pop_next_due(Instant::now())
2666            .expect("a released gate is due, or the release arm spins until max_hold");
2667        assert_eq!(unit.len(), 4);
2668    }
2669
2670    /// Releasing a gate frees the held unit **and the run behind it**.
2671    ///
2672    /// The head carries the gate; the three units behind it carry none, so
2673    /// the only thing that can make them due is their own deadline. They
2674    /// have one — `now` — and it is theirs, which is the whole point. When
2675    /// the queue stamped them with the hold's `max_hold` ceiling instead,
2676    /// one gate release wrote one object and stranded every object behind
2677    /// it, plus the stream's FIN, for 30 s.
2678    #[test]
2679    fn releasing_a_gate_frees_the_whole_run_queued_behind_it() {
2680        let (mut q, _c) = queue();
2681        let now = Instant::now();
2682        let gate = Gate::new();
2683        q.push(
2684            Pending::bytes(Bytes::from_static(b"held"), hold_ceiling(now, q.config()))
2685                .with_gate(gate.clone()),
2686        );
2687        // Three plain `Pass` units, decided after the hold, so they are
2688        // pushed for ordering and nothing else.
2689        for tail in [&b"1"[..], b"2", b"3"] {
2690            q.push(Pending::bytes(Bytes::copy_from_slice(tail), Instant::now()));
2691        }
2692        assert_eq!(q.len(), 4);
2693        assert!(q.pop_next_due(Instant::now()).is_none(), "the head holds the whole run");
2694
2695        gate.release();
2696        let at = Instant::now();
2697        let drained: Vec<usize> =
2698            std::iter::from_fn(|| q.pop_next_due(at)).map(|u| u.len()).collect();
2699        assert_eq!(
2700            drained,
2701            vec![4, 1, 1, 1],
2702            "one wake on the gate must free the head *and* everything behind it, in order",
2703        );
2704        assert!(q.is_empty(), "nothing may be left for the max_hold ceiling to release");
2705        assert_eq!(q.queued_bytes(), 0);
2706    }
2707
2708    /// A `Delay` queued behind a `Hold` keeps its **own** deadline.
2709    /// `Action::Delay` documents `arrived_at + by` — *a deadline, not a
2710    /// spacing* — and a `Hold` in front of it is not allowed to redefine that
2711    /// as the hold's ceiling. The unit is not due at the gate release, because
2712    /// its own 50 ms have not passed; it *is* due 50 ms later, rather than at
2713    /// the 20 s ceiling.
2714    #[test]
2715    fn a_delay_queued_behind_a_hold_keeps_its_own_deadline() {
2716        const CEILING: Duration = Duration::from_secs(20);
2717        const BY: Duration = Duration::from_millis(50);
2718
2719        let config = EgressConfig { max_hold: CEILING, ..EgressConfig::default() };
2720        let (mut q, _c) = queue_with(config);
2721        let now = Instant::now();
2722        let gate = Gate::new();
2723        q.push(
2724            Pending::bytes(Bytes::from_static(b"held"), hold_ceiling(now, q.config()))
2725                .with_gate(gate.clone()),
2726        );
2727        let queued = q.push(Pending::bytes(Bytes::from_static(b"late"), now + BY));
2728        assert!(
2729            queued.release_at >= now + CEILING,
2730            "the *estimate* stays conservative — the queue cannot know when a gate opens",
2731        );
2732
2733        gate.release();
2734        let head = q.pop_next_due(now + Duration::from_millis(10)).expect("a released gate is due");
2735        assert_eq!(head.len(), 4);
2736        assert!(
2737            q.pop_next_due(now + Duration::from_millis(10)).is_none(),
2738            "the delayed unit's own deadline still governs it: 10 ms is inside its {BY:?}",
2739        );
2740        let second = q
2741            .pop_next_due(now + BY + Duration::from_millis(10))
2742            .expect("arrived_at + by has passed, and that is the whole of the deadline");
2743        assert_eq!(second.len(), 4);
2744        assert_eq!(second.due_at(), now + BY, "its deadline was never rewritten");
2745    }
2746
2747    #[test]
2748    fn the_byte_budget_stops_the_read_branch_and_reports_once() {
2749        // A struct literal is legal here and not in `tests/`: this file is
2750        // inside the defining crate, where `#[non_exhaustive]` does not
2751        // apply.
2752        let config = EgressConfig { max_pending_bytes: 8, ..EgressConfig::default() };
2753        let (mut q, _c) = queue_with(config);
2754        let now = Instant::now();
2755        let first = q.push(Pending::bytes(Bytes::from_static(b"1234"), now));
2756        assert!(!first.entered_backpressure);
2757        assert!(q.accepts_more());
2758        let second = q.push(Pending::bytes(Bytes::from_static(b"5678"), now));
2759        assert!(second.entered_backpressure, "the transition into backpressure is reported");
2760        assert!(!q.accepts_more());
2761        let third = q.push(Pending::bytes(Bytes::from_static(b"9"), now));
2762        assert!(!third.entered_backpressure, "once per stream, not once per push");
2763    }
2764
2765    #[test]
2766    fn the_unit_cap_bounds_a_queue_of_zero_byte_units() {
2767        let (mut q, _c) = queue();
2768        let now = Instant::now();
2769        let mut reports = 0;
2770        for _ in 0..=MAX_PENDING_UNITS {
2771            if q.push(Pending::elided(now)).entered_backpressure {
2772                reports += 1;
2773            }
2774        }
2775        assert_eq!(q.queued_bytes(), 0, "elided units carry no bytes at all");
2776        assert!(!q.accepts_more(), "the byte budget alone would never stop this");
2777        assert_eq!(reports, 1);
2778    }
2779
2780    #[tokio::test]
2781    async fn an_elided_unit_writes_nothing_but_keeps_its_slot() {
2782        let (mut q, _c) = queue();
2783        let now = Instant::now();
2784        q.push(Pending::bytes(Bytes::from_static(b"a"), now));
2785        q.push(Pending::elided(now));
2786        q.push(Pending::bytes(Bytes::from_static(b"c"), now));
2787        let mut sink = RecordingSink::default();
2788        assert_eq!(q.drain_ignoring_release_times(&mut sink).await, DrainOutcome::Complete);
2789        assert_eq!(sink.written(), Bytes::from_static(b"ac"));
2790        assert_eq!(sink.writes.len(), 2);
2791    }
2792
2793    #[tokio::test]
2794    async fn a_truncate_terminal_writes_its_prefix_then_resets() {
2795        let (mut q, _c) = queue();
2796        let now = Instant::now();
2797        q.push(Pending::bytes(Bytes::from_static(b"ahead"), now));
2798        q.push(Pending::terminal(Terminal::Truncate {
2799            prefix: Bytes::from_static(b"pre"),
2800            code: 0x2,
2801        }));
2802        q.push(Pending::bytes(Bytes::from_static(b"never"), now));
2803        let mut sink = RecordingSink::default();
2804        let outcome = q.drain_ignoring_release_times(&mut sink).await;
2805        assert_eq!(outcome, DrainOutcome::Terminated { forwarded: 3, code: 0x2 });
2806        assert_eq!(sink.written(), Bytes::from_static(b"aheadpre"));
2807        assert_eq!(sink.reset_code, Some(0x2));
2808        assert!(q.is_empty(), "nothing behind a terminal is written");
2809        assert_eq!(q.queued_bytes(), 0);
2810    }
2811
2812    #[tokio::test]
2813    async fn a_reset_terminal_writes_nothing() {
2814        let mut sink = RecordingSink::default();
2815        let written = write_unit(Pending::terminal(Terminal::Reset { code: 7 }), &mut sink)
2816            .await
2817            .expect("reset never fails the write");
2818        assert_eq!(written, Written::Terminated { forwarded: 0, code: 7 });
2819        assert!(sink.writes.is_empty());
2820        assert_eq!(sink.reset_code, Some(7));
2821    }
2822
2823    #[tokio::test]
2824    async fn a_failed_drain_leaves_what_it_could_not_write_queued() {
2825        let (mut q, _c) = queue();
2826        let now = Instant::now();
2827        q.push(Pending::bytes(Bytes::from_static(b"aa"), now));
2828        q.push(Pending::bytes(Bytes::from_static(b"bbb"), now));
2829        q.push(Pending::bytes(Bytes::from_static(b"cccc"), now));
2830        let mut sink = RecordingSink { fail_after: Some(1), ..RecordingSink::default() };
2831        assert_eq!(q.drain_ignoring_release_times(&mut sink).await, DrainOutcome::WriteFailed);
2832        assert_eq!(sink.written(), Bytes::from_static(b"aa"));
2833        assert_eq!(q.len(), 2);
2834        // This is what `Impairment { QueuedBytesAtTeardown { bytes } }` carries.
2835        assert_eq!(q.queued_bytes(), 7);
2836    }
2837
2838    #[tokio::test]
2839    async fn the_honouring_drain_writes_in_order_at_release_time() {
2840        let (mut q, counters) = queue();
2841        let now = Instant::now();
2842        let head_due = now + Duration::from_millis(30);
2843        q.push(Pending::bytes(Bytes::from_static(b"0"), head_due));
2844        q.push(Pending::bytes(Bytes::from_static(b"1"), now));
2845        q.push(Pending::bytes(Bytes::from_static(b"2"), now));
2846        let cancel = CancellationToken::new();
2847        let mut sink = RecordingSink::default();
2848        let outcome = drain_honouring_release_times(&mut q, &mut sink, &cancel, no_shape_reports)
2849            .await
2850            .expect("no write failed");
2851        assert_eq!(outcome, DrainOutcome::Complete);
2852        // Measured against the head's own deadline, not a fresh clock read
2853        // taken after the pushes: the drain promises to write nothing before
2854        // `head_due`, and time the test thread spends preempted between
2855        // `now` and a later read is not time the drain owes.
2856        assert!(Instant::now() >= head_due, "release times were honoured");
2857        assert_eq!(sink.written(), Bytes::from_static(b"012"));
2858        assert_eq!(counters.snapshot().release_errors.count, 0, "drains are never release samples");
2859        assert_eq!(
2860            q.unconfirmed_bytes(),
2861            0,
2862            "a drain that ran to completion at its release times is not a teardown and owes \
2863             no `QueuedBytesAtTeardown`",
2864        );
2865    }
2866
2867    /// A teardown flush reports what it handed to a dying transport.
2868    ///
2869    /// The teardown-race loss, at queue level. `drain_ignoring_release_times`
2870    /// writes the held unit and `write_all` returns `Ok` — quinn buffered it —
2871    /// so **nothing is left queued**, and a report built on
2872    /// [`PendingQueue::queued_bytes`] says zero for exactly the case where
2873    /// `run_with_transport`'s `close()` then discards the buffer and the
2874    /// object never reaches the peer. `a_held_object_is_never_silently_lost_at_teardown`
2875    /// is the same claim over real QUIC; this is the mechanism, with no
2876    /// scheduler in it.
2877    #[tokio::test]
2878    async fn a_teardown_flush_reports_what_it_handed_to_a_dying_transport() {
2879        let (mut q, _c) = queue();
2880        // A hold nobody will release, so only the cancel fallback can move
2881        // it.
2882        q.push(
2883            Pending::bytes(Bytes::from_static(b"gone"), hold_ceiling(Instant::now(), q.config()))
2884                .with_gate(Gate::new()),
2885        );
2886        let cancel = CancellationToken::new();
2887        cancel.cancel();
2888        let mut sink = RecordingSink::default();
2889
2890        let outcome = drain_honouring_release_times(&mut q, &mut sink, &cancel, no_shape_reports)
2891            .await
2892            .expect("the fallback drain swallows write failures");
2893        assert_eq!(outcome, DrainOutcome::CancelledMidDrain);
2894        assert_eq!(sink.written(), Bytes::from_static(b"gone"), "delivered late beats lost");
2895        assert_eq!(q.queued_bytes(), 0, "the fallback handed everything to the transport");
2896        assert_eq!(
2897            q.unconfirmed_bytes(),
2898            4,
2899            "…and handing bytes to a transport the session is closing is not delivering \
2900             them: this is what `QueuedBytesAtTeardown` has to carry, or the object is \
2901             gone with no event at all",
2902        );
2903    }
2904
2905    #[tokio::test]
2906    async fn a_teardown_flush_that_could_not_write_reports_both_halves() {
2907        let (mut q, _c) = queue();
2908        let now = Instant::now();
2909        q.push(Pending::bytes(Bytes::from_static(b"aa"), now));
2910        q.push(Pending::bytes(Bytes::from_static(b"bbb"), now));
2911        q.push(Pending::bytes(Bytes::from_static(b"cccc"), now));
2912        let mut sink = RecordingSink { fail_after: Some(1), ..RecordingSink::default() };
2913        assert_eq!(q.drain_ignoring_release_times(&mut sink).await, DrainOutcome::WriteFailed);
2914        assert_eq!(q.queued_bytes(), 7, "what never reached the transport");
2915        assert_eq!(q.unconfirmed_bytes(), 9, "…plus the two bytes that did, and may be lost");
2916    }
2917
2918    #[tokio::test]
2919    async fn cancelling_a_drain_that_is_waiting_on_a_gate_falls_back_at_once() {
2920        let (mut q, _c) = queue();
2921        let now = Instant::now();
2922        // A hold nobody will ever release, at the 30 s ceiling.
2923        q.push(
2924            Pending::bytes(Bytes::from_static(b"held"), hold_ceiling(now, q.config()))
2925                .with_gate(Gate::new()),
2926        );
2927        let cancel = CancellationToken::new();
2928        let waker = cancel.clone();
2929        tokio::spawn(async move {
2930            tokio::time::sleep(Duration::from_millis(20)).await;
2931            waker.cancel();
2932        });
2933        let mut sink = RecordingSink::default();
2934        let started = Instant::now();
2935        let outcome = drain_honouring_release_times(&mut q, &mut sink, &cancel, no_shape_reports)
2936            .await
2937            .expect("no write failed");
2938        assert_eq!(outcome, DrainOutcome::CancelledMidDrain);
2939        assert!(
2940            started.elapsed() < Duration::from_secs(2),
2941            "teardown must not wait out max_hold; took {:?}",
2942            started.elapsed(),
2943        );
2944        assert_eq!(
2945            sink.written(),
2946            Bytes::from_static(b"held"),
2947            "delivered late beats lost silently"
2948        );
2949        assert_eq!(q.queued_bytes(), 0);
2950    }
2951
2952    #[tokio::test]
2953    async fn an_already_cancelled_drain_does_not_spin() {
2954        let (mut q, _c) = queue();
2955        q.push(Pending::bytes(Bytes::from_static(b"x"), Instant::now() + Duration::from_secs(60)));
2956        let cancel = CancellationToken::new();
2957        cancel.cancel();
2958        let mut sink = RecordingSink::default();
2959        let started = Instant::now();
2960        let outcome = drain_honouring_release_times(&mut q, &mut sink, &cancel, no_shape_reports)
2961            .await
2962            .unwrap();
2963        assert_eq!(outcome, DrainOutcome::CancelledMidDrain);
2964        assert!(started.elapsed() < Duration::from_secs(1), "the biased arm must win");
2965        assert_eq!(sink.written(), Bytes::from_static(b"x"));
2966    }
2967
2968    #[tokio::test]
2969    async fn wait_release_resolves_on_the_gate_the_deadline_or_the_cancel() {
2970        let (mut q, _c) = queue();
2971        let far = Instant::now() + Duration::from_secs(60);
2972
2973        // 1. The gate.
2974        let gate = Gate::new();
2975        q.push(Pending::bytes(Bytes::from_static(b"g"), far).with_gate(gate.clone()));
2976        gate.release();
2977        let cancel = CancellationToken::new();
2978        wait_release(q.head_release(), &cancel).await;
2979
2980        // 2. The deadline.
2981        let (mut q, _c) = queue();
2982        q.push(Pending::bytes(
2983            Bytes::from_static(b"d"),
2984            Instant::now() + Duration::from_millis(20),
2985        ));
2986        let started = Instant::now();
2987        wait_release(q.head_release(), &cancel).await;
2988        assert!(started.elapsed() >= Duration::from_millis(20));
2989
2990        // 3. The session cancel.
2991        let (mut q, _c) = queue();
2992        q.push(Pending::bytes(Bytes::from_static(b"c"), far));
2993        cancel.cancel();
2994        wait_release(q.head_release(), &cancel).await;
2995
2996        // 4. `None` parks until cancellation rather than resolving at once.
2997        wait_release(None, &cancel).await;
2998    }
2999
3000    #[tokio::test]
3001    async fn the_pipe_loop_shape_from_the_contract_compiles_and_keeps_order() {
3002        // The pipe loop's `select!`, with the read half standing in for
3003        // `recv.read` and a recording sink for `send`. What it proves is
3004        // the shape: both branch expressions are borrow-free, the read
3005        // guard and the release branch coexist, and a delayed head holds
3006        // everything behind it.
3007        let (mut pending, counters) = queue();
3008        let cancel = CancellationToken::new();
3009        let mut sink = RecordingSink::default();
3010        let (tx, mut rx) = tokio::sync::mpsc::channel::<Bytes>(8);
3011
3012        tokio::spawn(async move {
3013            for chunk in [&b"0"[..], b"1", b"2", b"3"] {
3014                tx.send(Bytes::from_static(chunk)).await.expect("receiver lives");
3015            }
3016            // Stay open past the head's release time, or the source FINs
3017            // first and the FIN drain — not the release branch — is what
3018            // writes everything.
3019            tokio::time::sleep(Duration::from_millis(200)).await;
3020        });
3021
3022        let mut first = true;
3023        loop {
3024            let can_read = pending.accepts_more();
3025            let head_release = pending.head_release();
3026
3027            tokio::select! {
3028                chunk = rx.recv(), if can_read => {
3029                    match chunk {
3030                        Some(raw) => {
3031                            // Object 0 is delayed; 1.. are not, and must
3032                            // still not overtake it.
3033                            let release_at = if std::mem::take(&mut first) {
3034                                Instant::now() + Duration::from_millis(40)
3035                            } else {
3036                                Instant::now()
3037                            };
3038                            if pending.is_empty() && release_at <= Instant::now() {
3039                                // Today's fast path: written inline in the read arm.
3040                                write_unit(Pending::bytes(raw, release_at), &mut sink)
3041                                    .await
3042                                    .expect("recording sink");
3043                            } else {
3044                                pending.push(Pending::bytes(raw, release_at));
3045                            }
3046                        }
3047                        None => {
3048                            let outcome =
3049                                drain_honouring_release_times(&mut pending, &mut sink, &cancel, no_shape_reports)
3050                                    .await
3051                                    .expect("recording sink");
3052                            assert_eq!(outcome, DrainOutcome::Complete);
3053                            break;
3054                        }
3055                    }
3056                }
3057                () = wait_release(head_release.clone(), &cancel), if head_release.is_some() => {
3058                    let now = Instant::now();
3059                    while let Some(unit) = pending.pop_next_due(now) {
3060                        pending.record_release(&unit, now);
3061                        write_unit(unit, &mut sink).await.expect("recording sink");
3062                    }
3063                }
3064                () = cancel.cancelled() => unreachable!("nothing cancels this test"),
3065            }
3066        }
3067
3068        assert_eq!(
3069            sink.written(),
3070            Bytes::from_static(b"0123"),
3071            "byte equality is the ordering assertion"
3072        );
3073        let snap = counters.snapshot();
3074        assert_eq!(snap.egress_items_queued, 4, "every unit went through the deque");
3075        assert_eq!(
3076            snap.release_errors.count, 4,
3077            "all four share the head's clamped release time, so one wake pops all four \
3078             — and only the release branch samples",
3079        );
3080    }
3081
3082    #[test]
3083    fn defer_by_is_a_deadline_and_clamps_to_max_hold() {
3084        let config =
3085            EgressConfig { max_hold: Duration::from_millis(100), ..EgressConfig::default() };
3086        let now = Instant::now();
3087
3088        let short = defer_by(now, Duration::from_millis(10), &config);
3089        assert_eq!(short.release_at, now + Duration::from_millis(10));
3090        assert!(!short.was_clamped());
3091
3092        let long = defer_by(now, Duration::from_secs(5), &config);
3093        assert_eq!(long.release_at, now + Duration::from_millis(100));
3094        assert!(long.was_clamped());
3095        assert_eq!(long.requested, Duration::from_secs(5));
3096        assert_eq!(long.applied, Duration::from_millis(100));
3097
3098        // Two units arriving together with the same `by` release together,
3099        // not at +by and +2by.
3100        let a = defer_by(now, Duration::from_millis(10), &config);
3101        let b = defer_by(now, Duration::from_millis(10), &config);
3102        assert_eq!(a.release_at, b.release_at);
3103
3104        assert_eq!(hold_ceiling(now, &config), now + Duration::from_millis(100));
3105    }
3106
3107    #[test]
3108    fn the_first_close_request_wins() {
3109        let cancel = CancellationToken::new();
3110        let closer = SessionCloser::new(cancel.clone());
3111        assert!(!closer.is_closing());
3112        assert_eq!(
3113            closer.close_args(),
3114            (0, Bytes::from_static(b"proxy session ended")),
3115            "the default is what run_with_transport has always sent",
3116        );
3117
3118        assert!(closer.request(3, Bytes::from_static(b"protocol violation")));
3119        assert!(!cancel.is_cancelled(), "recording a close does not yet end the session");
3120        closer.cancel();
3121        assert!(cancel.is_cancelled());
3122        assert!(closer.is_closing());
3123        assert!(!closer.request(1, Bytes::from_static(b"too late")));
3124        assert_eq!(closer.close_args(), (3, Bytes::from_static(b"protocol violation")));
3125        assert_eq!(
3126            closer.requested(),
3127            Some((3, Bytes::from_static(b"protocol violation"), CloseOrigin::Hook)),
3128            "a close that arrived through `request` is a hook's, and the session's own \
3129             `SessionEnded` reason says so in those words",
3130        );
3131    }
3132
3133    #[test]
3134    fn an_undelayed_head_arms_a_deadline_that_never_touches_the_wheel() {
3135        // `arm_at` on an already-passed instant returns an
3136        // already-cancelled `Deadline` without constructing the wheel, so
3137        // a session that never delays never links it into executed code.
3138        //
3139        // Asserted on the deadline's own token rather than on
3140        // `release_timer::started()`: that is process-global state, and
3141        // another test in this binary may legitimately have started the
3142        // wheel on another thread between the two reads.
3143        let (mut q, _c) = queue();
3144        q.push(Pending::bytes(Bytes::from_static(b"now"), Instant::now()));
3145        let release = q.head_release().expect("one unit queued");
3146        assert!(
3147            release.deadline().token().is_cancelled(),
3148            "an already-due head must not be registered with the wheel",
3149        );
3150
3151        let (mut q, _c) = queue();
3152        q.push(Pending::bytes(
3153            Bytes::from_static(b"later"),
3154            Instant::now() + Duration::from_secs(60),
3155        ));
3156        let release = q.head_release().expect("one unit queued");
3157        assert!(!release.deadline().token().is_cancelled(), "a future head is registered");
3158        assert!(release_timer::started(), "…and registering is what starts the wheel");
3159    }
3160
3161    // ── The session-wide byte gauge ─────────────────────────────────
3162
3163    /// A queue reporting into a gauge, plus the gauge.
3164    fn gauged() -> (PendingQueue, Arc<EgressGauge>) {
3165        let gauge = EgressGauge::new();
3166        let q = PendingQueue::new(EgressConfig::default(), Arc::new(Recorder::new()))
3167            .with_gauge(Arc::clone(&gauge));
3168        (q, gauge)
3169    }
3170
3171    /// The gauge is the sum of what the queues hold, on every route bytes
3172    /// take out of one.
3173    ///
3174    /// It has to be exact in both directions or the one caller that reads
3175    /// it — a requested close deciding whether it may stop waiting —
3176    /// either stops early on bytes that are still queued, or waits out its
3177    /// whole window on bytes that left long ago.
3178    #[tokio::test]
3179    async fn the_gauge_follows_every_route_bytes_leave_a_queue_by() {
3180        let (mut q, gauge) = gauged();
3181        let now = Instant::now();
3182        assert_eq!(gauge.queued(), 0);
3183
3184        q.push(Pending::bytes(Bytes::from_static(b"aaa"), now));
3185        q.push(Pending::bytes(Bytes::from_static(b"bb"), now));
3186        assert_eq!(gauge.queued(), 5, "two pushes, five bytes");
3187
3188        // Route one: released at its release time.
3189        q.pop_next_due(now).expect("both are due");
3190        assert_eq!(gauge.queued(), 2);
3191
3192        // Route two: flushed by the teardown drain.
3193        let mut sink = RecordingSink::default();
3194        assert_eq!(q.drain_ignoring_release_times(&mut sink).await, DrainOutcome::Complete);
3195        assert_eq!(gauge.queued(), 0);
3196
3197        // Route three: cleared wholesale, as a terminal or a shaping reset
3198        // does.
3199        q.push(Pending::bytes(Bytes::from_static(b"cccc"), now));
3200        assert_eq!(gauge.queued(), 4);
3201        q.clear();
3202        assert_eq!(gauge.queued(), 0);
3203
3204        // Route four: the stream ends holding bytes.
3205        q.push(Pending::bytes(Bytes::from_static(b"dddd"), now));
3206        assert_eq!(gauge.queued(), 4);
3207        drop(q);
3208        assert_eq!(
3209            gauge.queued(),
3210            0,
3211            "a queue that goes away takes its remainder with it, or every later close waits out \
3212             its whole window"
3213        );
3214    }
3215
3216    /// The wait ends when the queues empty, and answers zero.
3217    #[tokio::test]
3218    async fn the_drain_wait_ends_the_moment_the_queues_empty() {
3219        let (mut q, gauge) = gauged();
3220        let now = Instant::now();
3221        q.push(Pending::bytes(Bytes::from_static(b"payload"), now));
3222
3223        // A generous window, so finishing quickly can only be the queue
3224        // emptying and not the deadline.
3225        let waiting = tokio::spawn({
3226            let gauge = Arc::clone(&gauge);
3227            async move { gauge.wait_idle(Duration::from_secs(30)).await }
3228        });
3229
3230        tokio::task::yield_now().await;
3231        let mut sink = RecordingSink::default();
3232        assert_eq!(q.drain_ignoring_release_times(&mut sink).await, DrainOutcome::Complete);
3233
3234        let stranded = tokio::time::timeout(Duration::from_secs(5), waiting)
3235            .await
3236            .expect("the wait must end on the queue emptying, not on its own deadline")
3237            .expect("the waiting task must not panic");
3238        assert_eq!(stranded, 0, "nothing was left, so nothing is abandoned");
3239    }
3240
3241    /// The wait ends at its deadline holding the exact residue, and an
3242    /// expired window makes the teardown flush write nothing.
3243    ///
3244    /// The second half is what keeps the arithmetic closed. A flush at that
3245    /// point hands bytes to a connection that is about to discard its
3246    /// buffer, so they are neither confirmably delivered nor confirmably
3247    /// lost; declining to write leaves `unconfirmed_bytes` equal to what
3248    /// was abandoned, exactly.
3249    #[tokio::test]
3250    async fn an_expired_window_reports_the_residue_and_writes_nothing() {
3251        let (mut q, gauge) = gauged();
3252        let now = Instant::now();
3253        q.push(Pending::bytes(Bytes::from_static(b"held"), now + Duration::from_secs(60)));
3254
3255        let stranded = gauge.wait_idle(Duration::from_millis(20)).await;
3256        assert_eq!(stranded, 4, "the window closed on four bytes that had not been released");
3257
3258        gauge.begin_discarding();
3259        let mut sink = RecordingSink::default();
3260        assert_eq!(
3261            q.drain_ignoring_release_times(&mut sink).await,
3262            DrainOutcome::Discarded,
3263            "past the deadline the flush declines rather than best-efforts"
3264        );
3265        assert!(sink.writes.is_empty(), "nothing reached the transport");
3266        assert_eq!(
3267            q.unconfirmed_bytes(),
3268            4,
3269            "so the reported figure is what was abandoned, with nothing handed anywhere"
3270        );
3271        assert_eq!(
3272            q.queued_bytes(),
3273            q.unconfirmed_bytes(),
3274            "and the two agree, which is the point"
3275        );
3276    }
3277
3278    /// A close recorded on the closer does **not** end the session.
3279    ///
3280    /// A hook's close is cancelled by `exec::execute` the moment it has
3281    /// been reported, because the hook asked for the session to end. A
3282    /// control-plane close has to fix the pair first and let the session
3283    /// keep running through its drain window, and it would have no window
3284    /// at all if recording cancelled.
3285    ///
3286    /// It also checks who the close is *reported as*.
3287    /// `run_with_transport` turns the recorded triple into the prose on
3288    /// [`ProxyEvent::SessionEnded`](crate::event::ProxyEvent::SessionEnded),
3289    /// and both callers reach the same `OnceLock`, so without an origin
3290    /// beside the pair every operator-initiated close was reported to
3291    /// observers as a hook's decision.
3292    #[test]
3293    fn recording_a_close_leaves_the_session_running() {
3294        let cancel = CancellationToken::new();
3295        let closer = SessionCloser::new(cancel.clone());
3296
3297        assert!(closer.record(7, Bytes::from_static(b"asked")));
3298        assert!(!cancel.is_cancelled(), "the drain window has not even started yet");
3299        assert_eq!(closer.close_args(), (7, Bytes::from_static(b"asked")));
3300
3301        // First writer wins here exactly as it does through `request`, so a
3302        // hook and a control plane racing cannot end up with a code from
3303        // one and a reason from the other.
3304        assert!(!closer.record(9, Bytes::from_static(b"second")));
3305        assert!(!closer.request(9, Bytes::from_static(b"second")));
3306        assert_eq!(closer.close_args(), (7, Bytes::from_static(b"asked")));
3307        assert!(!cancel.is_cancelled(), "neither recording call ends the session");
3308
3309        // The losing `request` above is a hook's, and it lost. The origin
3310        // has to lose with it: a session whose close was recorded by the
3311        // control plane and then re-asked for by a hook must not report the
3312        // hook's name against the control plane's code and reason, which is
3313        // the mislabel this field exists to stop.
3314        assert_eq!(
3315            closer.requested(),
3316            Some((7, Bytes::from_static(b"asked"), CloseOrigin::ControlPlane)),
3317        );
3318    }
3319}