Skip to content

Commit

Permalink
better client backoff based on headers
Browse files Browse the repository at this point in the history
  • Loading branch information
MahdiBM committed Nov 3, 2022
1 parent 66f244a commit 9a2c131
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 19 deletions.
6 changes: 4 additions & 2 deletions Package.swift
Expand Up @@ -33,9 +33,11 @@ let package = Package(
),
.testTarget(
name: "DiscordBMTests",
dependencies: ["DiscordBM"]),
dependencies: ["DiscordBM"]
),
.testTarget(
name: "IntegrationTests",
dependencies: ["DiscordBM"]),
dependencies: ["DiscordBM"]
),
]
)
19 changes: 13 additions & 6 deletions Sources/DiscordBM/DefaultDiscordClient.swift
Expand Up @@ -350,21 +350,21 @@ public struct ClientConfiguration {
rate: Double = 2,
upToTimes: UInt = .max
)
/// Based on the `Retry-After` header.
/// Based on the `x-ratelimit-reset-after` or the `Retry-After` header.
///
/// Parameters:
/// - `maxAllowed`: Max allowed amount in `Retry-After`. In seconds.
/// - `retryIfGreater`: Retry or not even if the header time is greater than
/// `maxAllowed`. If yes, the retry will happen after `maxAllowed` amount of time.
/// - `else`: If the `Retry-After` header did not exist.
case basedOnTheRetryAfterHeader(
case basedOnHeaders(
maxAllowed: Double?,
retryIfGreater: Bool = false,
else: Backoff?
)

public static var `default`: Backoff {
.basedOnTheRetryAfterHeader(
.basedOnHeaders(
maxAllowed: 5,
retryIfGreater: false,
else: .exponential(base: 0.2, coefficient: 0.5, rate: 2, upToTimes: 10)
Expand All @@ -386,9 +386,9 @@ public struct ClientConfiguration {
let exponent = min(Int(upToTimes), times)
let time = base + coefficient * pow(rate, Double(exponent))
return time
case let .basedOnTheRetryAfterHeader(maxAllowed, retryIfGreater, elseBackoff):
if let retryAfterString = headers.first(name: "Retry-After"),
let retryAfter = Double(retryAfterString) {
case let .basedOnHeaders(maxAllowed, retryIfGreater, elseBackoff):
if let header = headers.resetOrRetryAfterHeaderValue(),
let retryAfter = Double(header) {
if retryAfter <= (maxAllowed ?? 0) {
return retryAfter
} else {
Expand Down Expand Up @@ -599,3 +599,10 @@ extension ClientConfiguration: Sendable { }
extension ClientConfiguration.CachingBehavior: Sendable { }
extension ClientConfiguration.RetryPolicy: Sendable { }
extension ClientConfiguration.RetryPolicy.Backoff: Sendable { }

//MARK: +HTTPHeaders
extension HTTPHeaders {
func resetOrRetryAfterHeaderValue() -> String? {
self.first(name: "x-ratelimit-reset-after") ?? self.first(name: "retry-after")
}
}
11 changes: 2 additions & 9 deletions Sources/DiscordBM/HTTPRateLimiter.swift
Expand Up @@ -10,17 +10,13 @@ actor HTTPRateLimiter {
private var limit: Int
private var remaining: Int
private var reset: Double
/// Seconds after the request that the bucket will expire after.
/// We use `reset` instead of this, they both do the same thing at the end.
private var resetAfter: Double

var description: String {
"Bucket(" +
"bucket: \(bucket.debugDescription), " +
"limit: \(limit), " +
"remaining: \(remaining), " +
"reset: \(reset.debugDescription), " +
"resetAfter: \(resetAfter.debugDescription)" +
"reset: \(reset.debugDescription)" +
")"
}

Expand All @@ -31,15 +27,12 @@ actor HTTPRateLimiter {
let remainingStr = headers.first(name: "x-ratelimit-remaining"),
let remaining = Int(remainingStr),
let resetStr = headers.first(name: "x-ratelimit-reset"),
let reset = Double(resetStr),
let resetAfterStr = headers.first(name: "x-ratelimit-reset-after"),
let resetAfter = Double(resetAfterStr)
let reset = Double(resetStr)
else { return nil }
self.bucket = bucket
self.limit = limit
self.remaining = remaining
self.reset = reset
self.resetAfter = resetAfter
}

func shouldRequest() -> Bool {
Expand Down
4 changes: 2 additions & 2 deletions Tests/DiscordBMTests/ClientConfiguration.swift
Expand Up @@ -110,7 +110,7 @@ class ClientConfigurationTests: XCTestCase {
let coefficient = 0.8
let linearBackoff = Backoff.linear(base: base, coefficient: coefficient, upToTimes: 3)
let maxAllowed = 12.0
let backoff = Backoff.basedOnTheRetryAfterHeader(
let backoff = Backoff.basedOnHeaders(
maxAllowed: 12,
retryIfGreater: true,
else: linearBackoff
Expand Down Expand Up @@ -167,7 +167,7 @@ class ClientConfigurationTests: XCTestCase {

func testRetryPolicyHeadersBackoffWithoutElse() throws {
let maxAllowed = 10.0
let backoff = Backoff.basedOnTheRetryAfterHeader(
let backoff = Backoff.basedOnHeaders(
maxAllowed: maxAllowed,
retryIfGreater: false,
else: nil
Expand Down

0 comments on commit 9a2c131

Please sign in to comment.