Merge pull request #2333 from krejov100/fix/discord-gateway-backoff
fix(channels): exponential backoff for gateway listener restarts
This commit is contained in:
@@ -307,8 +307,14 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
|||||||
// Start local HTTP server to receive forwarded Gateway events (including interactions)
|
// Start local HTTP server to receive forwarded Gateway events (including interactions)
|
||||||
const webhookUrl = await startLocalWebhookServer(gatewayAdapter, setupConfig, config.botToken);
|
const webhookUrl = await startLocalWebhookServer(gatewayAdapter, setupConfig, config.botToken);
|
||||||
|
|
||||||
|
// Exponential backoff capped at 1h. Without this, an unrecoverable
|
||||||
|
// failure (e.g., TokenInvalid) restarts ~10×/sec and Discord's
|
||||||
|
// Cloudflare layer issues a multi-hour IP block. A run that lasts
|
||||||
|
// longer than 5 minutes counts as healthy and resets the counter.
|
||||||
|
let consecutiveFailures = 0;
|
||||||
const startGateway = () => {
|
const startGateway = () => {
|
||||||
if (gatewayAbort?.signal.aborted) return;
|
if (gatewayAbort?.signal.aborted) return;
|
||||||
|
const startedAt = Date.now();
|
||||||
// Capture the long-running listener promise via waitUntil
|
// Capture the long-running listener promise via waitUntil
|
||||||
let listenerPromise: Promise<unknown> | undefined;
|
let listenerPromise: Promise<unknown> | undefined;
|
||||||
gatewayAdapter.startGatewayListener!(
|
gatewayAdapter.startGatewayListener!(
|
||||||
@@ -323,21 +329,30 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
|||||||
).then(() => {
|
).then(() => {
|
||||||
// startGatewayListener resolves immediately with a Response;
|
// startGatewayListener resolves immediately with a Response;
|
||||||
// the actual work is in the listenerPromise passed to waitUntil
|
// the actual work is in the listenerPromise passed to waitUntil
|
||||||
if (listenerPromise) {
|
if (!listenerPromise) return;
|
||||||
listenerPromise
|
const reschedule = (err?: unknown) => {
|
||||||
.then(() => {
|
if (gatewayAbort?.signal.aborted) return;
|
||||||
if (!gatewayAbort?.signal.aborted) {
|
const ranForMs = Date.now() - startedAt;
|
||||||
log.info('Gateway listener expired, restarting', { adapter: adapter.name });
|
if (ranForMs > 5 * 60 * 1000) consecutiveFailures = 0;
|
||||||
startGateway();
|
else consecutiveFailures++;
|
||||||
}
|
const delayMs = Math.min(60 * 60 * 1000, 2 ** consecutiveFailures * 1000);
|
||||||
})
|
if (err) {
|
||||||
.catch((err) => {
|
log.error('Gateway listener error, retrying', {
|
||||||
if (!gatewayAbort?.signal.aborted) {
|
adapter: adapter.name,
|
||||||
log.error('Gateway listener error, restarting in 5s', { adapter: adapter.name, err });
|
err,
|
||||||
setTimeout(startGateway, 5000);
|
consecutiveFailures,
|
||||||
}
|
delayMs,
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
|
log.info('Gateway listener expired, restarting', {
|
||||||
|
adapter: adapter.name,
|
||||||
|
consecutiveFailures,
|
||||||
|
delayMs,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setTimeout(startGateway, delayMs);
|
||||||
|
};
|
||||||
|
listenerPromise.then(() => reschedule()).catch(reschedule);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
startGateway();
|
startGateway();
|
||||||
|
|||||||
Reference in New Issue
Block a user