feat: implement graceful shutdown process for bot

- Added graceful shutdown functionality to handle SIGINT, SIGTERM, uncaught exceptions, and unhandled promise rejections.
- Integrated stopRecording and pause functionality during shutdown.
- Enhanced logging for shutdown steps and error handling.
- Updated package.json to include pino-pretty for improved logging output.
This commit is contained in:
MythEclipse
2026-05-13 16:32:14 +07:00
parent 3ae28157a3
commit 673a06376c
4 changed files with 69 additions and 1545 deletions

BIN
bun.lockb

Binary file not shown.

1532
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,6 +32,7 @@
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/fluent-ffmpeg": "^2.1.28", "@types/fluent-ffmpeg": "^2.1.28",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"pino-pretty": "^10.3.1",
"vitest": "latest" "vitest": "latest"
} }
} }

View File

@@ -5,10 +5,9 @@ import { getVoiceConnection } from "@discordjs/voice";
import { Client } from "discord.js-selfbot-v13"; import { Client } from "discord.js-selfbot-v13";
import { config } from "./config"; import { config } from "./config";
import { discordPlayer } from "./player"; import { discordPlayer } from "./player";
import { startRecording } from "./recorder"; import { startRecording, stopRecording } from "./recorder";
import { startWebserver } from "./webserver"; import { startWebserver } from "./webserver";
import { createChildLogger } from "./logger"; import { createChildLogger } from "./logger";
import { retryWithBackoff } from "./retry";
const logger = createChildLogger("bot"); const logger = createChildLogger("bot");
@@ -24,6 +23,58 @@ if (!guildId) throw new Error("Missing GUILD_ID in .env");
// Inisialisasi selfbot client // Inisialisasi selfbot client
const client = new Client(); const client = new Client();
// Track shutdown state
let isShuttingDown = false;
async function gracefulShutdown(signal: string) {
if (isShuttingDown) {
logger.warn(`Already shutting down, ignoring ${signal}`);
return;
}
isShuttingDown = true;
logger.info({ signal }, "Graceful shutdown initiated");
try {
// Step 1: Stop recording
if (guildId) {
logger.info("Stopping recording...");
stopRecording(guildId);
}
// Step 2: Pause player
logger.info("Pausing player...");
discordPlayer.pause();
// Step 3: Destroy voice connection
if (guildId) {
const connection = getVoiceConnection(guildId);
if (connection) {
logger.info("Destroying voice connection...");
try {
connection.destroy();
} catch (err) {
logger.warn({ error: err }, "Error destroying voice connection");
}
}
}
// Step 4: Destroy client
logger.info("Destroying Discord client...");
try {
client.destroy();
} catch (err) {
logger.warn({ error: err }, "Error destroying client");
}
logger.info("Graceful shutdown completed");
process.exit(0);
} catch (err) {
logger.error({ error: err }, "Error during graceful shutdown");
process.exit(1);
}
}
client.on("ready", async () => { client.on("ready", async () => {
if (config.verbose) { if (config.verbose) {
logger.info({ user: client.user?.tag }, "Bot logged in"); logger.info({ user: client.user?.tag }, "Bot logged in");
@@ -70,21 +121,25 @@ client.on("error", (err) => {
logger.error({ error: err }, "Client error"); logger.error({ error: err }, "Client error");
}); });
// Graceful shutdown // Graceful shutdown handlers
process.on("SIGINT", () => { process.on("SIGINT", () => {
if (config.verbose) { gracefulShutdown("SIGINT");
logger.info("Shutting down gracefully...");
}
client.destroy();
process.exit(0);
}); });
process.on("SIGTERM", () => { process.on("SIGTERM", () => {
if (config.verbose) { gracefulShutdown("SIGTERM");
logger.info("Terminating..."); });
}
client.destroy(); // Handle uncaught exceptions
process.exit(0); process.on("uncaughtException", (err) => {
logger.error({ error: err }, "Uncaught exception");
gracefulShutdown("uncaughtException");
});
// Handle unhandled promise rejections
process.on("unhandledRejection", (reason, promise) => {
logger.error({ reason, promise }, "Unhandled rejection");
gracefulShutdown("unhandledRejection");
}); });
client.login(token); client.login(token);