From 341731a946ef927d5f62de8d52ffa8e8067d5c24 Mon Sep 17 00:00:00 2001 From: dlawler489 <104159223@student.swin.edu.au> Date: Wed, 16 Sep 2026 17:50:48 +1000 Subject: [PATCH] Give downloads a deadline instead of hanging for ever MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Neither the FlareSolverr call nor the CDN fetch set a timeout, so a stalled connection hung the request indefinitely: the button sat on "Downloading…" with nothing to reset it and no error to show. FlareSolverr calls now give up 15s after the deadline FlareSolverr itself was given (and a timeout counts as retryable, like other transport failures), and a CDN download gives up after three minutes. Co-Authored-By: Claude Opus 5 --- lib/flaresolverr.js | 5 ++++- server.js | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/flaresolverr.js b/lib/flaresolverr.js index bd3fddf..031ccce 100644 --- a/lib/flaresolverr.js +++ b/lib/flaresolverr.js @@ -21,6 +21,9 @@ async function solve(flaresolverrUrl, targetUrl, opts = {}) { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), + // FlareSolverr is told to give up at maxTimeout; if it doesn't answer + // even then, stop waiting rather than hanging the request for ever. + signal: AbortSignal.timeout((opts.maxTimeout || 60000) + 15000), }); if (res.status >= 500) { lastErr = new Error(`FlareSolverr HTTP ${res.status}`); continue; } if (!res.ok) throw new Error(`FlareSolverr HTTP ${res.status}`); @@ -29,7 +32,7 @@ async function solve(flaresolverrUrl, targetUrl, opts = {}) { return data.solution.response; } catch (err) { lastErr = err; - if (!/HTTP 5\d\d|fetch failed|ECONNRESET|network/i.test(err.message)) throw err; + if (!/HTTP 5\d\d|fetch failed|ECONNRESET|network|timeout|aborted/i.test(err.message)) throw err; } } throw lastErr; diff --git a/server.js b/server.js index 2381a5b..2cfa59a 100644 --- a/server.js +++ b/server.js @@ -993,7 +993,8 @@ async function downloadMakerworldFiles(html, skipNames = new Set()) { } const original = dl.name || `instance-${inst.id}.3mf`; if (skipNames.has(original)) continue; - const resp = await fetch(dl.url); + // A stalled CDN connection would otherwise hang the download for ever. + const resp = await fetch(dl.url, { signal: AbortSignal.timeout(180000) }); if (!resp.ok) { console.warn(`MakerWorld CDN download ${resp.status} for ${original}`); problems.push({ message: `Downloading ${original} from MakerWorld failed (HTTP ${resp.status})` }); @@ -1064,7 +1065,8 @@ async function downloadThingiverseFiles(meta, skipNames = new Set()) { for (const dl of meta.downloads) { if (!fileProcessor.isAllowed(dl.name)) continue; // skip readmes/images if (skipNames.has(dl.name)) continue; - const resp = await fetch(dl.url); + // A stalled CDN connection would otherwise hang the download for ever. + const resp = await fetch(dl.url, { signal: AbortSignal.timeout(180000) }); if (!resp.ok) { console.error(`Download failed (${resp.status}): ${dl.name}`); continue; } const buffer = Buffer.from(await resp.arrayBuffer()); const fileName = uniqueName(dl.name);