Skip to content
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

fix regression when throttle timeout is marginal #1572

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions src/network/requestQueue/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const PRIVATE = {
}

const REQUEST_QUEUE_EMPTY = 'requestQueueEmpty'
const CHECK_PENDING_REQUESTS_INTERVAL = 10

module.exports = class RequestQueue extends EventEmitter {
/**
Expand Down Expand Up @@ -56,6 +55,13 @@ module.exports = class RequestQueue extends EventEmitter {
*/
this.throttledUntil = -1

/**
* Current timestamp when the throttling was set
*
* @type {number}
*/
this.throttleCurrentTimestamp = 0

/**
* Timeout id if we have scheduled a check for pending requests due to client-side throttling
*
Expand Down Expand Up @@ -105,7 +111,8 @@ module.exports = class RequestQueue extends EventEmitter {
maybeThrottle(clientSideThrottleTime) {
if (clientSideThrottleTime !== null && clientSideThrottleTime > 0) {
this.logger.debug(`Client side throttling in effect for ${clientSideThrottleTime}ms`)
const minimumThrottledUntil = Date.now() + clientSideThrottleTime
this.throttleCurrentTimestamp = Date.now()
const minimumThrottledUntil = this.throttleCurrentTimestamp + clientSideThrottleTime
this.throttledUntil = Math.max(minimumThrottledUntil, this.throttledUntil)
}
}
Expand Down Expand Up @@ -309,15 +316,14 @@ module.exports = class RequestQueue extends EventEmitter {
// will be fine, and potentially fix up a new timeout if needed at that time.
// Note that if we're merely "overloaded" by having too many inflight requests
// we will anyways check the queue when one of them gets fulfilled.
let scheduleAt = this.throttledUntil - Date.now()
if (!this.throttleCheckTimeoutId) {
if (this.pending.length > 0) {
scheduleAt = scheduleAt > 0 ? scheduleAt : CHECK_PENDING_REQUESTS_INTERVAL
}
const timeUntilUnthrottled = this.throttledUntil - this.throttleCurrentTimestamp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I'm missing something here. From reading this code it looks to me like we're going to be checking every single time around the event loop, since the setTimeout is always called with 0.

So the very first time we get throttled, we'll set throttleCurrentTimestamp to 0 and throttledUntil to 10. timeUntilThrottled is 10 - 0, which is greater than 0 and we don't already have a scheduled timer, so we schedule it for next tick. Next tick happens so we null out the timeout id and we update throttleCurrentTimestamp with the current time, let's say it's 1. Now timeUntilThrottled is 10 - 1 which is still greater than 0, so we do the same thing over again.

Copy link
Author

@MDSLKTR MDSLKTR May 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, this would probably create many more calls then necessary, i could imagine that instead using Math.max(timeUntilUnthrottled, 0) would then be more applicable?

This would delay the next call until it is predicting the throttleling to be over, this marginal offset is then solved by setting the timestamp and there would not be the case where timeUntilUnthrottled is 0 i think

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be helpful to read the previous implementation (and the original one). If you check the PR that introduced this (#1532 (comment)), I made a similar suggestion. We just need to make sure we don't end up in an infinite loop

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, i tried to align with what was there before, currently this works without causing a loop, and the added test from the previous PR also passes. Not sure if there is a better way of testing this properly

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to move this forward? so far it looks good and not causing any infinite loops


if (timeUntilUnthrottled > 0 && !this.throttleCheckTimeoutId) {
this.throttleCheckTimeoutId = setTimeout(() => {
this.throttleCheckTimeoutId = null
this.throttleCurrentTimestamp = Date.now()
this.checkPendingRequests()
}, scheduleAt)
}, 0)
}
}
}
1 change: 0 additions & 1 deletion src/network/requestQueue/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ describe('Network > RequestQueue', () => {
const before = Date.now()
const clientSideThrottleTime = 1
requestQueue.maybeThrottle(clientSideThrottleTime)
// Sleep until the marginal delay is passed before calling scheduleCheckPendingRequests()
await sleep(clientSideThrottleTime)
requestQueue.scheduleCheckPendingRequests()

Expand Down