Skip to content

Commit

Permalink
Implement a more robust workaround for swiftlang/swift#70557.
Browse files Browse the repository at this point in the history
  • Loading branch information
fumoboy007 committed Dec 22, 2023
1 parent 3cb4f2d commit 091cdd7
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,21 @@ where ClockType: Clock, RandomNumberGeneratorType: RandomNumberGenerator {
let maxDelayInClockTicks = min(baseDelayInClockTicks * Double(1 << exponent),
maxDelayInClockTicks)

let delayInClockTicks = Double.random(in: 0...maxDelayInClockTicks,
using: &randomNumberGenerator)
let delayInClockTicks = randomNumberGenerator.random(in: 0...maxDelayInClockTicks)

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

extension FullJitterExponentialBackoff where RandomNumberGeneratorType == SystemRandomNumberGenerator {
extension FullJitterExponentialBackoff where RandomNumberGeneratorType == StandardRandomNumberGenerator {
init(clock: ClockType,
baseDelay: ClockType.Duration,
maxDelay: ClockType.Duration?) {
self.init(clock: clock,
baseDelay: baseDelay,
maxDelay: maxDelay,
randomNumberGenerator: SystemRandomNumberGenerator())
randomNumberGenerator: StandardRandomNumberGenerator())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// MIT License
//
// Copyright © 2023 Darren Mo.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

/// A protocol that allows one to specify a different implementation from the standard one (e.g. for automated tests).
///
/// - Remark: Cannot use the Swift standard library’s `RandomNumberGenerator` protocol for this purpose as detailed here:
/// https://github.com/apple/swift/issues/70557
protocol RandomNumberGenerator {
func random<T>(
in range: ClosedRange<T>
) -> T where T: BinaryFloatingPoint, T.RawSignificand: FixedWidthInteger
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// MIT License
//
// Copyright © 2023 Darren Mo.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

struct StandardRandomNumberGenerator: RandomNumberGenerator {
func random<T>(
in range: ClosedRange<T>
) -> T where T: BinaryFloatingPoint, T.RawSignificand: FixedWidthInteger {
return T.random(in: range)
}
}
12 changes: 7 additions & 5 deletions Tests/RetryTests/Fakes/RandomNumberGeneratorFake.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

@testable import Retry

class RandomNumberGeneratorFake: RandomNumberGenerator {
enum Mode {
case min
Expand All @@ -31,15 +33,15 @@ class RandomNumberGeneratorFake: RandomNumberGenerator {
self.mode = mode
}

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

case .max:
return .max
return range.upperBound
}
}
}

0 comments on commit 091cdd7

Please sign in to comment.