Socket
Socket
Sign inDemoInstall

@ladjs/graceful

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@ladjs/graceful - npm Package Compare versions

Comparing version 0.0.1 to 1.0.0

186

index.js

@@ -1,3 +0,1 @@

const { promisify } = require('util');
const stopAgenda = require('stop-agenda');
const debug = require('debug')('@ladjs/graceful');

@@ -7,14 +5,11 @@

constructor(config) {
this.config = Object.assign(
{
server: false,
redisClient: false,
mongoose: false,
agenda: false,
logger: console,
stopAgenda: {},
timeoutMs: 5000
},
config
);
this.config = {
servers: [],
redisClients: [],
mongooses: [],
bulls: [],
logger: console,
timeoutMs: 5000,
...config
};

@@ -26,2 +21,14 @@ // shortcut logger

this._isExiting = false;
// bind this to everything
this.listen = this.listen.bind(this);
this.stopServer = this.stopServer.bind(this);
this.stopServers = this.stopServers.bind(this);
this.stopRedisClient = this.stopRedisClient.bind(this);
this.stopRedisClients = this.stopRedisClients.bind(this);
this.stopMongoose = this.stopMongoose.bind(this);
this.stopMongooses = this.stopMongooses.bind(this);
this.stopBull = this.stopBull.bind(this);
this.stopBulls = this.stopBulls.bind(this);
this.exit = this.exit.bind(this);
}

@@ -37,3 +44,3 @@

// handle uncaught exceptions
process.on('uncaughtException', err => {
process.once('uncaughtException', err => {
this.logger.error(err);

@@ -45,64 +52,93 @@ process.exit(1);

// <http://pm2.keymetrics.io/docs/usage/signals-clean-restart/#windows-graceful-stop>
process.on('message', msg => {
if (msg === 'shutdown') this.exit();
process.on('message', async message => {
if (message === 'shutdown') {
this.logger.info('Received shutdown message');
await this.exit();
}
});
// handle graceful restarts
process.on('SIGTERM', () => this.exit());
process.on('SIGHUP', () => this.exit());
process.on('SIGINT', () => this.exit());
// support nodemon (SIGUSR2 as well)
// <https://github.com/remy/nodemon#controlling-shutdown-of-your-script>
['SIGTERM', 'SIGHUP', 'SIGINT', 'SIGUSR2'].forEach(sig => {
process.once(sig, async () => {
await this.exit(sig);
});
});
}
exit() {
const { server, redisClient, mongoose, agenda, timeoutMs } = this.config;
async stopServer(server) {
try {
await server.close();
} catch (err) {
this.config.logger.error(err);
}
}
if (this._isExiting) {
debug('already in the process of a graceful exit');
return;
async stopServers() {
await Promise.all(
this.config.servers.map(server => this.stopServer(server))
);
}
async stopRedisClient(client) {
if (client.status === 'end') return;
// TODO: give it a max of 500ms
// https://github.com/OptimalBits/bull/blob/develop/lib/queue.js#L516
try {
await client.quit();
} catch (err) {
this.config.logger.error(err);
}
}
this._isExiting = true;
async stopRedisClients() {
await Promise.all(
this.config.redisClients.map(client => this.stopRedisClient(client))
);
}
debug('graceful exit started');
async stopMongoose(mongoose) {
try {
await mongoose.disconnect();
} catch (err) {
this.config.logger.error(err);
}
}
const promises = [];
async stopMongooses() {
await Promise.all(
this.config.mongooses.map(mongoose => this.stopMongoose(mongoose))
);
}
if (server) promises.push(promisify(server.close).bind(server));
async stopBull(bull) {
try {
await bull.close();
} catch (err) {
this.config.logger.error(err);
}
}
if (redisClient)
promises.push(promisify(redisClient.quit).bind(redisClient));
async stopBulls() {
await Promise.all(this.config.bulls.map(bull => this.stopBull(bull)));
}
if (mongoose) {
// we need to stop agenda and cancel recurring jobs
// before shutting down our mongodb connection
// otherwise cancellation wouldn't work gracefully
if (agenda) {
promises.push(
new Promise(async (resolve, reject) => {
try {
try {
await stopAgenda(agenda, this.config.stopAgenda);
} catch (err) {
this.logger.error(err);
} finally {
await mongoose.disconnect();
resolve();
}
} catch (err) {
reject(err);
}
})
);
} else {
promises.push(mongoose.disconnect);
}
} else if (agenda) {
promises.push(stopAgenda(agenda, this.config.stopAgenda));
async exit(code) {
if (code) this.logger.info(`Gracefully exiting from ${code}`);
if (this._isExiting) {
this.logger.info('Graceful exit already in progress');
return;
}
// give it only X ms to gracefully shut down
this._isExiting = true;
debug('graceful exit started');
// give it only X ms to gracefully exit
setTimeout(() => {
this.logger.error(
new Error(
`graceful shutdown failed, timeout of ${timeoutMs} ms exceeded`
`Graceful exit failed, timeout of ${this.config.timeoutMs}ms was exceeded`
)

@@ -112,15 +148,23 @@ );

process.exit(1);
}, timeoutMs);
}, this.config.timeoutMs);
Promise.all(promises)
.then(() => {
this.logger.debug('gracefully shut down');
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0);
})
.catch(err => {
this.logger.error(err);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
});
try {
await Promise.all([
// servers
this.stopServers(),
// redisClients
this.stopRedisClients(),
// mongooses
this.stopMongooses(),
// bulls
this.stopBulls()
]);
this.logger.info('Gracefully exited');
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0);
} catch (err) {
this.logger.error(err);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
}
}

@@ -127,0 +171,0 @@ }

{
"name": "@ladjs/graceful",
"description":
"Gracefully exit server (Koa), database (Mongo/Mongoose), and job scheduler (Agenda)",
"version": "0.0.1",
"description": "Gracefully exit server (Koa), database (Mongo/Mongoose), and job scheduler (Redis/Bull)",
"version": "1.0.0",
"author": "Nick Baugh <niftylettuce@gmail.com> (http://niftylettuce.com/)",

@@ -11,2 +10,7 @@ "bugs": {

},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"contributors": [

@@ -16,19 +20,19 @@ "Nick Baugh <niftylettuce@gmail.com> (http://niftylettuce.com/)"

"dependencies": {
"debug": "^3.0.1",
"stop-agenda": "^0.0.5"
"debug": "^4.1.1"
},
"devDependencies": {
"ava": "^0.22.0",
"codecov": "^2.3.0",
"cross-env": "^5.0.5",
"eslint": "^4.5.0",
"eslint-config-prettier": "^2.3.0",
"eslint-plugin-prettier": "^2.2.0",
"husky": "^0.14.3",
"lint-staged": "^4.0.4",
"nyc": "^11.1.0",
"prettier": "^1.6.1",
"remark-cli": "^4.0.0",
"remark-preset-github": "^0.0.6",
"xo": "^0.19.0"
"@commitlint/cli": "^8.1.0",
"@commitlint/config-conventional": "^8.1.0",
"ava": "^2.3.0",
"codecov": "^3.5.0",
"cross-env": "^5.2.1",
"eslint": "6.3.0",
"eslint-config-xo-lass": "^1.0.3",
"fixpack": "^2.3.1",
"husky": "^3.0.5",
"lint-staged": "^9.2.5",
"nyc": "^14.1.1",
"remark-cli": "^7.0.0",
"remark-preset-github": "^0.0.16",
"xo": "^0.24.0"
},

@@ -39,14 +43,37 @@ "engines": {

"homepage": "https://github.com/ladjs/graceful",
"keywords": ["@ladjs/graceful", "lass"],
"husky": {
"hooks": {
"pre-commit": "lint-staged && npm test",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"keywords": [
"@ladjs/graceful",
"lass"
],
"license": "MIT",
"lint-staged": {
"*.{js,jsx,mjs,ts,tsx,css,less,scss,json,graphql}": [
"prettier --write --single-quote --trailing-comma none",
"*.js": [
"xo --fix",
"git add"
],
"*.md": ["remark . -qfo", "git add"]
"*.md": [
"remark . -qfo",
"git add"
],
"package.json": [
"fixpack",
"git add"
]
},
"main": "index.js",
"prettier": {
"singleQuote": true,
"bracketSpacing": true,
"trailingComma": "none"
},
"remarkConfig": {
"plugins": ["preset-github"]
"plugins": [
"preset-github"
]
},

@@ -60,3 +87,2 @@ "repository": {

"lint": "xo && remark . -qfo",
"precommit": "lint-staged && npm test",
"test": "npm run lint && npm run test-coverage",

@@ -66,29 +92,8 @@ "test-coverage": "cross-env NODE_ENV=test nyc ava"

"xo": {
"extends": "prettier",
"plugins": ["prettier"],
"parserOptions": {
"sourceType": "script"
},
"rules": {
"prettier/prettier": [
"error",
{
"singleQuote": true,
"bracketSpacing": true,
"trailingComma": "none"
}
],
"max-len": [
"error",
{
"code": 80,
"ignoreUrls": true
}
],
"capitalized-comments": "off",
"camelcase": "off",
"no-warning-comments": "off"
},
"space": true
"prettier": true,
"space": true,
"extends": [
"xo-lass"
]
}
}

@@ -10,3 +10,3 @@ # [**@ladjs/graceful**](https://github.com/ladjs/graceful)

> Gracefully exit server (Koa), database (Mongo/Mongoose), and job scheduler (Agenda)
> Gracefully exit server (Koa), database (Mongo/Mongoose), Redis clients, and job scheduler (Redis/Bull)

@@ -43,64 +43,19 @@

* `process.on('unhandledRejection')` - will output via `config.logger.error`
* `process.on('uncaughtException')` - will output via `config.logger.error` and `process.exit(1)`
* `process.once('uncaughtException')` - will output via `config.logger.error` and `process.exit(1)` (_does not exit gracefully_)
* `process.on('message')` - support Windows (e.g. signals not available) and listen for message of `shutdown` and then exit gracefully
* `process.on('SIGTERM')` - will exit gracefully
* `process.on('SIGHUP')` - will exit gracefully
* `process.on('SIGINT')` - will exit gracefully
* `process.once('SIGTERM')` - will exit gracefully
* `process.once('SIGHUP')` - will exit gracefully
* `process.once('SIGINT')` - will exit gracefully
* `process.once('SIGUSR2')` - will exit gracefully (nodemon support)
This package also prevents multiple process/SIG events from triggering multiple graceful exits. Only one graceful exit can occur at a time.
An example below shows usage showing `server`, `redisClient`, `mongoose`, and `agenda` options all being passed.
See one of these following files from [Lad][] for the most up to date usage example:
Please note that NONE of these are required, as each one is completely optional and independent.
* API - <https://github.com/ladjs/lad/blob/master/template/api.js>
* Web - <https://github.com/ladjs/lad/blob/master/template/web.js>
* Bull - <https://github.com/ladjs/lad/blob/master/template/bull.js>
* Proxy - <https://github.com/ladjs/lad/blob/master/template/proxy.js>
However if you have both `mongoose` and `agenda` defined, it knows to stop `agenda` first using [stop-agenda][], then disconnect `mongoose`.
```js
const http = require('http');
const Graceful = require('@ladjs/graceful');
const Mongoose = require('@ladjs/mongoose');
const Koa = require('koa');
const redis = require('redis');
const Agenda = require('agenda');
const app = new Koa();
const agenda = new Agenda();
let server = http.createServer(app.callback());
server = server.listen();
const redisClient = redis.createClient();
const mongoose = new Mongoose({ agenda }).mongoose;
const graceful = new Graceful({
// uses `server.close` for graceful exit
server,
// uses `redisClient.quit` for graceful exit
redisClient,
// uses `mongoose.disconnect` for graceful exit
mongoose,
// uses `stop-agenda` package for graceful exit
agenda,
// default logger (you can also use `new Logger()` from @ladjs/logger)
logger: console,
// options get passed to `stop-agenda`
// <https://github.com/ladjs/stop-agenda>
stopAgenda: {},
// max time allowed in ms for graceful exit
timeoutMs: 5000
});
graceful.listen();
```
## Contributors

@@ -124,2 +79,2 @@

[stop-agenda]: https://github.com/ladjs/stop-agenda
[lad]: https://lad.js.org
const test = require('ava');
const Graceful = require('../');
const Graceful = require('..');

@@ -5,0 +5,0 @@ test('returns itself', t => {

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc