Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Priorities and aging

Classes

ClassBase score
Critical10,000
High1,000
Normal (default)100
Low10

Effective score

When a worker slot is free, the pool dispatches the ready task with the highest effective score:

score = base[class] + min(aging_rate[class] × wait_seconds, age_cap[class]) + urgency

with defaults aging_rate = 2.0 points per second and age_cap = 800 for every class. urgency is zero unless the task has a deadline. Ties are broken by class, then by submission order.

Aging

Aging prevents starvation: a task that keeps waiting slowly gains score until it overtakes newer work of a higher class.

With the defaults:

  • A Low task reaches 10 + 800 = 810 after 400 seconds. Against a continuous stream of fresh Normal work (score about 100), it wins once 10 + 2w > 100, that is after about 45 seconds of waiting.
  • Normal can reach 900 and High can reach 1800. Neither crosses the next class's base score (1000 and 10000), so fresh High work always beats aged Normal work, and Critical always beats everything except deadline urgency.

This is deliberate: aging rescues background work from floods of ordinary work, but never lets it delay urgent work. If you want stronger mixing, raise age_cap (Rust: Builder::priority(PriorityConfig { ... })).

The time is measured from submission. A task released by its dependencies, or retried after a failure, keeps its original place in its class.

Why dispatch never rescans the backlog

Within one class, every task ages at the same rate from its own submission time, so an older task always has a score at least as high as a younger one. A FIFO queue per class therefore always has its best task at the head. Deadline tasks live in a second per-class queue ordered by latest start time. In pools with fair sharing, tasks without deadlines live in a per-class fair-share queue ordered by start tag instead of the FIFO lane. Dispatch compares at most eight heads (four classes × two queues), computing their scores lazily. There is no periodic rescoring and no full-queue scan, so choosing the next task costs at most the logarithm of the backlog. Fresh tasks join the back of their lane; a retried or woken task goes back to its place by submission order, which in a very long lane moves the entries behind it.

Measuring adherence

Each pool counts priority inversions: dispatches made while a ready task of a strictly higher class was waiting in the same pool (tasks blocked on resources do not count). priority_adherence = 1 − inversions / dispatches over the metrics window. With aging and deadlines some inversions are intended; a sudden drop usually means an aging or deadline policy is overriding priorities more often than expected.

Configuration

SettingDefaultWhere
Base scores10000 / 1000 / 100 / 10PriorityConfig::base
Aging rate2.0 points/sPriorityConfig::aging_rate
Age cap800PriorityConfig::age_cap

All three are arrays indexed by class (critical, high, normal, low).