From d1767da26de2a7d690f6621264d3af90164a714e Mon Sep 17 00:00:00 2001 From: Naomi Seyfer Date: Mon, 2 Dec 2013 14:18:53 -0800 Subject: [PATCH] Short socket timeout while no pending request. Long timeout with pending req --- packages/livedata/stream_server.js | 7 ++++ packages/webapp/webapp_server.js | 51 +++++++++++++++--------------- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/packages/livedata/stream_server.js b/packages/livedata/stream_server.js index c675cb9e57..8e90ae49fb 100644 --- a/packages/livedata/stream_server.js +++ b/packages/livedata/stream_server.js @@ -46,7 +46,14 @@ StreamServer = function () { if (!Package.webapp) { throw new Error("Cannot create a DDP server without the webapp package"); } + // Install the sockjs handlers, but we want to keep around our own particular + // request handler that adjusts idle timeouts while we have an outstanding + // request. This compensates for the fact that sockjs removes all listeners + // for "request" to add its own. + Package.webapp.WebApp.httpServer.removeListener('request', Package.webapp.WebApp._timeoutAdjustmentRequestCallback); self.server.installHandlers(Package.webapp.WebApp.httpServer); + Package.webapp.WebApp.httpServer.addListener('request', Package.webapp.WebApp._timeoutAdjustmentRequestCallback); + Package.webapp.WebApp.httpServer.on('closing', function () { _.each(self.open_sockets, function (socket) { socket.end(); diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 1dbfb9d7ab..b4d22ce5de 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -12,6 +12,9 @@ var optimist = Npm.require('optimist'); var useragent = Npm.require('useragent'); var send = Npm.require('send'); +var SHORT_SOCKET_TIMEOUT = 3*1000; +var LONG_SOCKET_TIMEOUT = 120*1000; + WebApp = {}; WebAppInternals = {}; @@ -196,6 +199,23 @@ Meteor.startup(function () { }); + +// When we have a request pending, we want the socket timeout to be long, to +// give ourselves a while to serve it, and to allow sockjs long polls to +// complete. On the other hand, we want to close idle sockets relatively +// quickly, so that we can shut down relatively promptly but cleanly, without +// cutting off anyone's response. +WebApp._timeoutAdjustmentRequestCallback = function (req, res) { + req.setTimeout(LONG_SOCKET_TIMEOUT); // this is really just req.socket.setTimeout(LONG_AMOUNT); + // Insert our new finish listener to run BEFORE the existing one which removes the response from the socket. + var finishListeners = res.listeners('finish'); + res.removeAllListeners('finish'); + res.on('finish', function () { + res.setTimeout(SHORT_SOCKET_TIMEOUT); // again, basically just res.socket.setTimeout + }); + _.each(finishListeners, function (l) { res.on('finish', l); }); +}; + var runWebAppServer = function () { var shuttingDown = false; // read the control for the client we'll be serving up @@ -417,42 +437,21 @@ var runWebAppServer = function () { var httpServer = http.createServer(app); var onListeningCallbacks = []; - var longPollingSockets = {}; - // After 5 seconds of a socket being open, assume it is a long-polling // connection that we have to keep track of to shut down when we're shutting // down the server overall. - httpServer.setTimeout(5000, Meteor.bindEnvironment(function (socket) { - if (shuttingDown) { - socket.end(); - } else { - socket._meteorLongPollingId = Random.id(); - longPollingSockets[socket._meteorLongPollingId] = socket; - // give the socket another minute to live. - var destroy = Meteor.setTimeout(function () { - delete longPollingSockets[socket._meteorLongPollingId]; - socket.removeListener('close', onClose); - socket.destroy(); - }, 60*1000); + httpServer.setTimeout(SHORT_SOCKET_TIMEOUT); - var onClose = function () { - delete longPollingSockets[socket._meteorLongPollingId]; - Meteor.clearTimeout(destroy); - }; - socket.on('close', onClose); - } - }, function (err) { - console.log(err); - })); + // Do this here, and then also in livedata/stream_server.js, because + // stream_server.js kills all the current request handlers when installing its + // own. + httpServer.on('request', WebApp._timeoutAdjustmentRequestCallback); // For now, handle SIGHUP here. Later, this should be in some centralized // Meteor shutdown code. process.on('SIGHUP', Meteor.bindEnvironment(function () { shuttingDown = true; - _.each(longPollingSockets, function (socket, id) { - socket.end(); - }); // tell others with websockets open that we plan to close this. httpServer.emit('closing'); httpServer.close( function () {