Skip to content

Commit f8cb3cd

Browse files
authored
mr feedback (#1)
1 parent 03b623d commit f8cb3cd

File tree

5 files changed

+90
-101
lines changed

5 files changed

+90
-101
lines changed

Package.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ let package = Package(
1717
],
1818
targets: [
1919
.systemLibrary(name: "_CAsyncSequenceValidationSupport"),
20-
.target(name: "_CPowSupport"),
2120
.target(
2221
name: "AsyncAlgorithms",
2322
dependencies: [
24-
"_CPowSupport",
2523
.product(name: "OrderedCollections", package: "swift-collections"),
2624
.product(name: "DequeModule", package: "swift-collections"),
2725
],
Lines changed: 71 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import _CPowSupport
2-
31
#if compiler(<6.2)
42
@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, watchOS 11.0, *)
53
extension Duration {
@@ -15,53 +13,47 @@ extension Duration {
1513
@available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
1614
public protocol BackoffStrategy<Duration> {
1715
associatedtype Duration: DurationProtocol
18-
mutating func duration(_ attempt: Int) -> Duration
19-
mutating func duration(_ attempt: Int, using generator: inout some RandomNumberGenerator) -> Duration
20-
}
21-
22-
@available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
23-
extension BackoffStrategy {
24-
@inlinable public mutating func duration(_ attempt: Int, using generator: inout some RandomNumberGenerator) -> Duration {
25-
return duration(attempt)
26-
}
16+
mutating func nextDuration() -> Duration
2717
}
2818

2919
@available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
3020
@usableFromInline
3121
struct ConstantBackoffStrategy<Duration: DurationProtocol>: BackoffStrategy {
32-
@usableFromInline let c: Duration
33-
@usableFromInline init(c: Duration) {
34-
self.c = c
22+
@usableFromInline let constant: Duration
23+
@usableFromInline init(constant: Duration) {
24+
self.constant = constant
3525
}
36-
@inlinable func duration(_ attempt: Int) -> Duration {
37-
return c
26+
@inlinable func nextDuration() -> Duration {
27+
return constant
3828
}
3929
}
4030

4131
@available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
4232
@usableFromInline
4333
struct LinearBackoffStrategy<Duration: DurationProtocol>: BackoffStrategy {
44-
@usableFromInline let a: Duration
45-
@usableFromInline let b: Duration
46-
@usableFromInline init(a: Duration, b: Duration) {
47-
self.a = a
48-
self.b = b
34+
@usableFromInline var current: Duration
35+
@usableFromInline var increment: Duration
36+
@usableFromInline init(increment: Duration, initial: Duration) {
37+
self.current = initial
38+
self.increment = increment
4939
}
50-
@inlinable func duration(_ attempt: Int) -> Duration {
51-
return a * attempt + b
40+
@inlinable mutating func nextDuration() -> Duration {
41+
defer { current += increment }
42+
return current
5243
}
5344
}
5445

5546
@available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
56-
@usableFromInline struct ExponentialBackoffStrategy: BackoffStrategy {
57-
@usableFromInline let a: Duration
58-
@usableFromInline let b: Double
59-
@usableFromInline init(a: Duration, b: Double) {
60-
self.a = a
61-
self.b = b
47+
@usableFromInline struct ExponentialBackoffStrategy<Duration: DurationProtocol>: BackoffStrategy {
48+
@usableFromInline var current: Duration
49+
@usableFromInline let factor: Int
50+
@usableFromInline init(factor: Int, initial: Duration) {
51+
self.current = initial
52+
self.factor = factor
6253
}
63-
@inlinable func duration(_ attempt: Int) -> Duration {
64-
return a * pow(b, Double(attempt))
54+
@inlinable mutating func nextDuration() -> Duration {
55+
defer { current *= factor }
56+
return current
6557
}
6658
}
6759

@@ -74,11 +66,8 @@ struct MinimumBackoffStrategy<Base: BackoffStrategy>: BackoffStrategy {
7466
self.base = base
7567
self.minimum = minimum
7668
}
77-
@inlinable mutating func duration(_ attempt: Int) -> Base.Duration {
78-
return max(minimum, base.duration(attempt))
79-
}
80-
@inlinable mutating func duration(_ attempt: Int, using generator: inout some RandomNumberGenerator) -> Base.Duration {
81-
return max(minimum, base.duration(attempt, using: &generator))
69+
@inlinable mutating func nextDuration() -> Base.Duration {
70+
return max(minimum, base.nextDuration())
8271
}
8372
}
8473

@@ -91,86 +80,80 @@ struct MaximumBackoffStrategy<Base: BackoffStrategy>: BackoffStrategy {
9180
self.base = base
9281
self.maximum = maximum
9382
}
94-
@inlinable mutating func duration(_ attempt: Int) -> Base.Duration {
95-
return min(maximum, base.duration(attempt))
96-
}
97-
@inlinable mutating func duration(_ attempt: Int, using generator: inout some RandomNumberGenerator) -> Base.Duration {
98-
return min(maximum, base.duration(attempt, using: &generator))
83+
@inlinable mutating func nextDuration() -> Base.Duration {
84+
return min(maximum, base.nextDuration())
9985
}
10086
}
10187

10288
@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, watchOS 11.0, *)
10389
@usableFromInline
104-
struct FullJitterBackoffStrategy<Base: BackoffStrategy>: BackoffStrategy where Base.Duration == Swift.Duration {
90+
struct FullJitterBackoffStrategy<Base: BackoffStrategy, RNG: RandomNumberGenerator>: BackoffStrategy where Base.Duration == Swift.Duration {
10591
@usableFromInline var base: Base
106-
@usableFromInline init(base: Base) {
92+
@usableFromInline var generator: RNG
93+
@usableFromInline init(base: Base, generator: RNG) {
10794
self.base = base
95+
self.generator = generator
10896
}
109-
@inlinable mutating func duration(_ attempt: Int) -> Base.Duration {
110-
return .init(attoseconds: Int128.random(in: 0...base.duration(attempt).attoseconds))
111-
}
112-
@inlinable mutating func duration(_ attempt: Int, using generator: inout some RandomNumberGenerator) -> Base.Duration {
113-
return .init(attoseconds: Int128.random(in: 0...base.duration(attempt, using: &generator).attoseconds, using: &generator))
97+
@inlinable mutating func nextDuration() -> Base.Duration {
98+
return .init(attoseconds: Int128.random(in: 0...base.nextDuration().attoseconds))
11499
}
115100
}
116101

117102
@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, watchOS 11.0, *)
118103
@usableFromInline
119-
struct EqualJitterBackoffStrategy<Base: BackoffStrategy>: BackoffStrategy where Base.Duration == Swift.Duration {
104+
struct EqualJitterBackoffStrategy<Base: BackoffStrategy, RNG: RandomNumberGenerator>: BackoffStrategy where Base.Duration == Swift.Duration {
120105
@usableFromInline var base: Base
121-
@usableFromInline init(base: Base) {
106+
@usableFromInline var generator: RNG
107+
@usableFromInline init(base: Base, generator: RNG) {
122108
self.base = base
109+
self.generator = generator
123110
}
124-
@inlinable mutating func duration(_ attempt: Int) -> Base.Duration {
125-
let halfBase = (base.duration(attempt) / 2).attoseconds
126-
return .init(attoseconds: halfBase + Int128.random(in: 0...halfBase))
127-
}
128-
@inlinable mutating func duration(_ attempt: Int, using generator: inout some RandomNumberGenerator) -> Base.Duration {
129-
let halfBase = (base.duration(attempt, using: &generator) / 2).attoseconds
111+
@inlinable mutating func nextDuration() -> Base.Duration {
112+
let halfBase = (base.nextDuration() / 2).attoseconds
130113
return .init(attoseconds: halfBase + Int128.random(in: 0...halfBase, using: &generator))
131114
}
132115
}
133116

134117
@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, watchOS 11.0, *)
135118
@usableFromInline
136-
struct DecorrelatedJitterBackoffStrategy<Base: BackoffStrategy>: BackoffStrategy where Base.Duration == Swift.Duration {
119+
struct DecorrelatedJitterBackoffStrategy<Base: BackoffStrategy, RNG: RandomNumberGenerator>: BackoffStrategy where Base.Duration == Swift.Duration {
137120
@usableFromInline var base: Base
138-
@usableFromInline let divisor: Int128
139-
@usableFromInline var previousDuration: Duration?
140-
@usableFromInline init(base: Base, divisor: Int128) {
121+
@usableFromInline var generator: RNG
122+
@usableFromInline var current: Duration?
123+
@usableFromInline let factor: Int
124+
@usableFromInline init(base: Base, generator: RNG, factor: Int) {
141125
self.base = base
142-
self.divisor = divisor
143-
}
144-
@inlinable mutating func duration(_ attempt: Int) -> Base.Duration {
145-
let base = base.duration(attempt)
146-
let previousDuration = previousDuration ?? base
147-
self.previousDuration = previousDuration
148-
return .init(attoseconds: Int128.random(in: base.attoseconds...previousDuration.attoseconds / divisor))
126+
self.generator = generator
127+
self.factor = factor
149128
}
150-
@inlinable mutating func duration(_ attempt: Int, using generator: inout some RandomNumberGenerator) -> Base.Duration {
151-
let base = base.duration(attempt, using: &generator)
152-
let previousDuration = previousDuration ?? base
153-
self.previousDuration = previousDuration
154-
return .init(attoseconds: Int128.random(in: base.attoseconds...previousDuration.attoseconds / divisor, using: &generator))
129+
@inlinable mutating func nextDuration() -> Base.Duration {
130+
let base = base.nextDuration()
131+
let current = current ?? base
132+
let next = Duration(attoseconds: Int128.random(in: base.attoseconds...(current * factor).attoseconds, using: &generator))
133+
self.current = next
134+
return next
155135
}
156136
}
157137

158138
@available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
159139
public enum Backoff {
160-
@inlinable public static func constant<Duration: DurationProtocol>(_ c: Duration) -> some BackoffStrategy<Duration> {
161-
return ConstantBackoffStrategy(c: c)
140+
@inlinable public static func constant<Duration: DurationProtocol>(_ constant: Duration) -> some BackoffStrategy<Duration> {
141+
return ConstantBackoffStrategy(constant: constant)
142+
}
143+
@inlinable public static func constant(_ constant: Duration) -> some BackoffStrategy<Duration> {
144+
return ConstantBackoffStrategy(constant: constant)
162145
}
163-
@inlinable public static func constant(_ c: Duration) -> some BackoffStrategy<Duration> {
164-
return ConstantBackoffStrategy(c: c)
146+
@inlinable public static func linear<Duration: DurationProtocol>(increment: Duration, initial: Duration) -> some BackoffStrategy<Duration> {
147+
return LinearBackoffStrategy(increment: increment, initial: initial)
165148
}
166-
@inlinable public static func linear<Duration: DurationProtocol>(increment a: Duration, initial b: Duration) -> some BackoffStrategy<Duration> {
167-
return LinearBackoffStrategy(a: a, b: b)
149+
@inlinable public static func linear(increment: Duration, initial: Duration) -> some BackoffStrategy<Duration> {
150+
return LinearBackoffStrategy(increment: increment, initial: initial)
168151
}
169-
@inlinable public static func linear(increment a: Duration, initial b: Duration) -> some BackoffStrategy<Duration> {
170-
return LinearBackoffStrategy(a: a, b: b)
152+
@inlinable public static func exponential<Duration: DurationProtocol>(factor: Int, initial: Duration) -> some BackoffStrategy<Duration> {
153+
return ExponentialBackoffStrategy(factor: factor, initial: initial)
171154
}
172-
@inlinable public static func exponential(multiplier b: Double = 2, initial a: Duration) -> some BackoffStrategy<Duration> {
173-
return ExponentialBackoffStrategy(a: a, b: b)
155+
@inlinable public static func exponential(factor: Int, initial: Duration) -> some BackoffStrategy<Duration> {
156+
return ExponentialBackoffStrategy(factor: factor, initial: initial)
174157
}
175158
}
176159

@@ -186,13 +169,13 @@ extension BackoffStrategy {
186169

187170
@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, watchOS 11.0, *)
188171
extension BackoffStrategy where Duration == Swift.Duration {
189-
@inlinable public func fullJitter() -> some BackoffStrategy<Duration> {
190-
return FullJitterBackoffStrategy(base: self)
172+
@inlinable public func fullJitter<RNG: RandomNumberGenerator>(using generator: RNG = SystemRandomNumberGenerator()) -> some BackoffStrategy<Duration> {
173+
return FullJitterBackoffStrategy(base: self, generator: generator)
191174
}
192-
@inlinable public func equalJitter() -> some BackoffStrategy<Duration> {
193-
return EqualJitterBackoffStrategy(base: self)
175+
@inlinable public func equalJitter<RNG: RandomNumberGenerator>(using generator: RNG = SystemRandomNumberGenerator()) -> some BackoffStrategy<Duration> {
176+
return EqualJitterBackoffStrategy(base: self, generator: generator)
194177
}
195-
@inlinable public func decorrelatedJitter(divisor: Int = 3) -> some BackoffStrategy<Duration> {
196-
return DecorrelatedJitterBackoffStrategy(base: self, divisor: Int128(divisor))
178+
@inlinable public func decorrelatedJitter<RNG: RandomNumberGenerator>(factor: Int, using generator: RNG = SystemRandomNumberGenerator()) -> some BackoffStrategy<Duration> {
179+
return DecorrelatedJitterBackoffStrategy(base: self, generator: generator, factor: factor)
197180
}
198181
}

Sources/AsyncAlgorithms/Retry/Retry.swift

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
11
@available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
2-
public enum RetryStrategy<Duration: DurationProtocol> {
3-
case backoff(Duration)
4-
case stop
2+
public struct RetryStrategy<Duration: DurationProtocol> {
3+
enum Strategy {
4+
case backoff(Duration)
5+
case stop
6+
}
7+
8+
let strategy: Strategy
9+
10+
public static var stop: Self {
11+
return .init(strategy: .stop)
12+
}
13+
14+
public static func backoff(_ duration: Duration) -> Self {
15+
return .init(strategy: .backoff(duration))
16+
}
517
}
618

719
@available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
@@ -12,16 +24,16 @@ public func retry<Result, ErrorType, ClockType>(
1224
clock: ClockType,
1325
isolation: isolated (any Actor)? = #isolation,
1426
operation: () async throws(ErrorType) -> sending Result,
15-
strategy: (_ attempt: Int, ErrorType) -> RetryStrategy<ClockType.Instant.Duration> = { _, _ in .backoff(.zero) }
27+
strategy: (ErrorType) -> RetryStrategy<ClockType.Instant.Duration> = { _ in .backoff(.zero) }
1628
) async throws -> Result where ClockType: Clock, ErrorType: Error {
1729
precondition(maxAttempts > 0, "Must have at least one attempt")
18-
for attempt in 0..<maxAttempts - 1 {
30+
for _ in 0..<maxAttempts - 1 {
1931
do {
2032
return try await operation()
2133
} catch where Task.isCancelled {
2234
throw error
2335
} catch {
24-
switch strategy(attempt, error) {
36+
switch strategy(error).strategy {
2537
case .backoff(let duration):
2638
try await Task.sleep(for: duration, tolerance: tolerance, clock: clock)
2739
case .stop:
@@ -39,7 +51,7 @@ public func retry<Result, ErrorType>(
3951
tolerance: ContinuousClock.Instant.Duration? = nil,
4052
isolation: isolated (any Actor)? = #isolation,
4153
operation: () async throws(ErrorType) -> sending Result,
42-
strategy: (_ attempt: Int, ErrorType) -> RetryStrategy<ContinuousClock.Instant.Duration> = { _, _ in .backoff(.zero) }
54+
strategy: (ErrorType) -> RetryStrategy<ContinuousClock.Instant.Duration> = { _ in .backoff(.zero) }
4355
) async throws -> Result where ErrorType: Error {
4456
return try await retry(
4557
maxAttempts: maxAttempts,

Sources/_CPowSupport/_CPowSupport.c

Lines changed: 0 additions & 1 deletion
This file was deleted.

Sources/_CPowSupport/include/_CPowSupport.h

Lines changed: 0 additions & 3 deletions
This file was deleted.

0 commit comments

Comments
 (0)