sails
Version:
API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)
200 lines (162 loc) • 6.78 kB
JavaScript
/**
* Module dependencies.
*/
var _ = require('@sailshq/lodash');
var async = require('async');
/**
* Sails.prototype.lower()
*
* The inverse of `lift()`, this method
* shuts down all attached servers.
*
* It also unbinds listeners and terminates child processes.
*
* @api public
*/
module.exports = function lower(options, cb) {
var sails = this;
sails.log.verbose('Lowering sails...');
// `options` is optional.
if (_.isFunction(options)) {
cb = options;
options = undefined;
}
// Callback is optional
cb = cb || function(err) {
if (err) { return sails.log.error(err); }
};
options = options || {};
options.delay = options.delay || 100;
// Flag `sails._exiting` as soon as the app has begun to shut down.
// This may be used by core hooks and other parts of core
// (e.g. to stop handling HTTP requests and prevent ugly error msgs).
sails._exiting = true;
var beforeShutdown = (sails.config && sails.config.beforeShutdown) || function(cb) {
return cb();
};
// Wait until beforeShutdown logic runs
beforeShutdown(function(err) {
// If an error occurred, don't stop-- still go ahead and take care of other teardown tasks.
if (err) {
sails.log.error(err);
}
// Try to kill all child processes
_.each(sails.childProcesses, function kill(childProcess) {
sails.log.silly('Sent kill signal to child process (' + childProcess.pid + ')...');
try {
childProcess.kill('SIGINT');
} catch (e) {
sails.log.error('While lowering Sails app: received error killing child process:', e.stack);
}
});
// Shut down HTTP server
sails.emit('lower');
// (Note for future: would be cleaner to provide a way to defer this to the http
// and sockets hooks-- i.e. having hooks expose a `teardown(cb)` interceptor. Keep
// in mind we'd need a way to distinguish between a graceful shutdown and a force
// kill. In a force kill situation, it's never ok for the process to hang.)
async.series([
function shutdownSockets(cb) {
// If the sockets hook is disabled, skip this.
// Also skip if the socket server is piggybacking on the main HTTP server, to avoid
// the onClose event possibly being called multiple times (because you can't tell
// socket.io to close without it trying to close the http server). If we're piggybacking
// we'll call sails.io.close in the main "shutdownHTTP" code below.
if (!_.isObject(sails.hooks) || !sails.hooks.sockets || !sails.io || (sails.io && sails.io.httpServer && sails.hooks.http.server === sails.io.httpServer)) {
return cb();
}
var timeOut;
try {
sails.log.silly('Shutting down socket server...');
timeOut = setTimeout(function() {
sails.io.httpServer.removeListener('close', onClose);
return cb();
}, 100);
sails.io.httpServer.unref();
sails.io.httpServer.once('close', onClose);
sails.io.close();
} catch (e) {
sails.log.verbose('Error occurred closing socket server: ', e);
clearTimeout(timeOut);
return cb();
}
function onClose() {
sails.log.silly('Socket server shut down successfully.');
clearTimeout(timeOut);
cb();
}
},
function shutdownHTTP(cb) {
if (!_.isObject(sails.hooks) || !sails.hooks.http || !sails.hooks.http.server) {
return cb();
}
var timeOut;
try {
sails.log.silly('Shutting down HTTP server...');
// Allow process to exit once this server is closed
sails.hooks.http.server.unref();
// If we have a socket server and it's piggybacking on the main HTTP server, tell
// socket.io to close now. This may call `.close()` on the HTTP server, which will
// happen again below, but the second synchronous call to .close() will have no
// additional effect. Leaving this as-is in case future versions of socket.io
// DON'T automatically close the http server for you.
if (sails.io && sails.io.httpServer && sails.hooks.http.server === sails.io.httpServer) {
sails.io.close();
}
// If the "hard shutdown" option is on, destroy the server immediately,
// severing all connections
if (options.hardShutdown) {
sails.hooks.http.destroy();
}
// Otherwise just stop the server from accepting new connections,
// and wait options.delay for the existing connections to close
// gracefully before destroying.
else {
timeOut = setTimeout(sails.hooks.http.destroy, options.delay);
sails.hooks.http.server.close();
}
// Wait for the existing connections to close
sails.hooks.http.server.once('close', function () {
sails.log.silly('HTTP server shut down successfully.');
clearTimeout(timeOut);
cb();
});
} catch (e) {
sails.log.verbose('Error occurred closing HTTP server: ', e);
clearTimeout(timeOut);
return cb();
}
},
function removeListeners(cb) {
// Manually remove all event listeners
_.each(_.keys(sails._events)||[], function (eventName){
sails.removeAllListeners(eventName);
});
var listeners = sails._processListeners;
if (listeners) {
process.removeListener('SIGUSR2', listeners.sigusr2);
process.removeListener('SIGINT', listeners.sigint);
process.removeListener('SIGTERM', listeners.sigterm);
process.removeListener('exit', listeners.exit);
}
sails._processListeners = null;
// If `sails.config.process.removeAllListeners` is set, do that.
// This is no longer necessary due to https://github.com/balderdashy/sails/pull/2693
// Deprecating for v0.12.
if (sails.config && sails.config.process && sails.config.process.removeAllListeners) {
sails.log.debug('sails.config.process.removeAllListeners is deprecated; please remove listeners individually!');
process.removeAllListeners();
}
cb();
},
], function (err) {
if (err) {
// This should never happen because `err` is never passed in any of the async
// functions above. Still, just to be safe, we set up an error log.
sails.log.error('While lowering Sails app: received unexpected error:', err.stack);
return cb(err);
}
return cb();
});//</async.series>
});//</beforeShutdown()>
};