fix issue 210 slow source search reliability

This commit is contained in:
kuekhaoyang
2026-07-13 21:09:47 +08:00
parent 2ae0a03366
commit 66d8564dd2
14 changed files with 364 additions and 140 deletions
+10 -5
View File
@@ -24,12 +24,12 @@ export async function fetchWithTimeout(
// If an external signal is provided, propagate its abort
const externalSignal = options.signal;
let onAbort: (() => void) | undefined;
if (externalSignal) {
if (externalSignal.aborted) {
clearTimeout(timeoutId);
controller.abort();
} else {
const onAbort = () => controller.abort();
onAbort = () => controller.abort();
externalSignal.addEventListener('abort', onAbort, { once: true });
}
}
@@ -39,11 +39,12 @@ export async function fetchWithTimeout(
...options,
signal: controller.signal,
});
clearTimeout(timeoutId);
return response;
} catch (error) {
} finally {
clearTimeout(timeoutId);
throw error;
if (externalSignal && onAbort) {
externalSignal.removeEventListener('abort', onAbort);
}
}
}
@@ -62,6 +63,10 @@ export async function withRetry<T>(
} catch (error) {
lastError = error as Error;
if (lastError?.name === 'AbortError') {
throw lastError;
}
if (i < retries) {
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * (i + 1)));
}
+69
View File
@@ -0,0 +1,69 @@
export interface SourceLatencyResult {
latency: number;
success: boolean;
timeout: boolean;
method: 'HEAD' | 'GET';
}
type ProbeAttemptResult = SourceLatencyResult;
interface ProbeSourceLatencyOptions {
fetcher?: typeof fetch;
timeoutMs?: number;
now?: () => number;
}
async function probeAttempt(
url: string,
method: 'HEAD' | 'GET',
fetcher: typeof fetch,
timeoutMs: number,
now: () => number,
): Promise<ProbeAttemptResult> {
const controller = new AbortController();
let timedOut = false;
const timeoutId = setTimeout(() => {
timedOut = true;
controller.abort();
}, timeoutMs);
const startedAt = now();
try {
await fetcher(url, {
method,
signal: controller.signal,
redirect: 'follow',
...(method === 'GET' ? { headers: { Range: 'bytes=0-0' } } : {}),
});
return {
latency: Math.max(0, Math.round(now() - startedAt)),
success: true,
timeout: false,
method,
};
} catch (error) {
return {
latency: Math.max(0, Math.round(now() - startedAt)),
success: false,
timeout: timedOut || (error instanceof Error && error.name === 'AbortError'),
method,
};
} finally {
clearTimeout(timeoutId);
}
}
export async function probeSourceLatency(
url: string,
options: ProbeSourceLatencyOptions = {},
): Promise<SourceLatencyResult> {
const fetcher = options.fetcher ?? fetch;
const timeoutMs = options.timeoutMs ?? 5000;
const now = options.now ?? (() => performance.now());
const headResult = await probeAttempt(url, 'HEAD', fetcher, timeoutMs, now);
if (headResult.success) return headResult;
return probeAttempt(url, 'GET', fetcher, timeoutMs, now);
}