mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 23:44:07 +00:00
fix(signal): cap client request timeouts
This commit is contained in:
@@ -17,6 +17,8 @@ let signalCheck: typeof import("./client.js").signalCheck;
|
||||
let signalRpcRequest: typeof import("./client.js").signalRpcRequest;
|
||||
let streamSignalEvents: typeof import("./client.js").streamSignalEvents;
|
||||
|
||||
const MAX_TIMER_TIMEOUT_MS = 2_147_000_000;
|
||||
|
||||
const servers: http.Server[] = [];
|
||||
|
||||
async function readRequestBody(req: IncomingMessage): Promise<string> {
|
||||
@@ -51,6 +53,7 @@ beforeAll(async () => {
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
await Promise.all(
|
||||
servers.splice(0).map(
|
||||
(server) =>
|
||||
@@ -221,6 +224,24 @@ describe("signalRpcRequest", () => {
|
||||
}),
|
||||
).rejects.toThrow("Signal HTTP exceeded deadline after 25ms");
|
||||
});
|
||||
|
||||
it("caps oversized RPC request timeouts before scheduling", async () => {
|
||||
const timeoutSpy = vi
|
||||
.spyOn(globalThis, "setTimeout")
|
||||
.mockReturnValue(1 as unknown as ReturnType<typeof setTimeout>);
|
||||
vi.spyOn(globalThis, "clearTimeout").mockImplementation(() => undefined);
|
||||
const baseUrl = await withSignalServer((_req, res) => {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ jsonrpc: "2.0", result: { version: "0.13.22" }, id: "test-id" }));
|
||||
});
|
||||
|
||||
await signalRpcRequest("version", undefined, {
|
||||
baseUrl,
|
||||
timeoutMs: MAX_TIMER_TIMEOUT_MS + 1_000_000,
|
||||
});
|
||||
|
||||
expect(timeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS);
|
||||
});
|
||||
});
|
||||
|
||||
describe("signalCheck", () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import http, { type ClientRequest, type IncomingMessage } from "node:http";
|
||||
import https from "node:https";
|
||||
import { generateSecureUuid } from "openclaw/plugin-sdk/core";
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
export type SignalRpcOptions = {
|
||||
baseUrl: string;
|
||||
@@ -106,7 +107,7 @@ function normalizeSignalSseTimeoutMs(timeoutMs: number): number | null {
|
||||
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
||||
return null;
|
||||
}
|
||||
return timeoutMs;
|
||||
return resolveTimerTimeoutMs(timeoutMs, DEFAULT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function requestSignalHttpText(
|
||||
@@ -120,13 +121,14 @@ function requestSignalHttpText(
|
||||
},
|
||||
): Promise<SignalHttpResponse> {
|
||||
assertSignalHttpProtocol(url, "HTTP");
|
||||
const timeoutMs = resolveTimerTimeoutMs(options.timeoutMs, DEFAULT_TIMEOUT_MS);
|
||||
const client = url.protocol === "https:" ? https : http;
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
let request: ClientRequest | undefined;
|
||||
const deadline = setTimeout(() => {
|
||||
request?.destroy(new Error(`Signal HTTP exceeded deadline after ${options.timeoutMs}ms`));
|
||||
}, options.timeoutMs);
|
||||
request?.destroy(new Error(`Signal HTTP exceeded deadline after ${timeoutMs}ms`));
|
||||
}, timeoutMs);
|
||||
deadline.unref?.();
|
||||
const cleanup = () => {
|
||||
clearTimeout(deadline);
|
||||
@@ -180,8 +182,8 @@ function requestSignalHttpText(
|
||||
});
|
||||
},
|
||||
);
|
||||
request.setTimeout(options.timeoutMs, () => {
|
||||
request?.destroy(new Error(`Signal HTTP timed out after ${options.timeoutMs}ms`));
|
||||
request.setTimeout(timeoutMs, () => {
|
||||
request?.destroy(new Error(`Signal HTTP timed out after ${timeoutMs}ms`));
|
||||
});
|
||||
request.on("error", rejectOnce);
|
||||
if (options.body !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user