From 209ca6a6c0f77eda06c8d17f5039a580f2705534 Mon Sep 17 00:00:00 2001 From: averel10 Date: Fri, 13 Mar 2026 10:07:43 +0100 Subject: [PATCH] feat: implement exponential backoff and retry logic for file uploads --- src/components/UploadDatasetEntriesModal.tsx | 41 +++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/components/UploadDatasetEntriesModal.tsx b/src/components/UploadDatasetEntriesModal.tsx index 7dc8b24..3e6ed6d 100644 --- a/src/components/UploadDatasetEntriesModal.tsx +++ b/src/components/UploadDatasetEntriesModal.tsx @@ -113,6 +113,42 @@ export default function UploadDatasetEntriesModal({ file: File, index: number, queue: Array<{ file: File; status: 'pending' | 'uploading' | 'completed' | 'failed'; progress: number; message?: string }> + ): Promise<{ tempDir: string }> { + const MAX_RETRIES = 3; + + return (async () => { + let lastError: Error | null = null; + + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + const result = await uploadAttempt(file, index, queue, attempt, MAX_RETRIES); + return result; + } catch (error) { + lastError = error instanceof Error ? error : new Error('Unknown error'); + + if (attempt < MAX_RETRIES) { + // Exponential backoff: 1s, 2s, 4s + const delay = Math.pow(2, attempt - 1) * 1000; + queue[index].message = `Retrying in ${delay / 1000}s... (attempt ${attempt}/${MAX_RETRIES})`; + setUploadQueue([...queue]); + + await new Promise(resolve => setTimeout(resolve, delay)); + queue[index].progress = 0; // Reset progress for next attempt + setUploadQueue([...queue]); + } + } + } + + throw lastError || new Error('Upload failed after all retries'); + })(); + } + + function uploadAttempt( + file: File, + index: number, + queue: Array<{ file: File; status: 'pending' | 'uploading' | 'completed' | 'failed'; progress: number; message?: string }>, + attempt: number, + maxRetries: number ): Promise<{ tempDir: string }> { return new Promise((resolve, reject) => { const formData = new FormData(); @@ -126,6 +162,9 @@ export default function UploadDatasetEntriesModal({ if (event.lengthComputable) { const progress = Math.round((event.loaded / event.total) * 100); queue[index].progress = progress; + if (attempt > 1) { + queue[index].message = `Uploading (retry ${attempt}/${maxRetries})`; + } setUploadQueue([...queue]); } }); @@ -153,7 +192,7 @@ export default function UploadDatasetEntriesModal({ }); xhr.addEventListener('error', () => { - reject(new Error('Upload failed')); + reject(new Error('Network error')); }); xhr.addEventListener('abort', () => {