refactor(rest)!: make invalid bucket lazy (#2967)

* refactor(rest): make invalid bucket lazy
Instead of using timeouts we should aim to use a lazy bucket system.

* that might be better

* fix tests
This commit is contained in:
ITOH
2023-04-04 03:34:34 +00:00
committed by GitHub
parent e3549a8675
commit 8c9c3d53bf
10 changed files with 29 additions and 37 deletions
+29 -28
View File
@@ -9,19 +9,24 @@ import { delay, logger } from '@discordeno/utils'
*/
export function createInvalidRequestBucket(options: InvalidRequestBucketOptions): InvalidRequestBucket {
const bucket: InvalidRequestBucket = {
current: options.current ?? 0,
invalidRequests: options.current ?? 0,
max: options.max ?? 10000,
interval: options.interval ?? 600000,
timeoutId: options.timeoutId,
interval: options.interval ?? 600_000, // 10 minutes
resetAt: options.resetAt,
safety: options.safety ?? 1,
errorStatuses: options.errorStatuses ?? [401, 403, 429],
requested: options.requested ?? 0,
activeRequests: options.requested ?? 0,
processing: false,
waiting: [],
requestsAllowed: function () {
return bucket.max - bucket.current - bucket.requested - bucket.safety
if (bucket.resetAt !== undefined && Date.now() > bucket.resetAt) {
bucket.invalidRequests = 0
bucket.resetAt = Date.now() + bucket.interval
}
return bucket.max - bucket.invalidRequests - bucket.activeRequests - bucket.safety
},
isRequestAllowed: function () {
@@ -33,7 +38,7 @@ export function createInvalidRequestBucket(options: InvalidRequestBucketOptions)
return await new Promise(async (resolve) => {
// If whatever amount of requests is left is more than the safety margin, allow the request
if (bucket.isRequestAllowed()) {
bucket.requested++
bucket.activeRequests += 1
resolve()
} else {
bucket.waiting.push(resolve)
@@ -53,13 +58,14 @@ export function createInvalidRequestBucket(options: InvalidRequestBucketOptions)
while (bucket.waiting.length > 0) {
logger.info(`[InvalidBucket] processing waiting queue while loop ran with ${bucket.waiting.length} remaining.`)
if (bucket.isRequestAllowed()) {
bucket.requested++
// Resolve the next item in the queue
bucket.waiting.shift()?.()
} else {
await delay(1000)
if (bucket.resetAt !== undefined && !bucket.isRequestAllowed()) {
await delay(bucket.resetAt - Date.now())
}
bucket.activeRequests += 1
// Resolve the next item in the queue
bucket.waiting.shift()?.()
}
// Mark as false so next pending request can be triggered by new loop.
@@ -68,23 +74,18 @@ export function createInvalidRequestBucket(options: InvalidRequestBucketOptions)
handleCompletedRequest: function (code, sharedScope) {
// Since request is complete, we can remove one from requested.
bucket.requested--
bucket.activeRequests -= 1
// Since it is as a valid request, we don't need to do anything
if (!bucket.errorStatuses.includes(code)) return
// Shared scope is not considered invalid
if (code === 429 && sharedScope) return
// INVALID REQUEST WAS MADE
// Mark a request has been invalid
bucket.current++
// If a timeout was not started, start a timeout to reset this bucket
if (bucket.timeoutId === undefined) {
bucket.timeoutId = setTimeout(() => {
bucket.current = 0
bucket.timeoutId = undefined
}, bucket.interval)
if (bucket.resetAt === undefined) {
bucket.resetAt = Date.now() + bucket.interval
}
bucket.invalidRequests += 1
},
}
@@ -98,8 +99,8 @@ export interface InvalidRequestBucketOptions {
max?: number
/** The time that discord allows to make the max number of invalid requests. Defaults to 10 minutes */
interval?: number
/** timer to reset to 0 */
timeoutId?: NodeJS.Timeout
/** When the timeout for the bucket has started at. */
resetAt?: number
/** how safe to be from max. Defaults to 1 */
safety?: number
/** The request statuses that count as an invalid request. */
@@ -110,19 +111,19 @@ export interface InvalidRequestBucketOptions {
export interface InvalidRequestBucket {
/** current invalid amount */
current: number
invalidRequests: number
/** max invalid requests allowed until ban. Defaults to 10,000 */
max: number
/** The time that discord allows to make the max number of invalid requests. Defaults to 10 minutes */
interval: number
/** timer to reset to 0 */
timeoutId: NodeJS.Timeout | undefined
/** When the timeout for this bucket has started at. */
resetAt: number | undefined
/** how safe to be from max. Defaults to 1 */
safety: number
/** The request statuses that count as an invalid request. */
errorStatuses: number[]
/** The amount of requests that were requested from this bucket. */
requested: number
activeRequests: number
/** The requests that are currently pending. */
waiting: Array<(value: void | PromiseLike<void>) => void>
/** Whether or not the waiting queue is already processing. */
-1
View File
@@ -12,7 +12,6 @@ before(async () => {
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id && !e2ecache.deletedGuild) {
e2ecache.deletedGuild = true
await rest.deleteGuild(e2ecache.guild.id)
-1
View File
@@ -16,7 +16,6 @@ before(async () => {
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id && !e2ecache.deletedGuild) {
e2ecache.deletedGuild = true
await rest.deleteGuild(e2ecache.guild.id)
-1
View File
@@ -14,7 +14,6 @@ before(async () => {
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id && !e2ecache.deletedGuild) {
e2ecache.deletedGuild = true
await rest.deleteGuild(e2ecache.guild.id)
-1
View File
@@ -13,7 +13,6 @@ before(async () => {
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id && !e2ecache.deletedGuild) {
e2ecache.deletedGuild = true
await rest.deleteGuild(e2ecache.guild.id)
-1
View File
@@ -12,7 +12,6 @@ before(async () => {
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id && !e2ecache.deletedGuild) {
e2ecache.deletedGuild = true
await rest.deleteGuild(e2ecache.guild.id)
-1
View File
@@ -12,7 +12,6 @@ before(async () => {
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id && !e2ecache.deletedGuild) {
await rest.deleteGuild(e2ecache.guild.id)
}
-1
View File
@@ -14,7 +14,6 @@ before(async () => {
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id && !e2ecache.deletedGuild) {
e2ecache.deletedGuild = true
await rest.deleteGuild(e2ecache.guild.id)
-1
View File
@@ -13,7 +13,6 @@ before(async () => {
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id && !e2ecache.deletedGuild) {
await rest.deleteGuild(e2ecache.guild.id)
}
-1
View File
@@ -13,7 +13,6 @@ before(async () => {
})
after(async () => {
if (rest.invalidBucket.timeoutId) clearTimeout(rest.invalidBucket.timeoutId)
if (e2ecache.guild.id && !e2ecache.deletedGuild) {
e2ecache.deletedGuild = true
await rest.deleteGuild(e2ecache.guild.id)