Adding optional switch to enable graceful shutdown on Windows #4474
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Problem:
There is no official and easy way to implement the graceful shutdown on Windows.
For graceful shutdown PM2 pretty much relies on the
process.kill()
and applications being able to ignoreSIGINT
to delay shutdown, but it doesn't work on Windows.https://pm2.keymetrics.io/docs/usage/signals-clean-restart/
There are variables you can set to alter the PM2 kill behavior:
PM2_KILL_SIGNAL
(SIGINT
by default),PM2_KILL_TIMEOUT
(1600
by default), the--treekill
parameter (true
by default), and kill retry time.There are a couple of issues with PM2's graceful shutdown on Windows:
shutdown
message is only sent if thesoftReload
method is used:pm2/lib/God/Reload.js
Line 77 in bf1c605
pm2 startOrGracefulReload <json>
, and if the application is automatically restarted by the PM2. But it does not work for kill/stop/restart/reload and for scale down operations.process.kill()
behavior on Windows due to unclear documentation (https://nodejs.org/api/process.html#process_process_kill_pid_signal) - Windows doesn't support signals; instead, nodejs emulatesprocess.kill()
behavior for 3 signals and ignores everything else:SIGINT
/SIGTERM
- terminates app;0
- checks whether the process exists. What is often confusing is that if you run nodejs app in cmd and useCtrl-C
, it does receiveSIGINT
, - but it's emulated.Attempted solutions:
SIGINT
/SIGBREAK
by handlingSTDIN
closure: https://stackoverflow.com/questions/10021373/what-is-the-windows-equivalent-of-process-onsigint-in-node-jsDoesn't work, and it I'm not sure how well that idea is going to work with different CLI modes PM2 can launch the app.
Can actually invoke the signal handler in the nodejs, but it does so for all child processes too, which makes it unusable in Cluster mode - signal will be received by all instances.
But that module replaces global
process.kill
, so I thought about doing it manually.NOTE; If you play with the
process.kill()
, you have to disabletreekill
, because it uses Windows utility by default instead.process.kill()
substitution. You can wrap PM2 startup in another script and replaceprocess.kill
there. This will still allow running PM2 as a service, and not only in the standalone mode.The idea there is to handle the
SIGINT
signal to notify an app that is about to be closed, and rely on the PM2 kill timeout to send theSIGTERM
and explicitly close the app.It's actually a 'reasonable' workaround, but you can't use any messaging methods (like
sendDataToProcessId
ortrigger
custom action), because the process is marked asstopping
internally and all communication to such processes is blocked:pm2/lib/God/ActionMethods.js
Line 714 in 91470b6
So you eventually will have to get access to the
God.clusters_db[]
object to be able to useprocess.send
- this is not 'trivial'.process.kill()
replacement is quirky and is tightly coupled with PM2 version (relies on veriable names and etc.), you can notify the specific app instance with thesendDataToProcessId
or another way, if you can get the instance that is going to be shutdown.It's easy to guess, however, also PM2 version-specific, but somewhat less than previous item.
Let's say you want to stop/scale down an app. You need to call the
describe
orlist
first to get ID of the instances and their order. PM2 internally represents this list in the same order it kills apps - it uses filtered arrays and a simplefor
loop, so it's easy to guess and pre-notify apps. And you can rely on the 'shutdown' message for the auto-restarts.I thought about fixing the higher-level abstractions, but it feels like that the change in that business logic is too risky and won't be accepted (considering the previous PRs I've seen):
God.killProcess
is called only from theGod.stopProcessId
, so it could be refactored to accept switch to soft/hard stop the app. But if you do that, there is no need in thesoftReload
function, and entirestartOrGracefulReload
feature relies on it.And it's hard to say what kind of regressions could be introduced by affecting the timing of the
shutdown
message.Proposed solution:
I think the easiest way to provide a Windows-specific workaround is to introduce a new global switch (
PM2_KILL_USE_SEND
environment variable, name TBD) to change howGod.killProcess
works - it's intent actually not to kill the process, but to notify it, and then theGod.processIsDead
method explicitly kills the process on timeout, if it's not dead.So, by allowing
God.killProcess
to useprocess.send()
instead ofprocess.kill()
, apps can be notified and then killed by timeout, if needed.It's also possible to change the shutdown message with the existing
PM2_KILL_SIGNAL
variable.Sample app: