Skip to main content

Module scheduler

Module scheduler 

Source
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:

  1. Which class is this?Scheduler::classify, the Matcher over ObjectMeta plus the fall to Class::Default, plus the ShapeRuleUnmatchable report that keeps that fall from being silent.
  2. Does it fit?Scheduler::admit, the per-stream queue depth and the Overflow policy.
  3. May it go now?Scheduler::acquire, the token bucket and the Discipline that 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§

QueueDepth 🔒
The per-stream queue depth, in both units at once.
SchedState 🔒
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 ShapeStats a unit is charged to.