Expand description
The PIX fill planner — the pure rate law behind the Exit’s own keep-alives.
A PIX Exit is paid per return packet: each Exit → Entry packet spends one SURB carrying one
share, and a funded cycle only recovers once its whole emission has gone back. Nothing in the
protocol makes the application send those packets, so an idle Session strands the deposit it has
already been paid. The Exit therefore sends its own, at a rate this module derives once per
SAMPLING_INTERVAL from three quantities and nothing else:
remaining = (E − shares seen on the target cycle) × (1 + loss_margin)
finish_by = target.hard_deadline − (1 − finish_fraction) × max_recovery_time
required = remaining / max(finish_by − now, SAMPLING_INTERVAL) packets/s
organic = EMA of the gate's served counter over ORGANIC_WINDOW packets/s
fill = clamp(required − organic, heartbeat, max_rate)Everything here is pure: no clock is read, no I/O is performed, and every input arrives as an argument. The supervisor owns the target selection — which cycle the Exit is currently working for — because that is a lifecycle question; this module owns only the arithmetic and the decision of whether the answer is worth emitting.
§Why organic is the gate counter
served_total counts packets that passed the ServiceGate, which is
every application data packet and nothing else — fill itself is ungated, exactly as the SURB-level
keep-alives it reuses are. So the counter measures what the application is contributing and never
what this planner is, which is what makes required − organic a subtraction rather than a
feedback loop that would drive itself to zero.
§Why the stall rule cannot be the idle rule
SupervisorConfig::max_recovery_idle closes a Session whose cycle stops progressing while
service is being consumed, and it reads the same gate counter. Fill bypasses the gate, so a cycle
that is being filled and making no progress is invisible to it: the idle rule keeps re-arming
because, as far as it can see, nothing was served. Left alone, an Exit would fill a doomed cycle —
one whose polynomial failed, or whose Entry is supplying SURBs that carry no shares — at
max_rate for the whole of max_recovery_time. The rule below is the bound that the gate-based
one cannot supply: once the target has not moved for max_recovery_idle, fill falls back to the
heartbeat and stays there until the cycle moves again.
Structs§
- Fill
Planner 🔒 - The pure rate law. See the module documentation for what it computes and why.
- Fill
Target 🔒 - The cycle the Exit is currently filling for, as the supervisor sees it.