Skip to main content

ServiceGate

Struct ServiceGate 

Source
pub struct ServiceGate {
    served: AtomicU64,
    served_predeposit: AtomicU64,
    predeposit_budget: u64,
    remaining: AtomicU64,
    funded: AtomicBool,
    mode_epoch: AtomicU64,
    poisoned: AtomicBool,
    notify: SlotNotify,
    ceiling: AtomicU64,
    served_at_last_progress: AtomicU64,
}
Expand description

Bounded predeposit service gate for a single PIX session.

§Parking

While the front is unfunded, exhausting its predeposit budget parks acquire. Once funded, exceeding the served-without-progress ceiling parks on the same mechanism. On release_service, withhold_service, notify_progress, or poison, all parked callers are woken.

Fields§

§served: AtomicU64

Monotonic number of packets served.

§served_predeposit: AtomicU64

Of those, the ones taken from a predeposit allowance rather than from funded service.

Monotonic over the life of the gate and never reset by a rotation, so it is a share of served rather than a per-front figure — see served_split, which is the only reader.

§predeposit_budget: u64

Predeposit budget restored after each paid front-cycle handoff.

§remaining: AtomicU64

Remaining predeposit budget (tracked separately so we can park on 0).

§funded: AtomicBool

Whether the current front cycle is funded.

§mode_epoch: AtomicU64

Incremented after every mode publication.

Permit acquisition performs an RMW on this epoch before committing its counter update. That gives mode transitions and permits one atomic ordering point: a permit either belongs to the old front or observes the new mode, rather than loading funded before a transition and committing after it.

§poisoned: AtomicBool

Whether the gate is poisoned.

§notify: SlotNotify

Waker for parked writers.

§ceiling: AtomicU64

Ceiling on packets served since last progress notification.

§served_at_last_progress: AtomicU64

Snapshot of served at last progress notification.

Implementations§

Source§

impl ServiceGate

Source

pub fn new( predeposit_budget: u64, max_served_without_progress: u64, ) -> Arc<Self> ⓘ

Create a new gate with the given predeposit budget and progress ceiling.

Source

pub async fn acquire(self: &Arc<Self>) -> Result<(), GateClosed>

Acquire a service permit.

After funding, enforces a ceiling on packets served without SSA recovery progress (see the ceiling field). Parks on SlotNotify when the ceiling or predeposit budget is exceeded.

Source

pub fn served_total(&self) -> u64

Current value of the served counter.

Source

pub fn predeposit_exposure(&self) -> u64

Packets served against the current front’s predeposit allowance and not yet paid for.

Zero while funded, and that is the definition rather than an approximation: funding is exactly the event that converts the allowance the current front spent into service the Entry has paid for, so there is nothing outstanding to report. A later withhold_service restores the whole allowance for an unfunded successor, and the figure starts climbing again from zero for that cycle.

Read once per supervisor turn rather than per packet, so the two relaxed loads are not on any hot path.

Source

fn mode_is_current(&self, epoch: u64) -> bool

Linearization point shared by a permit and mode publication.

Source

pub fn release_service(self: &Arc<Self>)

Publish funded mode for the current front and wake all parked writers.

acquire then enforces the served-without-progress ceiling instead of the predeposit budget. Calling this while already funded represents a funded-to-funded front handoff and starts the new front with a fresh ceiling window.

Snapshots served_total into served_at_last_progress so the ceiling check starts from the moment of funding and does not count predeposit packets against the post-funding budget.

Source

pub fn withhold_service(self: &Arc<Self>)

Return to predeposit mode for the next unfunded front cycle.

The allowance is restored before the mode is published, so a permit that observes the new epoch also observes the complete budget. Parked ceiling waiters are woken to re-evaluate the predeposit branch; with a zero allowance they correctly park again.

Source

pub fn notify_progress(self: &Arc<Self>)

Record SSA recovery progress: snapshots the served counter so the ceiling reopens, and wakes any writers parked on the ceiling.

Source

pub fn poison(&self)

Poison the gate: prevent all further acquires.

Parked and future callers receive GateClosed.

§Semantics

After poison() returns, at most one in-flight acquire() per concurrent caller may still observe the gate as not poisoned (a parked awaiter that wakes before the poison store is visible).

Source

pub fn try_acquire_sync(&self) -> Result<GateVerdict, GateClosed>

Non-blocking try-acquire — the egress fast path.

Returns GateVerdict::Admitted on success, GateVerdict::Blocked with the reason if the predeposit budget is exhausted (and the gate not yet funded) or the ceiling is exceeded (gate funded), or GateClosed if poisoned.

Blocked means the gate refused, and only ever that: both branches retry a lost compare-exchange rather than reporting it. Contention on served is not a refusal — service was available and the caller would be turned away anyway — and with several concurrent egress writers, reporting it as one converts contention into spurious refusals on the documented fast path.

Every outgoing data packet of a supervised Session comes through here, and service is available for all but a vanishing fraction of them, so this answering synchronously is what keeps gating off the allocator: only acquire’s parking path needs a future large enough to box, and that path is about to block anyway. The same argument is why the funded branch below does not count anything of its own: it is the steady state, at the Session’s full packet rate, and the split it would produce is available by subtraction.

Source

pub fn served_split(&self) -> (u64, u64)

Packets this gate has admitted, split into (predeposit, funded).

Counted on the predeposit branch only, and derived for the funded one. That asymmetry is the whole point: at deployed dimensions the funded branch carries essentially all of a Session’s egress — tens of thousands of packets a second across a node — while the predeposit branch carries at most max_predeposit_packets per front rotation. A second fetch_add on the steady-state path would be paid on every packet to produce a number that subtraction already gives exactly.

§The publication order, and why it is that way round

The two loads are not atomic with respect to each other, so a concurrent predeposit permit can land between them and be seen by one load but not the other. Which of the two tears is possible decides whether that permit is merely late or is actually miscounted, and the writers pick which by the order they publish in.

A predeposit permit therefore increments served_predeposit first and publishes served with Release. The acquire load of served below synchronizes with that store, so seeing the permit in the total guarantees seeing it in the predeposit count too: the derived funded figure can never come out one high, which would report a predeposit packet as funded and then report it again as predeposit on the next read.

The surviving tear is the harmless one — served_predeposit new against served old, which makes the derived funded figure one low. Nothing is lost and the next read is consistent again, but a consumer keeping a watermark must not store the regressed value or it will re-cross that ground: see PixSessionTelemetry::flush_egress, which clamps.

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