Reputation: 20487
I am working on an application that has periodically-called background processes. One of these was being called by cron, but I am looking for something more robust, so am converting it to run under Supervisor. (It will probably run for 10 minutes, during which time it can detect work to do, or idle. Once it exits, Supervisor will automatically respawn a clean instance.)
Since Supervisor is better at ensuring that only a specified number of instances of something are running in parallel, I can get away with running them longer. This does mean however that my processes are more likely to receive termination signals, either from kill
directly, or because they have been stopped via Supervisor. I am therefore experimenting with how to handle this in PHP.
It looks like the basic solution is to use pcntl_signal()
like so:
declare(ticks = 1);
pcntl_signal(SIGTERM, 'signalHandler');
pcntl_signal(SIGINT, 'signalHandler');
function signalHandler($signal) {
switch($signal) {
case SIGTERM:
case SIGINT:
echo "Exiting now...\n";
exit();
}
}
However, I have several points in my code that could do with careful shutdown handling. One approach is to make one handler call those various things, but it would require a bit of refactoring, which I would like to avoid. The alternative is to add pcntl_signal()
everywhere I need it, but unfortunately it seems only one handler can be installed at once.
However, it looks like I might be able to use register_shutdown_function()
. This does not trap ^C
or other termination sigs on its own, and the manual is quite clear on this point:
Shutdown functions will not be executed if the process is killed with a SIGTERM or SIGKILL signal
What is surprising is that I have found that if I employ pcntl_signal()
to just do an exit, then the shutdown handlers are indeed called. Furthermore, since one can have many shutdown handlers, this solves my problem nicely - each class in my code that wishes to handle termination gracefully can capture and manage its own shutdown.
My initial question is, why does this work? I have tried registering a shutdown function without a signal handler, and this does not seem to be called, as the manual says. I guess the process is kept alive by PHP in order to handle the signal, which causes shutdown handlers to be called?
Also, can I rely on this behaviour, when the manual casts doubt on it? I am using PHP 5.5, and am not looking to upgrade to PHP7 just yet. So I'd be interested in whether this works on 5.5 and 5.6, on a variety of distributions.
Of course, whether it would (or would not) work on 7.0 and/or 7.1, would be interesting too - I am aware though that ticks will be handled differently in 7.1, so there is a greater chance of this having a different behaviour.
Upvotes: 16
Views: 2592
Reputation: 58741
PHP user shutdown functions are called during ordinary process termination, somewhat analogous to functions registered with atexit
in other programming languages. An explicit exit
or implicit exit at end-of-script is an ordinary process termination.
Signal death, however, is abnormal process termination. The default behavior for SIGTERM (and the mandatory behavior for SIGKILL) is to terminate the running process immediately, without running any cleanup handlers.
When you intercept a SIGTERM with pcntl_signal
, you are installing different behavior and giving yourself the opportunity to experience ordinary process termination.
Yes, you can rely on this behavior. While the main manual entry is not explicit, the rest of the "Note" you quote reads:
[Y]ou can use
pcntl_signal()
to install a handler for a SIGTERM which usesexit()
to end cleanly.
Upvotes: 8
Reputation: 23787
In addition to pilcrows correct answer:
The signal handler is necessary for changing the behavior of the signal (i.e. instead of the default action). Whatever happens after that is irrelevant.
You might use the signal handler to just do emergency cleanup and then call posix_kill(getmypid(), SIGKILL);
to force termination without cleanup. If you exit()
here, normally the shutdown sequence (i.e. calling of all destructors, shutdown handlers etc.) is initiated.
When you say in addition to, you're not strictly correct. You need to use the signal handler instead and may additionally have a shutdown handler. [referring to your comment]
Be also aware that calling exit()
after the shutdown sequence has been initiated will abort the current part of it (i.e. when you're currently inside a shutdown handler and exit()
gets called, all subsequent shutdown handlers are ignored - but e.g. destructors will still be called). Ideally you have some check against this:
register_shutdown_function(function() { $GLOBALS["#__in_shutdown"] = 1; });
and have a check around exit()
:
if (empty($GLOBALS["#__in_shutdown"])) {
exit;
}
Just in case you want to be really safe unless someone fires SIGKILL on it.
Note that 7.0 or 7.1 won't change this behavior; all what changes in 7.1 is declare(ticks=1);
being superfluous, the exhibited behavior is still the same.
Upvotes: 4