-
Notifications
You must be signed in to change notification settings - Fork 2k
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
@uppy/tus: pause all requests in response to server rate limiting #3394
Conversation
When the remote server responds with HTTP 429, all requests are paused for a while in the hope that it can resolve the rate limiting. Failed requests are also now queued up after the retry delay. Before that, they were simply scheduled which would sometimes end up overflowing the `limit` option.
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.
Can this be tested relatively easily in Jest? If so, would be great to have a test for it.
There are no "real" tests for |
I see. If we could pass a callback to the plugin that is used for every request we could measure that, like I did for aws-s3-multipart: uppy/packages/@uppy/aws-s3-multipart/src/index.test.js Lines 168 to 218 in 87c716a
But that's not the case and it's probably weird to add it just for testing purposes. E2E would also still be a bit challenging so I think we can skip it for now. But it is critical functionality that ideally should be tested in my opinion. |
The hard truth has been spoken ☝️ Having a test suite that allows us to easily mock HTTP responses would be huge, and would let us write more interesting test cases. |
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.
Code lgtm to me overall, had some suggestions for naming and questions.
It's a pity that we cannot really have a test for this right now. we definitely need to make it happen. The complexity of the code is asking for bugs.
} else { | ||
setTimeout(() => { | ||
queuedRequest = this.requests.run(queuedRequest) | ||
}, options.retryDelays[retryAttempt]) |
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.
I'd expect retryAttempt
to start with 1, as in the "first" attempt to retry. Also means if retryAttempt is 4, there were 4 retry attempts. Just reads more easily.
But options.retryDelays
is a 0-indexed array. Might be useful to add a comment here that the first retry attempt means retryAttempt === 0
(if that is the case, ofc).
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.
retryAttempt
starts at 0
, because before on the first attempt, it's not a retry.
Looking at the https://github.com/tus/tus-js-client/blob/8574294c30765dbe3a71a7a066a88b060e0e84d6/lib/upload.js#L453, it looks like if we return false
the retryAttempt will always be 0
, so we need to hack our way around it..
packages/@uppy/tus/src/index.js
Outdated
uploadOptions.onShouldRetry = (err, retryAttempt, options) => { | ||
if (err?.originalResponse?.getStatus() === 429) { | ||
if (!this.requests.isPaused) { | ||
const { done, value } = this.#retryDelayIterator.next() |
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.
What if this.opts.retryDelays
was null
, then this.#retryDelayIterator
might be null
and not be an iterator and thus have no next()
method, causing a hard error. Should this.opts.retryDelays
be set to []
if the consumer passes a useless value for it?
packages/@uppy/tus/src/index.js
Outdated
}) | ||
|
||
let queuedRequest = this.requests.run(() => { | ||
queueRequest = () => { |
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.
The naming suggests this to be an object (with a run
method?), not a function, but I realize the pattern is used all over the file, so feel free to ignore me. This would be a big refactoring to keep it consistent in the file.
Does this PR also pause the requests towards Transloadit api2 (polling assembly json status) when they return 429? If not, then I believe that is also something that needs to be handled, because they share the same rate limiting counters in haproxy. |
packages/@uppy/tus/src/index.js
Outdated
if (next == null || next.done) { | ||
return false | ||
} | ||
this.requests.limit = Math.ceil(this.requests.limit / 2) |
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.
Not sure if this is the most efficient way of doing it, but it seems to mostly do the trick for now.
Is the exponential backoff specifically implemented for transloadit and its HAProxy setup? Because we are implementing these changes in @uppy/tus. Will this work if people have their own tus server and don't upload to transloadit? |
I've made a separate PR for changes that I think are specific to Transloadit: #3414 |
for (let i = this.#downLimit; i <= this.limit; i++) { | ||
this.#queueNext() | ||
} |
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.
nit: might be nice to write a tiny times(n, fn)
function for these kinds of behaviors.
| Package | Version | Package | Version | | -------------------- | ------- | -------------------- | ------- | | @uppy/drag-drop | 2.0.6 | @uppy/tus | 2.2.0 | | @uppy/image-editor | 1.1.1 | @uppy/utils | 4.0.5 | | @uppy/screen-capture | 2.0.6 | @uppy/robodog | 2.2.0 | | @uppy/transloadit | 2.1.0 | uppy | 2.4.0 | - @uppy/transloadit: ignore rate limiting errors when polling (Antoine du Hamel / #3418) - @uppy/tus: pause all requests in response to server rate limiting (Antoine du Hamel / #3394) - @uppy/transloadit: better defaults for rate limiting (Antoine du Hamel / #3414) - @uppy/companion: Fix Companion deploys (kiloreux / #3388) - meta: update aws-presigned-url example to use esm (Antoine du Hamel / #3413) - @uppy/image-editor: namespace input range css (Merlijn Vos / #3406) - @uppy/screen-capture: Add missing option to the screen capture types (Mustafa Navruz / #3400) - @uppy/drag-drop: fix `undefined is not a function` TypeError (Antoine du Hamel / #3397) - website: update december 2021 blog post (Antoine du Hamel / #3396) - website: Polished the latest update blog (AJvanLoon / #3390) - website: docs: fix typo in audio.md (heocoi / #3389) - website: 2.0-2.3 post draft (Artur Paikin / #3370)
* main: dev: move configuration to a `.env` file (#3430) Update ci.yml (#3428) @uppy/transloadit: simplify `#onTusError` (#3419) Force include babel numeric separator (#3422) Release: [email protected] (#3420) @uppy/transloadit: ignore rate limiting errors when polling (#3418) @uppy/tus: pause all requests in response to server rate limiting (#3394) @uppy/transloadit: better defaults for rate limiting (#3414) Update BACKLOG.md Fix Companion deploys (#3388) update aws-presigned-url example to use esm (#3413) @uppy/image-editor: namespace input range css (#3406) Release: [email protected] (#3410) improve private ip check (#3403) Add missing option to the screen capture types (#3400) @uppy/drag-drop: fix `undefined is not a function` TypeError (#3397) website: update december 2021 blog post (#3396)
* main: dev: move configuration to a `.env` file (transloadit#3430) Update ci.yml (transloadit#3428) @uppy/transloadit: simplify `#onTusError` (transloadit#3419) Force include babel numeric separator (transloadit#3422) Release: [email protected] (transloadit#3420) @uppy/transloadit: ignore rate limiting errors when polling (transloadit#3418) @uppy/tus: pause all requests in response to server rate limiting (transloadit#3394) @uppy/transloadit: better defaults for rate limiting (transloadit#3414) Update BACKLOG.md Fix Companion deploys (transloadit#3388) update aws-presigned-url example to use esm (transloadit#3413) @uppy/image-editor: namespace input range css (transloadit#3406) Release: [email protected] (transloadit#3410) improve private ip check (transloadit#3403) Add missing option to the screen capture types (transloadit#3400) @uppy/drag-drop: fix `undefined is not a function` TypeError (transloadit#3397) website: update december 2021 blog post (transloadit#3396)
When the remote server responds with HTTP 429, all requests are
paused for a while in the hope that it can resolve the rate limiting.
Failed requests are also now queued up after the retry delay. Before
that, they were simply scheduled which would sometimes end up
overflowing the
limit
option.