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)
|
||||
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 = () => {
|
||||
if (gatewayAbort?.signal.aborted) return;
|
||||
const startedAt = Date.now();
|
||||
// Capture the long-running listener promise via waitUntil
|
||||
let listenerPromise: Promise<unknown> | undefined;
|
||||
gatewayAdapter.startGatewayListener!(
|
||||
@@ -323,21 +329,30 @@ export function createChatSdkBridge(config: ChatSdkBridgeConfig): ChannelAdapter
|
||||
).then(() => {
|
||||
// startGatewayListener resolves immediately with a Response;
|
||||
// the actual work is in the listenerPromise passed to waitUntil
|
||||
if (listenerPromise) {
|
||||
listenerPromise
|
||||
.then(() => {
|
||||
if (!gatewayAbort?.signal.aborted) {
|
||||
log.info('Gateway listener expired, restarting', { adapter: adapter.name });
|
||||
startGateway();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!gatewayAbort?.signal.aborted) {
|
||||
log.error('Gateway listener error, restarting in 5s', { adapter: adapter.name, err });
|
||||
setTimeout(startGateway, 5000);
|
||||
}
|
||||
if (!listenerPromise) return;
|
||||
const reschedule = (err?: unknown) => {
|
||||
if (gatewayAbort?.signal.aborted) return;
|
||||
const ranForMs = Date.now() - startedAt;
|
||||
if (ranForMs > 5 * 60 * 1000) consecutiveFailures = 0;
|
||||
else consecutiveFailures++;
|
||||
const delayMs = Math.min(60 * 60 * 1000, 2 ** consecutiveFailures * 1000);
|
||||
if (err) {
|
||||
log.error('Gateway listener error, retrying', {
|
||||
adapter: adapter.name,
|
||||
err,
|
||||
consecutiveFailures,
|
||||
delayMs,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
log.info('Gateway listener expired, restarting', {
|
||||
adapter: adapter.name,
|
||||
consecutiveFailures,
|
||||
delayMs,
|
||||
});
|
||||
}
|
||||
setTimeout(startGateway, delayMs);
|
||||
};
|
||||
listenerPromise.then(() => reschedule()).catch(reschedule);
|
||||
});
|
||||
};
|
||||
startGateway();
|
||||
|
||||
Reference in New Issue
Block a user