Skip to main content

PullDeadlines

Struct PullDeadlines 

Source
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_bps bound 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 trailing window fall below floor_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 == 0 leaves 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_cap is an optional overall wall-clock escape hatch, None by default. It exists for a caller that must bound total runtime regardless; it is not how a stalled peer is detected.

  • open bounds the OPEN stage (dial → request → verified StreamResponse) 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

Source

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.

Source

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.

Source

pub const fn open(&self) -> Duration

The open-stage wall clock.

Source

pub const fn window(&self) -> Duration

The streaming-stage throughput-floor window.

Source

pub const fn floor_bps(&self) -> u64

The streaming-stage throughput floor, in bytes per second. 0 = idle detection only.

Source

pub const fn hard_cap(&self) -> Option<Duration>

The overall cap, if any.

Trait Implementations§

Source§

impl Clone for PullDeadlines

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for PullDeadlines

Source§

impl Debug for PullDeadlines

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> MaybeSend for T
where T: Send,

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more