From c58875c07234311c5e334e2e818e7e4a01d4b5b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastjan=20Arti=C4=8D?= Date: Thu, 30 Apr 2026 16:28:27 +0000 Subject: [PATCH] Upload: timeout + retry + ne ustavi loop ob enem fail-u MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User: 'zakaj se je ustavilo? Naložil sem več kot 70.' Diagnoza: 12/70+ je prišlo do server-ja (vsi 200 OK). Browser-side problem: en upload je stuck → cel for-loop blokiran. Fixes: 1. xhr.timeout = 10min per file (prej: večnost) 2. xhr.ontimeout, xhr.upload.ontimeout — proper error handling 3. NEW: uploadFileWithRetry() — 2x retry z 2s/4s eksponentnim delay-om za očasne mrežne odpovedi 4. Catch v loop ne kliče liveFail() (kar bi naredil disabled submit-btn) ampak samo showLive() z 'preskočil' sporočilo → loop nadaljuje 5. Console.warn v retry attempts za debugging Sedaj če eno failes: - Retry 2x avtomatsko (2s + 4s delay) - Če še vedno ne uspe → preskoči to datoteko, nadaljuj z naslednjo - Pri koncu vidiš katere so failed (v console + showLive) --- templates/index.html | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/templates/index.html b/templates/index.html index 7c7c8be..8289747 100644 --- a/templates/index.html +++ b/templates/index.html @@ -843,7 +843,7 @@ fd.append("batch_id", batchId); try { - const uploadResp = await uploadFileXHR(fd, (loaded, total) => { + const uploadResp = await uploadFileWithRetry(fd, (loaded, total) => { const filePct = (loaded / total) * 100; showLive( `Nalaganje ${i + 1}/${totalFiles}`, @@ -861,8 +861,12 @@ }); } catch (e) { console.error(`Failed to upload ${f.name}: ${e.message}`); - liveFail(`Napaka pri ${f.name}: ${e.message}`); - // Continue z naslednjimi + showLive( + `⚠ Preskočil ${i + 1}/${totalFiles}`, + `${f.name}: ${e.message} (nadaljujem z naslednjim)`, + ((i + 1) / totalFiles) * 100 + ); + // Continue z naslednjimi (ne breakaj cel loop) } } @@ -887,6 +891,8 @@ function uploadFileXHR(formData, onProgress) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); + // 10 min timeout per file (za velike komade) + xhr.timeout = 10 * 60 * 1000; xhr.upload.onprogress = e => { if (e.lengthComputable && onProgress) { onProgress(e.loaded, e.total); @@ -896,12 +902,32 @@ if (xhr.status === 200) resolve(xhr.responseText); else reject(new Error(`HTTP ${xhr.status}: ${xhr.responseText.slice(0, 200)}`)); }; - xhr.onerror = () => reject(new Error("Upload error")); + xhr.onerror = () => reject(new Error("Network error (offline?)")); + xhr.ontimeout = () => reject(new Error("Timeout (10min)")); xhr.upload.onerror = () => reject(new Error("Upload transfer error")); + xhr.upload.ontimeout = () => reject(new Error("Upload timeout")); xhr.open("POST", "/api/upload"); xhr.send(formData); }); } + + // Retry helper: če upload faila, poskusi 2x ponovno z malim delay-om + async function uploadFileWithRetry(formData, onProgress, maxRetries = 2) { + let lastErr = null; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + return await uploadFileXHR(formData, onProgress); + } catch (e) { + lastErr = e; + console.warn(`Upload attempt ${attempt + 1} failed: ${e.message}`); + if (attempt < maxRetries) { + // Eksponentni delay: 2s, 4s + await new Promise(r => setTimeout(r, 2000 * (attempt + 1))); + } + } + } + throw lastErr; + } // ─── Watch job (SSE) ──────────────────────────── function watchJob(jobId) {