From ce16728235ec773c8c0c39416ee09fe32d6c43b0 Mon Sep 17 00:00:00 2001 From: Unitech Date: Tue, 27 Jun 2017 23:53:52 +0200 Subject: [PATCH] #2951 pm2 reload timestamped lockfile --- CHANGELOG.md | 1 + constants.js | 1 + examples/wait-ready/ecosystem.json | 2 +- lib/API.js | 25 ++++++++-- lib/Common.js | 30 +++++++++++- paths.js | 2 + test/programmatic/lazy_api.mocha.js | 20 ++++---- test/programmatic/reload-locker.mocha.js | 62 ++++++++++++++++++++++++ 8 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 test/programmatic/reload-locker.mocha.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ab9ff783..ab4e15f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 2.5.1 +- #2951 pm2 reload command locker via timestamped lock file - force reverse interaction reconnection on internet discovery - `--instances -1` when having a 1 cpu is no-longer spawning no processes #2953 - refactor the context retrieving from error diff --git a/constants.js b/constants.js index f2cb789f..3f2fe455 100644 --- a/constants.js +++ b/constants.js @@ -61,6 +61,7 @@ var csts = { REMOTE_REVERSE_PORT : isNaN(parseInt(process.env.KEYMETRICS_REVERSE_PORT)) ? 43554 : parseInt(process.env.KEYMETRICS_REVERSE_PORT), REMOTE_HOST : 's1.keymetrics.io', SEND_INTERVAL : 1000, + RELOAD_LOCK_TIMEOUT : parseInt(process.env.PM2_RELOAD_LOCK_TIMEOUT) || 30000, GRACEFUL_TIMEOUT : parseInt(process.env.PM2_GRACEFUL_TIMEOUT) || 8000, GRACEFUL_LISTEN_TIMEOUT : parseInt(process.env.PM2_GRACEFUL_LISTEN_TIMEOUT) || 3000, LOGS_BUFFER_SIZE : 8, diff --git a/examples/wait-ready/ecosystem.json b/examples/wait-ready/ecosystem.json index 47fc2cbb..05cc5607 100644 --- a/examples/wait-ready/ecosystem.json +++ b/examples/wait-ready/ecosystem.json @@ -5,6 +5,6 @@ "instances": 2, "exec_mode": "cluster", "wait_ready": true, - "listen_timeout": 16000 + "listen_timeout": 1000 }] } diff --git a/lib/API.js b/lib/API.js index d5af544b..42969eac 100644 --- a/lib/API.js +++ b/lib/API.js @@ -320,8 +320,9 @@ API.prototype.start = function(cmd, opts, cb) { if (Common.isConfigFile(cmd) || (typeof(cmd) === 'object')) that._startJson(cmd, opts, 'restartProcessId', cb); - else + else { that._startScript(cmd, opts, cb); + } }; /** @@ -449,12 +450,30 @@ API.prototype.reload = function(process_name, opts, cb) { opts = {}; } + if (Common.lockReload() == false) { + Common.printError(conf.PREFIX_MSG_ERR + 'Reload already in progress, please try again later'); + return cb ? cb(new Error('Reload in progress')) : that.exitCli(conf.ERROR_EXIT); + } + if (Common.isConfigFile(process_name)) - that._startJson(process_name, opts, 'reloadProcessId'); + that._startJson(process_name, opts, 'reloadProcessId', function(err, apps) { + Common.unlockReload(); + if (err) + return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT); + return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT);; + }); else { if (opts && !opts.updateEnv) Common.printOut(IMMUTABLE_MSG); - that._operate('reloadProcessId', process_name, opts, cb); + + + that._operate('reloadProcessId', process_name, opts, function(err, apps) { + Common.unlockReload(); + + if (err) + return cb ? cb(err) : that.exitCli(conf.ERROR_EXIT); + return cb ? cb(null, apps) : that.exitCli(conf.SUCCESS_EXIT);; + }); } }; diff --git a/lib/Common.js b/lib/Common.js index 91d3e571..14df2823 100644 --- a/lib/Common.js +++ b/lib/Common.js @@ -14,7 +14,7 @@ var shelljs = require('shelljs'); var chalk = require('chalk'); var fclone = require('fclone'); var semver = require('semver'); - +var moment = require('moment'); var isBinary = require('./tools/isbinaryfile.js'); var cst = require('../constants.js'); var extItps = require('./API/interpreter.json'); @@ -69,6 +69,34 @@ function resolveCWD(custom_path, default_path) { return target_cwd; } +Common.lockReload = function() { + try { + var t1 = fs.readFileSync(cst.PM2_RELOAD_LOCKFILE).toString(); + + // Check if content and if time < 30 return locked + // Else if content detected (lock file staled), allow and rewritte + if (t1 && t1 != '' && moment().diff(parseInt(t1), 'seconds') < cst.RELOAD_LOCK_TIMEOUT) { + return false; + } + } catch(e) {} + + try { + // Write latest timestamp + fs.writeFileSync(cst.PM2_RELOAD_LOCKFILE, moment.now()); + return true; + } catch(e) { + console.error(e.message || e); + } +}; + +Common.unlockReload = function() { + try { + fs.writeFileSync(cst.PM2_RELOAD_LOCKFILE, ''); + } catch(e) { + console.error(e.message || e); + } +}; + /** * Resolve app paths and replace missing values with defaults. * @method prepareAppConf diff --git a/paths.js b/paths.js index 660979d3..5ae70642 100644 --- a/paths.js +++ b/paths.js @@ -41,6 +41,8 @@ module.exports = function(PM2_HOME) { PM2_LOG_FILE_PATH : p.resolve(PM2_HOME, 'pm2.log'), PM2_PID_FILE_PATH : p.resolve(PM2_HOME, 'pm2.pid'), + PM2_RELOAD_LOCKFILE : p.resolve(PM2_HOME, 'reload.lock'), + DEFAULT_PID_PATH : p.resolve(PM2_HOME, 'pids'), DEFAULT_LOG_PATH : p.resolve(PM2_HOME, 'logs'), DEFAULT_MODULE_PATH : p.resolve(PM2_HOME, 'node_modules'), diff --git a/test/programmatic/lazy_api.mocha.js b/test/programmatic/lazy_api.mocha.js index bab56011..c610042c 100644 --- a/test/programmatic/lazy_api.mocha.js +++ b/test/programmatic/lazy_api.mocha.js @@ -1,7 +1,10 @@ process.chdir(__dirname); +process.env.PM2_RELOAD_LOCK_TIMEOUT = 1000; + var PM2 = require('../..'); + var should = require('should'); describe('Lazy API usage', function() { @@ -10,10 +13,7 @@ describe('Lazy API usage', function() { }); it('should start a script without passing any args', function(done) { - PM2.start('./../fixtures/child.js'); - setTimeout(function() { - done(); - }, 300); + PM2.start('./../fixtures/child.js', done); }); it('should list one process', function(done) { @@ -24,10 +24,10 @@ describe('Lazy API usage', function() { }); it('should fail to start script', function(done) { - PM2.start('./../fixtures/child.js'); - setTimeout(function() { + PM2.start('./../fixtures/child.js', function(err) { + should.exists(err); done(); - }, 300); + }); }); it('should list one process', function(done) { @@ -39,10 +39,10 @@ describe('Lazy API usage', function() { it('should reload', function(done) { - PM2.reload('./../fixtures/child.js'); - setTimeout(function() { + PM2.reload('child', function(err) { + should.not.exists(err); done(); - }, 300); + }); }); it('should process been restarted', function(done) { diff --git a/test/programmatic/reload-locker.mocha.js b/test/programmatic/reload-locker.mocha.js new file mode 100644 index 00000000..1cfa0fea --- /dev/null +++ b/test/programmatic/reload-locker.mocha.js @@ -0,0 +1,62 @@ + + +process.env.NODE_ENV = 'test'; +process.env.PM2_RELOAD_LOCK_TIMEOUT = 2000; + +var PM2 = require('../..'); +var should = require('should'); +var path = require('path'); +var Plan = require('../helpers/plan.js'); +var fs = require('fs'); +var cst = require('../../constants.js'); + +process.chdir(__dirname); + +describe('Reload locker system', function() { + this.timeout(5000); + + var pm2 = new PM2.custom({ + cwd : '../fixtures' + }); + + after(function(done) { + pm2.kill(done) + }); + + it('should start app', function(done) { + pm2.start({ + script : './echo.js', + instances : 4 + }, function(err, data) { + should(err).be.null(); + + pm2.list(function(err, ret) { + should(err).be.null(); + ret.length.should.eql(4); + done(); + }); + }); + }); + + it('should trigger one reload and forbid the second', function(done) { + + pm2.reload('all', function() { + + }) + + setTimeout(function() { + fs.statSync(cst.PM2_RELOAD_LOCKFILE); + var dt = fs.readFileSync(cst.PM2_RELOAD_LOCKFILE); + console.log(dt.toString()); + pm2.reload('all', function(err) { + console.log(arguments); + if (err) { + done(err) + } + }); + }, 100); + }); + + + +});