From 16ac64b20c3af4392e33e099864cb2a466e556f0 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Sat, 24 May 2025 12:11:56 +0200 Subject: [PATCH 1/3] Add failing test #46 --- .../ExponentialBackoffRetryPolicy.swift | 5 ++++- Tests/SwiftRetrierTests/ExponentialBackoffTest.swift | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 Tests/SwiftRetrierTests/ExponentialBackoffTest.swift diff --git a/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift b/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift index fe79baf..48ed422 100644 --- a/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift +++ b/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift @@ -1,6 +1,6 @@ import Foundation -public struct ExponentialBackoffRetryPolicy: RetryPolicy { +public struct ExponentialBackoffRetryPolicy { public enum Jitter: Sendable { case none @@ -72,6 +72,9 @@ public struct ExponentialBackoffRetryPolicy: RetryPolicy { return decorrelatedJitterDelay(attemptIndex: attemptIndex, growthFactor: growthFactor) } } +} + +extension ExponentialBackoffRetryPolicy: RetryPolicy { public func retryDelay(for attemptFailure: AttemptFailure) -> TimeInterval { min(maxDelay, uncappedDelay(attemptIndex: attemptFailure.index)) diff --git a/Tests/SwiftRetrierTests/ExponentialBackoffTest.swift b/Tests/SwiftRetrierTests/ExponentialBackoffTest.swift new file mode 100644 index 0000000..1206ef8 --- /dev/null +++ b/Tests/SwiftRetrierTests/ExponentialBackoffTest.swift @@ -0,0 +1,12 @@ +import XCTest +@testable import SwiftRetrier +@preconcurrency import Combine + +// swiftlint:disable:next type_body_length +final class ExponentialBackoffTest: XCTestCase { + + func test_When_exponentationGoesUp_Then_noOverflow() { + let policy = ExponentialBackoffRetryPolicy() + _ = policy.retryDelay(for: AttemptFailure(trialStart: Date(), index: .max, error: TestError())) + } +} From 1e849acfc21e1e78cd10f9b182d3918504d0d4e5 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Sat, 24 May 2025 12:19:27 +0200 Subject: [PATCH 2/3] Implement safe multiplication #46 --- .../ExponentialBackoffRetryPolicy.swift | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift b/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift index 48ed422..2196ddf 100644 --- a/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift +++ b/Sources/SwiftRetrier/Core/ExponentialBackoffPolicy/ExponentialBackoffRetryPolicy.swift @@ -23,23 +23,38 @@ public struct ExponentialBackoffRetryPolicy { self.previousDelay = previousDelay } - public func exponentiationBySquaring(_ base: T, _ multiplier: T, _ exponent: T) -> T { - precondition(exponent >= 0) - if exponent == 0 { - return base + private func safeMultiply(_ lhs: UInt, _ rhs: UInt) -> UInt { + if UInt.max / rhs < lhs || UInt.max / lhs < rhs { + UInt.max + } else { + lhs * rhs + } + } + + public func exponentiationBySquaring(base: UInt, multiplier: UInt, exponent: UInt) -> UInt { + if exponent == .zero { + base } else if exponent == 1 { - return base * multiplier + safeMultiply(base, multiplier) } else if exponent.isMultiple(of: 2) { - return exponentiationBySquaring(base, multiplier * multiplier, exponent / 2) + exponentiationBySquaring( + base: base, + multiplier: safeMultiply(multiplier, multiplier), + exponent: exponent / 2 + ) } else { // exponent is odd - return exponentiationBySquaring(base * multiplier, multiplier * multiplier, (exponent - 1) / 2) + exponentiationBySquaring( + base: safeMultiply(base, multiplier), + multiplier: safeMultiply(multiplier, multiplier), + exponent: (exponent - 1) / 2 + ) } } // swiftlint:disable:next line_length // See https://stackoverflow.com/questions/24196689/how-to-get-the-power-of-some-integer-in-swift-language/39021464#39021464 - public func pow(_ base: T, _ power: T) -> T { - return exponentiationBySquaring(1, base, power) + public func pow(_ base: UInt, _ power: UInt) -> UInt { + exponentiationBySquaring(base: 1, multiplier: base, exponent: power) } public func noJitterDelay(attemptIndex: UInt) -> TimeInterval { From e30602ceddbc7aa7782ab8050ed572431b3787f8 Mon Sep 17 00:00:00 2001 From: Pierre Mardon Date: Sat, 24 May 2025 12:25:18 +0200 Subject: [PATCH 3/3] Fix lint --- Tests/SwiftRetrierTests/ExponentialBackoffTest.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/SwiftRetrierTests/ExponentialBackoffTest.swift b/Tests/SwiftRetrierTests/ExponentialBackoffTest.swift index 1206ef8..3eb5017 100644 --- a/Tests/SwiftRetrierTests/ExponentialBackoffTest.swift +++ b/Tests/SwiftRetrierTests/ExponentialBackoffTest.swift @@ -2,7 +2,6 @@ import XCTest @testable import SwiftRetrier @preconcurrency import Combine -// swiftlint:disable:next type_body_length final class ExponentialBackoffTest: XCTestCase { func test_When_exponentationGoesUp_Then_noOverflow() {