refactor(qa): simplify gateway lifecycle state (#121809)

Amp-Thread-ID: https://ampcode.com/threads/T-019fec02-d170-73eb-aec8-b364ab706745

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Peter Steinberger
2026-08-10 19:47:45 -07:00
committed by GitHub
parent f5aba54437
commit b44c9a8515
2 changed files with 15 additions and 31 deletions
+1 -3
View File
@@ -682,7 +682,6 @@ function createQaGatewayChildLogCollector() {
};
let recent = "";
let end = 0;
let dropped = false;
const readFrom = (mark: number) => {
const start = end - recent.length;
@@ -697,7 +696,6 @@ function createQaGatewayChildLogCollector() {
recent += text;
if (recent.length > QA_GATEWAY_CHILD_RECENT_LOG_CHARS) {
recent = sliceUtf16Safe(recent, -QA_GATEWAY_CHILD_RECENT_LOG_CHARS);
dropped = true;
}
},
mark() {
@@ -707,7 +705,7 @@ function createQaGatewayChildLogCollector() {
return readFrom(mark);
},
text() {
return `${dropped ? QA_GATEWAY_CHILD_LOG_TRUNCATION_MARKER : ""}${recent}`.trim();
return `${end > recent.length ? QA_GATEWAY_CHILD_LOG_TRUNCATION_MARKER : ""}${recent}`.trim();
},
};
}
+14 -28
View File
@@ -18,10 +18,10 @@ type QaGatewayRpcClient = {
};
type QaGatewayConnectionGate = {
connected: boolean;
promise: Promise<void>;
resolve: () => void;
reject: (error: Error) => void;
state: "pending" | "connected" | "failed";
};
const QA_GATEWAY_RPC_TIMEOUT_MS = 20_000;
@@ -29,30 +29,13 @@ const QA_GATEWAY_RPC_TIMEOUT_MS = 20_000;
function createQaGatewayConnectionGate(): QaGatewayConnectionGate {
let resolvePromise!: () => void;
let rejectPromise!: (error: Error) => void;
const gate: QaGatewayConnectionGate = {
promise: new Promise<void>((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
}),
resolve: () => {
if (gate.state !== "pending") {
return;
}
gate.state = "connected";
resolvePromise();
},
reject: (error) => {
if (gate.state !== "pending") {
return;
}
gate.state = "failed";
rejectPromise(error);
},
state: "pending",
};
const promise = new Promise<void>((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
});
// A terminal reconnect error can arrive without an active request waiter.
void gate.promise.catch(() => {});
return gate;
void promise.catch(() => {});
return { connected: false, promise, reject: rejectPromise, resolve: resolvePromise };
}
function formatQaGatewayRpcError(error: unknown, logs: () => string) {
@@ -110,9 +93,12 @@ export async function startQaGatewayRpcClient(params: {
deviceIdentity: null,
mode: "backend",
scopes: ["operator.admin"],
onHelloOk: () => connection.resolve(),
onHelloOk: () => {
connection.connected = true;
connection.resolve();
},
onClose: () => {
if (!stopped && connection.state === "connected") {
if (!stopped && connection.connected) {
connection = createQaGatewayConnectionGate();
}
},
@@ -120,7 +106,7 @@ export async function startQaGatewayRpcClient(params: {
const error = new Error(
`gateway reconnect paused (${info.code}): ${info.reason}${info.detailCode ? ` [${info.detailCode}]` : ""}`,
);
if (connection.state === "connected") {
if (connection.connected) {
connection = createQaGatewayConnectionGate();
}
connection.reject(error);
@@ -175,7 +161,7 @@ export async function startQaGatewayRpcClient(params: {
assertNotStopped();
// A close can race between gate resolution and request dispatch. No frame was sent,
// so waiting for the next hello and retrying is safe even for non-idempotent methods.
if (connection === requestConnection && connection.state === "connected") {
if (connection === requestConnection && connection.connected) {
connection = createQaGatewayConnectionGate();
}
}