Skip to content

Commit 3283355

Browse files
committed
fix(downloads): add exponential backoff with jitter between retries
1 parent 8496a7d commit 3283355

1 file changed

Lines changed: 22 additions & 1 deletion

File tree

StabilityMatrix.Core/Models/TrackedDownload.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ private void DoCleanup()
319319

320320
/// <summary>
321321
/// Returns true for transient network/SSL exceptions that are safe to retry.
322+
/// Catches direct IOException/AuthenticationException, the same types wrapped as
323+
/// InnerException (common AggregateException shape from HttpClient), and any leg
324+
/// of a multi-inner AggregateException — covering VPN tunnel resets
325+
/// ("Connection reset by peer") and TLS re-key failures (OpenSSL SSL_ERROR_SSL).
322326
/// </summary>
323327
private static bool IsTransientNetworkException(Exception? ex) =>
324328
ex is IOException or AuthenticationException
@@ -369,9 +373,26 @@ private void OnDownloadTaskCompleted(Task task)
369373
attempts
370374
);
371375

376+
// Exponential backoff: 2 s → 4 s → 8 s, capped at 30 s, ±500 ms jitter.
377+
// Gives the VPN tunnel time to re-key/re-route before reconnecting,
378+
// which prevents the retry from hitting the same torn connection.
379+
var delayMs =
380+
(int)Math.Min(2000 * Math.Pow(2, attempts - 1), 30_000) + Random.Shared.Next(-500, 500);
381+
Logger.Debug(
382+
"Download {Download} retrying in {Delay}ms (attempt {Attempt}/3)",
383+
FileName,
384+
delayMs,
385+
attempts
386+
);
387+
388+
// Persist Inactive state to disk before the delay so that a restart
389+
// during the backoff window loads the download as a resumable entry.
372390
OnProgressStateChanging(ProgressState.Inactive);
373391
ProgressState = ProgressState.Inactive;
374-
Resume();
392+
OnProgressStateChanged(ProgressState.Inactive);
393+
394+
// Fire-and-forget the delayed resume to avoid blocking the task continuation thread.
395+
Task.Delay(Math.Max(delayMs, 0)).ContinueWith(_ => Resume()).SafeFireAndForget();
375396
return;
376397
}
377398

0 commit comments

Comments
 (0)