Skip to content

Commit 3a884f7

Browse files
authored
1.0.0 Release (#35)
* Swift 6 mode (#26) * Swift 6 mode, refs #25 * Update workflow * Enhance RetryPolicy API, refs #27 (#28) * Revert Swift 6 upgrade, activate concurrency checks, closes #31 (#32) * Enhance README regarding stateless nature of retry policies, refs #33 (#34)
1 parent 4db9d72 commit 3a884f7

28 files changed

Lines changed: 151 additions & 110 deletions

.github/workflows/swift.yml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,24 @@ on:
66

77
jobs:
88
build:
9-
runs-on: macos-13
9+
runs-on: macos-14
1010

1111
steps:
12-
- uses: maxim-lobanov/setup-xcode@v1
13-
with:
14-
xcode-version: latest-stable
15-
- name: Check XCode Version
12+
- uses: actions/checkout@v4
13+
- name: List available Xcode versions
14+
run: ls /Applications | grep Xcode
15+
- name: Set up Xcode version
16+
run: sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer
17+
- name: Show current version of Xcode
1618
run: xcodebuild -version
17-
- uses: actions/checkout@v3
1819
- name: Build
1920
run: xcodebuild -scheme SwiftRetrier build -destination "platform=OS X"
2021
- name: Run tests
2122
run: xcodebuild -scheme SwiftRetrier test -destination "platform=OS X"
2223
lint:
2324
runs-on: ubuntu-latest
2425
steps:
25-
- uses: actions/checkout@v1
26+
- uses: actions/checkout@v4
2627
- name: SwiftLint
2728
uses: raphaelbussa/swiftlint-action@main
2829
with:

Package.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ let lint = false
88
var extraDependencies: [Package.Dependency] = []
99
var extraPlugins: [Target.PluginUsage] = []
1010
if lint {
11-
extraDependencies = [.package(url: "https://github.com/realm/SwiftLint", exact: "0.52.4")]
12-
extraPlugins = [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]
11+
extraDependencies = [.package(url: "https://github.com/realm/SwiftLint.git", from: "0.55.1")]
12+
extraPlugins = [.plugin(name: "SwiftLintBuildToolPlugin", package: "SwiftLint")]
1313
}
1414

1515
let package = Package(
@@ -28,12 +28,19 @@ let package = Package(
2828
.target(
2929
name: "SwiftRetrier",
3030
dependencies: [],
31+
swiftSettings: [
32+
.enableExperimentalFeature("StrictConcurrency=complete")
33+
],
3134
plugins: [] + extraPlugins
3235
),
3336
.testTarget(
3437
name: "SwiftRetrierTests",
3538
dependencies: ["SwiftRetrier"],
39+
swiftSettings: [
40+
.enableExperimentalFeature("StrictConcurrency=complete")
41+
],
3642
plugins: [] + extraPlugins
3743
)
38-
]
44+
],
45+
swiftLanguageVersions: [.version("5.8")]
3946
)

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ You can add failure conditions using `giveUp*()` functions.
135135
You can create your own policies that conform `RetryPolicy` and they will benefit from the same modifiers.
136136
Have a look at `ConstantDelayRetryPolicy.swift` for a basic example.
137137

138+
⚠️ Policies should be stateless. To ensure that, I recommend implementing them with `struct` types.
139+
140+
If a policy needs to know about attempts history, ensure you propagate what's needed when implementing
141+
`policyAfter(attemptFailure:, delay:) -> any RetryPolicy`.
142+
138143
To create a DSL entry point using your policy:
139144

140145
```swift

Sources/SwiftRetrier/Core/ConstantDelayPolicy/ConstantDelayRetryPolicy.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
22

3-
open class ConstantDelayRetryPolicy: RetryPolicy {
3+
public struct ConstantDelayRetryPolicy: RetryPolicy {
44

55
public let delay: TimeInterval
66

@@ -16,7 +16,7 @@ open class ConstantDelayRetryPolicy: RetryPolicy {
1616
.retry(delay: retryDelay(for: attemptFailure))
1717
}
1818

19-
public func freshCopy() -> RetryPolicy {
19+
public func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy {
2020
self
2121
}
2222
}

Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import Foundation
22

3-
open class ExponentialBackoffRetryPolicy: RetryPolicy {
3+
public struct ExponentialBackoffRetryPolicy: RetryPolicy {
44

5-
public enum Jitter {
5+
public enum Jitter: Sendable {
66
case none
77
case full
88
case decorrelated(growthFactor: Double = ExponentialBackoffConstants.defaultDecorrelatedJitterGrowthFactor)
@@ -11,14 +11,16 @@ open class ExponentialBackoffRetryPolicy: RetryPolicy {
1111
public let timeSlot: TimeInterval
1212
public let maxDelay: TimeInterval
1313
public let jitter: Jitter
14-
private var previousDelay: TimeInterval?
14+
private let previousDelay: TimeInterval?
1515

1616
public init(timeSlot: TimeInterval = ExponentialBackoffConstants.defaultTimeSlot,
1717
maxDelay: TimeInterval = ExponentialBackoffConstants.defaultMaxDelay,
18-
jitter: Jitter = ExponentialBackoffConstants.defaultJitter) {
18+
jitter: Jitter = ExponentialBackoffConstants.defaultJitter,
19+
previousDelay: TimeInterval? = nil) {
1920
self.timeSlot = timeSlot
2021
self.maxDelay = maxDelay
2122
self.jitter = jitter
23+
self.previousDelay = previousDelay
2224
}
2325

2426
public func exponentiationBySquaring<T: BinaryInteger>(_ base: T, _ multiplier: T, _ exponent: T) -> T {
@@ -57,7 +59,6 @@ open class ExponentialBackoffRetryPolicy: RetryPolicy {
5759
} else {
5860
delay = fullJitterDelay(attemptIndex: attemptIndex)
5961
}
60-
previousDelay = delay
6162
return delay
6263
}
6364

@@ -72,15 +73,15 @@ open class ExponentialBackoffRetryPolicy: RetryPolicy {
7273
}
7374
}
7475

75-
open func retryDelay(for attemptFailure: AttemptFailure) -> TimeInterval {
76+
public func retryDelay(for attemptFailure: AttemptFailure) -> TimeInterval {
7677
min(maxDelay, uncappedDelay(attemptIndex: attemptFailure.index))
7778
}
7879

7980
public func shouldRetry(on attemptFailure: AttemptFailure) -> RetryDecision {
8081
.retry(delay: retryDelay(for: attemptFailure))
8182
}
8283

83-
public func freshCopy() -> RetryPolicy {
84-
ExponentialBackoffRetryPolicy(timeSlot: timeSlot, maxDelay: maxDelay, jitter: jitter)
84+
public func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy {
85+
ExponentialBackoffRetryPolicy(timeSlot: timeSlot, maxDelay: maxDelay, jitter: jitter, previousDelay: delay)
8586
}
8687
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
public typealias Job<Value> = () async throws -> Value
1+
public typealias Job<Value> = @Sendable () async throws -> Value

Sources/SwiftRetrier/Core/Model/Policies/AttemptFailure.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
22

3-
public struct AttemptFailure {
3+
public struct AttemptFailure: Sendable {
44
public let trialStart: Date
55
public let index: UInt
66
public let error: Error
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
22

3-
public enum RetryDecision {
3+
public enum RetryDecision: Sendable {
44
case giveUp
55
case retry(delay: TimeInterval)
66
}
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import Foundation
22

3-
public protocol RetryPolicy {
3+
public protocol RetryPolicy: Sendable {
44
func shouldRetry(on attemptFailure: AttemptFailure) -> RetryDecision
5-
func retryDelay(for attemptFailure: AttemptFailure) -> TimeInterval
6-
func freshCopy() -> RetryPolicy
5+
func policyAfter(attemptFailure: AttemptFailure, delay: TimeInterval) -> any RetryPolicy
76
}

Sources/SwiftRetrier/Core/Model/Retriers/Retrier.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import Foundation
22
import Combine
33

4-
public protocol Retrier: Cancellable, AnyObject {
5-
associatedtype Output
4+
public protocol Retrier: Cancellable, AnyObject, Sendable {
5+
associatedtype Output: Sendable
66

77
func publisher() -> AnyPublisher<RetrierEvent<Output>, Never>
88
}

0 commit comments

Comments
 (0)