Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// Pure decision helpers for the zombie-stream (half-open gRPC) watchdog.
|
||||
//
|
||||
// spectrum-ts only reconnects when its inbound async iterator throws or ends.
|
||||
// A half-open ("zombie") socket makes the iterator hang forever — no error,
|
||||
// no end — so inbound silently dies while /healthz still looks fine. The
|
||||
// watchdog in index.mjs tracks the last time the inbound iterator yielded and,
|
||||
// once the stream has been silent past a conservative threshold, drives a
|
||||
// cheap authenticated unary read over the same channel. STRICT semantics:
|
||||
//
|
||||
// - probe resolves, or rejects with a not-found-shaped error for our
|
||||
// synthetic id -> ALIVE (the wire round-tripped)
|
||||
// - probe rejects any other way (UNAVAILABLE, DEADLINE_EXCEEDED, network
|
||||
// down, ...) -> INCONCLUSIVE — never treated as alive, and
|
||||
// never treated as zombie-proof either
|
||||
//
|
||||
// A zombie is only declared when the stream is silent past the threshold AND
|
||||
// a probe proves connectivity (the wire works but the stream is deaf). Silence
|
||||
// alone NEVER degrades the stream: shared lines can be legitimately quiet for
|
||||
// hours. Inconclusive probes NEVER degrade it either: the network may simply
|
||||
// be down, and in that case the iterator will eventually throw and the
|
||||
// existing re-subscribe loop recovers on its own.
|
||||
//
|
||||
// These helpers are pure (no SDK, no timers) so tests can execute them under
|
||||
// node — see tests/plugins/platforms/photon/test_zombie_stream_watchdog.py.
|
||||
|
||||
// gRPC NOT_FOUND is code 5; SDKs also surface it as "not found" / "NotFound"
|
||||
// message text. Anything not clearly not-found is inconclusive.
|
||||
const NOT_FOUND_RE = /not[\s_-]?found/i;
|
||||
|
||||
/**
|
||||
* Classify the rejection of the synthetic-id probe read.
|
||||
*
|
||||
* @param {unknown} err error thrown by `space.getMessage(<synthetic id>)`
|
||||
* @returns {{alive: boolean, inconclusive: boolean, reason: string}}
|
||||
*/
|
||||
export function classifyProbeRejection(err) {
|
||||
const code = err && typeof err === "object" ? err.code : undefined;
|
||||
const message =
|
||||
err && typeof err === "object" && err.message
|
||||
? String(err.message)
|
||||
: String(err);
|
||||
if (code === 5 || code === "notFound" || NOT_FOUND_RE.test(message)) {
|
||||
// Expected: the synthetic id doesn't exist. The unary call completed a
|
||||
// round-trip, so the channel is provably alive.
|
||||
return { alive: true, inconclusive: false, reason: "not-found round-trip" };
|
||||
}
|
||||
// Anything else (UNAVAILABLE, DEADLINE_EXCEEDED, TLS, auth, ...) does NOT
|
||||
// prove liveness — and doesn't prove a zombie either.
|
||||
return { alive: false, inconclusive: true, reason: message };
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the watchdog probe at all this tick?
|
||||
*
|
||||
* @param {number} silentForMs ms since the inbound iterator last yielded
|
||||
* @param {number} thresholdMs silence threshold (<= 0 disables the watchdog)
|
||||
* @param {number} sinceLastProbeMs ms since the previous probe attempt
|
||||
* @param {number} probeCooldownMs min spacing between probe attempts
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldProbe(silentForMs, thresholdMs, sinceLastProbeMs, probeCooldownMs) {
|
||||
if (!(thresholdMs > 0)) return false;
|
||||
if (silentForMs < thresholdMs) return false;
|
||||
return sinceLastProbeMs >= probeCooldownMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Final classification: zombie only on silence past threshold + probe-proven
|
||||
* connectivity. Never on silence alone, never on an inconclusive probe.
|
||||
*
|
||||
* @param {number} silentForMs ms since the inbound iterator last yielded
|
||||
* @param {number} thresholdMs silence threshold (<= 0 disables the watchdog)
|
||||
* @param {{alive: boolean}} probeOutcome
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isZombieSuspect(silentForMs, thresholdMs, probeOutcome) {
|
||||
if (!(thresholdMs > 0)) return false;
|
||||
if (silentForMs < thresholdMs) return false;
|
||||
return probeOutcome != null && probeOutcome.alive === true;
|
||||
}
|
||||
Reference in New Issue
Block a user