Skip to content

Commit 93a653d

Browse files
committed
fix: race limit.map against abort signal to bound in-flight tasks
The AbortSignal.timeout only prevents queued tasks from starting but doesn't abort already in-flight HTTP requests. Race limit.map against the abort signal so the function returns on timeout even if some tasks are still running. Results are written to a shared array so partial results survive the race.
1 parent 4d6e199 commit 93a653d

File tree

1 file changed

+28
-11
lines changed

1 file changed

+28
-11
lines changed

src/lib/resolve-target.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -595,15 +595,15 @@ const DSN_RESOLVE_TIMEOUT_MS = 15_000;
595595
/**
596596
* Resolve DSNs with a concurrency limit and overall timeout.
597597
*
598-
* Uses p-limit's `map` helper for concurrency control and
599-
* `AbortSignal.timeout` for a self-cleaning deadline. Queued tasks
600-
* check the abort signal before doing work (same pattern as
601-
* code-scanner's earlyExit flag from PR #414).
598+
* Uses p-limit's `map` helper for concurrency control and races it
599+
* against `AbortSignal.timeout` so the CLI never hangs indefinitely.
600+
* Queued tasks check the abort signal before doing work (same pattern
601+
* as code-scanner's earlyExit flag from PR #414). In-flight tasks that
602+
* already started are abandoned on timeout — their individual HTTP
603+
* timeouts (30s in sentry-client.ts) bound them independently.
602604
*
603-
* Note: Cannot use `limit.clearQueue()` — as documented in
604-
* code-scanner.ts, it causes cleared promises to never settle,
605-
* hanging the map forever. Instead, queued tasks return null when
606-
* the signal is aborted.
605+
* Results are written to a shared array so that tasks completing before
606+
* the deadline are captured even when the overall operation times out.
607607
*
608608
* @param dsns - Deduplicated DSNs to resolve
609609
* @returns Array of resolved targets (null for failures/timeouts)
@@ -614,14 +614,31 @@ async function resolveDsnsWithTimeout(
614614
const limit = pLimit(DSN_RESOLVE_CONCURRENCY);
615615
const signal = AbortSignal.timeout(DSN_RESOLVE_TIMEOUT_MS);
616616

617-
const results = await limit.map(dsns, (dsn) => {
617+
// Shared results array — tasks write their result as they complete,
618+
// so partial results survive timeout.
619+
const results: (ResolvedTarget | null)[] = new Array(dsns.length).fill(null);
620+
621+
const mapDone = limit.map(dsns, (dsn, i) => {
618622
if (signal.aborted) {
619623
return Promise.resolve(null);
620624
}
621-
return resolveDsnToTarget(dsn);
625+
return resolveDsnToTarget(dsn).then((target) => {
626+
results[i] = target;
627+
return target;
628+
});
629+
});
630+
631+
// Race limit.map against the abort signal so in-flight tasks
632+
// don't block the timeout.
633+
const aborted = new Promise<"timeout">((resolve) => {
634+
signal.addEventListener("abort", () => resolve("timeout"), { once: true });
622635
});
636+
const raceResult = await Promise.race([
637+
mapDone.then(() => "done" as const),
638+
aborted,
639+
]);
623640

624-
if (signal.aborted) {
641+
if (raceResult === "timeout") {
625642
log.warn(
626643
`DSN resolution timed out after ${DSN_RESOLVE_TIMEOUT_MS / 1000}s, returning partial results`
627644
);

0 commit comments

Comments
 (0)