fix(gateway): disarm wrapper timeout before teardown (#70704)

This commit is contained in:
Tak Hoffman
2026-04-23 12:36:30 -05:00
committed by GitHub
parent 5c229e32b7
commit d39e34d31f
2 changed files with 64 additions and 13 deletions

View File

@@ -875,6 +875,61 @@ describe("callGateway error details", () => {
expect(callResolved).toBe(true);
});
it("clears the wrapper timeout before awaiting gateway teardown", async () => {
setLocalLoopbackGatewayConfig();
vi.useFakeTimers();
let releaseStop!: () => void;
let stopStarted = false;
__testing.setDepsForTests({
createGatewayClient: (opts) =>
({
async request(
method: string,
params: unknown,
requestOpts?: { expectFinal?: boolean; timeoutMs?: number | null },
) {
lastRequestOptions = { method, params, opts: requestOpts };
return { ok: true };
},
start() {
opts.onHelloOk?.({
features: {
methods: helloMethods ?? [],
events: [],
},
} as unknown as Parameters<NonNullable<typeof opts.onHelloOk>>[0]);
},
stop() {},
async stopAndWait() {
stopStarted = true;
await new Promise<void>((resolve) => {
releaseStop = resolve;
});
},
}) as never,
loadConfig: loadConfig as unknown as () => OpenClawConfig,
loadOrCreateDeviceIdentity: () => deviceIdentityState.value,
resolveGatewayPort: resolveGatewayPort as unknown as (
cfg?: OpenClawConfig,
env?: NodeJS.ProcessEnv,
) => number,
});
const promise = callGateway<{ ok: true }>({ method: "health", timeoutMs: 5 });
await vi.waitFor(() => {
expect(stopStarted).toBe(true);
});
await vi.advanceTimersByTimeAsync(5);
releaseStop();
await expect(promise).resolves.toEqual({ ok: true });
});
it("fails fast when remote mode is missing remote url", async () => {
loadConfig.mockReturnValue({
gateway: { mode: "remote", bind: "loopback", remote: {} },

View File

@@ -490,11 +490,13 @@ async function executeGatewayRequestWithScopes<T>(params: {
}
settled = true;
clearTimeout(timer);
if (err) {
reject(err);
} else {
resolve(value as T);
}
void stopGatewayClient(client).finally(() => {
if (err) {
reject(err);
} else {
resolve(value as T);
}
});
};
const client = gatewayCallDeps.createGatewayClient({
@@ -528,11 +530,9 @@ async function executeGatewayRequestWithScopes<T>(params: {
timeoutMs: opts.timeoutMs,
});
ignoreClose = true;
await stopGatewayClient(client);
stop(undefined, result);
} catch (err) {
ignoreClose = true;
await stopGatewayClient(client);
stop(err as Error);
}
},
@@ -541,17 +541,13 @@ async function executeGatewayRequestWithScopes<T>(params: {
return;
}
ignoreClose = true;
void stopGatewayClient(client).finally(() => {
stop(new Error(formatGatewayCloseError(code, reason, params.connectionDetails)));
});
stop(new Error(formatGatewayCloseError(code, reason, params.connectionDetails)));
},
});
const timer = setTimeout(() => {
ignoreClose = true;
void stopGatewayClient(client).finally(() => {
stop(new Error(formatGatewayTimeoutError(timeoutMs, params.connectionDetails)));
});
stop(new Error(formatGatewayTimeoutError(timeoutMs, params.connectionDetails)));
}, safeTimerTimeoutMs);
client.start();