Package Exports
- graceful-shutdown-manager
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (graceful-shutdown-manager) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
node-graceful-shutdown-manager
All in one solution to gracefully shutdown your application through a simple delayed shutdown process. Optionally supports nodemon. (initialise, free, lock critical sections)
Use for any node.js project which might be negatively impacted by immediate termination.
Locking is a mechanism which avoids beginning the shutdown process until a block of code has finished. Wrap important code such as file writing with lock()
and unlock()
. This will ensure no corruption occurs or resources are freed when they might be required. Ensure the code within the lock is synchronous. I recommend the fs-extra
package for a drop-in fs promise replacement.
Nodemon (optional)
Used for auto reloading upon source file changes, useful for development. This assumes your entry point is the usual index.js
and that your primary source code is somewhere inside the src
directory.
Install
npm install -g nodemon
package.json
nodemon --signal SIGTERM -x 'node index.js'
"scripts": {
"development": "NODE_ENV=development nodemon --signal SIGTERM -x 'node index.js'"
},
"nodemonConfig": {
"watch": [
"./src"
],
"ext": "js",
"delay": "1"
}
Template (example is below this template)
main.js
Program entry
/*
GSM Functions:
gsm.exit() Gracefully shutdown application
gsm.free() Gracefully run the free events, without exiting. (autoreload code, etc)
gsm.isExiting() Avoid continuing a loop if the application is waiting to exit
gsm.isFreeing() Check if freeing
Base Functions:
base.lock() Stop the application from freeing if an object is locked
base.unlock()
base.isLocked()
*/
async function main() {
global.gsm = require("graceful-shutdown-manager").Manager.create();
try {
global.database = require("./Database").create();
// Start
global.gsm.events.on("init", async () => {
await global.database.init();
});
// Main
global.gsm.events.on("main", async () => {
// Run code after everything has been initialised
});
// Cleanup
global.gsm.events.on("free", async () => {
await global.database.free();
});
await global.gsm.init(); // Calls init, and then main, free will only be called when invoked
} catch (e) {
// All program errors end up here, even during the initialisation phase
console.error(e);
// await global.gsm.free(); // Use if you just want to free everything, without shutting down, useful for live reloading without nodemon (bad for runtime memory leaks)
global.gsm.exit(); // Use instead of 'process.exit()' to gracefully shut down
}
}
main();
Database.js
const Base = require('graceful-shutdown-manager').Base;
class Database extends Base {
constructor() {
super();
this.events.on("init", async () => {
await this.connect();
});
this.events.on("free", async () => {
await this.close();
});
// Remember to wrap any important code in locks, place them in your functions
//this.lock();
// Write to file or do something which cannot be interrupted
//this.unlock();
}
connect() {
return new Promise((resolve, reject)) => {
// Connect and resolve, mysql uses callbacks
resolve();
});
}
close() {
return new Promise((resolve, reject)) => {
// End and resolve, mysql uses callbacks
resolve();
});
}
}
module.exports = Database;
Example (mysql, express, locking)
main.js
/*
GSM Functions:
gsm.exit() Gracefully shutdown application
gsm.free() Gracefully run the free events, without exiting. (autoreload code, etc)
gsm.isExiting() Avoid continuing a loop if the application is waiting to exit
gsm.isFreeing() Check if freeing
Base Functions:
base.lock() Stop the application from freeing if an object is locked
base.unlock()
base.isLocked()
*/
async function main() {
global.gsm = require("graceful-shutdown-manager").Manager.create();
try {
global.database = require("./Database").create();
global.express = require("./Express").create();
global.app = require("./App").create();
// Start
global.gsm.events.on("init", async () => {
await global.database.init();
await global.express.init();
await global.app.init();
});
// Main
global.gsm.events.on("main", async () => {
// Run code after everything has been initialised
});
// Cleanup
global.gsm.events.on("free", async () => {
await global.express.free();
await global.database.free();
await global.app.free();
});
await global.gsm.init(); // Calls init, and then main, free will only be called when invoked
} catch (e) {
// All program errors end up here, even during the initialisation phase
console.error(e);
// await global.gsm.free(); // Use if you just want to free everything, without shutting down, useful for live reloading without nodemon (bad for runtime memory leaks)
global.gsm.exit(); // Use instead of 'process.exit()' to gracefully shut down
}
}
main();
Database.js
const Base = require('graceful-shutdown-manager').Base;
const Mysql = require('mysql');
const connection = Mysql.createConnection({
host : 'localhost',
user : 'user',
password : 'pass',
database : 'db'
});
class Database extends Base {
constructor() {
super();
this.events.on("init", async () => {
await this.connect();
});
this.events.on("free", async () => {
await this.close();
});
}
connect() {
return new Promise((resolve, reject)) => {
connection.connect((err) => {
if (err) {
console.error('error connecting: ' + err.stack);
reject(err);
return;
}
console.log('connected as id ' + connection.threadId);
resolve();
});
});
}
close() {
return new Promise((resolve, reject)) => {
connection.end((err) => {
if (err) {
console.error('error: ' + err.stack);
reject(err);
return;
}
resolve();
});
});
}
}
module.exports = Database;
Express.js
const Base = require('graceful-shutdown-manager').Base;
const express = require('express');
const app = express();
class Express extends Base {
constructor() {
super();
// Initialise
this.events.on("init", async () => {
await this.listen();
});
// Free
this.events.on("free", async () => {
await this.close();
});
}
listen() {
return new Promise((resolve, reject)) => {
app.listen(3000, () => {
console.log(`Listening at http://localhost:3000`);
resolve();
})
});
}
close() {
return new Promise((resolve, reject)) => {
app.close(() => {
console.log(`Stopped listening`);
resolve();
})
});
}
}
module.exports = Express;
App.js
/*
This file demonstrated the locking mechanism, which
stops a program exiting while it's doing something important.
An example would be writing to a file, processing a request, etc.
*/
const Base = require("graceful-shutdown-manager").Base;
const fs = require("fs").promises;
class App extends Base {
constructor() {
super();
// Initialise
this.events.on("init", async () => {
this.logs = [];
// Store logs to an array (for file saving), by overwriting the function
this.oldConsoleLog = console.log;
console.log = (...args) => {
this.logs.push(args.join(" "));
this.oldConsoleLog(...args);
};
// Save every minute
this.saveInterval = setInterval(() => {
this.saveLog();
}, 1 * 60 * 1000);
});
// Free
this.events.on("free", async () => {
clearInterval(this.saveInterval);
await this.saveLog();
console.log = this.oldConsoleLog;
});
}
async saveLog() {
// This will cause the manager to wait until this object is unlocked before exiting, saving you a corrupted file
this.lock();
await fs.writeFile("log.txt", JSON.stringify(this.logs, NULL, 2));
this.unlock();
}
}
module.exports = App;