Implement cluster and exceptions handler
This commit is contained in:
1 parent
8b5cd384ab
commit
220d1a1748
6 files changed
+175
-18
No files matched your search
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"server": {
|
"server": {
|
||||||
|
"workers": 4,
|
||||||
|
"interface": "0.0.0.0",
|
||||||
"port": 3000,
|
"port": 3000,
|
||||||
"nbi": "http://127.0.0.1:7557/",
|
"nbi": "http://127.0.0.1:7557/",
|
||||||
"mongodbConnectionUrl": "mongodb://127.0.0.1:27017",
|
"mongodbConnectionUrl": "mongodb://127.0.0.1:27017",
|
||||||
@@ -284,6 +286,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"logger": {
|
"logger": {
|
||||||
|
"logFile": "",
|
||||||
|
"accessLogFile": "",
|
||||||
"logFormat": "simple",
|
"logFormat": "simple",
|
||||||
"accessLogFormat": "simple"
|
"accessLogFormat": "simple"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"lint": "eslint --fix common/*.js common/validators/*.js server/*.js client/*.js client/components/*.js;prettier --write *.js common/*.js common/validators/*.js server/*.js client/*.js client/components/*.js client/css/*.css",
|
"lint": "eslint --fix common/*.js common/validators/*.js server/*.js client/*.js client/components/*.js;prettier --write *.js common/*.js common/validators/*.js server/*.js client/*.js client/components/*.js client/css/*.css",
|
||||||
"build": "npm run lint;webpack;postcss -c postcss.config.js client/css/app.css -o dist/public/app.css",
|
"build": "npm run lint;webpack;postcss -c postcss.config.js client/css/app.css -o dist/public/app.css",
|
||||||
"start": "cd dist && node server"
|
"start": "cd dist && node cluster"
|
||||||
},
|
},
|
||||||
"author": "Zaid Abdulla <zaid@genieacs.com>",
|
"author": "Zaid Abdulla <zaid@genieacs.com>",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const cluster = require("cluster");
|
||||||
|
const config = require("./config");
|
||||||
|
const logger = require("./logger");
|
||||||
|
|
||||||
|
const VERSION = require("../package.json")["version"];
|
||||||
|
|
||||||
|
let respawnTimestamp = 0;
|
||||||
|
let tooManyCrashesTimestamp = 0;
|
||||||
|
|
||||||
|
function restartWorker(worker, code, signal) {
|
||||||
|
const msg = {
|
||||||
|
message: "Worker died",
|
||||||
|
pid: worker.process.pid
|
||||||
|
};
|
||||||
|
|
||||||
|
if (code != null) msg.exitCode = code;
|
||||||
|
|
||||||
|
if (signal != null) msg.signal = signal;
|
||||||
|
|
||||||
|
logger.error(msg);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
respawnTimestamp = Math.max(now, respawnTimestamp + 2000);
|
||||||
|
if (respawnTimestamp === now) {
|
||||||
|
tooManyCrashesTimestamp = now;
|
||||||
|
cluster.fork();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (now - tooManyCrashesTimestamp > 60000) {
|
||||||
|
process.exitCode = 1;
|
||||||
|
cluster.removeListener("exit", restartWorker);
|
||||||
|
for (const pid in cluster.workers) cluster.workers[pid].kill();
|
||||||
|
|
||||||
|
logger.error({
|
||||||
|
message: "Too many crashes, exiting",
|
||||||
|
pid: process.pid
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (process.exitCode) return;
|
||||||
|
cluster.fork();
|
||||||
|
}, respawnTimestamp - now);
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
logger.init(VERSION);
|
||||||
|
|
||||||
|
logger.info({
|
||||||
|
message: "GenieACS UI starting",
|
||||||
|
pid: process.pid,
|
||||||
|
version: VERSION
|
||||||
|
});
|
||||||
|
|
||||||
|
cluster.on("listening", (worker, address) => {
|
||||||
|
logger.info({
|
||||||
|
message: "Worker listening",
|
||||||
|
pid: worker.process.pid,
|
||||||
|
address: address.address,
|
||||||
|
port: address.port
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cluster.on("exit", restartWorker);
|
||||||
|
|
||||||
|
process.on("SIGINT", () => {
|
||||||
|
logger.info({
|
||||||
|
message: "Received signal SIGINT, exiting",
|
||||||
|
pid: process.pid
|
||||||
|
});
|
||||||
|
|
||||||
|
cluster.removeListener("exit", restartWorker);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("SIGTERM", () => {
|
||||||
|
logger.info({
|
||||||
|
message: "Received signal SIGTERM, exiting",
|
||||||
|
pid: process.pid
|
||||||
|
});
|
||||||
|
|
||||||
|
cluster.removeListener("exit", restartWorker);
|
||||||
|
for (const pid in cluster.workers) cluster.workers[pid].kill();
|
||||||
|
});
|
||||||
|
|
||||||
|
cluster.setupMaster({ exec: "./server" });
|
||||||
|
|
||||||
|
let workerCount = config.server.workers;
|
||||||
|
|
||||||
|
if (!workerCount) workerCount = Math.max(2, require("os").cpus().length);
|
||||||
|
|
||||||
|
for (let i = 0; i < workerCount; ++i) cluster.fork();
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on("uncaughtException", err => {
|
||||||
|
logger.error({
|
||||||
|
message: "Uncaught exception in master process, exiting",
|
||||||
|
exception: err,
|
||||||
|
pid: process.pid
|
||||||
|
});
|
||||||
|
process.exitCode = 1;
|
||||||
|
cluster.removeListener("exit", restartWorker);
|
||||||
|
for (const pid in cluster.workers) cluster.workers[pid].kill();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Starting GenieACS UI
|
||||||
|
start();
|
||||||
+4
-4
@@ -72,14 +72,14 @@ function reopen() {
|
|||||||
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
|
setTimeout(reopen, REOPEN_EVERY - (Date.now() % REOPEN_EVERY)).unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(service, version) {
|
function init(version) {
|
||||||
defaultMeta.hostname = os.hostname();
|
defaultMeta.hostname = os.hostname();
|
||||||
defaultMeta.pid = process.pid;
|
defaultMeta.pid = process.pid;
|
||||||
defaultMeta.name = `genieacs-ui-${service}`;
|
defaultMeta.name = "genieacs-ui";
|
||||||
defaultMeta.version = version;
|
defaultMeta.version = version;
|
||||||
|
|
||||||
LOG_FILE = config.logger[`${service}LogFile`];
|
LOG_FILE = config.logger.logFile;
|
||||||
ACCESS_LOG_FILE = config.logger[`${service}AccessLogFile`];
|
ACCESS_LOG_FILE = config.logger.accessLogFile;
|
||||||
|
|
||||||
if (LOG_FILE) {
|
if (LOG_FILE) {
|
||||||
logStream = fs.createWriteStream(null, { fd: fs.openSync(LOG_FILE, "a") });
|
logStream = fs.createWriteStream(null, { fd: fs.openSync(LOG_FILE, "a") });
|
||||||
|
|||||||
+47
-12
@@ -1,5 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
const http = require("http");
|
||||||
|
const cluster = require("cluster");
|
||||||
const Koa = require("koa");
|
const Koa = require("koa");
|
||||||
const Router = require("koa-router");
|
const Router = require("koa-router");
|
||||||
const koaStatic = require("koa-static");
|
const koaStatic = require("koa-static");
|
||||||
@@ -13,6 +15,10 @@ const api = require("./api");
|
|||||||
const Authorizer = require("../common/authorizer");
|
const Authorizer = require("../common/authorizer");
|
||||||
const expression = require("../common/expression");
|
const expression = require("../common/expression");
|
||||||
const logger = require("./logger");
|
const logger = require("./logger");
|
||||||
|
const db = require("./db");
|
||||||
|
|
||||||
|
const VERSION = require("../package.json")["version"];
|
||||||
|
logger.init("server", VERSION);
|
||||||
|
|
||||||
const koa = new Koa();
|
const koa = new Koa();
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
@@ -20,14 +26,6 @@ const router = new Router();
|
|||||||
const JWT_SECRET = config.server.jwtSecret;
|
const JWT_SECRET = config.server.jwtSecret;
|
||||||
const JWT_COOKIE = "genieacs-ui-jwt";
|
const JWT_COOKIE = "genieacs-ui-jwt";
|
||||||
|
|
||||||
const VERSION = require("../package.json")["version"];
|
|
||||||
|
|
||||||
logger.info({
|
|
||||||
message: "GenieACS UI starting",
|
|
||||||
pid: process.pid,
|
|
||||||
version: VERSION
|
|
||||||
});
|
|
||||||
|
|
||||||
function getPermissionSets(roles) {
|
function getPermissionSets(roles) {
|
||||||
const allPermissions = config.permissions;
|
const allPermissions = config.permissions;
|
||||||
const permissionSets = roles.map(role => {
|
const permissionSets = roles.map(role => {
|
||||||
@@ -149,10 +147,47 @@ koa.use(
|
|||||||
koa.use(router.routes());
|
koa.use(router.routes());
|
||||||
koa.use(koaStatic("./public"));
|
koa.use(koaStatic("./public"));
|
||||||
|
|
||||||
koa.listen(config.server.port, "0.0.0.0", () => {
|
let server = http
|
||||||
logger.info({
|
.createServer(koa.callback())
|
||||||
message: "Worker litening",
|
.listen(config.server.port, config.server.interface);
|
||||||
port: config.server.port,
|
|
||||||
|
function closeServer(timeout) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!resolve) return;
|
||||||
|
// Ignore HTTP requests from alive connections
|
||||||
|
server.removeListener("request", koa.callback());
|
||||||
|
server.setTimeout(1);
|
||||||
|
|
||||||
|
setTimeout(resolve, 1000);
|
||||||
|
}, timeout).unref();
|
||||||
|
|
||||||
|
// prevent new sockets to connect and close server eventually.
|
||||||
|
server.close(resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function exit() {
|
||||||
|
setTimeout(() => {
|
||||||
|
process.exit(1);
|
||||||
|
}, 30000).unref();
|
||||||
|
|
||||||
|
closeServer(20000).then(() => {
|
||||||
|
db.disconnect();
|
||||||
|
if (cluster.worker) cluster.worker.disconnect();
|
||||||
|
logger.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on("uncaughtException", err => {
|
||||||
|
logger.error({
|
||||||
|
message: "Uncaught exception",
|
||||||
|
exception: err,
|
||||||
pid: process.pid
|
pid: process.pid
|
||||||
});
|
});
|
||||||
|
process.exitCode = 1;
|
||||||
|
exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
process.on("SIGINT", exit);
|
||||||
|
process.on("SIGTERM", exit);
|
||||||
+9
-1
@@ -8,7 +8,7 @@ function transformPackageJson(content) {
|
|||||||
const pkg = JSON.parse(content);
|
const pkg = JSON.parse(content);
|
||||||
delete pkg["devDependencies"];
|
delete pkg["devDependencies"];
|
||||||
pkg["scripts"] = {
|
pkg["scripts"] = {
|
||||||
start: "node server"
|
start: "node cluster"
|
||||||
};
|
};
|
||||||
return JSON.stringify(pkg, null, 2);
|
return JSON.stringify(pkg, null, 2);
|
||||||
}
|
}
|
||||||
@@ -39,6 +39,14 @@ module.exports = [
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
mode: mode,
|
||||||
|
entry: "./server/cluster.js",
|
||||||
|
target: "node",
|
||||||
|
output: {
|
||||||
|
filename: "cluster.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
mode: mode,
|
mode: mode,
|
||||||
entry: "./client/app.js",
|
entry: "./client/app.js",
|
||||||
|
|||||||
Reference in new issue
Block a user