Some checks failed
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
87 lines
2.7 KiB
Dart
87 lines
2.7 KiB
Dart
import 'dart:math';
|
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:pshared/utils/exception.dart';
|
|
|
|
|
|
class RetryHelper {
|
|
static final _logger = Logger('auth.retry');
|
|
|
|
/// Executes an operation with exponential backoff retry logic
|
|
static Future<T> withExponentialBackoff<T>(
|
|
Future<T> Function() operation, {
|
|
int maxRetries = 3,
|
|
Duration initialDelay = const Duration(milliseconds: 500),
|
|
double backoffMultiplier = 2.0,
|
|
Duration maxDelay = const Duration(seconds: 30),
|
|
bool Function(Exception)? shouldRetry,
|
|
}) async {
|
|
Exception? lastException;
|
|
|
|
// Total attempts = initial attempt + maxRetries
|
|
final totalAttempts = maxRetries + 1;
|
|
|
|
for (int attempt = 1; attempt <= totalAttempts; attempt++) {
|
|
try {
|
|
_logger.fine('Attempting operation (attempt $attempt/$totalAttempts)');
|
|
return await operation();
|
|
} catch (e) {
|
|
lastException = toException(e);
|
|
|
|
// Don't retry if we've reached max attempts
|
|
if (attempt == totalAttempts) {
|
|
_logger.warning('Operation failed after $totalAttempts attempts: $lastException');
|
|
rethrow;
|
|
}
|
|
|
|
// Check if we should retry this specific error
|
|
if (shouldRetry != null && !shouldRetry(lastException)) {
|
|
_logger.fine('Operation failed with non-retryable error: $lastException');
|
|
rethrow;
|
|
}
|
|
|
|
// Calculate delay with exponential backoff
|
|
final delayMs = min(
|
|
initialDelay.inMilliseconds * pow(backoffMultiplier, attempt - 1).toInt(),
|
|
maxDelay.inMilliseconds,
|
|
);
|
|
final delay = Duration(milliseconds: delayMs);
|
|
|
|
_logger.fine('Operation failed (attempt $attempt), retrying in ${delay.inMilliseconds}ms: $lastException');
|
|
await Future.delayed(delay);
|
|
}
|
|
}
|
|
|
|
// This should never be reached due to rethrow above, but just in case
|
|
throw lastException ?? Exception('Retry logic error');
|
|
}
|
|
|
|
/// Determines if an error is retryable (network/temporary errors)
|
|
static bool isRetryableError(Exception error) {
|
|
final errorString = error.toString().toLowerCase();
|
|
|
|
// Network connectivity issues
|
|
if (errorString.contains('socket') ||
|
|
errorString.contains('connection') ||
|
|
errorString.contains('timeout') ||
|
|
errorString.contains('network')) {
|
|
return true;
|
|
}
|
|
|
|
// Server temporary errors (5xx)
|
|
if (errorString.contains('500') ||
|
|
errorString.contains('502') ||
|
|
errorString.contains('503') ||
|
|
errorString.contains('504')) {
|
|
return true;
|
|
}
|
|
|
|
// Rate limiting
|
|
if (errorString.contains('429')) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
} |