CorvidLabs/swift-retry
π Try, try, try again (with backoff)
Features
- Pure Swift 6 with strict concurrency checking
- Sendable and thread-safe throughout
- Multiple retry strategies: Constant, Linear, Exponential, Fibonacci
- Jitter support: Full, Equal, Decorrelated, and None
- Circuit Breaker pattern to prevent cascading failures
- Flexible configuration with predicates and timeouts
- No external dependencies (except swift-docc-plugin)
- Comprehensive test coverage
Platform Support
- iOS 15+
- macOS 12+
- tvOS 15+
- watchOS 8+
- visionOS 1+
Installation
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/CorvidLabs/swift-retry.git", from: "0.1.0")
]Quick Start
Basic Usage
import Retry
let result = try await Retry.execute(maxAttempts: 3) {
try await fetchData()
}With Exponential Backoff and Jitter
let result = try await Retry.execute(
maxAttempts: 5,
strategy: .exponential(base: 1.0, multiplier: 2.0),
jitter: .full
) {
try await networkRequest()
}With Circuit Breaker
let breaker = CircuitBreaker(failureThreshold: 5, resetTimeout: 60.0)
let result = try await Retry.execute(
maxAttempts: 3,
strategy: .fibonacci(base: 1.0),
circuitBreaker: breaker
) {
try await externalAPICall()
}With Configuration
let config = RetryConfiguration(
maxAttempts: 5,
maxDelay: 30.0,
timeout: 120.0,
shouldRetry: { error in
// Only retry network errors
return error is URLError
}
)
let result = try await Retry.execute(
configuration: config,
strategy: .exponential(base: 2.0),
jitter: .decorrelated()
) {
try await operation()
}Retry Strategies
Constant
Fixed delay between retries:
.constant(2.0) // 2 second delay each timeLinear
Linearly increasing delay:
.linear(base: 1.0, increment: 0.5)
// Delays: 1.0, 1.5, 2.0, 2.5...Exponential
Exponential backoff:
.exponential(base: 1.0, multiplier: 2.0)
// Delays: 1.0, 2.0, 4.0, 8.0...Fibonacci
Fibonacci sequence delays:
.fibonacci(base: 1.0)
// Delays: 1.0, 1.0, 2.0, 3.0, 5.0, 8.0...Jitter Types
No Jitter
Uses exact delay from strategy:
jitter: .noneFull Jitter
Random delay between 0 and calculated delay:
jitter: .fullEqual Jitter
Half delay + random half:
jitter: .equalDecorrelated Jitter
AWS-style decorrelated jitter:
jitter: .decorrelated(base: 1.0)Circuit Breaker
Prevent cascading failures by opening the circuit after a threshold:
let breaker = CircuitBreaker(
failureThreshold: 5, // Open after 5 failures
resetTimeout: 60.0 // Try again after 60 seconds
)
// Use with retry
let result = try await Retry.execute(
maxAttempts: 3,
circuitBreaker: breaker
) {
try await operation()
}
// Check state
let state = await breaker.currentState // .closed, .open, or .halfOpen
// Reset manually if needed
await breaker.reset()Error Handling
The library provides specific error types:
do {
let result = try await Retry.execute(maxAttempts: 3) {
try await operation()
}
} catch RetryError.maxAttemptsExceeded(let attempts, let lastError) {
print("Failed after \(attempts) attempts: \(lastError)")
} catch RetryError.timeout(let duration) {
print("Timed out after \(duration) seconds")
} catch RetryError.circuitBreakerOpen {
print("Circuit breaker is open")
} catch {
print("Operation failed: \(error)")
}Result-Based API
For non-throwing contexts:
let result = await Retry.executeReturningResult(
maxAttempts: 3,
strategy: .exponential(base: 1.0)
) {
try await operation()
}
switch result {
case .success(let value):
print("Success: \(value)")
case .failure(let error):
print("Failed: \(error)")
}Advanced Configuration
Predefined Configurations
// Default: 3 attempts, no limits
.default
// Conservative: 5 attempts with timeouts
.conservative
// Aggressive: 10 attempts with longer timeouts
.aggressiveCustom Error Filtering
enum APIError: Error, Equatable {
case rateLimit
case serverError
case badRequest
}
let config = RetryConfiguration.forErrors(
maxAttempts: 5,
retryableErrors: Set([APIError.rateLimit, APIError.serverError])
)
// Only retries on rateLimit and serverError
try await Retry.execute(configuration: config, strategy: .exponential(base: 2.0)) {
try await apiCall()
}Design Philosophy
This library follows protocol-oriented design principles:
- Protocols over classes:
RetryStrategyandJitterare protocols - Value types: Strategies and configurations are structs
- Composition: Mix and match strategies, jitter, and circuit breakers
- Type safety: Strong typing prevents runtime errors
- Sendable: Safe for concurrent use with async/await
- Clean API: Static member syntax for common cases
Testing
Run tests with:
swift testBuild the package:
swift buildLicense
MIT
Contributing
Contributions welcome! Please ensure:
- Swift 6 compatibility
- Sendable conformance
- Comprehensive tests
- Documentation for public APIs
Package Metadata
Repository: CorvidLabs/swift-retry
Homepage: https://corvidlabs.github.io/swift-retry/documentation/retry
Stars: 1
Forks: 1
Open issues: 0
Default branch: main
Primary language: swift
License: MIT
Topics: backoff, error-handling, retry, swift, swift-package
README: README.md