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: AtomicU64Monotonic number of packets served.
served_predeposit: AtomicU64Of 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: u64Predeposit budget restored after each paid front-cycle handoff.
remaining: AtomicU64Remaining predeposit budget (tracked separately so we can park on 0).
funded: AtomicBoolWhether the current front cycle is funded.
mode_epoch: AtomicU64Incremented 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: AtomicBoolWhether the gate is poisoned.
notify: SlotNotifyWaker for parked writers.
ceiling: AtomicU64Ceiling on packets served since last progress notification.
served_at_last_progress: AtomicU64Snapshot of served at last progress notification.
Implementations§
Source§impl ServiceGate
impl ServiceGate
Sourcepub fn new(
predeposit_budget: u64,
max_served_without_progress: u64,
) -> Arc<Self> ⓘ
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.
Sourcepub async fn acquire(self: &Arc<Self>) -> Result<(), GateClosed>
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.
Sourcepub fn served_total(&self) -> u64
pub fn served_total(&self) -> u64
Current value of the served counter.
Sourcepub fn predeposit_exposure(&self) -> u64
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.
Sourcefn mode_is_current(&self, epoch: u64) -> bool
fn mode_is_current(&self, epoch: u64) -> bool
Linearization point shared by a permit and mode publication.
Sourcepub fn release_service(self: &Arc<Self>)
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.
Sourcepub fn withhold_service(self: &Arc<Self>)
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.
Sourcepub fn notify_progress(self: &Arc<Self>)
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.
Sourcepub fn poison(&self)
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).
Sourcepub fn try_acquire_sync(&self) -> Result<GateVerdict, GateClosed>
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.
Sourcepub fn served_split(&self) -> (u64, u64)
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§
impl !Freeze for ServiceGate
impl !RefUnwindSafe for ServiceGate
impl !UnwindSafe for ServiceGate
impl Send for ServiceGate
impl Sync for ServiceGate
impl Unpin for ServiceGate
impl UnsafeUnpin for ServiceGate
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.