fix(photon): use GUID for liveness probe message id
(cherry picked from commit cdb6ccadb045e599db795823a100ae7b61d377ec)
This commit is contained in:
@@ -69,6 +69,7 @@ import { patchSpectrumTs } from "./patch-spectrum-mixed-attachments.mjs";
|
||||
import { chooseSendFormat } from "./send-format.mjs";
|
||||
import {
|
||||
classifyProbeRejection,
|
||||
createProbeMessageId,
|
||||
shouldProbe,
|
||||
isZombieSuspect,
|
||||
} from "./stream-staleness.mjs";
|
||||
@@ -267,7 +268,7 @@ console.log = (...args) => {
|
||||
// half-open ("zombie") one. `space.get` is purely local in shared/dedicated
|
||||
// mode (no chat is created or messaged); only the message read hits the wire.
|
||||
const PROBE_SPACE_ID = process.env.PHOTON_PROBE_SPACE_ID || "any;-;+10000000000";
|
||||
const PROBE_MSG_PREFIX = "hermes-liveness-probe-";
|
||||
|
||||
|
||||
if (!projectId || !projectSecret || !sharedToken) {
|
||||
console.error(
|
||||
@@ -730,8 +731,7 @@ async function probeUpstream() {
|
||||
if (typeof app?.stop !== "function") {
|
||||
return { alive: false, hung: false, reason: "spectrum app not constructed" };
|
||||
}
|
||||
const probeId =
|
||||
PROBE_MSG_PREFIX + Date.now() + "-" + Math.random().toString(36).slice(2);
|
||||
const probeId = createProbeMessageId();
|
||||
let timer = null;
|
||||
const timeout = new Promise((resolve) => {
|
||||
timer = setTimeout(
|
||||
|
||||
@@ -23,9 +23,15 @@
|
||||
// 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.
|
||||
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
// 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;
|
||||
/** Return a unique message id accepted by Spectrum's message-id parser. */
|
||||
export function createProbeMessageId() {
|
||||
return randomUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify the rejection of the synthetic-id probe read.
|
||||
|
||||
@@ -25,12 +25,14 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
from gateway.config import PlatformConfig
|
||||
from plugins.platforms.photon.adapter import PhotonAdapter
|
||||
|
||||
@@ -48,7 +50,8 @@ def _make_adapter(monkeypatch: pytest.MonkeyPatch) -> PhotonAdapter:
|
||||
|
||||
def _run_staleness_harness(script: str) -> Dict[str, Any]:
|
||||
harness = (
|
||||
"import { classifyProbeRejection, shouldProbe, isZombieSuspect } "
|
||||
"import { classifyProbeRejection, shouldProbe, isZombieSuspect, "
|
||||
"createProbeMessageId } "
|
||||
f"from {json.dumps(_MODULE.as_uri())};\n"
|
||||
+ script
|
||||
)
|
||||
@@ -63,6 +66,20 @@ def _run_staleness_harness(script: str) -> Dict[str, Any]:
|
||||
return json.loads(run.stdout)
|
||||
|
||||
|
||||
def test_probe_message_id_is_guid_shaped_and_unique() -> None:
|
||||
out = _run_staleness_harness(
|
||||
"""
|
||||
const first = createProbeMessageId();
|
||||
const second = createProbeMessageId();
|
||||
process.stdout.write(JSON.stringify({ first, second }));
|
||||
"""
|
||||
)
|
||||
guid_re = r"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"
|
||||
assert re.fullmatch(guid_re, out["first"])
|
||||
assert re.fullmatch(guid_re, out["second"])
|
||||
assert out["first"] != out["second"]
|
||||
|
||||
|
||||
def test_probe_rejection_classification_is_strict() -> None:
|
||||
"""Only not-found-shaped rejections prove liveness; everything else is
|
||||
inconclusive — a rejected probe is NEVER treated as alive (#45580's
|
||||
|
||||
Reference in New Issue
Block a user