From b3bff8d7357d75d3871aa68bc71db35dd79506a9 Mon Sep 17 00:00:00 2001 From: LouisLam Date: Fri, 16 Jul 2021 01:44:51 +0800 Subject: [PATCH] add graceful shutdown --- package-lock.json | 11 +++- package.json | 1 + server/server.js | 86 +++++++++++++++++++++++++-- server/util.js | 4 +- src/components/CountUp.vue | 2 +- src/components/NotificationDialog.vue | 6 +- 6 files changed, 98 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index c589c8d9f..e7486fc81 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,8 @@ { "name": "uptime-kuma", - "requires": true, + "version": "1.0.4", "lockfileVersion": 1, + "requires": true, "dependencies": { "@babel/helper-validator-identifier": { "version": "7.14.5", @@ -1518,6 +1519,14 @@ "toidentifier": "1.0.0" } }, + "http-graceful-shutdown": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/http-graceful-shutdown/-/http-graceful-shutdown-3.1.2.tgz", + "integrity": "sha512-2vmU3kWOsZqZy4Kn4EZp00CF+6glpNNN/NAYJPkO9bnMX/D8sRl29TsxIu9Vgyo8ygtCWazWJp720zHfqhSdXg==", + "requires": { + "debug": "^4.3.1" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", diff --git a/package.json b/package.json index 0dc8452ef..af0840183 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "dayjs": "^1.10.4", "express": "^4.17.1", "form-data": "^4.0.0", + "http-graceful-shutdown": "^3.1.2", "jsonwebtoken": "^8.5.1", "nodemailer": "^6.6.2", "password-hash": "^1.2.2", diff --git a/server/server.js b/server/server.js index 46a6d3714..93e43cd6e 100644 --- a/server/server.js +++ b/server/server.js @@ -1,9 +1,8 @@ +console.log("Welcome to Uptime Kuma ") +console.log("Importing libraries") const express = require('express'); -const app = express(); const http = require('http'); -const server = http.createServer(app); const { Server } = require("socket.io"); -const io = new Server(server); const dayjs = require("dayjs"); const {R} = require("redbean-node"); const passwordHash = require('./password-hash'); @@ -12,12 +11,20 @@ const Monitor = require("./model/monitor"); const fs = require("fs"); const {getSettings} = require("./util-server"); const {Notification} = require("./notification") +const gracefulShutdown = require('http-graceful-shutdown'); +const {sleep} = require("./util"); const args = require('args-parser')(process.argv); const version = require('../package.json').version; const hostname = args.host || "0.0.0.0" const port = args.port || 3001 +console.log("Version: " + version) + +console.log("Creating express and socket.io instance") +const app = express(); +const server = http.createServer(app); +const io = new Server(server); app.use(express.json()) let totalClient = 0; @@ -539,11 +546,11 @@ async function initDatabase() { const path = './data/kuma.db'; if (! fs.existsSync(path)) { - console.log("Copy Database") + console.log("Copying Database") fs.copyFileSync("./db/kuma.db", path); } - console.log("Connect to Database") + console.log("Connecting to Database") R.setup('sqlite', { filename: path @@ -660,3 +667,72 @@ async function sendImportantHeartbeatList(socket, monitorID) { socket.emit("importantHeartbeatList", monitorID, list) } + + + +const startGracefulShutdown = async () => { + console.log('Shutdown requested'); + + + await (new Promise((resolve) => { + server.close(async function () { + console.log('Stopped Express.'); + process.exit(0) + setTimeout(async () =>{ + await R.close(); + console.log("Stopped DB") + + resolve(); + }, 5000) + + }); + })); + + +} + +let noReject = true; +process.on('unhandledRejection', (reason, p) => { + noReject = false; +}); + +async function shutdownFunction(signal) { + console.log('Called signal: ' + signal); + + console.log("Stopping all monitors") + for (let id in monitorList) { + let monitor = monitorList[id] + monitor.stop() + } + await sleep(2000) + + console.log("Closing DB") + + // Special handle, because tarn.js throw a promise reject that cannot be caught + while (true) { + noReject = true; + await R.close() + await sleep(2000) + + if (noReject) { + break; + } else { + console.log("Waiting...") + } + } + + console.log("OK") +} + +function finalFunction() { + console.log('Graceful Shutdown') +} + +gracefulShutdown(server, { + signals: 'SIGINT SIGTERM', + timeout: 30000, // timeout: 30 secs + development: false, // not in dev mode + forceExit: true, // triggers process.exit() at the end of shutdown process + onShutdown: shutdownFunction, // shutdown function (async) - e.g. for cleanup DB, ... + finally: finalFunction // finally function (sync) - e.g. for logging +}); diff --git a/server/util.js b/server/util.js index dfe4eaa06..d1c9266a8 100644 --- a/server/util.js +++ b/server/util.js @@ -5,11 +5,11 @@ -export function sleep(ms) { +exports.sleep = function (ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -export function ucfirst(str) { +exports.ucfirst = function (str) { if (! str) { return str; } diff --git a/src/components/CountUp.vue b/src/components/CountUp.vue index b929e52eb..b90f430e4 100644 --- a/src/components/CountUp.vue +++ b/src/components/CountUp.vue @@ -5,7 +5,7 @@