Skip to content

Commit 76ded67

Browse files
committed
Implement a more robust workaround for swiftlang/swift#70557.
1 parent be395ae commit 76ded67

4 files changed

Lines changed: 70 additions & 9 deletions

File tree

Sources/Retry/Backoff/Algorithms/FullJitterExponentialBackoff.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,22 +79,21 @@ where ClockType: Clock, RandomNumberGeneratorType: RandomNumberGenerator {
7979
let maxDelayInClockTicks = min(baseDelayInClockTicks * Double(1 << exponent),
8080
maxDelayInClockTicks)
8181

82-
let delayInClockTicks = Double.random(in: 0...maxDelayInClockTicks,
83-
using: &randomNumberGenerator)
82+
let delayInClockTicks = randomNumberGenerator.random(in: 0...maxDelayInClockTicks)
8483

8584
// Unfortunately, `DurationProtocol` does not have a `Duration * Double` operator, so we need to cast to `Int`.
8685
// We make sure to cast to `Int` at the end rather than at the beginning so that the imprecision is bounded.
8786
return clockMinResolution * Int(clamping: UInt(delayInClockTicks.rounded()))
8887
}
8988
}
9089

91-
extension FullJitterExponentialBackoff where RandomNumberGeneratorType == SystemRandomNumberGenerator {
90+
extension FullJitterExponentialBackoff where RandomNumberGeneratorType == StandardRandomNumberGenerator {
9291
init(clock: ClockType,
9392
baseDelay: ClockType.Duration,
9493
maxDelay: ClockType.Duration?) {
9594
self.init(clock: clock,
9695
baseDelay: baseDelay,
9796
maxDelay: maxDelay,
98-
randomNumberGenerator: SystemRandomNumberGenerator())
97+
randomNumberGenerator: StandardRandomNumberGenerator())
9998
}
10099
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// MIT License
2+
//
3+
// Copyright © 2023 Darren Mo.
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
/// A protocol that allows one to specify a different implementation from the standard one (e.g. for automated tests).
24+
///
25+
/// - Remark: Cannot use the Swift standard library’s `RandomNumberGenerator` protocol for this purpose as detailed here:
26+
/// https://github.com/apple/swift/issues/70557
27+
protocol RandomNumberGenerator {
28+
func random<T>(
29+
in range: ClosedRange<T>
30+
) -> T where T: BinaryFloatingPoint, T.RawSignificand: FixedWidthInteger
31+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// MIT License
2+
//
3+
// Copyright © 2023 Darren Mo.
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
struct StandardRandomNumberGenerator: RandomNumberGenerator {
24+
func random<T>(
25+
in range: ClosedRange<T>
26+
) -> T where T: BinaryFloatingPoint, T.RawSignificand: FixedWidthInteger {
27+
return T.random(in: range)
28+
}
29+
}

Tests/RetryTests/Fakes/RandomNumberGeneratorFake.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121
// SOFTWARE.
2222

23+
@testable import Retry
24+
2325
class RandomNumberGeneratorFake: RandomNumberGenerator {
2426
enum Mode {
2527
case min
@@ -31,15 +33,15 @@ class RandomNumberGeneratorFake: RandomNumberGenerator {
3133
self.mode = mode
3234
}
3335

34-
func next() -> UInt64 {
36+
func random<T>(
37+
in range: ClosedRange<T>
38+
) -> T where T : BinaryFloatingPoint, T.RawSignificand : FixedWidthInteger {
3539
switch mode {
3640
case .min:
37-
// Add `1` to work around the following issue with Swift’s random number generator implementation:
38-
// https://github.com/apple/swift/issues/70557
39-
return .min + 1
41+
return range.lowerBound
4042

4143
case .max:
42-
return .max
44+
return range.upperBound
4345
}
4446
}
4547
}

0 commit comments

Comments
 (0)