diff --git a/lib/app/lower.js b/lib/app/lower.js index fff9bf5cdb..98b06cb503 100644 --- a/lib/app/lower.js +++ b/lib/app/lower.js @@ -17,15 +17,25 @@ var _ = require('lodash'); * @api public */ -module.exports = function lower(cb) { +module.exports = function lower(options, cb) { var sails = this; sails.log.verbose('Lowering sails...'); + + // Options are optional + if ('function' == typeof options) { + cb = options; + options = null; + } + // 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 shutdown. // This may be used by hooks and other parts of core. // (e.g. to stop handling HTTP requests and prevent ugly error msgs) @@ -62,24 +72,37 @@ module.exports = function lower(cb) { async.series([ function shutdownSockets(cb) { - if (!_.isObject(sails.hooks) || !sails.hooks.sockets) { + + // 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.httpServer && sails.hooks.http.server === sails.io.httpServer)) { return cb(); } try { sails.log.verbose('Shutting down socket server...'); - var timeOut = setTimeout(cb, 100); - sails.io.server.unref(); - sails.io.server.close(); - sails.io.server.on('close', function() { - sails.log.verbose('Socket server shut down successfully.'); - clearTimeout(timeOut); - cb(); - }); + 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); cb(); } + + function onClose() { + sails.log.verbose('Socket server shut down successfully.'); + clearTimeout(timeOut); + cb(); + } + }, function shutdownHTTP(cb) { @@ -92,25 +115,36 @@ module.exports = function lower(cb) { try { sails.log.verbose('Shutting down HTTP server...'); - // Give the server 100ms to close all existing connections - // and emit the "close" event. After that, unbind our - // "close" listener and continue (this prevents the cb - // from being called twice). - timeOut = setTimeout(function() { - sails.hooks.http.server.removeListener('close', onClose); - return cb(); - }, 100); - // Allow process to exit once this server is closed sails.hooks.http.server.unref(); - // Stop the server from accepting new connections - sails.hooks.http.server.close(); + // 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.on('close', onClose); + sails.hooks.http.server.once('close', onClose); } catch (e) { + sails.log.verbose("Error occurred closing HTTP server: ", e); clearTimeout(timeOut); cb(); } diff --git a/lib/hooks/http/initialize.js b/lib/hooks/http/initialize.js index 5a94c6f32a..4340c4195f 100644 --- a/lib/hooks/http/initialize.js +++ b/lib/hooks/http/initialize.js @@ -48,6 +48,25 @@ module.exports = function(sails) { sails.hooks.http.server = createServer(serverOptions, sails.hooks.http.app); } else sails.hooks.http.server = createServer(sails.hooks.http.app); + // Keep track of all connections that come in, so we can destroy + // them later if we want to. + var connections = {}; + + sails.hooks.http.server.on('connection', function(conn) { + var key = conn.remoteAddress + ':' + conn.remotePort; + connections[key] = conn; + conn.on('close', function() { + delete connections[key]; + }); + }); + + // Create a `destroy` method we can use to do a hard shutdown of the server. + sails.hooks.http.destroy = function(cb) { + sails.log.verbose("Destroying http server..."); + sails.hooks.http.server.close(cb); + for (var key in connections) + connections[key].destroy(); + }; // Configure views if hook enabled if (sails.hooks.views) {