mirror of
https://github.com/discordeno/discordeno.git
synced 2026-09-17 08:47:22 +00:00
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:
@@ -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. */
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user