Expand description
The session-scoped shaper: classification, admission and release.
Scheduler is the one object a shaped session’s forwarding tasks
share. It owns the validated ShapeProfile, the report-once state
that keeps a run from repeating itself, and the token buckets, and it
answers three questions per unit:
- Which class is this? —
Scheduler::classify, theMatcheroverObjectMetaplus the fall toClass::Default, plus theShapeRuleUnmatchablereport that keeps that fall from being silent. - Does it fit? —
Scheduler::admit, the per-stream queue depth and theOverflowpolicy. - May it go now? —
Scheduler::acquire, the token bucket and theDisciplinethat arbitrates between classes sharing one.
The first two are pure functions of their arguments and the profile:
neither reads a clock, touches a queue or writes a counter. The third
owns state — that is what a bucket is — but it still takes now as a
parameter and reads no clock, which is why the arithmetic under it stays
provable from a table. The statistics are the caller’s to record, and the
reports are handed to a callback, so everything below is testable with no
QUIC, no runtime and no session.
§Release is per class; the stream performs it
Scheduler::acquire is the whole of the release decision, and it is
reachable from exactly one place: PendingQueue::pop_next_due, which is
&mut self, is the sole ordering authority, and is called exactly once
per released unit. It is deliberately not reachable from
PendingQueue::head_release, which is &self, fires once per select!
iteration rather than once per unit, and is also called by the two
teardown drains and by the control pipes.
The control pipes are never shaped. pipe_control_passthrough and
pipe_control_mutating install no Scheduler on their queues at all,
so no control frame can reach a bucket even by accident. Pacing
SUBSCRIBE and ANNOUNCE behind a video bucket would stall MoQT’s steady
state and make an idle control stream look like a dead session.
§Why starvation is a gate and not a poll
A class that a discipline refuses has no deadline to arm — nothing about
time will make it eligible, only the blocking class draining will. So
Acquire::Starved hands back a Gate, created under the same lock
that reads the demand it is waiting on, and the withdrawal of that demand
releases it. Level-triggered, so a withdrawal that races the park is
observed rather than lost; a fresh gate per park, so a release cannot be
mistaken for the next one. Polling instead would either spin a starved
stream hot or add a latency nobody configured.
§Why admission is per stream and release is per class
Queue depth, Overflow and the framer’s elide fix-up all live where
note_elided is legal: at admission, on the arriving unit, before the
framer’s positional cursor has moved past it. Dropping an
already-queued unit at release time would not leave a gap in absolute
object IDs on drafts 14-19 — it would leave every successor decoding a
wrong ID. That single fact is why Overflow::DropTail exists and
DropHead does not.
Structs§
- Queue
Depth 🔒 - The per-stream queue depth, in both units at once.
- Sched
State 🔒 - The scheduler’s mutable half.
- Scheduler 🔒
- One session’s shaper.
Enums§
- Acquire 🔒
- What release decided about one queued unit.
- Admission 🔒
- What admission decided about one arriving unit.
- Class 🔒
- Which row of
ShapeStatsa unit is charged to.