39 lines
1.6 KiB
Dart
39 lines
1.6 KiB
Dart
import 'package:nextcloud/nextcloud.dart';
|
|
|
|
/// True for errors worth a short automatic retry: HTTP 5xx thrown by the
|
|
/// nextcloud/dynamite clients. Our instance intermittently answers the slow
|
|
/// root PROPFIND with a quick empty 500 via nginx that succeeds on retry.
|
|
/// Timeouts are deliberately not transient — the root listing already has a
|
|
/// multi-minute ceiling, so repeating it would multiply the wait.
|
|
bool isTransientServerError(Object error) =>
|
|
error is DynamiteApiException &&
|
|
error.statusCode >= 500 &&
|
|
error.statusCode <= 599;
|
|
|
|
Duration _defaultRetryDelay(int retry) =>
|
|
retry == 1 ? const Duration(seconds: 1) : const Duration(seconds: 3);
|
|
|
|
/// Runs [attempt] up to [maxAttempts] times, waiting [delayFor] between
|
|
/// tries. Errors that don't match [shouldRetry] and the final failure are
|
|
/// rethrown unchanged. [onRetry] fires before each wait with the 1-based
|
|
/// number of the upcoming attempt, e.g. `(2, 3)` — used to surface retry
|
|
/// progress in the UI. [delayFor] receives the 1-based index of the retry
|
|
/// being scheduled and is injectable so tests run instantly.
|
|
Future<T> retryOnTransientError<T>(
|
|
Future<T> Function() attempt, {
|
|
int maxAttempts = 3,
|
|
Duration Function(int retry) delayFor = _defaultRetryDelay,
|
|
bool Function(Object error) shouldRetry = isTransientServerError,
|
|
void Function(int nextAttempt, int maxAttempts)? onRetry,
|
|
}) async {
|
|
for (var attemptNo = 1; ; attemptNo++) {
|
|
try {
|
|
return await attempt();
|
|
} catch (e) {
|
|
if (attemptNo >= maxAttempts || !shouldRetry(e)) rethrow;
|
|
onRetry?.call(attemptNo + 1, maxAttempts);
|
|
await Future<void>.delayed(delayFor(attemptNo));
|
|
}
|
|
}
|
|
}
|