pub struct LivenessProbe { /* private fields */ }Expand description
The production driver of the liminal connection dead-man switch.
Shares the server’s shutdown watch, so it drains with the transports exactly
like HeartbeatSweeper and the outbox dispatcher.
Implementations§
Source§impl LivenessProbe
impl LivenessProbe
Sourcepub fn new(
notifier: Arc<LiminalConnectionNotifier>,
tracker: HeartbeatTracker,
registry: ConnectedWorkerRegistry,
heartbeat_window: Duration,
) -> Self
pub fn new( notifier: Arc<LiminalConnectionNotifier>, tracker: HeartbeatTracker, registry: ConnectedWorkerRegistry, heartbeat_window: Duration, ) -> Self
Build a probe over the notifier that owns the liminal connections, the shared liveness tracker whose leases a pong refreshes, and the registry the WARN lines resolve a worker’s queue through.
Both timings derive from heartbeat_window (see the module docs); there
is no separate configuration surface.
This construction probes the LIMINAL transport only: it carries no gRPC
answer-correlation registry, so it does not enumerate gRPC deliveries.
Use Self::across_transports for the production probe.
Sourcepub fn across_transports(
notifier: Option<Arc<LiminalConnectionNotifier>>,
grpc: Option<GrpcLivenessWaiters>,
tracker: HeartbeatTracker,
registry: ConnectedWorkerRegistry,
heartbeat_window: Duration,
) -> Self
pub fn across_transports( notifier: Option<Arc<LiminalConnectionNotifier>>, grpc: Option<GrpcLivenessWaiters>, tracker: HeartbeatTracker, registry: ConnectedWorkerRegistry, heartbeat_window: Duration, ) -> Self
Build the probe that covers EVERY transport a worker may be delivered over (#197).
notifier is None on a boot with no liminal listener; grpc is the
correlation registry the gRPC stream handler delivers answers into, and
is None only for a probe that must not push gRPC pings it could never
hear the answers to.
There is exactly ONE probe per server. Two would each publish a whole eligibility set over the other’s — the verdict is a replacement, not a merge — so the last writer would silently erase the other transport’s findings every cadence.
Sourcepub const fn silence_window(&self) -> Duration
pub const fn silence_window(&self) -> Duration
The silence window this probe declares to every worker it pings.
Sourcepub async fn run(self, shutdown: Receiver<bool>)
pub async fn run(self, shutdown: Receiver<bool>)
Run the probe until shutdown flips to true.
Rounds never overlap: each tick’s pings are awaited to completion (each bounded by the cadence) before the next round starts, and a missed tick is skipped rather than queued.
That bounds the concurrent WAITS to one per connection. It was once
claimed to bound the outstanding PUSHES to one as well, “so it can never
crowd out real dispatches against liminal’s per-connection pending-push
cap.” That claim was false and the failure it denied is exactly what
happened on run dfd2117c: a push slot is not released by the caller
giving up, only by a consumed reply, a deadline expiry, or a connection
close. Awaiting a round to completion ends the wait, not the slot. So
abandoning one unanswered no-deadline ping per round leaked one slot per
round — 32 of them, then total refusal of every push on that connection.
The bound is now real because
LiminalWorkerDelivery::push_payload_with_deadline attaches the
cadence as the push’s own reply deadline, so an unanswered ping’s slot
expires and RELEASES its cap admission instead of accumulating.
Sourcepub async fn probe_once(&self, sequence: u64)
pub async fn probe_once(&self, sequence: u64)
Ping every reachable worker once — liminal connections and gRPC task streams alike, concurrently — apply each answer to the worker’s dispatch probation, and publish ONE verdict for the round.
The two transports are pinged over their own wires, CONCURRENTLY with
each other, and are otherwise indistinguishable from here down: the same
probation, the same tracker, the same single
Self::publish_reachability_verdict call.
EVERY round publishes, including one that found no target to ping. This module used to skip publication for an empty round, reasoning that “publishing an empty exclusion set would be indistinguishable from clearing one”. They are the same act, and skipping it is what left a stale verdict standing: a verdict is published by REPLACEMENT, so a round that publishes nothing leaves the previous round’s exclusions in force over a fleet this round measured afresh. The set is derived from the tracker’s standing evidence rather than from this round’s targets, so it is well defined whether or not anything was pinged (#25).
Public because it is the whole unit of work: Self::run is a timer
around it and adds nothing but the schedule. Driving rounds directly is
how a test states a probation in ROUNDS rather than in wall-clock
sleeps, which is the difference between asserting the law and asserting
this machine’s timing.
sequence must advance between rounds: it is the echo a worker’s answer
is matched against, and repeating one would let a previous round’s late
answer satisfy this round’s ping.
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for LivenessProbe
impl !UnwindSafe for LivenessProbe
impl Freeze for LivenessProbe
impl Send for LivenessProbe
impl Sync for LivenessProbe
impl Unpin for LivenessProbe
impl UnsafeUnpin for LivenessProbe
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
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> IntoMaybeUndefined<T> for T
impl<T> IntoMaybeUndefined<T> for T
Source§fn into_maybe_undefined(self) -> MaybeUndefined<T>
fn into_maybe_undefined(self) -> MaybeUndefined<T>
Source§impl<T> IntoOption<T> for T
impl<T> IntoOption<T> for T
Source§fn into_option(self) -> Option<T>
fn into_option(self) -> Option<T>
Source§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request