Skip to main content

PixFillControl

Struct PixFillControl 

Source
pub(crate) struct PixFillControl {
    session_id: SessionId,
    controller: RateController,
    notify_period: Option<Duration>,
    min_surb_reserve: u64,
    estimator: AtomicSurbFlowEstimator,
    state: Mutex<PixFillState>,
}
Expand description

The Exit’s control over the single Exit → Entry keep-alive stream a Session runs.

One stream, two producers with different jobs, and this is what reconciles them:

  • the SURB-level notification, sent every surb_balance_notify_period, which tells the Entry’s balancer how many SURBs the Exit believes it holds. It is the signal that asks for more, so it must go out even when there are none left;
  • PIX fill, whose rate the supervisor plans once a second so that a funded cycle completes inside its own deadline even if the application never sends a byte.

The effective rate is the faster of the two, because both are the same packet: a keep-alive carrying the SURB level is exactly what fill needs to send, and sending two streams of them would double the traffic to say the same thing twice. Every shipped Entry already parses it, counts it as a consumed SURB so its balancer refills, and — since the fix that precedes this — credits it as service against the successor gate.

Fields§

§session_id: SessionId§controller: RateController

The rate limiter of the keep-alive stream this steers.

§notify_period: Option<Duration>

The configured SURB-level notification period, if the node has one.

None disables the notification half entirely; the stream then runs at the fill rate alone, which is how a PIX Session on a node with surb_balance_notify_period: None still fills.

§min_surb_reserve: u64

SURBs that must remain estimated-available before a fill packet is let through.

The effective reserve for this Session, not the configured one: the Entry decides how deep the Exit’s SURB buffer for the Session is, so a reserve above what the Entry ever intends to supply would refuse every fill packet forever. See effective_surb_reserve.

§estimator: AtomicSurbFlowEstimator

The Session’s SURB flow estimate, which is what the reserve is measured against.

§state: Mutex<PixFillState>

Implementations§

Source§

impl PixFillControl

Source

fn new( session_id: SessionId, controller: RateController, notify_period: Option<Duration>, min_surb_reserve: u64, estimator: AtomicSurbFlowEstimator, ) -> Self

Source

pub(crate) fn start_notify(&self)

Turns the SURB-level notification on, after its initial delay.

A no-op once the Session is draining, for the reason enter_drain gives — and it has to be checked here rather than only there, because the one-shot delay task holds a clone of this control and nothing bounds how long an operator may make its period.

Source

pub(crate) fn enter_drain(&self) -> bool

Puts the stream into post-close drain mode, silencing the SURB-level notification.

Returns false if the stream was draining already, which is how two closes racing through begin_surb_drain settle which of them owns the drain. The read and the write have to be one lock acquisition for that: callers that each saw is_draining before either wrote it would both start a drain, and both tell the supervisor about it.

The notification is the single packet admit_at exempts from the SURB reserve, and the exemption exists for one reason: it is the message that asks the Entry for more SURBs. It asks on behalf of a Session that a drain has already lost — the Entry itself may well still be there, but nothing is left to act on the request — so leaving it on would originate below the reserve into a buffer nothing is refilling, which is the one failure mode PixFillConfig::min_surb_reserve exists to prevent, since a return packet with no SURB holds up every packet this node originates. With it off, notification_due is always false and the reserve is the only gate.

Source

pub(crate) fn is_draining(&self) -> bool

Whether this Session is draining its SURB buffer after having been closed.

Source

pub(crate) fn drainable_surbs(&self) -> u64

The SURBs this Session could still spend on a drain, net of the reserve it must leave behind.

An estimate and an upper bound at that — ring-buffer overwrites, store eviction and invalidated relayers are all unobserved — which is why the supervisor weighs it against a cycle’s whole remaining emission rather than a discounted one. The reserve subtracted is the effective one already derived for this Session, not the configured ceiling.

Source

pub(crate) fn set_fill(&self, rate: FillRate)

Applies a rate planned by the PIX supervisor.

Source

fn effective(state: &PixFillState, notify_period: Option<Duration>) -> FillRate

Source

fn apply(&self, state: &PixFillState)

Source

pub(crate) fn admit(&self) -> bool

Decides whether the next keep-alive may go out, and books it if so.

The SURB-level notification always may: it is the message that asks the Entry for more SURBs, so refusing it when SURBs are scarce is refusing to ask for the thing that is scarce.

Fill above that rate is refused while the estimate is below the reserve, and that refusal is not an optimisation. A return packet that finds no SURB is not dropped — the routing resolver holds it, and with it every packet this node originates, for the whole of surb_resolution_wait. An Exit filling a cycle down to its last SURB would therefore stall the Session it is filling for, and every other Session on the node with it.

§Which packet is the notification

Nothing about a packet says which producer it belongs to, so the notification is the first one released no sooner than a period after the last — less a slack of min(MAX_WAIT_CHUNK, period / 2), which is what keeps the classification from resting on scheduling latency. The rate limiter that releases these packets sleeps in chunks of MAX_WAIT_CHUNK, re-reads its controller on each wake, and rounds a rate into an integer packet count, so a stream asked for one packet per period releases them a shade over or a shade under it. Compared against the period exactly, a period the controller rounds short by a microsecond would classify every other notification as fill — counted in hopr_session_pix_fill_packets_total and, below the reserve, dropped. That is the signal asking the Entry for the SURBs whose absence caused the drop, and it runs on every Exit Session with a surb_balance_notify_period configured, PIX or not.

The slack cannot let fill masquerade as the notification: last_notify_at advances to the instant the packet actually went out, so only the first packet past the slack window is the notification, and every fill packet that follows it inside the period is still fill and is still bounded by the reserve. The cap at half the period is what stops a very short period from having its window widened away to nothing.

Source

fn admit_at(&self, now: Instant) -> bool

admit against a caller-supplied instant, so the classification can be tested as the rule it is rather than by sleeping out a period and hoping the scheduler cooperates.

Source

fn set_withholding(state: &mut PixFillState, withholding: bool)

Moves the surb_starved gauge on the edges only, so it counts stalled Sessions rather than refused packets — the same distinction PixGateBlockEpisode draws for the egress gate.

Trait Implementations§

Source§

impl Drop for PixFillControl

Returns this Session’s surb_starved count if it was withholding when it went away.

Drop rather than a call on the close paths, for the reason PixGateBlockEpisode gives: a Session can end by teardown, by drain, or by its last Arc going out of scope in a task nobody is waiting on, and this is the one mechanism that covers all three. Without it a Session that dies mid-stall leaves the gauge standing for the life of the process.

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. 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
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Conv for T

§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
§

impl<T> FmtForward for T

§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> FutureExt for T

§

fn with_context(self, otel_cx: Context) -> WithContext<Self>

Attaches the provided Context to this type, returning a WithContext wrapper. Read more
§

fn with_current_context(self) -> WithContext<Self>

Attaches the current Context to this type, returning a WithContext wrapper. Read more
§

impl<T> Instrument for T

§

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

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

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
§

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

§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows 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) -> R
where R: 'a,

Mutably borrows 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
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows 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
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows 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
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
§

impl<T> Pointable for T

§

const ALIGN: usize

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
§

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

§

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
§

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
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<T> Tap for T

§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .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
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .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
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
§

impl<T> TryConv for T

§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

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

Source§

type Error = Infallible

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

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

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.
§

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

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

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
§

fn with_current_subscriber(self) -> WithDispatch<Self>

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