From 7e6bcf47e424860ff72e32a272946f00d2814504 Mon Sep 17 00:00:00 2001 From: Aleksey Kabanov Date: Thu, 17 Oct 2019 14:55:52 -0700 Subject: [PATCH] Extracted kill signal to the constants.js. Added new environment variable 'PM2_KILL_USE_SEND' (false by default) to allow PM2 to use process.send() instead of the process.kill() from the God.killProcess() method. This allows applications not to depend on the platform-specific signal behavior and eventually have a graceful shutdown capability on Windows. --- constants.js | 3 + lib/God/Methods.js | 20 +++- test/e2e/cli/reload.sh | 20 ++++ test/fixtures/signal-send.js | 7 ++ test/fixtures/signals/delayed_send.js | 11 +++ test/programmatic/signals.js | 130 +++++++++++++++++++++++++- 6 files changed, 187 insertions(+), 4 deletions(-) create mode 100644 test/fixtures/signal-send.js create mode 100644 test/fixtures/signals/delayed_send.js diff --git a/constants.js b/constants.js index 897bff476..272b49e05 100644 --- a/constants.js +++ b/constants.js @@ -100,6 +100,9 @@ var csts = { WORKER_INTERVAL : process.env.PM2_WORKER_INTERVAL || 30000, KILL_TIMEOUT : process.env.PM2_KILL_TIMEOUT || 1600, + KILL_SIGNAL : process.env.PM2_KILL_SIGNAL || 'SIGINT', + KILL_USE_SEND : process.env.PM2_KILL_USE_SEND || false, + PM2_PROGRAMMATIC : typeof(process.env.pm_id) !== 'undefined' || process.env.PM2_PROGRAMMATIC, PM2_LOG_DATE_FORMAT : process.env.PM2_LOG_DATE_FORMAT !== undefined ? process.env.PM2_LOG_DATE_FORMAT : 'YYYY-MM-DDTHH:mm:ss' diff --git a/lib/God/Methods.js b/lib/God/Methods.js index 04534ba27..cd090c13b 100644 --- a/lib/God/Methods.js +++ b/lib/God/Methods.js @@ -207,18 +207,32 @@ module.exports = function(God) { God.killProcess = function(pid, pm2_env, cb) { if (!pid) return cb({msg : 'no pid passed or null'}); - var mode = pm2_env.exec_mode; + if (cst.KILL_USE_SEND && typeof(pm2_env.pm_id) === 'number') { + var proc = God.clusters_db[pm2_env.pm_id]; + + if (proc && proc.send) { + try { + proc.send(cst.KILL_SIGNAL); + } catch (e) { + console.error('[SimpleKill] %s pid can not be killed', pid, e.stack, e.message); + } + return God.processIsDead(pid, pm2_env, cb); + } + else { + console.log('[SimpleKill] %s pid cannot be notified with send()', pid); + } + } if (pm2_env.treekill !== true) { try { - process.kill(parseInt(pid), process.env.PM2_KILL_SIGNAL || 'SIGINT'); + process.kill(parseInt(pid), cst.KILL_SIGNAL); } catch(e) { console.error('[SimpleKill] %s pid can not be killed', pid, e.stack, e.message); } return God.processIsDead(pid, pm2_env, cb); } else { - treekill(parseInt(pid), process.env.PM2_KILL_SIGNAL || 'SIGINT', function(err) { + treekill(parseInt(pid), cst.KILL_SIGNAL, function(err) { return God.processIsDead(pid, pm2_env, cb); }); } diff --git a/test/e2e/cli/reload.sh b/test/e2e/cli/reload.sh index 5b4c8e5c8..67282ef44 100644 --- a/test/e2e/cli/reload.sh +++ b/test/e2e/cli/reload.sh @@ -107,3 +107,23 @@ should 'should not restart' 'restart_time: 0' 1 #$pm2 web #$pm2 reload all $pm2 kill + +############### SEND() instead of KILL() +$pm2 kill +export PM2_KILL_USE_SEND='true' + +$pm2 start signal-send.js +should 'should start processes' 'online' 1 + +OUT_LOG=`$pm2 prettylist | grep -m 1 -E "pm_out_log_path:" | sed "s/.*'\([^']*\)',/\1/"` +> $OUT_LOG + +$pm2 reload signal-send.js +sleep 1 + +OUT=`grep "SIGINT" "$OUT_LOG" | wc -l` +[ $OUT -eq 1 ] || fail "Signal not received by the process name" +success "Processes sucessfully receives the signal" + +unset PM2_KILL_USE_SEND +$pm2 kill diff --git a/test/fixtures/signal-send.js b/test/fixtures/signal-send.js new file mode 100644 index 000000000..18dddbddb --- /dev/null +++ b/test/fixtures/signal-send.js @@ -0,0 +1,7 @@ + +setInterval(function() { +}, 1000); + +process.on('message', function (msg) { + console.log(msg); +}); diff --git a/test/fixtures/signals/delayed_send.js b/test/fixtures/signals/delayed_send.js new file mode 100644 index 000000000..76ba90245 --- /dev/null +++ b/test/fixtures/signals/delayed_send.js @@ -0,0 +1,11 @@ + +setInterval(function() { + // Do nothing to keep process alive +}, 1000); + +process.on('message', function (msg) { + if (msg === 'SIGINT') { + console.log('SIGINT message received but forbid exit'); + } +}); + diff --git a/test/programmatic/signals.js b/test/programmatic/signals.js index 27e51a220..a0c332f3c 100644 --- a/test/programmatic/signals.js +++ b/test/programmatic/signals.js @@ -76,7 +76,7 @@ describe('Signal kill (+delayed)', function() { it('should start a script', function(done) { pm2.start({ - script : './delayed_sigint.js', + script : './signals/delayed_sigint.js', name : 'delayed-sigint' }, function(err, data) { proc1 = data[0]; @@ -206,3 +206,131 @@ describe('Signal kill (+delayed)', function() { }); }); + +describe('Message kill (signal behavior override via PM2_KILL_USE_SEND, +delayed)', function() { + var proc1 = null; + var appName = 'delayed-send'; + + process.env.PM2_KILL_USE_SEND = true; + + var pm2 = new PM2.custom({ + cwd : __dirname + '/../fixtures', + }); + + after(function(done) { + pm2.delete('all', function(err, ret) { + pm2.kill(done); + }); + }); + + before(function(done) { + pm2.connect(function() { + pm2.delete('all', function(err, ret) { + done(); + }); + }); + }); + + describe('with 1000ms PM2_KILL_TIMEOUT (environment variable)', function() { + it('should set 1000ms to PM2_KILL_TIMEOUT', function(done) { + process.env.PM2_KILL_TIMEOUT = 1000; + + pm2.update(function() { + done(); + }); + }); + + it('should start a script', function(done) { + pm2.start({ + script : './signals/delayed_send.js', + name : appName, + }, function(err, data) { + proc1 = data[0]; + should(err).be.null(); + setTimeout(done, 1000); + }); + }); + + it('should stop script after 1000ms', function(done) { + setTimeout(function() { + pm2.describe(appName, function(err, list) { + should(err).be.null(); + list[0].pm2_env.status.should.eql('stopping'); + }); + }, 500); + + setTimeout(function() { + pm2.describe(appName, function(err, list) { + should(err).be.null(); + list[0].pm2_env.status.should.eql('stopped'); + done(); + }); + }, 1500); + + pm2.stop(appName, function(err, app) { + //done(err); + }); + + }); + }); + + describe('[CLUSTER MODE] with 1000ms PM2_KILL_TIMEOUT (environment variable)', function() { + it('should set 1000ms to PM2_KILL_TIMEOUT', function(done) { + process.env.PM2_KILL_TIMEOUT = 1000; + + pm2.update(function() { + done(); + }); + }); + + it('should start a script', function(done) { + pm2.start({ + script : './signals/delayed_send.js', + name : appName, + exec_mode : 'cluster' + }, function(err, data) { + proc1 = data[0]; + should(err).be.null(); + setTimeout(done, 1000); + }); + }); + + it('should stop script after 1000ms', function(done) { + setTimeout(function() { + pm2.describe(appName, function(err, list) { + should(err).be.null(); + list[0].pm2_env.status.should.eql('stopping'); + }); + }, 500); + + setTimeout(function() { + pm2.describe(appName, function(err, list) { + should(err).be.null(); + list[0].pm2_env.status.should.eql('stopped'); + done(); + }); + }, 1500); + + pm2.stop(appName, function(err, app) { + //done(err); + }); + + }); + + it('should reload script', function(done) { + setTimeout(function() { + pm2.describe(appName, function(err, list) { + should(err).be.null(); + list[0].pm2_env.status.should.eql('online'); + done(); + }); + }, 1500); + + pm2.reload(appName, function(err, app) { + //done(err); + }); + + }); + }); + +});