Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

exiting

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

exiting - npm Package Compare versions

Comparing version 6.0.0 to 6.0.1

427

lib/index.js

@@ -9,8 +9,9 @@ 'use strict';

manager: null,
signals: {
SIGINT: true,
SIGQUIT: true,
SIGTERM: true,
SIGHUP: false
},
signals: new Map([
['SIGINT', true],
['SIGQUIT', true],
['SIGTERM', true],
['SIGHUP', false]
]),
listeners: new Map(),
processExit: null

@@ -20,301 +21,305 @@ };

internals.exit = async function (code) {
internals.addExitHook = function (event, handler, prepend = false) {
const manager = internals.manager;
prepend ? process.prependListener(event, handler) : process.on(event, handler);
internals.listeners.set(event, handler);
};
if (!manager) {
return; // exit processing was disabled
}
if (typeof code === 'number' && code > manager.exitCode) {
manager.exitCode = code;
}
internals.teardownExitHooks = function () {
if (!manager.exitTimer) {
manager.exitTimer = setTimeout(() => {
process.exit = internals.processExit;
manager.state = 'timeout';
return internals.exit(255);
}, manager.exitTimeout);
for (const listener of internals.listeners) {
process.removeListener(...listener);
}
if (manager.state === 'starting') {
manager.state = 'startAborted';
return;
}
internals.listeners.clear();
internals.processExit = null;
};
if (manager.state === 'startAborted') { // wait until started
return;
}
if (manager.state === 'started') {
exports.Manager = class {
// change state to prestopped as soon as the first server is stopping
for (const server of internals.manager.servers) {
server.ext('onPreStop', internals.listenerStopHandler);
}
exitTimeout = 5000;
servers;
state; // 'starting', 'started', 'stopping', 'prestopped', 'stopped', 'startAborted', 'errored', 'timeout'
exitTimer;
exitCode = 0;
active = true;
try {
await internals.stop({ timeout: manager.exitTimeout - 500 });
}
catch (err) {
exports.log('Server stop failed:', err.stack);
}
constructor(servers, options = {}) {
return internals.exit();
}
Hoek.assert(!internals.manager, 'Only one manager can be created');
if (manager.state === 'stopping') { // wait until stopped
return;
this.exitTimeout = options.exitTimeout || this.exitTimeout;
this.servers = typeof servers[Symbol.iterator] === 'function' ? [...servers] : [servers];
internals.manager = this;
}
if (manager.state === 'prestopped') {
if (manager.exitCode === 0) {
return; // defer to prestop logic
async start() {
if (!this.state) {
this._setupExitHooks();
}
manager.state = 'errored';
}
this.state = 'starting';
// Perform actual exit
let startError = null;
let active = [];
internals.processExit(manager.exitCode);
};
const safeStop = async (server) => {
try {
await server.stop();
}
catch (err) {
Bounce.rethrow(err, 'system');
}
};
internals.abortHandler = function (event) {
const safeStart = async (server) => {
if (this.listenerCount(event) === 1) {
return internals.exit(1);
}
};
// "atomic" start, which immediately stops servers on errors
try {
await server.start();
if (startError) {
throw new Error('Start aborted');
}
active.push(server);
}
catch (err) {
Bounce.rethrow(err, 'system');
internals.gracefulHandler = function (event) {
if (!startError) {
startError = err;
}
if (this.listenerCount(event) === 1) {
return internals.exit(0);
}
};
const stopping = active.concat(server);
active = [];
await Promise.all(stopping.map(safeStop));
}
};
try {
await Promise.all(this.servers.map(safeStart));
}
finally {
const aborted = (this.state === 'startAborted');
this.state = startError ? 'errored' : 'started';
internals.unhandledError = function (type, err) {
if (aborted) { // Note that throw is not returned when aborted
return this._exit(); // eslint-disable-line no-unsafe-finally
}
}
if (err instanceof exports.ProcessExitError) { // Ignore ProcessExitError, since we are already handling it
return;
}
if (startError) {
throw startError;
}
exports.log(`Fatal ${type}:`, (err || {}).stack || err);
// Attach close listeners to catch spurious closes
if (internals.manager.state === 'stopping') { // Exceptions while stopping advance to error state immediately
for (const server of this.servers) {
server.listener.once('close', this._listenerClosedHandler.bind(this));
}
internals.manager.state = 'errored';
return this;
}
return internals.exit(1);
};
stop(options = {}) {
Hoek.assert(this.state === 'started', 'Stop requires that server is started');
internals.uncaughtExceptionHandler = function (err) {
return this._stop(options);
}
return internals.unhandledError('exception', err);
};
deactivate() {
if (this.active) {
if (internals.processExit) {
internals.teardownExitHooks();
}
internals.unhandledRejectionHandler = function (err) {
clearTimeout(this.exitTimer);
internals.manager = undefined;
return internals.unhandledError('rejection', err);
};
this.active = false;
}
}
// Private
internals.listenerClosedHandler = function () {
async _exit(code) {
// If server is closed without stopping, exit with error
if (!this.active) {
return;
}
if (internals.manager && internals.manager.state === 'started') {
return internals.exit(255);
}
};
if (typeof code === 'number' && code > this.exitCode) {
this.exitCode = code;
}
if (!this.exitTimer) {
this.exitTimer = setTimeout(() => {
internals.listenerStopHandler = function (server) {
this.state = 'timeout';
return this._exit(255);
}, this.exitTimeout);
}
internals.manager.state = 'prestopped';
if (this.state === 'starting') {
this.state = 'startAborted';
return;
}
if (internals.manager.exitCode !== 0) {
throw new Error('Process aborted');
}
};
if (this.state === 'startAborted') { // wait until started
return;
}
if (this.state === 'started') {
internals.stop = async function (options) {
// change state to prestopped as soon as the first server is stopping
for (const server of this.servers) {
server.ext('onPreStop', this._listenerStopHandler.bind(this));
}
const manager = internals.manager;
try {
await this._stop({ timeout: this.exitTimeout - 500 });
}
catch (err) {
this._log('Server stop failed:', err.stack);
}
try {
manager.state = 'stopping';
await Promise.all(manager.servers.map((server) => server.stop(options)));
manager.state = 'stopped';
}
catch (err) {
manager.state = 'errored';
throw err;
}
};
return this._exit();
}
if (this.state === 'stopping') { // wait until stopped
return;
}
internals.badExitCheck = function () {
if (this.state === 'prestopped') {
if (this.exitCode === 0) {
return; // defer to prestop logic
}
const state = internals.manager.state;
if (state !== 'stopped' && state !== 'errored' && state !== 'timeout') {
exports.log('Process exiting without stopping server (state == ' + state + ')');
}
};
this.state = 'errored';
}
// Perform actual exit
internals.setupExitHooks = function () {
internals.processExit(this.exitCode);
}
process.on('uncaughtException', internals.uncaughtExceptionHandler);
process.on('unhandledRejection', internals.unhandledRejectionHandler);
_abortHandler(event) {
for (const event in internals.signals) {
let handler = internals.signals[event];
if (handler === true) {
handler = internals.signals[event] = internals.gracefulHandler.bind(process, event);
if (process.listenerCount(event) === 1) {
return this._exit(1);
}
else if (handler === false) {
handler = internals.signals[event] = internals.abortHandler.bind(process, event);
}
process.prependListener(event, handler);
}
process.on('beforeExit', internals.exit);
process.on('exit', internals.badExitCheck);
_gracefulHandler(event) {
// Monkey patch process.exit()
if (process.listenerCount(event) === 1) {
return this._exit(0);
}
}
internals.processExit = process.exit;
process.exit = (code) => {
_unhandledError(type, err) {
internals.exit(code);
if (err instanceof exports.ProcessExitError) { // Ignore ProcessExitError, since we are already handling it
return;
}
// Since we didn't actually exit, throw an error to escape the current scope
this._log(`Fatal ${type}:`, (err || {}).stack || err);
throw new exports.ProcessExitError();
};
};
if (this.state === 'stopping') { // Exceptions while stopping advance to error state immediately
this.state = 'errored';
}
return this._exit(1);
}
internals.teardownExitHooks = function () {
_uncaughtExceptionHandler(err) {
process.exit = internals.processExit;
for (const event in internals.signals) {
process.removeListener(event, internals.signals[event]);
return this._unhandledError('exception', err);
}
process.removeListener('beforeExit', internals.exit);
process.removeListener('exit', internals.badExitCheck);
process.removeListener('unhandledRejection', internals.unhandledRejectionHandler);
process.removeListener('uncaughtException', internals.uncaughtExceptionHandler);
_unhandledRejectionHandler(err) {
internals.processExit = null;
};
return this._unhandledError('rejection', err);
}
_listenerClosedHandler() {
exports.Manager = class {
// If server is closed without stopping, exit with error
constructor(servers, options = {}) {
if (this.state === 'started') {
return this._exit(255);
}
}
Hoek.assert(!internals.manager, 'Only one manager can be created');
_listenerStopHandler(/*server*/) {
this.exitTimeout = options.exitTimeout || 5000;
this.state = 'prestopped';
this.servers = Array.isArray(servers) ? servers : [servers];
if (this.exitCode !== 0) {
throw new Error('Process aborted');
}
}
this.state = null; // ['starting', 'started', 'stopping', 'prestopped', 'stopped', 'startAborted', 'errored', 'timeout']
this.exitTimer = null;
this.exitCode = 0;
async _stop(options) {
internals.manager = this;
try {
this.state = 'stopping';
await Promise.all(this.servers.map((server) => server.stop(options)));
this.state = 'stopped';
}
catch (err) {
this.state = 'errored';
throw err;
}
}
async start() {
_badExitCheck() {
if (!this.state) {
internals.setupExitHooks();
if (this.state !== 'stopped' && this.state !== 'errored' && this.state !== 'timeout') {
this._log('Process exiting without stopping server (state == ' + this.state + ')');
}
}
this.state = 'starting';
_setupExitHooks() {
let startError = null;
let active = [];
internals.addExitHook('uncaughtException', this._uncaughtExceptionHandler.bind(this));
internals.addExitHook('unhandledRejection', this._unhandledRejectionHandler.bind(this));
const safeStop = async (server) => {
for (const [event, graceful] of internals.signals) {
const handler = graceful ? this._gracefulHandler.bind(this, event) : this._abortHandler.bind(this, event);
internals.addExitHook(event, handler, true);
}
try {
await server.stop();
}
catch (err) {
Bounce.rethrow(err, 'system');
}
};
internals.addExitHook('beforeExit', this._exit.bind(this));
internals.addExitHook('exit', this._badExitCheck.bind(this));
const safeStart = async (server) => {
// Monkey patch process.exit()
// "atomic" start, which immediately stops servers on errors
try {
await server.start();
if (startError) {
throw new Error('Start aborted');
}
internals.processExit = process.exit;
process.exit = (code) => {
active.push(server);
}
catch (err) {
Bounce.rethrow(err, 'system');
this._exit(code);
if (!startError) {
startError = err;
}
// Since we didn't actually exit, throw an error to escape the current scope
const stopping = active.concat(server);
active = [];
await Promise.all(stopping.map(safeStop));
}
throw new exports.ProcessExitError();
};
}
_log(...args) {
try {
await Promise.all(this.servers.map(safeStart));
return exports.log(...args);
}
finally {
const aborted = (this.state === 'startAborted');
this.state = startError ? 'errored' : 'started';
if (aborted) { // Note that throw is not returned when aborted
return internals.exit(); // eslint-disable-line no-unsafe-finally
}
}
if (startError) {
throw startError;
}
// Attach close listeners to catch spurious closes
this.servers.forEach((server) => {
server.listener.once('close', internals.listenerClosedHandler);
});
return this;
catch {}
}
stop(options = {}) {
Hoek.assert(this.state === 'started', 'Stop requires that server is started');
return internals.stop(options);
}
};

@@ -337,11 +342,5 @@

if (internals.processExit) {
internals.teardownExitHooks();
}
if (internals.manager) {
clearTimeout(internals.manager.exitTimer);
internals.manager.deactivate();
}
internals.manager = null;
};

@@ -348,0 +347,0 @@

{
"name": "exiting",
"version": "6.0.0",
"version": "6.0.1",
"description": "Gracefully stop hapi.js servers",

@@ -5,0 +5,0 @@ "main": "lib/index.js",

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