pub(crate) struct FillPlanner {Show 13 fields
cfg: PixFillConfig,
max_recovery_time: Duration,
max_recovery_idle: Duration,
cycle_shares: u64,
last_tick: Instant,
last_served_total: u64,
organic_pps: f64,
target: Option<SsaId<HoprPseudonym>>,
target_seen: u64,
target_progress_at: Option<Instant>,
last_rate: FillRate,
stall_warned: bool,
stall_onset: bool,
}Expand description
The pure rate law. See the module documentation for what it computes and why.
Fields§
§cfg: PixFillConfig§max_recovery_time: DurationSupervisorConfig::max_recovery_time, the budget finish_fraction is a fraction of.
max_recovery_idle: DurationSupervisorConfig::max_recovery_idle, reused as the stall horizon so that fill gives up on
a motionless cycle at the same point the idle rule would have, had it been able to see it.
E — every share one cycle of the negotiated dimensions emits, and therefore every packet it
takes to complete one.
last_tick: Instant§last_served_total: u64The gate’s served counter at last_tick, so the next tick can difference it.
organic_pps: f64Exponential average of organic egress, in packets per second.
target: Option<SsaId<HoprPseudonym>>The cycle the counters below belong to, so a handoff resets them rather than reading the successor’s progress as the predecessor’s stall.
target_seen: u64largest_shares_seen as of the last tick that observed movement.
target_progress_at: Option<Instant>When the target last moved, or when it became the target. None while there is no target.
last_rate: FillRateThe rate the stream is believed to be running at, and the value hysteresis compares against.
stall_warned: boolWhether the stall has already been reported, so a stalled cycle warns once rather than once a
second for the whole of max_recovery_time.
stall_onset: boolSet on the tick a stall begins, and taken by the worker so it can count the event.
An edge rather than a level, because the metric it feeds counts stalls: a level would count one per second for as long as the cycle stayed motionless, which is a measure of how long the operator waited rather than of how often this happened.
Implementations§
Source§impl FillPlanner
impl FillPlanner
Sourcepub(crate) fn new(
cfg: &SupervisorConfig,
dims: &PixParams,
now: Instant,
) -> Self
pub(crate) fn new( cfg: &SupervisorConfig, dims: &PixParams, now: Instant, ) -> Self
Builds a planner for a Session of the given dimensions.
now seeds next_tick, so the first plan happens one sampling interval
after the Session starts rather than immediately. Nothing is lost by that: a Session has no
funded cycle to fill for until at least a commitment and a deposit have crossed the wire.
Sourcepub(crate) fn is_enabled(&self) -> bool
pub(crate) fn is_enabled(&self) -> bool
Whether fill is configured on at all.
Sourcepub(crate) fn take_stall_onset(&mut self) -> bool
pub(crate) fn take_stall_onset(&mut self) -> bool
Reports, once, that the target cycle has gone motionless and fill has fallen back.
Consumed rather than read so the caller counts the stall rather than its duration. The caller is the worker, which is the layer allowed to have side effects; this module stays pure.
Sourcepub(crate) fn is_stalled(&self) -> bool
pub(crate) fn is_stalled(&self) -> bool
Whether the target has gone motionless and fill has fallen back to its heartbeat.
The level behind take_stall_onset’s edge, so the census can
report a stall for as long as it lasts without the caller tracking it.
Sourcepub(crate) fn is_idle(&self) -> bool
pub(crate) fn is_idle(&self) -> bool
Whether the stream this planner drives is currently emitting nothing.
The supervisor uses it to decide whether a tick is worth waking for when there is no target: a planner that has already stopped has nothing left to say, while one that is still running owes the stream a zero.
Sourcepub(crate) fn stop(&mut self) -> Option<FillRate>
pub(crate) fn stop(&mut self) -> Option<FillRate>
Silences the stream, if it is running, and forgets the target.
Returns the action’s payload only when there is something to stop, so a Session that never filled — a disabled one, or one that closed before its first cycle funded — emits nothing and its action stream is byte-for-byte what it was before fill existed.
Sourcepub(crate) fn plan(
&mut self,
now: Instant,
served_total: u64,
target: Option<FillTarget>,
) -> Option<FillRate>
pub(crate) fn plan( &mut self, now: Instant, served_total: u64, target: Option<FillTarget>, ) -> Option<FillRate>
Re-derives the fill rate, returning it only if it is worth emitting.
served_total is the service gate’s counter — organic egress and nothing else. target is
the cycle the Exit is working for, or None when it has none, which is the case that stops
the stream.
Sourcefn observe_organic(&mut self, now: Instant, served_total: u64)
fn observe_organic(&mut self, now: Instant, served_total: u64)
Folds this interval’s organic egress into the exponential average.
alpha = dt / ORGANIC_WINDOW, capped at one, so the average has the same time constant
however irregularly the ticks land — a worker that was busy for five seconds contributes five
seconds of weight, not one tick’s worth. A tick with no elapsed time contributes nothing
rather than dividing by zero.
Sourcefn observe_target(&mut self, now: Instant, target: &FillTarget)
fn observe_target(&mut self, now: Instant, target: &FillTarget)
Tracks whether the target has moved, which is the input to the stall rule.
A change of target resets the clock rather than inheriting the predecessor’s: a successor that has just reached the front has made no progress yet, and reading that as a stall would hold every cycle after the first at the heartbeat.
Sourcefn required_rate(&self, now: Instant, target: &FillTarget) -> f64
fn required_rate(&self, now: Instant, target: &FillTarget) -> f64
The rate at which the cycle’s remaining emission clears the aim point, in packets per second.
The aim point sits (1 − finish_fraction) × max_recovery_time before the hard deadline, so
the last part of the budget is margin: for loss beyond loss_margin, for a mixnet delay
spike, and for the commitment and deposit round trip the successor still needs. An aim point
already in the past collapses to one sampling interval, which asks for the whole remainder at
once and is then bounded by max_rate — the correct behaviour for a cycle that is out of time,
since there is no rate at which it can still be saved and no reason to stop trying.
A drain collapses the horizon the same way for a different reason: pacing
a cycle to its deadline only makes sense while the Session is still there to be paced, and a
drained one is spending a buffer nothing will refill. The ceiling in
bound then becomes the only thing setting the rate, which is the intent
rather than a side effect.
Sourcefn bound(&mut self, now: Instant, wanted: f64) -> FillRate
fn bound(&mut self, now: Instant, wanted: f64) -> FillRate
Applies the stall rule and the two bounds, and expresses the result as a FillRate.
The heartbeat is a floor rather than a special case, so a Session whose application is keeping up still emits one packet per period: it refreshes the Entry’s idle eviction, it carries the SURB level the Entry’s balancer acts on, and it is what distinguishes a planner that decided on nothing from one that has stopped planning.
The stall rule still applies while draining.
Without it, a doomed drain could sit at max_rate until the deadline and waste SURBs. A
healthy drain pays nothing for the rule, because progress refreshes
target_progress_at. And max_recovery_idle does not replace
it: that deadline ends the drain, this bounds what it spends before then.
Sourcefn should_emit(&self, rate: FillRate) -> bool
fn should_emit(&self, rate: FillRate) -> bool
Whether rate differs from what the stream is already doing by enough to be worth an action.
The planner runs every second for the whole life of a cycle — hours, at production dimensions — and the required rate drifts continuously as the remainder and the horizon both shrink. Emitting every one of those would put a per-second action on a channel sized for lifecycle transitions, and the driver would reconfigure a rate controller for a change no packet schedule can express. So a change is reported when it is a real one:
- any change to or from zero, because that is the difference between filling and not;
- any crossing of the heartbeat, because that is the difference between filling and idling;
- otherwise, a relative change of more than ten per cent.
Auto Trait Implementations§
impl Freeze for FillPlanner
impl RefUnwindSafe for FillPlanner
impl Send for FillPlanner
impl Sync for FillPlanner
impl Unpin for FillPlanner
impl UnsafeUnpin for FillPlanner
impl UnwindSafe for FillPlanner
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
§impl<T> Conv for T
impl<T> Conv for T
§impl<T> FmtForward for T
impl<T> FmtForward for T
§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.§fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
fn fmt_list(self) -> FmtList<Self>where
&'a Self: for<'a> IntoIterator,
§impl<T> FutureExt for T
impl<T> FutureExt for T
§fn with_context(self, otel_cx: Context) -> WithContext<Self>
fn with_context(self, otel_cx: Context) -> WithContext<Self>
§fn with_current_context(self) -> WithContext<Self>
fn with_current_context(self) -> WithContext<Self>
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read more§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read more§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.§impl<T> Pointable for T
impl<T> Pointable for T
§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
§impl<T> Tap for T
impl<T> Tap for T
§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read more§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read more§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read more§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read more§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read more§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read more§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.