diff --git a/gateway/gateway_manager.ts b/gateway/gateway_manager.ts index 7f38056bc..cd513b9a6 100644 --- a/gateway/gateway_manager.ts +++ b/gateway/gateway_manager.ts @@ -1,5 +1,6 @@ import { GatewayIntents, GatewayPayload, StatusUpdate } from "../types/mod.ts"; import { Collection } from "../util/collection.ts"; +import { safeRequestsPerShard } from "./safeRequestsPerShard.ts"; import { closeWS } from "./closeWs.ts"; import { createShard } from "./createShard.ts"; import { handleOnMessage } from "./handleOnMessage.ts"; @@ -18,7 +19,7 @@ import { resume } from "./resume.ts"; import { sendShardMessage } from "./sendShardMessage.ts"; import { prepareBuckets, spawnShards } from "./spawnShards.ts"; import { tellWorkerToIdentify } from "./tellWorkerToIdentify.ts"; -import { DiscordenoShard } from "./ws.ts"; +import { DiscordenoShard } from "./shard.ts"; /** Create a new Gateway Manager. * @@ -30,6 +31,8 @@ export function createGatewayManager( options: Partial & Pick, ): GatewayManager { return { + queueResetInterval: 60000, + maxRequestsPerInterval: 120, cache: { guildIds: new Set(), loadingGuildIds: new Set(), @@ -87,6 +90,7 @@ export function createGatewayManager( closeWS: options.closeWS ?? closeWS, sendShardMessage: options.sendShardMessage ?? sendShardMessage, resume: options.resume ?? resume, + safeRequestsPerShard: options.safeRequestsPerShard ?? safeRequestsPerShard, handleDiscordPayload: options.handleDiscordPayload, }; } @@ -155,6 +159,10 @@ export interface GatewayManager { } >; utf8decoder: TextDecoder; + /** The amount of milliseconds the gateway rate limit will reset in. By default 60000 or 1 minute. */ + queueResetInterval: number; + /** The maximum amount of requests that the gateway can make before being rate limited. By default 120. */ + maxRequestsPerInterval: number; cache: { guildIds: Set; @@ -205,4 +213,6 @@ export interface GatewayManager { sendShardMessage: typeof sendShardMessage; /** Properly resume an old shards session. */ resume: typeof resume; + /** Calculates the number of requests in a shard that are safe to be used. */ + safeRequestsPerShard: typeof safeRequestsPerShard; } diff --git a/gateway/handleOnMessage.ts b/gateway/handleOnMessage.ts index f0ab40905..08930adc2 100644 --- a/gateway/handleOnMessage.ts +++ b/gateway/handleOnMessage.ts @@ -47,6 +47,8 @@ export async function handleOnMessage(gateway: GatewayManager, message: any, sha break; case GatewayOpcodes.Hello: gateway.heartbeat(gateway, shardId, (messageData.d as DiscordHello).heartbeat_interval); + // UPDATES THE SAFE AMOUNT OF SHARDS BASED ON THE INTERVAL + if (shard) shard.safeRequestsPerShard = gateway.safeRequestsPerShard(gateway, shard); break; case GatewayOpcodes.HeartbeatACK: if (gateway.shards.has(shardId)) { diff --git a/gateway/identify.ts b/gateway/identify.ts index 2807a0cbe..58ac03a16 100644 --- a/gateway/identify.ts +++ b/gateway/identify.ts @@ -36,6 +36,8 @@ export function identify(gateway: GatewayManager, shardId: number, maxShards: nu processingQueue: false, queueStartedAt: Date.now(), queueCounter: 0, + // BY DEFAULT SET TO 120. EDIT IN HELLO + safeRequestsPerShard: 120, }); socket.onopen = () => { diff --git a/gateway/mod.ts b/gateway/mod.ts index d5b3a6e7d..f48e31e32 100644 --- a/gateway/mod.ts +++ b/gateway/mod.ts @@ -10,5 +10,6 @@ export * from "./spawnShards.ts"; export * from "./sendShardMessage.ts"; export * from "./startGatewayOptions.ts"; export * from "./tellWorkerToIdentify.ts"; -export * from "./ws.ts"; +export * from "./shard.ts"; export * from "./gateway_manager.ts"; +export * from "./safeRequestsPerShard.ts"; diff --git a/gateway/processGatewayQueue.ts b/gateway/processGatewayQueue.ts index 9ada3bbb2..c565bde90 100644 --- a/gateway/processGatewayQueue.ts +++ b/gateway/processGatewayQueue.ts @@ -15,7 +15,7 @@ export async function processGatewayQueue(gateway: GatewayManager, id: number) { } const now = Date.now(); - if (now - shard.queueStartedAt >= 60000) { + if (now - shard.queueStartedAt >= gateway.queueResetInterval) { shard.queueStartedAt = now; shard.queueCounter = 0; } @@ -28,16 +28,20 @@ export async function processGatewayQueue(gateway: GatewayManager, id: number) { shard.ws.send(JSON.stringify(request)); - // Counter is useful for preventing 120/m requests. + // Counter is useful for preventing max requests. shard.queueCounter++; // Handle if the requests have been maxed - if (shard.queueCounter >= 116) { - gateway.debug("GW MAX_REQUESTS", { - message: "Max gateway requests per minute reached setting timeout for one minute", - shardId: shard.id, - }); - await delay(60000); + if (shard.queueCounter >= shard.safeRequestsPerShard) { + const remaining = shard.queueStartedAt + gateway.queueResetInterval - Date.now(); + if (remaining > 0) { + gateway.debug("GW MAX REQUESTS", { + message: `Max gateway requests per minute reached setting timeout for ${remaining}ms`, + shardId: shard.id, + }); + await delay(remaining); + } + shard.queueCounter = 0; continue; } diff --git a/gateway/resume.ts b/gateway/resume.ts index aea08b1a7..72fedd2d6 100644 --- a/gateway/resume.ts +++ b/gateway/resume.ts @@ -43,6 +43,7 @@ export function resume(gateway: GatewayManager, shardId: number) { processingQueue: false, queueStartedAt: Date.now(), queueCounter: 0, + safeRequestsPerShard: oldShard.safeRequestsPerShard || 120, }); // Resume on open diff --git a/gateway/safeRequestsPerShard.ts b/gateway/safeRequestsPerShard.ts new file mode 100644 index 000000000..15e25e660 --- /dev/null +++ b/gateway/safeRequestsPerShard.ts @@ -0,0 +1,9 @@ +import { GatewayManager } from "./gateway_manager.ts"; +import { DiscordenoShard } from "./shard.ts"; + +export function safeRequestsPerShard(gateway: GatewayManager, shard: DiscordenoShard) { + // * 2 adds extra safety layer for discords OP 1 requests that we need to respond to + const safeRequests = gateway.maxRequestsPerInterval - + Math.ceil(gateway.queueResetInterval / shard.heartbeat.interval) * 2; + return safeRequests > 0 ? safeRequests : 0; +} diff --git a/gateway/sendShardMessage.ts b/gateway/sendShardMessage.ts index 39fa741b7..fd6617ddb 100644 --- a/gateway/sendShardMessage.ts +++ b/gateway/sendShardMessage.ts @@ -1,5 +1,5 @@ import { GatewayManager } from "./gateway_manager.ts"; -import { DiscordenoShard, WebSocketRequest } from "./ws.ts"; +import { DiscordenoShard, WebSocketRequest } from "./shard.ts"; export function sendShardMessage( gateway: GatewayManager, diff --git a/gateway/ws.ts b/gateway/shard.ts similarity index 92% rename from gateway/ws.ts rename to gateway/shard.ts index ac9b4edce..20480293d 100644 --- a/gateway/ws.ts +++ b/gateway/shard.ts @@ -40,13 +40,11 @@ export interface DiscordenoShard { queueStartedAt: number; /** The request counter of the queue. */ queueCounter: number; + /** The safe number of requests that can be made while preserving some for required things like heartbeating. */ + safeRequestsPerShard: number; } export interface WebSocketRequest { op: GatewayOpcodes; d: unknown; - // guildId: bigint; - // shardId: number; - // nonce?: bigint; - // options?: Record; }