pub struct PullDeadlines { /* private fields */ }Expand description
The bounds on a pull, each matched to the stage it governs (#1134, #1797).
A single whole-transfer deadline is mostly useless as a health signal: it has to be
sized against blob size × link speed, so it kills legitimate large or slow-but-healthy
transfers while a value small enough to catch a dead peer quickly cannot serve a big blob
at all. The operator ends up tuning a number that has nothing to do with node health.
Split by stage instead:
-
window+floor_bpsbound the STREAMING stage by THROUGHPUT. The requester counts bytes read off the QUIC stream (sub-frame, so the measure is frame-size independent) and trips when the bytes across the trailingwindowfall belowfloor_bps · window. This catches both a wedge (throughput drops to zero) and a slow drip (throughput below the floor), and is indifferent to transfer size and link speed.floor_bps == 0leaves pure idle detection: at least one byte per window. This is the primary mechanism. The abort is requester-local policy and does not score the peer. -
hard_capis an optional overall wall-clock escape hatch,Noneby default. It exists for a caller that must bound total runtime regardless; it is not how a stalled peer is detected. -
openbounds the OPEN stage (dial → request → verifiedStreamResponse) by WALL CLOCK. That stage is bounded work whose duration does not depend on the blob, so a slow one really is a stall and a wall clock is the right tool.
Every stage must carry a bound of its own. It is not enough for a caller to
wrap the whole pull in a timeout and call the open “bounded”: the handshake
happens inside [open_progressive_pull], and the production callers run
with no overall cap, so leaving the bound to the caller would leave the
StreamResponse read with no bound at all — a peer that accepts a connection
and then says nothing would hang the pull forever. open exists so that
cannot be expressed.
§The relational invariant
hard_cap, when set, must STRICTLY EXCEED open + window. Both clocks below run inside
the cap’s, and in the worst case consecutively — the open can legitimately consume its
whole budget before the streaming stage begins, and the floor needs one full window to
fire — so a cap that does not outlast both means the cap always fires first and the
throughput floor can never trip under it. The pull then looks fully configured while its
peer-health signal is dead.
Self::capped is fallible and the fields are private BECAUSE of that (#1145 review).
The invariant belongs on this type: with pub fields, callers build it with a struct
literal and the only check is a hardcoded timeout > 2 × window in the CLI’s
ClientFetchArgs::validate, correct only because those call sites set open from the same
knob as window. Adding an
--open-timeout-ms flag would have made it silently wrong, in the direction that reopens
the hole. The invariant belongs to the type that has the values.
Implementations§
Source§impl PullDeadlines
impl PullDeadlines
Sourcepub const fn new(
open: Duration,
window: Duration,
floor_bps: u64,
) -> Result<Self, DeadlineError>
pub const fn new( open: Duration, window: Duration, floor_bps: u64, ) -> Result<Self, DeadlineError>
The recommended shape: a wall clock on the open, inactivity on the stream, and no overall cap — so a pull of any size completes as long as the upstream keeps feeding it bytes.
§Errors
DeadlineError::ZeroBudget for a zero open or window. floor_bps may be zero
(idle-detection mode: one byte per window).
A zero open or window must fail here, not pass. One might argue it is a bound that
fires too EAGERLY — loud, immediately obvious — rather than one that silently never
fires. That is exactly backwards: a zero window makes the throughput floor demand
progress over no time at all, so the streaming stage can never satisfy it.
The only thing standing between config and that state was a > 0 check in a resolver
in another crate — the same advisory-invariant shape ChunkData had before #1088, and
the reason this type owns its bounds at all.
Sourcepub fn capped(
open: Duration,
window: Duration,
floor_bps: u64,
hard_cap: Duration,
) -> Result<Self, DeadlineError>
pub fn capped( open: Duration, window: Duration, floor_bps: u64, hard_cap: Duration, ) -> Result<Self, DeadlineError>
As Self::new, plus an overall wall-clock cap on the whole exchange.
§Errors
DeadlineError::CapCannotOutlastItsStages if hard_cap does not strictly exceed
open + window, and DeadlineError::ZeroBudget for a zero open or window. See
the type’s own docs for why this is the one constructor that must be fallible.
Trait Implementations§
Source§impl Clone for PullDeadlines
impl Clone for PullDeadlines
impl Copy for PullDeadlines
Auto Trait Implementations§
impl Freeze for PullDeadlines
impl RefUnwindSafe for PullDeadlines
impl Send for PullDeadlines
impl Sync for PullDeadlines
impl Unpin for PullDeadlines
impl UnsafeUnpin for PullDeadlines
impl UnwindSafe for PullDeadlines
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
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§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