-
Notifications
You must be signed in to change notification settings - Fork 319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Retry Requests with HTTP Status 429 #4048
Changes from 36 commits
7d3d883
21dfb9e
bdc6c03
e5db1b9
5fe37d3
6428262
dfa171f
6a74a22
177ba49
9af7146
3117b86
85c809d
23e7a85
664d80d
aa41a7e
e438e1b
d9f4111
e1c588f
5f5333d
e0d09c9
36450e5
3ae998a
7e360e4
fc3a871
17bb1ad
34a0015
b034eaa
0accbe2
0a65fef
566598a
b9c1f9b
71e8345
2ab4382
50d7453
0c0775f
f31bd21
5a3992e
5914732
21399f5
7426811
619a361
3634049
3c2fb0e
2aefc4b
2dc4477
972f504
3b20186
da10908
95848e4
f886206
ff28f23
03fc420
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// | ||
// Copyright RevenueCat Inc. All Rights Reserved. | ||
// | ||
// Licensed under the MIT License (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// https://opensource.org/licenses/MIT | ||
// | ||
// TimeInterval+Extensions.swift | ||
// | ||
// Created by Will Taylor on 7/12/24. | ||
|
||
import Foundation | ||
|
||
extension TimeInterval { | ||
|
||
init(milliseconds: Double) { | ||
self = milliseconds / 1000.0 | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,11 +18,12 @@ import Foundation | |
/// | ||
/// These delays prevent DDOS if a notification leads to many users opening an app at the same time, | ||
/// by spreading asynchronous operations over time. | ||
enum Delay { | ||
enum JitterableDelay: Equatable { | ||
|
||
case none | ||
case `default` | ||
case long | ||
case timeInterval(TimeInterval) | ||
|
||
static func `default`(forBackgroundedApp inBackground: Bool) -> Self { | ||
return inBackground ? .default : .none | ||
|
@@ -56,15 +57,19 @@ class OperationDispatcher { | |
Self.dispatchOnMainActor(block) | ||
} | ||
|
||
func dispatchOnWorkerThread(delay: Delay = .none, block: @escaping @Sendable () -> Void) { | ||
func dispatchOnWorkerThread(delay: JitterableDelay = .none, block: @escaping @Sendable () -> Void) { | ||
if delay.hasDelay { | ||
self.workerQueue.asyncAfter(deadline: .now() + delay.random(), execute: block) | ||
} else { | ||
self.workerQueue.async(execute: block) | ||
} | ||
} | ||
|
||
func dispatchOnWorkerThread(delay: Delay = .none, block: @escaping @Sendable () async -> Void) { | ||
func dispatchOnWorkerThread(after timeInterval: TimeInterval, block: @escaping @Sendable () -> Void) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This might be a small Josh nit but Not a thing that has to change in the PR but just a thought 🤷♂️ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I totally agree with you! The only difference is that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe we can rename the param to It might be slightly confusing on first glance but at least you'd hesitate long enough to actually think of which one to use |
||
self.workerQueue.asyncAfter(deadline: .now() + timeInterval, execute: block) | ||
} | ||
|
||
func dispatchOnWorkerThread(delay: JitterableDelay = .none, block: @escaping @Sendable () async -> Void) { | ||
Task.detached(priority: .background) { | ||
if delay.hasDelay { | ||
try? await Task.sleep(nanoseconds: DispatchTimeInterval(delay.random()).nanoseconds) | ||
|
@@ -89,7 +94,7 @@ extension OperationDispatcher { | |
// MARK: - | ||
|
||
/// Visible for testing | ||
extension Delay { | ||
extension JitterableDelay { | ||
|
||
var hasDelay: Bool { | ||
return self.maximum > 0 | ||
|
@@ -101,13 +106,15 @@ extension Delay { | |
|
||
} | ||
|
||
private extension Delay { | ||
private extension JitterableDelay { | ||
|
||
var minimum: TimeInterval { | ||
switch self { | ||
case .none: return 0 | ||
case .`default`: return 0 | ||
case .long: return Self.maxJitter | ||
case .timeInterval(let timeInterval): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we don't need the jitter, we might as well make another method in operation dispatcher that just takes an exact value as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the argument can be made either way. If we were to make that change, we'd then have two similar dispatch functions that happen after a period of time:
When I read these function signatures, it isn't clear which one I should choose because the function signatures signify the same thing. If we were to go through with this change, I'd recommend renaming There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Update: I've gone ahead and renamed |
||
return max(timeInterval, 0) | ||
Comment on lines
+117
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. when would this be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't think of any instances where the timeInterval would be <0 in the scope of this PR, but this is just defensive programming in case someone ever tries to pass in a negative |
||
} | ||
} | ||
|
||
|
@@ -116,6 +123,8 @@ private extension Delay { | |
case .none: return 0 | ||
case .`default`: return Self.maxJitter | ||
case .long: return Self.maxJitter * 2 | ||
case .timeInterval(let timeInterval): | ||
return max(timeInterval, 0) | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to log the retry number in this message as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this, will add it in!