|
1 |
| -#if compiler(<6.2) |
2 |
| -@available(iOS 18.0, macCatalyst 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, watchOS 11.0, *) |
3 |
| -extension Duration { |
4 |
| - @usableFromInline var attoseconds: Int128 { |
5 |
| - return Int128(_low: _low, _high: _high) |
6 |
| - } |
7 |
| - @usableFromInline init(attoseconds: Int128) { |
8 |
| - self.init(_high: attoseconds._high, low: attoseconds._low) |
9 |
| - } |
10 |
| -} |
11 |
| -#endif |
12 |
| - |
| 1 | +#if compiler(>=6.2) |
| 2 | +/// A protocol for defining backoff strategies that generate delays between retry attempts. |
| 3 | +/// |
| 4 | +/// Backoff strategies are stateful and generate progressively changing delays based on their |
| 5 | +/// internal algorithm. Each call to `nextDuration()` returns the delay for the next retry attempt. |
| 6 | +/// |
| 7 | +/// ## Example |
| 8 | +/// |
| 9 | +/// ```swift |
| 10 | +/// var strategy = Backoff.exponential(factor: 2, initial: .milliseconds(100)) |
| 11 | +/// strategy.nextDuration() // 100ms |
| 12 | +/// strategy.nextDuration() // 200ms |
| 13 | +/// strategy.nextDuration() // 400ms |
| 14 | +/// ``` |
13 | 15 | @available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
|
14 | 16 | public protocol BackoffStrategy<Duration> {
|
15 | 17 | associatedtype Duration: DurationProtocol
|
@@ -135,49 +137,193 @@ public protocol BackoffStrategy<Duration> {
|
135 | 137 |
|
136 | 138 | @available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
|
137 | 139 | public enum Backoff {
|
| 140 | + /// Creates a constant backoff strategy that always returns the same delay. |
| 141 | + /// |
| 142 | + /// Formula: `f(n) = constant` |
| 143 | + /// |
| 144 | + /// - Parameter constant: The fixed duration to wait between retry attempts. |
| 145 | + /// - Returns: A backoff strategy that always returns the constant duration. |
138 | 146 | @inlinable public static func constant<Duration: DurationProtocol>(_ constant: Duration) -> some BackoffStrategy<Duration> {
|
139 | 147 | return ConstantBackoffStrategy(constant: constant)
|
140 | 148 | }
|
| 149 | + |
| 150 | + /// Creates a constant backoff strategy that always returns the same delay. |
| 151 | + /// |
| 152 | + /// Formula: `f(n) = constant` |
| 153 | + /// |
| 154 | + /// - Parameter constant: The fixed duration to wait between retry attempts. |
| 155 | + /// - Returns: A backoff strategy that always returns the constant duration. |
| 156 | + /// |
| 157 | + /// ## Example |
| 158 | + /// |
| 159 | + /// ```swift |
| 160 | + /// var backoff = Backoff.constant(.milliseconds(100)) |
| 161 | + /// backoff.nextDuration() // 100ms |
| 162 | + /// backoff.nextDuration() // 100ms |
| 163 | + /// ``` |
141 | 164 | @inlinable public static func constant(_ constant: Duration) -> some BackoffStrategy<Duration> {
|
142 | 165 | return ConstantBackoffStrategy(constant: constant)
|
143 | 166 | }
|
| 167 | + |
| 168 | + /// Creates a linear backoff strategy where delays increase by a fixed increment. |
| 169 | + /// |
| 170 | + /// Formula: `f(n) = initial + increment * n` |
| 171 | + /// |
| 172 | + /// - Parameters: |
| 173 | + /// - increment: The amount to increase the delay by on each attempt. |
| 174 | + /// - initial: The initial delay for the first retry attempt. |
| 175 | + /// - Returns: A backoff strategy with linearly increasing delays. |
144 | 176 | @inlinable public static func linear<Duration: DurationProtocol>(increment: Duration, initial: Duration) -> some BackoffStrategy<Duration> {
|
145 | 177 | return LinearBackoffStrategy(increment: increment, initial: initial)
|
146 | 178 | }
|
| 179 | + |
| 180 | + /// Creates a linear backoff strategy where delays increase by a fixed increment. |
| 181 | + /// |
| 182 | + /// Formula: `f(n) = initial + increment * n` |
| 183 | + /// |
| 184 | + /// - Parameters: |
| 185 | + /// - increment: The amount to increase the delay by on each attempt. |
| 186 | + /// - initial: The initial delay for the first retry attempt. |
| 187 | + /// - Returns: A backoff strategy with linearly increasing delays. |
| 188 | + /// |
| 189 | + /// ## Example |
| 190 | + /// |
| 191 | + /// ```swift |
| 192 | + /// var backoff = Backoff.linear(increment: .milliseconds(100), initial: .milliseconds(100)) |
| 193 | + /// backoff.nextDuration() // 100ms |
| 194 | + /// backoff.nextDuration() // 200ms |
| 195 | + /// backoff.nextDuration() // 300ms |
| 196 | + /// ``` |
147 | 197 | @inlinable public static func linear(increment: Duration, initial: Duration) -> some BackoffStrategy<Duration> {
|
148 | 198 | return LinearBackoffStrategy(increment: increment, initial: initial)
|
149 | 199 | }
|
| 200 | + |
| 201 | + /// Creates an exponential backoff strategy where delays grow exponentially. |
| 202 | + /// |
| 203 | + /// Formula: `f(n) = initial * factor^n` |
| 204 | + /// |
| 205 | + /// - Parameters: |
| 206 | + /// - factor: The multiplication factor for each retry attempt. |
| 207 | + /// - initial: The initial delay for the first retry attempt. |
| 208 | + /// - Returns: A backoff strategy with exponentially increasing delays. |
150 | 209 | @inlinable public static func exponential<Duration: DurationProtocol>(factor: Int, initial: Duration) -> some BackoffStrategy<Duration> {
|
151 | 210 | return ExponentialBackoffStrategy(factor: factor, initial: initial)
|
152 | 211 | }
|
| 212 | + |
| 213 | + /// Creates an exponential backoff strategy where delays grow exponentially. |
| 214 | + /// |
| 215 | + /// Formula: `f(n) = initial * factor^n` |
| 216 | + /// |
| 217 | + /// - Parameters: |
| 218 | + /// - factor: The multiplication factor for each retry attempt. |
| 219 | + /// - initial: The initial delay for the first retry attempt. |
| 220 | + /// - Returns: A backoff strategy with exponentially increasing delays. |
| 221 | + /// |
| 222 | + /// ## Example |
| 223 | + /// |
| 224 | + /// ```swift |
| 225 | + /// var backoff = Backoff.exponential(factor: 2, initial: .milliseconds(100)) |
| 226 | + /// backoff.nextDuration() // 100ms |
| 227 | + /// backoff.nextDuration() // 200ms |
| 228 | + /// backoff.nextDuration() // 400ms |
| 229 | + /// ``` |
153 | 230 | @inlinable public static func exponential(factor: Int, initial: Duration) -> some BackoffStrategy<Duration> {
|
154 | 231 | return ExponentialBackoffStrategy(factor: factor, initial: initial)
|
155 | 232 | }
|
156 | 233 | }
|
157 | 234 |
|
158 | 235 | @available(iOS 18.0, macCatalyst 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, watchOS 11.0, *)
|
159 | 236 | extension Backoff {
|
| 237 | + /// Creates a decorrelated jitter backoff strategy that uses randomized delays. |
| 238 | + /// |
| 239 | + /// Formula: `f(n) = random(base, f(n - 1) * factor)` where `f(0) = base` |
| 240 | + /// |
| 241 | + /// Jitter prevents the "thundering herd" problem where multiple clients retry |
| 242 | + /// simultaneously, reducing server load spikes and improving system stability. |
| 243 | + /// |
| 244 | + /// - Parameters: |
| 245 | + /// - factor: The multiplication factor for calculating the upper bound of randomness. |
| 246 | + /// - base: The base duration used as the minimum delay and initial reference. |
| 247 | + /// - generator: The random number generator to use. Defaults to `SystemRandomNumberGenerator()`. |
| 248 | + /// - Returns: A backoff strategy with decorrelated jitter. |
160 | 249 | @inlinable public static func decorrelatedJitter<RNG: RandomNumberGenerator>(factor: Int, base: Duration, using generator: RNG = SystemRandomNumberGenerator()) -> some BackoffStrategy<Duration> {
|
161 | 250 | return DecorrelatedJitterBackoffStrategy(base: base, factor: factor, generator: generator)
|
162 | 251 | }
|
163 | 252 | }
|
164 | 253 |
|
165 | 254 | @available(iOS 16.0, macCatalyst 16.0, macOS 13.0, tvOS 16.0, visionOS 1.0, watchOS 9.0, *)
|
166 | 255 | extension BackoffStrategy {
|
| 256 | + /// Applies a minimum duration constraint to this backoff strategy. |
| 257 | + /// |
| 258 | + /// Formula: `f(n) = max(minimum, g(n))` where `g(n)` is the base strategy |
| 259 | + /// |
| 260 | + /// This modifier ensures that no delay returned by the strategy is less than |
| 261 | + /// the specified minimum duration. |
| 262 | + /// |
| 263 | + /// - Parameter minimum: The minimum duration to enforce. |
| 264 | + /// - Returns: A backoff strategy that never returns delays shorter than the minimum. |
| 265 | + /// |
| 266 | + /// ## Example |
| 267 | + /// |
| 268 | + /// ```swift |
| 269 | + /// var backoff = Backoff |
| 270 | + /// .exponential(factor: 2, initial: .milliseconds(100)) |
| 271 | + /// .minimum(.milliseconds(200)) |
| 272 | + /// backoff.nextDuration() // 200ms (enforced minimum) |
| 273 | + /// ``` |
167 | 274 | @inlinable public func minimum(_ minimum: Duration) -> some BackoffStrategy<Duration> {
|
168 | 275 | return MinimumBackoffStrategy(base: self, minimum: minimum)
|
169 | 276 | }
|
| 277 | + |
| 278 | + /// Applies a maximum duration constraint to this backoff strategy. |
| 279 | + /// |
| 280 | + /// Formula: `f(n) = min(maximum, g(n))` where `g(n)` is the base strategy |
| 281 | + /// |
| 282 | + /// This modifier ensures that no delay returned by the strategy exceeds |
| 283 | + /// the specified maximum duration, effectively capping exponential growth. |
| 284 | + /// |
| 285 | + /// - Parameter maximum: The maximum duration to enforce. |
| 286 | + /// - Returns: A backoff strategy that never returns delays longer than the maximum. |
| 287 | + /// |
| 288 | + /// ## Example |
| 289 | + /// |
| 290 | + /// ```swift |
| 291 | + /// var backoff = Backoff |
| 292 | + /// .exponential(factor: 2, initial: .milliseconds(100)) |
| 293 | + /// .maximum(.seconds(5)) |
| 294 | + /// // Delays will cap at 5 seconds instead of growing indefinitely |
| 295 | + /// ``` |
170 | 296 | @inlinable public func maximum(_ maximum: Duration) -> some BackoffStrategy<Duration> {
|
171 | 297 | return MaximumBackoffStrategy(base: self, maximum: maximum)
|
172 | 298 | }
|
173 | 299 | }
|
174 | 300 |
|
175 | 301 | @available(iOS 18.0, macCatalyst 18.0, macOS 15.0, tvOS 18.0, visionOS 2.0, watchOS 11.0, *)
|
176 | 302 | extension BackoffStrategy where Duration == Swift.Duration {
|
| 303 | + /// Applies full jitter to this backoff strategy. |
| 304 | + /// |
| 305 | + /// Formula: `f(n) = random(0, g(n))` where `g(n)` is the base strategy |
| 306 | + /// |
| 307 | + /// Jitter prevents the "thundering herd" problem where multiple clients retry |
| 308 | + /// simultaneously, reducing server load spikes and improving system stability. |
| 309 | + /// |
| 310 | + /// - Parameter generator: The random number generator to use. Defaults to `SystemRandomNumberGenerator()`. |
| 311 | + /// - Returns: A backoff strategy with full jitter applied. |
177 | 312 | @inlinable public func fullJitter<RNG: RandomNumberGenerator>(using generator: RNG = SystemRandomNumberGenerator()) -> some BackoffStrategy<Duration> {
|
178 | 313 | return FullJitterBackoffStrategy(base: self, generator: generator)
|
179 | 314 | }
|
| 315 | + |
| 316 | + /// Applies equal jitter to this backoff strategy. |
| 317 | + /// |
| 318 | + /// Formula: `f(n) = random(g(n) / 2, g(n))` where `g(n)` is the base strategy |
| 319 | + /// |
| 320 | + /// Jitter prevents the "thundering herd" problem where multiple clients retry |
| 321 | + /// simultaneously, reducing server load spikes and improving system stability. |
| 322 | + /// |
| 323 | + /// - Parameter generator: The random number generator to use. Defaults to `SystemRandomNumberGenerator()`. |
| 324 | + /// - Returns: A backoff strategy with equal jitter applied. |
180 | 325 | @inlinable public func equalJitter<RNG: RandomNumberGenerator>(using generator: RNG = SystemRandomNumberGenerator()) -> some BackoffStrategy<Duration> {
|
181 | 326 | return EqualJitterBackoffStrategy(base: self, generator: generator)
|
182 | 327 | }
|
183 | 328 | }
|
| 329 | +#endif |
0 commit comments