mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-18 18:40:47 -04:00
fix discord bot config restart and startup connection retry
Container image / image (push) Failing after 1m1s
Container image / image (push) Failing after 1m1s
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// Owns connection replacement and recovery from failed initial logins.
|
||||
function createConnectionManager({ createRuntime, logger }) {
|
||||
let desired = { enabled: false, token: '' };
|
||||
let revision = 0;
|
||||
let runtime = null;
|
||||
let queue = Promise.resolve();
|
||||
let retryTimer = null;
|
||||
let retryDelay = 5000;
|
||||
|
||||
function enqueue(operation) {
|
||||
const pending = queue.then(operation);
|
||||
// Report failures without poisoning subsequent configuration changes.
|
||||
queue = pending.catch((err) => logger.error('Discord connection update failed', err.message));
|
||||
return pending;
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
const previous = runtime;
|
||||
runtime = null;
|
||||
if (!previous) return;
|
||||
previous.stop();
|
||||
// discord.js cannot cancel login while gateway discovery is pending.
|
||||
// Let that attempt settle before destroying it, otherwise it can open a
|
||||
// socket after destroy() has already run and leave an orphan connection.
|
||||
await previous.login;
|
||||
await previous.client.destroy();
|
||||
}
|
||||
|
||||
function scheduleRetry(version) {
|
||||
if (version !== revision || !desired.enabled) return;
|
||||
const delay = retryDelay;
|
||||
retryDelay = Math.min(retryDelay * 2, 60000);
|
||||
logger.warn('Retrying Discord login', { delayMs: delay });
|
||||
retryTimer = setTimeout(() => {
|
||||
retryTimer = null;
|
||||
enqueue(() => start(version));
|
||||
}, delay);
|
||||
retryTimer.unref?.();
|
||||
}
|
||||
|
||||
async function start(version) {
|
||||
if (version !== revision || !desired.enabled) return;
|
||||
await stop();
|
||||
if (version !== revision || !desired.enabled) return;
|
||||
const current = createRuntime();
|
||||
runtime = current;
|
||||
const { client } = current;
|
||||
client.on('error', (err) => logger.error('Discord client error', err.message));
|
||||
client.on('shardError', (err, shardId) => {
|
||||
logger.warn('Discord connection error', { shardId, error: err.message });
|
||||
});
|
||||
client.on('shardDisconnect', (event, shardId) => {
|
||||
logger.warn('Discord disconnected', { shardId, code: event.code });
|
||||
});
|
||||
client.on('clientReady', () => { retryDelay = 5000; });
|
||||
|
||||
// Login must not block configuration saves while the network is down.
|
||||
// Each attempt has its own client, so stale login completion/cleanup cannot
|
||||
// clear the credentials or stop the connection of its replacement.
|
||||
current.login = client.login(desired.token).catch((err) => {
|
||||
if (version !== revision || runtime !== current) return;
|
||||
logger.error('Discord login failed', err.message);
|
||||
enqueue(async () => {
|
||||
if (version !== revision || runtime !== current) return;
|
||||
await stop();
|
||||
// Credentials require an operator edit; retry network/service failures.
|
||||
if (err.code === 'TokenInvalid' || err.status === 401) return;
|
||||
scheduleRetry(version);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function configure({ enabled, token }) {
|
||||
const next = { enabled: Boolean(enabled), token };
|
||||
if (next.enabled === desired.enabled && next.token === desired.token) return queue;
|
||||
desired = next;
|
||||
const version = ++revision;
|
||||
clearTimeout(retryTimer);
|
||||
retryTimer = null;
|
||||
retryDelay = 5000;
|
||||
return enqueue(async () => {
|
||||
if (version !== revision) return;
|
||||
if (desired.enabled) await start(version);
|
||||
else await stop();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
configure,
|
||||
refresh() { runtime?.refresh(); },
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { createConnectionManager };
|
||||
Reference in New Issue
Block a user