From bef94e583dfc26fa01d7cd3e8e85bf24b0b01fe2 Mon Sep 17 00:00:00 2001 From: deptyped Date: Fri, 17 May 2024 00:46:14 +0300 Subject: [PATCH] Improve graceful shutdown --- package-lock.json | 31 +++++------- package.json | 5 +- src/main.ts | 121 ++++++++++++++++++++++++++++++---------------- 3 files changed, 92 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5f9287bd..65561b8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,10 +20,9 @@ "grammy-guard": "0.5.0", "hono": "4.3.7", "iso-639-1": "3.1.2", - "node-graceful-shutdown": "1.1.5", "pino": "8.20.0", "pino-pretty": "10.3.1", - "tsx": "4.10.2", + "tsx": "4.10.3", "znv": "0.4.0", "zod": "3.23.8" }, @@ -1372,9 +1371,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001618", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001618.tgz", - "integrity": "sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==", + "version": "1.0.30001620", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", + "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==", "dev": true, "funding": [ { @@ -1712,9 +1711,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.771", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.771.tgz", - "integrity": "sha512-b/CmBh1c5SXZy5oFu4a0o+2TdM0AYStiwoQebEoImGAINstCsS/s/MOvPKMoxu1nA2BJtEOJI1nC/VoVRzdXWA==", + "version": "1.4.773", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.773.tgz", + "integrity": "sha512-87eHF+h3PlCRwbxVEAw9KtK3v7lWfc/sUDr0W76955AdYTG4bV/k0zrl585Qnj/skRMH2qOSiE+kqMeOQ+LOpw==", "dev": true }, "node_modules/emoji-regex": { @@ -3871,14 +3870,6 @@ } } }, - "node_modules/node-graceful-shutdown": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/node-graceful-shutdown/-/node-graceful-shutdown-1.1.5.tgz", - "integrity": "sha512-tlz8XpPr+pqrEGWFNLtMwd0HdFsCAKp5NCmMvwcTZTA0hyrVd7gkKbivDcSM5uYB1331/cEjNTtmVtqKy8OSBw==", - "engines": { - "node": ">=6" - } - }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -5313,12 +5304,12 @@ "dev": true }, "node_modules/tsx": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.10.2.tgz", - "integrity": "sha512-gOfACgv1ElsIjvt7Fp0rMJKGnMGjox0JfGOfX3kmZCV/yZumaNqtHGKBXt1KgaYS9KjDOmqGeI8gHk/W7kWVZg==", + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.10.3.tgz", + "integrity": "sha512-f0g60aFSVRVkzcQkEflh8fPLRfmt+HJHgWi/plG5UgvVaV+9TcpOwJ0sZJSACXmwmjMPg9yQR0BhTLbhkfV2uA==", "dependencies": { "esbuild": "~0.20.2", - "get-tsconfig": "^4.7.3" + "get-tsconfig": "^4.7.5" }, "bin": { "tsx": "dist/cli.mjs" diff --git a/package.json b/package.json index b30721ac..2a7b5b16 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,9 @@ "grammy-guard": "0.5.0", "hono": "4.3.7", "iso-639-1": "3.1.2", - "node-graceful-shutdown": "1.1.5", "pino": "8.20.0", "pino-pretty": "10.3.1", - "tsx": "4.10.2", + "tsx": "4.10.3", "znv": "0.4.0", "zod": "3.23.8" }, @@ -61,6 +60,6 @@ "npm": ">=8.0.0" }, "lint-staged": { - "*.ts": "npm run lint" + "*.ts": "eslint" } } diff --git a/src/main.ts b/src/main.ts index e263eef8..147073d9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,65 +1,102 @@ #!/usr/bin/env tsx import { serve } from "@hono/node-server"; -import { onShutdown } from "node-graceful-shutdown"; import { createBot } from "#root/bot/index.js"; import { config } from "#root/config.js"; import { logger } from "#root/logger.js"; import { createServer } from "#root/server/index.js"; +import { AddressInfo } from "node:net"; -try { +function onShutdown(cleanUp: () => Promise) { + let isShuttingDown = false; + const handleShutdown = async () => { + if (isShuttingDown) return; + isShuttingDown = true; + logger.info("Shutdown"); + await cleanUp(); + }; + process.on("SIGINT", handleShutdown); + process.on("SIGTERM", handleShutdown); +} + +async function startPolling() { const bot = createBot(config.BOT_TOKEN); - const server = await createServer(bot); // graceful shutdown onShutdown(async () => { - logger.info("Shutdown"); - await bot.stop(); }); - if (config.BOT_MODE === "webhook") { - // to prevent receiving updates before the bot is ready - await bot.init(); - - // start server - serve( - { - fetch: server.fetch, - hostname: config.BOT_SERVER_HOST, - port: config.BOT_SERVER_PORT, - }, - (info) => { - const url = - info.family === "IPv6" - ? `http://[${info.address}]:${info.port}` - : `http://${info.address}:${info.port}`; + // start bot + await bot.start({ + allowed_updates: config.BOT_ALLOWED_UPDATES, + onStart: ({ username }) => + logger.info({ + msg: "Bot running...", + username, + }), + }); +} - logger.info({ - msg: "Server started", - url, - }); - }, - ); +async function startWebhook() { + const bot = createBot(config.BOT_TOKEN); + const server = await createServer(bot); - // set webhook - await bot.api.setWebhook(config.BOT_WEBHOOK, { - allowed_updates: config.BOT_ALLOWED_UPDATES, - secret_token: config.BOT_WEBHOOK_SECRET, + let serverHandle: undefined | ReturnType; + const startServer = () => + new Promise((resolve) => { + serverHandle = serve( + { + fetch: server.fetch, + hostname: config.BOT_SERVER_HOST, + port: config.BOT_SERVER_PORT, + }, + (info) => resolve(info), + ); }); - logger.info({ - msg: "Webhook was set", - url: config.BOT_WEBHOOK, + const stopServer = async () => + new Promise((resolve) => { + if (serverHandle) { + serverHandle.close(() => resolve()); + } else { + resolve(); + } }); + + // graceful shutdown + onShutdown(async () => { + await stopServer(); + }); + + // to prevent receiving updates before the bot is ready + await bot.init(); + + // start server + const info = await startServer(); + logger.info({ + msg: "Server started", + url: + info.family === "IPv6" + ? `http://[${info.address}]:${info.port}` + : `http://${info.address}:${info.port}`, + }); + + // set webhook + await bot.api.setWebhook(config.BOT_WEBHOOK, { + allowed_updates: config.BOT_ALLOWED_UPDATES, + secret_token: config.BOT_WEBHOOK_SECRET, + }); + logger.info({ + msg: "Webhook was set", + url: config.BOT_WEBHOOK, + }); +} + +try { + if (config.BOT_MODE === "webhook") { + await startWebhook(); } else if (config.BOT_MODE === "polling") { - await bot.start({ - allowed_updates: config.BOT_ALLOWED_UPDATES, - onStart: ({ username }) => - logger.info({ - msg: "Bot running...", - username, - }), - }); + await startPolling(); } } catch (error) { logger.error(error);