mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-02 00:40:07 +00:00
Merge pull request #936 from discordeno/some-new-way-of-handling-ready
change: check shard unavailability using last received guild create event
This commit is contained in:
@@ -31,6 +31,9 @@ export async function startBot(config: BotConfig) {
|
||||
// Initial API connection to get info about bots connection
|
||||
ws.botGatewayData = await getGatewayBot();
|
||||
ws.maxShards = ws.maxShards || ws.botGatewayData.shards;
|
||||
ws.lastShardId = ws.lastShardId === 1
|
||||
? ws.botGatewayData.shards - 1
|
||||
: ws.lastShardId;
|
||||
|
||||
// Explicitly append gateway version and encoding
|
||||
ws.botGatewayData.url += `?v=${GATEWAY_VERSION}&encoding=json`;
|
||||
@@ -94,7 +97,7 @@ export async function startBigBrainBot(options: BigBrainBotConfig) {
|
||||
|
||||
// Initial API connection to get info about bots connection
|
||||
ws.botGatewayData = await getGatewayBot();
|
||||
ws.maxShards = ws.maxShards ||
|
||||
ws.maxShards = options.lastShardId || ws.maxShards ||
|
||||
ws.botGatewayData.shards;
|
||||
ws.lastShardId = options.lastShardId || ws.botGatewayData.shards;
|
||||
// Explicitly append gateway version and encoding
|
||||
|
||||
@@ -24,8 +24,8 @@ export async function handleGuildCreate(
|
||||
|
||||
if (shard?.unavailableGuildIds.has(guild.id)) {
|
||||
await cacheHandlers.delete("unavailableGuilds", guild.id);
|
||||
|
||||
shard.unavailableGuildIds.delete(guild.id);
|
||||
shard.lastAvailable = Date.now();
|
||||
|
||||
return eventHandlers.guildAvailable?.(guild);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ export async function handleGuildDelete(
|
||||
if (payload.unavailable) {
|
||||
const shard = ws.shards.get(shardId);
|
||||
if (shard) shard.unavailableGuildIds.add(guild.id);
|
||||
|
||||
await cacheHandlers.set("unavailableGuilds", guild.id, Date.now());
|
||||
|
||||
eventHandlers.guildUnavailable?.(guild);
|
||||
|
||||
@@ -3,33 +3,33 @@ import { cache } from "../../cache.ts";
|
||||
import type { DiscordGatewayPayload } from "../../types/gateway/gateway_payload.ts";
|
||||
import type { Ready } from "../../types/gateway/ready.ts";
|
||||
import { snowflakeToBigint } from "../../util/bigint.ts";
|
||||
import { ws } from "../../ws/ws.ts";
|
||||
import { DiscordenoShard, ws } from "../../ws/ws.ts";
|
||||
|
||||
export function handleReady(
|
||||
data: DiscordGatewayPayload,
|
||||
shardId: number,
|
||||
) {
|
||||
// Triggered on each shard
|
||||
eventHandlers.shardReady?.(shardId);
|
||||
|
||||
// The bot has already started, the last shard is resumed, however.
|
||||
if (cache.isReady) return;
|
||||
|
||||
const shard = ws.shards.get(shardId);
|
||||
if (!shard) return;
|
||||
|
||||
const payload = data.d as Ready;
|
||||
setBotId(payload.user.id);
|
||||
setApplicationId(payload.application.id);
|
||||
|
||||
// Triggered on each shard
|
||||
eventHandlers.shardReady?.(shardId);
|
||||
// Save when the READY event was received to prevent infinite load loops
|
||||
const now = Date.now();
|
||||
|
||||
const shard = ws.shards.get(shardId);
|
||||
if (!shard) return;
|
||||
|
||||
// Set ready to false just to go sure
|
||||
shard.ready = false;
|
||||
// All guilds are unavailable at first
|
||||
shard.unavailableGuildIds = new Set(
|
||||
payload.guilds.map((g) => snowflakeToBigint(g.id)),
|
||||
);
|
||||
// Set the last available to now
|
||||
shard.lastAvailable = Date.now();
|
||||
|
||||
// Start ready check in 2 seconds
|
||||
setTimeout(() => {
|
||||
@@ -37,58 +37,51 @@ export function handleReady(
|
||||
"loop",
|
||||
`1. Running setTimeout in READY file.`,
|
||||
);
|
||||
checkReady(payload, shardId, now);
|
||||
checkReady(payload, shard);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Don't pass the shard itself because unavailableGuilds won't be updated by the GUILD_CREATE event
|
||||
/** This function checks if the shard is fully loaded */
|
||||
function checkReady(payload: Ready, shardId: number, now: number) {
|
||||
const shard = ws.shards.get(shardId);
|
||||
if (!shard) return;
|
||||
|
||||
function checkReady(payload: Ready, shard: DiscordenoShard) {
|
||||
// Check if all guilds were loaded
|
||||
if (shard.unavailableGuildIds.size) {
|
||||
if (Date.now() - now > 10000) {
|
||||
eventHandlers.shardFailedToLoad?.(shardId, shard.unavailableGuildIds);
|
||||
// Force execute the loaded function to prevent infinite loop
|
||||
loaded(shardId);
|
||||
} else {
|
||||
// Not all guilds were loaded but 10 seconds haven't passed so check again
|
||||
setTimeout(() => {
|
||||
eventHandlers.debug?.(
|
||||
"loop",
|
||||
`2. Running setTimeout in READY file.`,
|
||||
);
|
||||
checkReady(payload, shardId, now);
|
||||
}, 2000);
|
||||
}
|
||||
} else {
|
||||
// All guilds were loaded
|
||||
loaded(shardId);
|
||||
if (!shard.unavailableGuildIds.size) return loaded(shard);
|
||||
|
||||
// If the last GUILD_CREATE has been received before 5 seconds if so most likely the remaining guilds are unavailable
|
||||
if (shard.lastAvailable + 5000 < Date.now()) {
|
||||
eventHandlers.shardFailedToLoad?.(shard.id, shard.unavailableGuildIds);
|
||||
// Force execute the loaded function to prevent infinite loop
|
||||
return loaded(shard);
|
||||
}
|
||||
|
||||
// Not all guilds were loaded but 5 seconds haven't passed so check again
|
||||
setTimeout(() => {
|
||||
eventHandlers.debug?.(
|
||||
"loop",
|
||||
`2. Running setTimeout in READY file.`,
|
||||
);
|
||||
checkReady(payload, shard);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function loaded(shardId: number) {
|
||||
const shard = ws.shards.get(shardId);
|
||||
if (!shard) return;
|
||||
|
||||
function loaded(shard: DiscordenoShard) {
|
||||
shard.ready = true;
|
||||
|
||||
// If it is the last shard we can go full ready
|
||||
if (shardId === ws.lastShardId) {
|
||||
// Still some shards are loading so wait another 2 seconds for them
|
||||
if (ws.shards.some((shard) => !shard.ready)) {
|
||||
setTimeout(() => {
|
||||
eventHandlers.debug?.(
|
||||
"loop",
|
||||
`3. Running setTimeout in READY file.`,
|
||||
);
|
||||
loaded(shardId);
|
||||
}, 2000);
|
||||
} else {
|
||||
cache.isReady = true;
|
||||
eventHandlers.ready?.();
|
||||
}
|
||||
// If it is not the last shard we can't go full ready
|
||||
if (shard.id !== ws.lastShardId) return;
|
||||
|
||||
// Still some shards are loading so wait another 2 seconds for them
|
||||
if (ws.shards.some((shard) => !shard.ready)) {
|
||||
setTimeout(() => {
|
||||
eventHandlers.debug?.(
|
||||
"loop",
|
||||
`3. Running setTimeout in READY file.`,
|
||||
);
|
||||
loaded(shard);
|
||||
}, 2000);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
cache.isReady = true;
|
||||
eventHandlers.ready?.();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { DiscordenoMember } from "../../structures/member.ts";
|
||||
import { DiscordenoMessage } from "../../structures/message.ts";
|
||||
import { DiscordenoRole } from "../../structures/role.ts";
|
||||
import { Collection } from "../../util/collection.ts";
|
||||
import { DiscordenoShard } from "../../ws/ws.ts";
|
||||
import { ThreadMember } from "../channels/threads/thread_member.ts";
|
||||
import { ThreadMembersUpdate } from "../channels/threads/thread_members_update.ts";
|
||||
import { IntegrationCreateUpdate } from "../integrations/integration_create_update.ts";
|
||||
|
||||
@@ -24,6 +24,7 @@ export async function identify(shardId: number, maxShards: number) {
|
||||
resuming: false,
|
||||
ready: false,
|
||||
unavailableGuildIds: new Set(),
|
||||
lastAvailable: 0,
|
||||
heartbeat: {
|
||||
lastSentAt: 0,
|
||||
lastReceivedAt: 0,
|
||||
|
||||
@@ -30,6 +30,7 @@ export async function resume(shardId: number) {
|
||||
resuming: false,
|
||||
ready: false,
|
||||
unavailableGuildIds: new Set(),
|
||||
lastAvailable: 0,
|
||||
heartbeat: {
|
||||
lastSentAt: 0,
|
||||
lastReceivedAt: 0,
|
||||
|
||||
22
src/ws/ws.ts
22
src/ws/ws.ts
@@ -17,7 +17,7 @@ import { tellClusterToIdentify } from "./tell_cluster_to_identify.ts";
|
||||
|
||||
// CONTROLLER LIKE INTERFACE FOR WS HANDLING
|
||||
export const ws = {
|
||||
/** The secret key authorization header the bot will expect when sending payloads */
|
||||
/** The secret key authorization header the bot will expect when sending payloads. */
|
||||
secretKey: "",
|
||||
/** The url that all discord payloads for the dispatch type should be sent to. */
|
||||
url: "",
|
||||
@@ -29,7 +29,7 @@ export const ws = {
|
||||
maxShards: 0,
|
||||
/** Whether or not the resharder should automatically switch to LARGE BOT SHARDING when you are above 100K servers. */
|
||||
useOptimalLargeBotSharding: true,
|
||||
/** The amount of shards to load per cluster */
|
||||
/** The amount of shards to load per cluster. */
|
||||
shardsPerCluster: 25,
|
||||
/** The maximum amount of clusters to use for your bot. */
|
||||
maxClusters: 4,
|
||||
@@ -96,7 +96,7 @@ export const ws = {
|
||||
createShard,
|
||||
/** Begins identification of the shard to discord. */
|
||||
identify,
|
||||
/** Begins heartbeating of the shard to keep it alive */
|
||||
/** Begins heartbeating of the shard to keep it alive. */
|
||||
heartbeat,
|
||||
/** Sends the discord payload to another server. */
|
||||
handleDiscordPayload,
|
||||
@@ -114,16 +114,16 @@ export const ws = {
|
||||
processQueue,
|
||||
/** Closes shard WebSocket connection properly. */
|
||||
closeWS,
|
||||
/** Properly adds a message to the shards queue */
|
||||
/** Properly adds a message to the shards queue. */
|
||||
sendShardMessage,
|
||||
};
|
||||
|
||||
export interface DiscordenoShard {
|
||||
/** The shard id number */
|
||||
/** The shard id number. */
|
||||
id: number;
|
||||
/** The websocket for this shard */
|
||||
/** The websocket for this shard. */
|
||||
ws: WebSocket;
|
||||
/** The amount of milliseconds to wait between heartbeats */
|
||||
/** The amount of milliseconds to wait between heartbeats. */
|
||||
resumeInterval: number;
|
||||
/** The session id important for resuming connections. */
|
||||
sessionId: string;
|
||||
@@ -131,12 +131,14 @@ export interface DiscordenoShard {
|
||||
previousSequenceNumber: number | null;
|
||||
/** Whether the shard is currently resuming. */
|
||||
resuming: boolean;
|
||||
/** Whether the shard has received the ready event */
|
||||
/** Whether the shard has received the ready event. */
|
||||
ready: boolean;
|
||||
/** The list of guild ids that are currently unavailable due to an outage. */
|
||||
unavailableGuildIds: Set<bigint>;
|
||||
/** Last time when a GUILD_CREATE event has been received for an unavailable guild. This is used to prevent infinite loops in the READY event handler. */
|
||||
lastAvailable: number;
|
||||
heartbeat: {
|
||||
/** The exact timestamp the last heartbeat was sent */
|
||||
/** The exact timestamp the last heartbeat was sent. */
|
||||
lastSentAt: number;
|
||||
/** The timestamp the last heartbeat ACK was received from discord. */
|
||||
lastReceivedAt: number;
|
||||
@@ -155,7 +157,7 @@ export interface DiscordenoShard {
|
||||
processingQueue: boolean;
|
||||
/** When the first request for this minute has been sent. */
|
||||
queueStartedAt: number;
|
||||
/** The request counter of the queue */
|
||||
/** The request counter of the queue. */
|
||||
queueCounter: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user