Filed by the Account-2 read-only amplifier (2026-09-18, HEAD 246b710; coordinator + 3 read-only workers; static evidence only — no benchmark or script was run, per the read-only contract). Strong confidence; semantics verified by reading every file cited.
Finding
mapLimit is fail-fast: the first worker rejection sets failed = true so free workers stop taking items, but in-flight calls run to completion and their results are discarded — the contract is documented in the code itself (src/pool.mjs:13,22-35). downloadPart's worst case is 3 × 60 s idle-timeout attempts plus backoff ≈ ~180 s per part (src/download.mjs:24-26,101-108), and there is no AbortSignal / cancellation plumbing anywhere in src/ (rg over src/ + bin/: only per-request timeout req.destroy).
So when part k of a title fails terminally, the 1–2 sibling parts still in flight keep downloading — each for up to ~180 s of retry cycles — after the title's failure is already decided.
Impact
Decision question (implementation-ready shape, but adopt/decline first)
Thread a cancellation signal mapLimit → fn (for archive.mjs:98: downloadPart attempt loop → followRedirects), so fail-fast aborts siblings: mapLimit(items, limit, fn, { signal }) or a returned AbortController; downloadPart(part, dest, { signal }) checks between retries and destroys the in-flight request. Alternatively accept and document the bounded waste.
Evaluate whether the failure-path waste justifies the signal plumbing, or decline with reasons.
Filed by the Account-2 read-only amplifier (2026-09-18, HEAD 246b710; coordinator + 3 read-only workers; static evidence only — no benchmark or script was run, per the read-only contract). Strong confidence; semantics verified by reading every file cited.
Finding
mapLimitis fail-fast: the first worker rejection setsfailed = trueso free workers stop taking items, but in-flight calls run to completion and their results are discarded — the contract is documented in the code itself (src/pool.mjs:13,22-35).downloadPart's worst case is 3 × 60 s idle-timeout attempts plus backoff ≈ ~180 s per part (src/download.mjs:24-26,101-108), and there is no AbortSignal / cancellation plumbing anywhere insrc/(rg over src/ + bin/: only per-request timeoutreq.destroy).So when part k of a title fails terminally, the 1–2 sibling parts still in flight keep downloading — each for up to ~180 s of retry cycles — after the title's failure is already decided.
Impact
bin/libby.mjs:556-568: thearchive --allloop catches the failure and moves to the next loan while the orphans keep transferring. Orphaned parts hold sockets and bandwidth on the shared keep-alive agent (maxSockets 8, src/http.mjs:20-38) and compete with the next title's downloads — wasted transfer plus socket contention on the failure path.main()resolves withoutprocess.exit(only error paths call it; bin/libby.mjs:597), so the event loop stays alive until the orphans drain: the summary line prints, then the terminal can sit for seconds-to-minutes in the pathological stall case.libby archive --all— titles currently download strictly sequentially #14's title-level parallelism — it reports an unintended side effect of the existing within-title pool.Decision question (implementation-ready shape, but adopt/decline first)
Thread a cancellation signal
mapLimit → fn(forarchive.mjs:98:downloadPartattempt loop →followRedirects), so fail-fast aborts siblings:mapLimit(items, limit, fn, { signal })or a returnedAbortController;downloadPart(part, dest, { signal })checks between retries and destroys the in-flight request. Alternatively accept and document the bounded waste.Evaluate whether the failure-path waste justifies the signal plumbing, or decline with reasons.