Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 43 additions & 40 deletions app/lib/shared/storage.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,34 @@ extension StorageExt on Storage {
extension BucketExt on Bucket {
/// Returns an [ObjectInfo] if [name] exists, `null` otherwise.
Future<ObjectInfo?> tryInfo(String name) async {
try {
return await info(name);
} on DetailedApiRequestError catch (e) {
if (e.status == 404) return null;
rethrow;
}
return await retry(
() async {
try {
return await info(name);
} on DetailedApiRequestError catch (e) {
if (e.status == 404) return null;
rethrow;
}
},
maxAttempts: 3,
retryIf: _retryIf,
);
}

/// Deletes [name] if it exists, ignores 404 otherwise.
Future<void> tryDelete(String name) async {
try {
return await delete(name);
} on DetailedApiRequestError catch (e) {
if (e.status == 404) return null;
rethrow;
}
return await retry(
() async {
try {
return await delete(name);
} on DetailedApiRequestError catch (e) {
if (e.status == 404) return null;
rethrow;
}
},
maxAttempts: 3,
retryIf: _retryIf,
);
}

Future uploadPublic(String objectName, int length,
Expand Down Expand Up @@ -139,22 +151,7 @@ extension BucketExt on Bucket {
return builder.toBytes();
},
maxAttempts: 3,
retryIf: (e) {
if (e is TimeoutException) {
return true; // Timeouts we can retry
}
if (e is IOException) {
return true; // I/O issues are worth retrying
}
if (e is http.ClientException) {
return true; // HTTP issues are worth retrying
}
if (e is DetailedApiRequestError) {
final status = e.status;
return status == null || status >= 500; // 5xx errors are retried
}
return e is ApiRequestError; // Unknown API errors are retried
},
retryIf: _retryIf,
);
}

Expand All @@ -164,6 +161,23 @@ extension BucketExt on Bucket {
}
}

bool _retryIf(Exception e) {
if (e is TimeoutException) {
return true; // Timeouts we can retry
}
if (e is IOException) {
return true; // I/O issues are worth retrying
}
if (e is http.ClientException) {
return true; // HTTP issues are worth retrying
}
if (e is DetailedApiRequestError) {
final status = e.status;
return status == null || status >= 500; // 5xx errors are retried
}
return e is ApiRequestError; // Unknown API errors are retried
}

/// Returns a valid `gs://` URI for a given [bucket] + [path] combination.
String bucketUri(Bucket bucket, String path) =>
'gs://${bucket.bucketName}/$path';
Expand Down Expand Up @@ -264,18 +278,7 @@ Future uploadWithRetry(Bucket bucket, String objectName, int length,
await sink.close();
},
description: 'Upload to $objectName',
shouldRetryOnError: (e) {
// upstream proxy or rate limit issue
if (e is DetailedApiRequestError) {
return _retryStatusCodes.contains(e.status);
}
// network connection failures
if (e is http.ClientException || e is SocketException) {
return true;
}
// otherwise no retry
return false;
},
shouldRetryOnError: _retryIf,
sleep: Duration(seconds: 10),
);
}
Expand Down
4 changes: 2 additions & 2 deletions app/lib/shared/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,14 @@ List<T> boundedList<T>(List<T> list, {int? offset, int? limit}) {
Future<R> retryAsync<R>(
Future<R> Function() body, {
int maxAttempt = 3,
bool Function(Object)? shouldRetryOnError,
bool Function(Exception)? shouldRetryOnError,
String description = 'Async operation',
Duration sleep = const Duration(seconds: 1),
}) async {
for (int i = 1;; i++) {
try {
return await body();
} catch (e, st) {
} on Exception catch (e, st) {
_logger.info('$description failed (attempt: $i of $maxAttempt).', e, st);
if (i < maxAttempt &&
(shouldRetryOnError == null || shouldRetryOnError(e))) {
Expand Down
Loading