pub fn charge(
state: &mut BucketState,
rate_bps: Option<u64>,
burst_bytes: u64,
bytes: u64,
now: Instant,
) -> GrantExpand description
Charge bytes against a bucket and say whether the unit may go.
Pure with respect to the clock — now is a parameter and no clock
is read, following release_timer::plan. state is refilled to now
before the decision and debited only on Grant::Now; every refusal
costs nothing, so re-charging the same unit after its deadline is correct
rather than double-billing.
The bound this function upholds, exactly: over any interval, the
bytes it grants never exceed rate_bps * elapsed + burst_bytes. That is
what makes the rate claim provable arithmetic rather than a measurement,
and the_bucket_never_grants_above_rate_plus_burst proves it over
10 001 fabricated steps.
§It is not the bound the shaper upholds
Stated here because the difference is a factor of thousands and a reader
of this line will otherwise assume the wrong one. PendingQueue’s
release seam checks the unit’s max_hold clamp before it calls
acquire, and under the default Expiry::Deliver a
clamped unit goes out without a bucket being consulted at all — that is
what “a 0-bps class still delivers, at max_hold” means. So the
end-to-end ceiling is
min(rate_bps * elapsed + burst_bytes, <- this function
depth / max_hold * elapsed) <- the clamp, per streamMeasured, with rate_bps: Some(0), depth_bytes: 4096 and max_hold: 300 ms: a bucket configured at 0 bytes/s sustained ~25 kB/s. The
clamp is the deliberate non-destructive default — a starved unit is
delivered late rather than dropped — and not a defect, but a fixture that
means to observe this function’s bound has to pin
max_hold far enough out that the clamp cannot bind inside its sampling
window — which is why every starvation fixture is required to name it.
now is expected to be monotonic. A now earlier than the last one
refills nothing and does not rewind the bucket’s clock, so an
out-of-order caller under-grants rather than manufacturing tokens.