Graceful reload doesn't wait for new process to launch before killing old one
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 43.3k
- Forks
- 2.7k
- PR merge metrics
- No merged PRs in 30d
Description
What's going wrong?
Graceful reload basically doesn't work at all, as far as I can tell. Am I doing something wrong?
How could we reproduce this issue?
Tail your pm2 logs using pm2 log, then run the following test file with pm2 start test.js --kill-timeout 20000 --listen-timeout 20000 --wait-ready:
console.log(new Date(), 'New process launched')
var http = require('http');
var app = http.createServer((req, res) => {
// Simple request handler that waits for a long time and then emits a hard-coded string.
setTimeout(() => {
res.writeHead(200);
res.end('MOOOOOO');
}, 10000);
});
process.on('SIGINT', function() {
console.log(new Date(), 'Got SIGINT')
// Stop accepting new connections, wait for all existing connections to be handled, and then exit.
app.close(() => {
console.log(new Date(), 'exiting')
process.exit()
});
});
process.on('message', function(msg) {
// Never gets called
console.log(new Date(), 'msg:', msg)
})
app.listen(1234, function() {
console.log(new Date(), 'Listening on port 1234');
// Tell pm2 that we're ready to replace the old process
process.send('ready');
});
Then:
- optionally run
curl localhost:1234(not needed to see that something is wrong) - run
pm2 gracefulReload all
Expected behaviour:
The following things happen in order:
- a new process spawns and starts listening for connections
- the old process receives SIGINT, stops listening for connections, finishes handling any requests it's already handling, then exits
There should be zero downtime with the flow described above.
Observed behaviour
The new process doesn't even launch until the old process is completely terminated, as shown by these logs that are printed when I do pm2 gracefulReload all:
PM2 | Process 0 in a stopped status, starting it
PM2 | Stopping app:test id:0
0|test | 2017-08-09T13:27:22.385Z 'Got SIGINT'
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
PM2 | pid=66127 msg=failed to kill - retrying in 100ms
0|test | 2017-08-09T13:27:27.742Z 'exiting'
PM2 | App [test] with id [0] and pid [66127], exited with code [0] via signal [SIGINT]
PM2 | pid=66127 msg=process killed
PM2 | Starting execution sequence in -fork mode- for app name:test id:0
0|test | 2017-08-09T13:27:28.050Z 'New process launched'
0|test | 2017-08-09T13:27:28.083Z 'Listening on port 1234'
PM2 | App name:test id:0 online
Supporting information
The docs at http://pm2.keymetrics.io/docs/usage/pm2-doc-single-page/ seem to back up my expectations of what the behaviour should be; they state:
When PM2 starts a new process to replace an old one, it will wait for the new process to begin listening to a connection or a timeout before sending the shutdown message to the old one
This is very clearly not what's happening here! Am I doing something wrong, or misunderstanding the docs, or is this feature fundamentally broken? I'm pretty confused!
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the behavior with test.js using the shown pm2 start options, then invoke pm2 gracefulReload all while watching pm2 log. Trace the graceful reload entry point and verify that the replacement process reaches its listening or ready state before the old process receives SIGINT; done means the documented zero-downtime order is observed.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- cli, devops, infrastructure
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100