From 8fdd275540be1d140607213dc8e60b6c9f25c09d Mon Sep 17 00:00:00 2001 From: Evan Tahler Date: Tue, 4 Dec 2012 22:44:07 -0800 Subject: [PATCH] creating _teardown methods and moving shut down behavior into each module from code --- actionHero.js | 93 ++++++++++--------------------- initializers/initActions.js | 95 +++++++++++++++++--------------- initializers/initCache.js | 77 ++++++++++++++++++++++++-- initializers/initRedis.js | 30 ++++++++-- initializers/initSocketServer.js | 14 +++-- initializers/initTasks.js | 82 ++++++++++++++++----------- initializers/initWebServer.js | 20 ++++++- initializers/initWebSockets.js | 7 +++ test/core_actionCluster.js | 35 +++++++----- versions.md | 8 +++ 10 files changed, 286 insertions(+), 175 deletions(-) diff --git a/actionHero.js b/actionHero.js index 7fb6889ae..9b500f208 100755 --- a/actionHero.js +++ b/actionHero.js @@ -6,7 +6,6 @@ var actionHero = function(){ var self = this; - self.running = false; self.initalizers = {}; self.api = {}; @@ -53,6 +52,8 @@ actionHero.prototype.start = function(params, next){ restart: self.restart, } + self.api.running = true; + if (params == null){params = {};} self.startingParams = params; @@ -117,7 +118,6 @@ actionHero.prototype.start = function(params, next){ self.api.bootTime = new Date().getTime(); self.api.log("server ID: " + self.api.id); self.api.log(successMessage, ["green", "bold"]); - self.running = true; if(next != null){ next(null, self.api); } @@ -128,75 +128,38 @@ actionHero.prototype.start = function(params, next){ actionHero.prototype.stop = function(next){ var self = this; - - if(self.running == true){ + if(self.api.running == true){ + self.api.running = false; self.api.log("Shutting down open servers and pausing tasks", "bold"); - for(var i in self.api.watchedFiles){ - self.api.fs.unwatchFile(self.api.watchedFiles[i]); - } - for(var worker_id in self.api.tasks.processTimers){ - clearTimeout(self.api.tasks.processTimers[worker_id]); - } - // allow running timers to finish, but do no work on next cycle. - self.api.tasks.process = function(api, worker_id){ } - - var cont = function(){ - var closed = 0; - var neededClosed = 0; - if(self.api.configData.httpServer.enable){ neededClosed++; } - if(self.api.configData.tcpServer.enable){ neededClosed++; } - - var checkForDone = function(serverType){ - if(serverType != null){ - self.api.log("The " + serverType + " server has ended its connections and closed"); - } - if(closed == neededClosed){ - closed = -1; - self.running = false; - self.api.pids.clearPidFile(); - self.api.log("The actionHero has been stopped", "bold"); - self.api.log("***"); - if(typeof next == "function"){ next(null, self.api); } - } - } - if(self.api.configData.httpServer.enable){ - self.api.webServer.server.on("close", function(){ - for(var i in self.api.webServer.clientClearTimers){ clearTimeout(self.api.webServer.clientClearTimers[i]); } - closed++; - checkForDone("http"); - }); - if(self.api.configData.webSockets.enable){ - self.api.webSockets.disconnectAll(self.api, function(){ - self.api.webServer.server.close(); - }); - }else{ - self.api.webServer.server.close(); - } + var orderedTeardowns = {}; + orderedTeardowns['watchedFiles'] = function(next){ + self.api.log(" > teardown: watchedFiles", 'gray'); + for(var i in self.api.watchedFiles){ + self.api.fs.unwatchFile(self.api.watchedFiles[i]); } + next(); + } - if(self.api.configData.tcpServer.enable){ - self.api.socketServer.gracefulShutdown(self.api, function(){ - closed++; - checkForDone("tcpServer"); - }); + for(var i in self.api){ + if(typeof self.api[i]._teardown == "function"){ + (function(name) { + orderedTeardowns[name] = function(next){ + self.api.log(" > teardown: " + name, 'gray'); + self.api[name]._teardown(self.api, next); + }; + })(i) } - checkForDone(); } - - // remove from the list of hosts - if(self.api.redis.enable){ - clearTimeout(self.api.redis.pingTimer); - clearTimeout(self.api.redis.lostPeerTimer); - self.api.redis.client.lrem("actionHero:peers", 1, self.api.id, function(err, count){ - if(count != 1){ self.api.log("Error removing myself from the peers list", "red"); } - self.api.redis.client.hdel("actionHero:peerPings", self.api.id, function(){ - cont(); - }); - }); - }else{ - cont(); + + orderedTeardowns['_complete'] = function(){ + self.api.pids.clearPidFile(); + self.api.log("The actionHero has been stopped", "bold"); + self.api.log("***"); + if(typeof next == "function"){ next(null, self.api); } } + + self.api.async.series(orderedTeardowns); }else{ self.api.log("Cannot shut down (not running any servers)"); if(typeof next == "function"){ next(null, self.api); } @@ -206,7 +169,7 @@ actionHero.prototype.stop = function(next){ actionHero.prototype.restart = function(next){ var self = this; - if(self.running == true){ + if(self.api.running == true){ self.stop(function(err){ self.start(self.startingParams, function(err, api){ api.log('actionHero restarted', "green"); diff --git a/initializers/initActions.js b/initializers/initActions.js index 52a741c59..40e12e582 100644 --- a/initializers/initActions.js +++ b/initializers/initActions.js @@ -121,66 +121,71 @@ var initActions = function(api, next) }); api.processAction = function(api, connection, messageID, next){ - if(connection.params.limit == null){ - connection.params.limit = api.configData.general.defaultLimit; - }else{ - connection.params.limit = parseFloat(connection.params.limit); - } + if(api.running != true){ + connection.error = "the server is shutting down"; + next(connection, true); + }else{ + if(connection.params.limit == null){ + connection.params.limit = api.configData.general.defaultLimit; + }else{ + connection.params.limit = parseFloat(connection.params.limit); + } - if(connection.params.offset == null){ - connection.params.offset = api.configData.general.defaultOffset; - }else{ - connection.params.offset = parseFloat(connection.params.offset); - } - - if (connection.error === null){ - if(connection.type == "web"){ api.utils.processRoute(api, connection); } - connection.action = connection.params["action"]; - if(api.actions[connection.action] != undefined){ - api.utils.requiredParamChecker(api, connection, api.actions[connection.action].inputs.required); - if(connection.error === null){ - process.nextTick(function() { - if(api.domain != null){ - var actionDomain = api.domain.create(); - actionDomain.on("error", function(err){ - api.exceptionHandlers.action(actionDomain, err, connection, next); - }); - actionDomain.run(function(){ + if(connection.params.offset == null){ + connection.params.offset = api.configData.general.defaultOffset; + }else{ + connection.params.offset = parseFloat(connection.params.offset); + } + + if (connection.error === null){ + if(connection.type == "web"){ api.utils.processRoute(api, connection); } + connection.action = connection.params["action"]; + if(api.actions[connection.action] != undefined){ + api.utils.requiredParamChecker(api, connection, api.actions[connection.action].inputs.required); + if(connection.error === null){ + process.nextTick(function() { + if(api.domain != null){ + var actionDomain = api.domain.create(); + actionDomain.on("error", function(err){ + api.exceptionHandlers.action(actionDomain, err, connection, next); + }); + actionDomain.run(function(){ + api.actions[connection.action].run(api, connection, function(connection, toRender){ + connection.respondingTo = messageID; + // actionDomain.dispose(); + next(connection, toRender); + }); + }) + }else{ api.actions[connection.action].run(api, connection, function(connection, toRender){ connection.respondingTo = messageID; - // actionDomain.dispose(); next(connection, toRender); }); - }) - }else{ - api.actions[connection.action].run(api, connection, function(connection, toRender){ - connection.respondingTo = messageID; - next(connection, toRender); - }); - } - }); + } + }); + }else{ + process.nextTick(function() { + connection.respondingTo = messageID; + next(connection, true); + }); + } }else{ - process.nextTick(function() { + if(connection.action == "" || connection.action == null){ connection.action = "{no action}"; } + connection.error = new Error(connection.action + " is not a known action."); + if(api.configData.commonWeb.returnErrorCodes == true && connection.type == "web"){ + connection.responseHttpCode = 404; + } + process.nextTick(function(){ connection.respondingTo = messageID; - next(connection, true); + next(connection, true); }); } }else{ - if(connection.action == "" || connection.action == null){ connection.action = "{no action}"; } - connection.error = new Error(connection.action + " is not a known action."); - if(api.configData.commonWeb.returnErrorCodes == true && connection.type == "web"){ - connection.responseHttpCode = 404; - } process.nextTick(function(){ connection.respondingTo = messageID; next(connection, true); }); } - }else{ - process.nextTick(function(){ - connection.respondingTo = messageID; - next(connection, true); - }); } } } diff --git a/initializers/initCache.js b/initializers/initCache.js index 914c18904..fbad9e550 100644 --- a/initializers/initCache.js +++ b/initializers/initCache.js @@ -5,6 +5,17 @@ var initCache = function(api, next){ api.cache = {}; + api.cache.sweeperTimer = null; + api.cache.sweeperTimeout = 1000; + + api.cache.stopTimers = function(api){ + clearTimeout(api.cache.sweeperTimer); + } + + api.cache._teardown = function(api, next){ + api.cache.stopTimers(api); + next(); + } if(api.redis && api.redis.enable === true){ @@ -12,14 +23,14 @@ var initCache = function(api, next){ api.cache.size = function(api, next){ api.redis.client.hlen(redisCacheKey, function(err, count){ - next(count); + next(null, count); }); } api.cache.load = function(api, key, next){ - api.redis.client.hget(redisCacheKey, key, function (err, cacheObj){ + api.redis.client.hget(redisCacheKey, key, function(err, cacheObj){ if(err != null){ api.log(err, red); } - cacheObj = JSON.parse(cacheObj); + try{ var cacheObj = JSON.parse(cacheObj); }catch(e){ } if(cacheObj == null){ if(typeof next == "function"){ process.nextTick(function() { next(new Error("Object not found"), null, null, null, null); }); @@ -48,12 +59,44 @@ var initCache = function(api, next){ }); }; + api.cache.sweeper = function(api, next){ + api.redis.client.hkeys(redisCacheKey, function(err, keys){ + var started = 0; + var sweepedKeys = []; + keys.forEach(function(key){ + started++; + api.redis.client.hget(redisCacheKey, key, function(err, cacheObj){ + if(err != null){ api.log(err, red); } + try{ var cacheObj = JSON.parse(cacheObj); }catch(e){ } + if(cacheObj != null){ + if(cacheObj.expireTimestamp < new Date().getTime()){ + api.redis.client.hdel(redisCacheKey, key, function(err, count){ + sweepedKeys.push(key); + started--; + if(started == 0 && typeof next == "function"){ next(err, sweepedKeys); } + }); + }else{ + started--; + if(started == 0 && typeof next == "function"){ next(err, sweepedKeys); } + } + }else{ + started--; + if(started == 0 && typeof next == "function"){ next(err, sweepedKeys); } + } + }); + }); + if(keys.length == 0 && typeof next == "function"){ next(err, sweepedKeys); } + }); + } + }else{ api.cache.data = {}; api.cache.size = function(api, next){ - next(api.utils.hashLength(api.cache.data)); + process.nextTick(function(){ + next(null, api.utils.hashLength(api.cache.data)); + }); } api.cache.load = function(api, key, next){ @@ -83,6 +126,18 @@ var initCache = function(api, next){ if(typeof next == "function"){ process.nextTick(function() { next(null, true); }); } } }; + + api.cache.sweeper = function(api, next){ + var sweepedKeys = []; + for (var i in api.cache.data){ + var entry = api.cache.data[i]; + if ( entry.expireTimestamp != null && entry.expireTimestamp < new Date().getTime() ){ + sweepedKeys.push(i); + delete api.cache.data[i]; + } + } + if(typeof next == "function"){ next(null, sweepedKeys); } + } } api.cache.save = function(api, key, value, expireTimeMS, next){ @@ -116,8 +171,20 @@ var initCache = function(api, next){ } }; - next(); + api.cache.runSweeper = function(api){ + clearTimeout(api.cache.sweeperTimer); + api.cache.sweeper(api, function(err, sweepedKeys){ + if(sweepedKeys.length > 0){ + api.log("cleaned " + sweepedKeys.length + " expired cache keys"); + } + if(api.running){ + api.cache.sweeperTimer = setTimeout(api.cache.runSweeper, api.cache.sweeperTimeout, api); + } + }); + } + api.cache.runSweeper(api); + next(); } ///////////////////////////////////////////////////////////////////// diff --git a/initializers/initRedis.js b/initializers/initRedis.js index 24bf4f073..bf308a050 100644 --- a/initializers/initRedis.js +++ b/initializers/initRedis.js @@ -26,8 +26,7 @@ actionHero will create the following stores within your redis database: var c = {}; -var initRedis = function(api, next) -{ +var initRedis = function(api, next){ c = api.configData.redis; api.redis = {}; api.redis.enable = c.enable; @@ -139,10 +138,27 @@ var initPubSub = function(api, c, next){ var initPingAndCheck = function(api, next){ + api.redis.stopTimers = function(api){ + clearTimeout(api.redis.pingTimer); + clearTimeout(api.redis.lostPeerTimer); + } + + api.redis._teardown = function(api, next){ + api.redis.stopTimers(api); + api.redis.client.lrem("actionHero:peers", 1, api.id, function(err, count){ + if(count != 1){ api.log("Error removing myself from the peers list", "red"); } + api.redis.client.hdel("actionHero:peerPings", api.id, function(){ + next(); + }); + }); + } + api.redis.ping = function(api, next){ clearTimeout(api.redis.pingTimer); api.redis.client.hset("actionHero:peerPings", api.id, new Date().getTime(), function(){ - api.redis.pingTimer = setTimeout(api.redis.ping, api.redis.pingTime, api); + if(api.running){ + api.redis.pingTimer = setTimeout(api.redis.ping, api.redis.pingTime, api); + } if (typeof next == "function"){ next(); } }); } @@ -174,14 +190,18 @@ var initPingAndCheck = function(api, next){ api.tasks.enqueue(api, task.taskName, new Date().getTime(), task.params); tasksCleaned--; if(tasksCleaned == 0){ - api.redis.lostPeerTimer = setTimeout(api.redis.checkForDroppedPeers, api.redis.lostPeerCheckTime, api); + if(api.running){ + api.redis.lostPeerTimer = setTimeout(api.redis.checkForDroppedPeers, api.redis.lostPeerCheckTime, api); + } if (typeof next == "function"){ next(); } } }); } }); }else{ - api.redis.lostPeerTimer = setTimeout(api.redis.checkForDroppedPeers, api.redis.lostPeerCheckTime, api); + if(api.running){ + api.redis.lostPeerTimer = setTimeout(api.redis.checkForDroppedPeers, api.redis.lostPeerCheckTime, api); + } if (typeof next == "function"){ next(); } } }); diff --git a/initializers/initSocketServer.js b/initializers/initSocketServer.js index c571a63d2..1453f287b 100644 --- a/initializers/initSocketServer.js +++ b/initializers/initSocketServer.js @@ -13,17 +13,17 @@ var initSocketServer = function(api, next){ // server if(api.configData.tcpServer.secure == false){ api.socketServer.server = api.net.createServer(function(connection){ - handleConnection(connection); + api.socketServer.handleConnection(connection); }); }else{ var key = api.fs.readFileSync(api.configData.httpServer.keyFile); var cert = api.fs.readFileSync(api.configData.httpServer.certFile); api.socketServer.server = api.tls.createServer({key: key, cert: cert}, function(connection){ - handleConnection(connection); + api.socketServer.handleConnection(connection); }); } - var handleConnection = function(connection){ + api.socketServer.handleConnection = function(connection){ api.socketServer.numberOfLocalSocketRequests++; api.utils.setupConnection(api, connection, "socket", connection.remotePort, connection.remoteAddress); @@ -299,11 +299,15 @@ var initSocketServer = function(api, next){ api.log("[socket] waiting on shutdown, there are still " + pendingConnections + " connected clients waiting on a response"); setTimeout(function(){ api.socketServer.gracefulShutdown(api, next, alreadyShutdown); - }, 3000); + }, 1000); }else{ - next(); + if(typeof next == 'function'){ next(); } } } + + api.socketServer._teardown = function(api, next){ + api.socketServer.gracefulShutdown(api, next); + } //////////////////////////////////////////////////////////////////////////// // listen diff --git a/initializers/initTasks.js b/initializers/initTasks.js index 9bf819d8d..578d283ed 100644 --- a/initializers/initTasks.js +++ b/initializers/initTasks.js @@ -24,6 +24,18 @@ var initTasks = function(api, next) if(api.configData.general.workers == null){ api.configData.general.workers = 1; } + + api.tasks.stopTimers = function(api){ + clearTimeout(api.tasks.periodicTaskReloader); + for(var worker_id in api.tasks.processTimers){ + clearTimeout(api.tasks.processTimers[worker_id]); + } + } + + api.tasks._teardown = function(api, next){ + api.tasks.stopTimers(api); + next(); + } api.tasks.enqueue = function(api, taskName, runAtTime, params, next, toAnnounce){ if(toAnnounce == null){ toAnnounce = true; } @@ -265,24 +277,34 @@ var initTasks = function(api, next) api.tasks.process = function(api, worker_id){ clearTimeout(api.tasks.processTimers[worker_id]); - if(api.tasks.enqueLock == true){ - api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); - }else{ - api.tasks.getNextTask(api, function(err, task){ - if(task == null){ - api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); - }else{ - api.tasks.currentTasks[worker_id] = JSON.stringify(task); - api.tasks.run(api, task.taskName, task.params, function(run){ - if(run){ - api.log("[timer "+worker_id+"] ran task: "+task.taskName, "yellow"); - }else{ - api.log("[timer "+worker_id+"] task failed to run: "+JSON.stringify(task), "red") - } - delete api.tasks.currentTasks[worker_id]; - if(api.redis.enable === true){ - // remove the task from the processing queue (redis only) - api.redis.client.hdel(api.tasks.redisProcessingQueue, task.taskName, function(){ + if(api.running){ + if(api.tasks.enqueLock == true){ + api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); + }else{ + api.tasks.getNextTask(api, function(err, task){ + if(task == null){ + api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); + }else{ + api.tasks.currentTasks[worker_id] = JSON.stringify(task); + api.tasks.run(api, task.taskName, task.params, function(run){ + if(run){ + api.log("[timer "+worker_id+"] ran task: "+task.taskName, "yellow"); + }else{ + api.log("[timer "+worker_id+"] task failed to run: "+JSON.stringify(task), "red") + } + delete api.tasks.currentTasks[worker_id]; + if(api.redis.enable === true){ + // remove the task from the processing queue (redis only) + api.redis.client.hdel(api.tasks.redisProcessingQueue, task.taskName, function(){ + if(api.tasks.tasks[task.taskName].frequency > 0){ + api.tasks.enqueuePeriodicTask(api, api.tasks.tasks[task.taskName], function(){ + api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); + }, false); + }else{ + api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); + } + }); + }else{ if(api.tasks.tasks[task.taskName].frequency > 0){ api.tasks.enqueuePeriodicTask(api, api.tasks.tasks[task.taskName], function(){ api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); @@ -290,19 +312,11 @@ var initTasks = function(api, next) }else{ api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); } - }); - }else{ - if(api.tasks.tasks[task.taskName].frequency > 0){ - api.tasks.enqueuePeriodicTask(api, api.tasks.tasks[task.taskName], function(){ - api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); - }, false); - }else{ - api.tasks.processTimers[worker_id] = setTimeout(api.tasks.process, api.tasks.cycleTimeMS, api, worker_id); } - } - }); - } - }); + }); + } + }); + } } }; @@ -327,14 +341,18 @@ var initTasks = function(api, next) api.tasks.enqueuePeriodicTask(api, task, function(){ started--; if(started == 0){ - api.tasks.periodicTaskReloader = setTimeout(api.tasks.startPeriodicTasks, api.tasks.reloadPeriodicsTime, api); + if(api.running){ + api.tasks.periodicTaskReloader = setTimeout(api.tasks.startPeriodicTasks, api.tasks.reloadPeriodicsTime, api); + } if(typeof next == "function"){ next(); }; } }); } } if(started == 0){ - api.tasks.periodicTaskReloader = setTimeout(api.tasks.startPeriodicTasks, api.tasks.reloadPeriodicsTime, api); + if(api.running){ + api.tasks.periodicTaskReloader = setTimeout(api.tasks.startPeriodicTasks, api.tasks.reloadPeriodicsTime, api); + } if(typeof next == "function"){ next(); }; } } diff --git a/initializers/initWebServer.js b/initializers/initWebServer.js index 65b5e986c..82cf85831 100644 --- a/initializers/initWebServer.js +++ b/initializers/initWebServer.js @@ -16,17 +16,17 @@ var initWebServer = function(api, next) // server if(api.configData.httpServer.secure == false){ api.webServer.server = api.http.createServer(function (req, res) { - handleRequest(req, res); + api.webServer.handleRequest(req, res); }); }else{ var key = api.fs.readFileSync(api.configData.httpServer.keyFile); var cert = api.fs.readFileSync(api.configData.httpServer.certFile); api.webServer.server = api.https.createServer({key: key, cert: cert}, function (req, res) { - handleRequest(req, res); + api.webServer.handleRequest(req, res); }); } - var handleRequest = function(req, res){ + api.webServer.handleRequest = function(req, res){ api.stats.increment(api, "numberOfWebRequests"); api.webServer.numberOfLocalWebRequests++; @@ -222,6 +222,20 @@ var initWebServer = function(api, next) }); }; + api.webServer.stopTimers = function(api){ + for(var i in api.webServer.clientClearTimers){ + clearTimeout(api.webServer.clientClearTimers[i]); + } + } + + api.webServer._teardown = function(api, next){ + api.webServer.stopTimers(api); + if(api.configData.webSockets.enable != true){ + api.webServer.server.close(); + } + next(); + } + //////////////////////////////////////////////////////////////////////////// // Helpers to ensure uniqueness on response headers api.webServer.cleanHeaders = function(api, connection){ diff --git a/initializers/initWebSockets.js b/initializers/initWebSockets.js index b07ea97dc..15f1cf2c8 100644 --- a/initializers/initWebSockets.js +++ b/initializers/initWebSockets.js @@ -220,6 +220,13 @@ var initWebSockets = function(api, next){ if(typeof next == "function"){ next(); } } + api.webSockets._teardown = function(api, next){ + api.webSockets.disconnectAll(api, function(){ + api.webServer.server.close(); + next(); + }); + } + api.webSockets.io = io; api.log("webSockets bound to " + api.configData.httpServer.port, "green"); next(); diff --git a/test/core_actionCluster.js b/test/core_actionCluster.js index 9aef6fc70..c85b5d470 100644 --- a/test/core_actionCluster.js +++ b/test/core_actionCluster.js @@ -171,32 +171,37 @@ describe('Core: actionCluster', function(){ before(function(done){ clearTimeout(apis[2].redis.pingTimer); - apis[2].redis.ping = null; + apis[2].running = false; done(); }); it("If a peer goes away, it should be removed from the list of peers (ping)", function(done){ this.timeout(0); var sleepTime = (apis[0].redis.lostPeerCheckTime * 3) + 1; - setTimeout(function(){ - apis[0].redis.checkForDroppedPeers(apis[0], function(){ - apis[0].redis.client.hgetall("actionHero:peerPings", function (err, peerPings){ - apis[0].redis.client.llen("actionHero:peers", function(err, length){ - apis[0].redis.client.lrange("actionHero:peers", 0, length, function(err, peers){ - var count = 0; - for (var i in peerPings){ - count++; - } - count.should.equal(2); - peers.length.should.equal(2); - done(); - }); + setTimeout(function(){ + apis[0].redis.checkForDroppedPeers(apis[0], function(){ + apis[0].redis.client.hgetall("actionHero:peerPings", function (err, peerPings){ + apis[0].redis.client.llen("actionHero:peers", function(err, length){ + apis[0].redis.client.lrange("actionHero:peers", 0, length, function(err, peers){ + var count = 0; + for (var i in peerPings){ + count++; + } + count.should.equal(2); + peers.length.should.equal(2); + done(); }); }); }); - }, sleepTime ); + }); + }, sleepTime ); }); + after(function(done){ + apis[2].running = true; + done(); + }) + }); }); diff --git a/versions.md b/versions.md index 16e5d0d3d..c319b4dca 100644 --- a/versions.md +++ b/versions.md @@ -2,11 +2,19 @@ ## Version 4.0.3 +** initializers ** + +- you can now define api.{module}._teardown in any api module to be called at shutdown. +- `_teardown(api, next)` will passed to this method +- a `_teardown` method is not required + ** Bugs ** - fix duplicate headers sometimes returned to http(s) clients - fixed logging for actionCluster - fixed SIGWINCH so only daemonized clusters can use it +- added in a sweeper for api.cache, so that expired values will be deleted eventually +- better locking out of internal timers when the cluster is off ## Version 4.0.2