Initial commit: unionflow-mobile-apps
Application Flutter complète (sans build artifacts). Signed-off-by: lions dev Team
This commit is contained in:
160
lib/core/network/retry_policy.dart
Normal file
160
lib/core/network/retry_policy.dart
Normal file
@@ -0,0 +1,160 @@
|
||||
/// Retry policy with exponential backoff for network requests
|
||||
library retry_policy;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
/// Configuration for retry behavior
|
||||
class RetryConfig {
|
||||
/// Maximum number of retry attempts
|
||||
final int maxAttempts;
|
||||
|
||||
/// Initial delay before first retry (milliseconds)
|
||||
final int initialDelayMs;
|
||||
|
||||
/// Maximum delay between retries (milliseconds)
|
||||
final int maxDelayMs;
|
||||
|
||||
/// Multiplier for exponential backoff
|
||||
final double backoffMultiplier;
|
||||
|
||||
/// Whether to add jitter to retry delays
|
||||
final bool useJitter;
|
||||
|
||||
const RetryConfig({
|
||||
this.maxAttempts = 3,
|
||||
this.initialDelayMs = 1000,
|
||||
this.maxDelayMs = 30000,
|
||||
this.backoffMultiplier = 2.0,
|
||||
this.useJitter = true,
|
||||
});
|
||||
|
||||
/// Default configuration for standard API calls
|
||||
static const standard = RetryConfig();
|
||||
|
||||
/// Configuration for critical operations
|
||||
static const critical = RetryConfig(
|
||||
maxAttempts: 5,
|
||||
initialDelayMs: 500,
|
||||
maxDelayMs: 60000,
|
||||
);
|
||||
|
||||
/// Configuration for background sync
|
||||
static const backgroundSync = RetryConfig(
|
||||
maxAttempts: 10,
|
||||
initialDelayMs: 2000,
|
||||
maxDelayMs: 120000,
|
||||
);
|
||||
}
|
||||
|
||||
/// Retry policy implementation with exponential backoff
|
||||
class RetryPolicy {
|
||||
final RetryConfig config;
|
||||
final Random _random = Random();
|
||||
|
||||
RetryPolicy({RetryConfig? config}) : config = config ?? RetryConfig.standard;
|
||||
|
||||
/// Executes an operation with retry logic
|
||||
///
|
||||
/// [operation]: The async operation to execute
|
||||
/// [shouldRetry]: Optional function to determine if error is retryable
|
||||
/// [onRetry]: Optional callback when retry attempt is made
|
||||
///
|
||||
/// Returns the result of the operation
|
||||
/// Throws the last error if all retries fail
|
||||
Future<T> execute<T>({
|
||||
required Future<T> Function() operation,
|
||||
bool Function(dynamic error)? shouldRetry,
|
||||
void Function(int attempt, dynamic error, Duration delay)? onRetry,
|
||||
}) async {
|
||||
int attempt = 0;
|
||||
dynamic lastError;
|
||||
|
||||
while (attempt < config.maxAttempts) {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
attempt++;
|
||||
|
||||
// Check if we should retry this error
|
||||
final retryable = shouldRetry?.call(error) ?? _isRetryableError(error);
|
||||
|
||||
if (!retryable || attempt >= config.maxAttempts) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Calculate delay with exponential backoff
|
||||
final delay = _calculateDelay(attempt);
|
||||
|
||||
// Notify about retry
|
||||
onRetry?.call(attempt, error, delay);
|
||||
|
||||
// Wait before next attempt
|
||||
await Future.delayed(delay);
|
||||
}
|
||||
}
|
||||
|
||||
// Should never reach here, but throw last error just in case
|
||||
throw lastError!;
|
||||
}
|
||||
|
||||
/// Calculates delay for given attempt number
|
||||
Duration _calculateDelay(int attempt) {
|
||||
// Exponential backoff: initialDelay * (multiplier ^ (attempt - 1))
|
||||
final exponentialDelay = config.initialDelayMs *
|
||||
pow(config.backoffMultiplier, attempt - 1).toInt();
|
||||
|
||||
// Cap at max delay
|
||||
var delayMs = min(exponentialDelay, config.maxDelayMs);
|
||||
|
||||
// Add jitter to prevent thundering herd
|
||||
if (config.useJitter) {
|
||||
final jitter = _random.nextDouble() * 0.3; // ±30% jitter
|
||||
delayMs = (delayMs * (1 + jitter - 0.15)).toInt();
|
||||
}
|
||||
|
||||
return Duration(milliseconds: delayMs);
|
||||
}
|
||||
|
||||
/// Determines if an error is retryable
|
||||
bool _isRetryableError(dynamic error) {
|
||||
// Network errors are retryable
|
||||
if (error is TimeoutException) return true;
|
||||
if (error is SocketException) return true;
|
||||
|
||||
// HTTP status codes that are retryable
|
||||
if (error.toString().contains('500')) return true; // Internal Server Error
|
||||
if (error.toString().contains('502')) return true; // Bad Gateway
|
||||
if (error.toString().contains('503')) return true; // Service Unavailable
|
||||
if (error.toString().contains('504')) return true; // Gateway Timeout
|
||||
if (error.toString().contains('429')) return true; // Too Many Requests
|
||||
|
||||
// Client errors (4xx) are generally not retryable
|
||||
if (error.toString().contains('400')) return false; // Bad Request
|
||||
if (error.toString().contains('401')) return false; // Unauthorized
|
||||
if (error.toString().contains('403')) return false; // Forbidden
|
||||
if (error.toString().contains('404')) return false; // Not Found
|
||||
|
||||
// Default: don't retry unknown errors
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension to add retry capability to Future operations
|
||||
extension RetryExtension<T> on Future<T> Function() {
|
||||
/// Executes this operation with retry logic
|
||||
Future<T> withRetry({
|
||||
RetryConfig? config,
|
||||
bool Function(dynamic error)? shouldRetry,
|
||||
void Function(int attempt, dynamic error, Duration delay)? onRetry,
|
||||
}) {
|
||||
final policy = RetryPolicy(config: config);
|
||||
return policy.execute(
|
||||
operation: this,
|
||||
shouldRetry: shouldRetry,
|
||||
onRetry: onRetry,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user