mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 20:35:39 -06:00
fix(e2e): abort kitchen sink retry waits
This commit is contained in:
@@ -892,14 +892,10 @@ export async function fetchJson(url, options = {}) {
|
||||
let removeExternalAbort = () => {};
|
||||
const abortPromise = externalSignal
|
||||
? new Promise((_, reject) => {
|
||||
const abortError = () =>
|
||||
externalSignal.reason instanceof Error
|
||||
? externalSignal.reason
|
||||
: new Error("fetch aborted");
|
||||
const onAbort = () => {
|
||||
const error = abortError();
|
||||
const error = getExternalAbortReason(externalSignal);
|
||||
controller.abort(error);
|
||||
reject(new Error(error.message, { cause: error }));
|
||||
reject(createExternalAbortError(externalSignal));
|
||||
};
|
||||
if (externalSignal.aborted) {
|
||||
onAbort();
|
||||
@@ -939,7 +935,7 @@ export async function fetchJson(url, options = {}) {
|
||||
if (attempt >= attempts || !isRetryableTransientNetworkError(error)) {
|
||||
throw error;
|
||||
}
|
||||
await delay(options.retryDelayMs ?? 250);
|
||||
await delayWithAbort(options.retryDelayMs ?? 250, externalSignal);
|
||||
} finally {
|
||||
removeExternalAbort();
|
||||
if (timeout) {
|
||||
@@ -950,6 +946,33 @@ export async function fetchJson(url, options = {}) {
|
||||
throw toLintErrorObject(lastError ?? new Error(`fetch ${url} failed`), "Non-Error thrown");
|
||||
}
|
||||
|
||||
function getExternalAbortReason(signal) {
|
||||
return signal?.reason instanceof Error ? signal.reason : new Error("fetch aborted");
|
||||
}
|
||||
|
||||
function createExternalAbortError(signal) {
|
||||
const reason = getExternalAbortReason(signal);
|
||||
return new Error(reason.message, { cause: reason });
|
||||
}
|
||||
|
||||
async function delayWithAbort(delayMs, signal) {
|
||||
if (!signal) {
|
||||
await delay(delayMs);
|
||||
return;
|
||||
}
|
||||
if (signal.aborted) {
|
||||
throw createExternalAbortError(signal);
|
||||
}
|
||||
try {
|
||||
await delay(delayMs, undefined, { signal });
|
||||
} catch (error) {
|
||||
if (signal.aborted) {
|
||||
throw createExternalAbortError(signal);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readBoundedResponseText(response, byteLimit, timeoutPromise) {
|
||||
const resolvedByteLimit = byteLimit ?? resolveKitchenSinkRpcConfig().fetchBodyMaxBytes;
|
||||
const contentLength = response.headers?.get?.("content-length");
|
||||
|
||||
@@ -1815,6 +1815,30 @@ describe("kitchen-sink RPC process sampling", () => {
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("aborts HTTP probe retry backoff when the external signal fires", async () => {
|
||||
const controller = new AbortController();
|
||||
const reset = new TypeError("fetch failed", {
|
||||
cause: Object.assign(new Error("read ECONNRESET"), { code: "ECONNRESET" }),
|
||||
});
|
||||
const fetchImpl = vi.fn().mockRejectedValue(reset);
|
||||
const startedAt = Date.now();
|
||||
|
||||
setTimeout(() => {
|
||||
controller.abort(new Error("gateway exited before ready"));
|
||||
}, 25);
|
||||
|
||||
await expect(
|
||||
fetchJson("http://127.0.0.1:19680/healthz", {
|
||||
attempts: 2,
|
||||
fetchImpl,
|
||||
retryDelayMs: 5_000,
|
||||
signal: controller.signal,
|
||||
}),
|
||||
).rejects.toThrow("gateway exited before ready");
|
||||
expect(fetchImpl).toHaveBeenCalledOnce();
|
||||
expect(Date.now() - startedAt).toBeLessThan(500);
|
||||
});
|
||||
|
||||
it("bounds HTTP probe response bodies", async () => {
|
||||
const fetchImpl = vi.fn().mockResolvedValue(new Response("x".repeat(1025), { status: 200 }));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user