Unitech / Unitech/pm2

Graceful reload doesn't wait for new process to launch before killing old one

Open
#3,078 25 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

S: Need More Data
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:

  1. a new process spawns and starts listening for connections
  2. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.