PHP pcntl_signal callbacks are not called - php

This is the complete reproducible code.
<?php
class console{
public static function log($msg, $arr=array()){
$str = vsprintf($msg, $arr);
fprintf(STDERR, "$str\n");
}
}
function cleanup(){
echo "cleaning up\n";
}
function signal_handler($signo){
console::log("Caught a signal %d", array($signo));
switch ($signo) {
case SIGTERM:
// handle shutdown tasks
cleanup();
break;
case SIGHUP:
// handle restart tasks
cleanup();
break;
default:
fprintf(STDERR, "Unknown signal ". $signo);
}
}
if(version_compare(PHP_VERSION, "5.3.0", '<')){
// tick use required as of PHP 4.3.0
declare(ticks = 1);
}
pcntl_signal(SIGTERM, "signal_handler");
pcntl_signal(SIGHUP, "signal_handler");
if(version_compare(PHP_VERSION, "5.3.0", '>=')){
pcntl_signal_dispatch();
console::log("Signal dispatched");
}
echo "Running an infinite loop\n";
while(true){
sleep(1);
echo date(DATE_ATOM). "\n";
}
When I run this I see the date values each second. Now if I press Ctrl+C cleanup function is not called. In fact signal_handler is not called.
Here is the sample output.
$ php testsignal.php
Signal dispatched
Running an infinite loop
2012-10-04T13:54:22+06:00
2012-10-04T13:54:23+06:00
2012-10-04T13:54:24+06:00
2012-10-04T13:54:25+06:00
2012-10-04T13:54:26+06:00
2012-10-04T13:54:27+06:00
^C

The CTRL+C fires SIGINT, which you have not installed handler for:
<?php
...
function signal_handler($signo){
...
case SIGINT:
// handle restart tasks
cleanup();
break;
...
}
...
pcntl_signal(SIGINT, "signal_handler");
...
Now it works:
$ php testsignal.php
Signal dispatched
Running an infinite loop
2012-10-08T09:57:51+02:00
2012-10-08T09:57:52+02:00
^CCaught a signal 2
cleaning up
2012-10-08T09:57:52+02:00
2012-10-08T09:57:53+02:00
^\Quit

It's important to understand how declare works: http://www.php.net/manual/en/control-structures.declare.php
it either needs to wrap what ever it's affecting to be declared in the 'root' script (index.php or something similar).
fun fact; the IF check around declare(ticks = 1); doesn't matter, if you remove those 3 lines the script will stop working, if you change the check to if (false) it will work, the declare seems to be evaluated outside of the normal code flow xD

Following Ruben de Vries correction to the compile-time use of declare ticks, there are two things wrong with the original code (other than missing the INT signal).
As Ruben says:
if(false) declare(ticks=1);
This DOES declare ticks to be handled. Try it!
The docs go on to say how you cannot use variables etc. It's a compile time hack.
Furthermore the use of pcntl_signal_dispatch() is not a straight replacement for declaring ticks - it must be called during code execution - like ticks do automatically. So calling it once in the head of script like that will only process any signals pending at that moment.
To make pcntl_signal_dispatch() operate like using ticks to drive it you'd need to sprinkle it all over your code... depending on precisely where/when you want the interrupts to take effect.
At least:
while(true){
sleep(1);
echo date(DATE_ATOM). "\n";
pcntl_signal_dispatch();
}
So the original code is actually using ticks for all versions of PHP, and additionally checking for just one signal received during the initial boot process if version is greater than 5.3. Not what the OP intended.
Personally I find the use of ticks to drive PCNTL signals a huge messy pain. The "loop" in my case is in third party code, so I cannot add a dispatch method, and it the declare ticks at the top level doesn't work on production unless I add it to every file (some autoload, scoping or PHP7 version specific issue I think).

Related

Is there a way to know the exit status code from within a shutdown function?

Is there a way to know the value passed to exit() from within a shutdown function?
For example:
<?php
function handleShutdown(){
// Is there a way to know the value passed to exit() at this point?
someCleanup();
}
register_shutdown_function('handleShutdown');
if(somethingWentWrong()){
exit(3);
}
exit(0);
This is in the context of a complex set of legacy scripts running in CLI mode. I am looking for a solution to instrument their exit status within php that does not involve modifying all the points where the scripts invoke exit() (so, not using a global variable, define or forking the whole script).
There is this other SO question asking to know whether the exit was is "clean", but it does not address the exit code at all.
You can install uopz, which will give you uopz_get_exit_status().
Then use it in your handleShutdown function:
$exitCode = uopz_get_exit_status();

Cleanup console command on any termination

I have the following (simple) lock code for a Laravel 5.3 command:
private $hash = null;
public final function handle() {
try {
$this->hash = md5(serialize([ static::class, $this->arguments(), $this->options() ]));
$this->info("Generated signature ".$this->hash,"v");
if (Redis::exists($this->hash)) {
$this->hash = null;
throw new \Exception("Method ".$this->signature." is already running");
}
Redis::set($this->hash, true);
$this->info("Running method","vv");
$this->runMutuallyExclusiveCommand(); //Actual command is not important here
$this->cleanup();
} catch (\Exception $e) {
$this->error($e->getMessage());
}
}
public function cleanup() {
if (is_string($this->hash)) {
Redis::del($this->hash);
}
}
This works fine if the command is allowed to go through its execution cycle normally (including handling when there's a PHP exception). However the problem arises when the command is interrupted via other means (e.g. CTRL-C or when the terminal window is closed). In that case the cleanup code is not ran and the command is considered to be still "executing" so I need to manually remove the entry from the cache in order to restart it. I have tried running the cleanup code in a __destruct function but that does not seem to be called either.
My question is, is there a way to set some code to be ran when a command is terminated regardless how it was terminated?
Short answer is no. When you kill the running process, either by Ctrl-C or just closing the terminal, you terminate it. You would need to have an interrupt in your shell that links to your cleanup code, but that is way out of scope.
There are other options however. Cron jobs can be run at intermittent intervals to perform clean up tasks and other helpful things. You could also create a start up routine that runs prior to your current code. When you execute the start up routine, it could do the cleanup for you, then call your current routine. I believe your best bet is to use a cron job that simply runs at given intervals that then looks for entries in the cache that are no longer appropriate, and then cleans them. Here is a decent site to get you started with cron jobs https://www.linux.com/learn/scheduling-magic-intro-cron-linux

Execute code after script abort on CLI

I try to execute some final code after my script got aborted in PHP. So let's say I have this PHP script:
while(true) {
echo 'loop';
sleep(1);
}
if I execute the script with $ php script.php it run's till the given execution time.
Now I like to execute some final code after the script has been aborted. So if I
hit Ctrl+C
the execution time is over
Is there even a possibility to do some clean up in those cases?
I tried it with pcntl_signal but with no luck. Also with register_shutdown_function but this get only called if the script ends successfully.
UPDATE
I found out (thx to rch's link) that I somehow can "catch" the events with:
pcntl_signal(SIGTERM, $restartMyself); // kill
pcntl_signal(SIGHUP, $restartMyself); // kill -s HUP or kill -1
pcntl_signal(SIGINT, $restartMyself); // Ctrl-C
But if I extend my code with
$cleanUp = function() {
echo 'clean up';
exit;
};
pcntl_signal(SIGINT, $cleanUp);
the script keeps executing but does not respect the code in $cleanUp closure if I hit Ctrl+C.
The function pcntl_signal() is the answer for the situation when the script is interrupted using Ctrl-C (and by other signals). You have to pay attention to the documentation. It says:
You must use the declare() statement to specify the locations in your program where callbacks are allowed to occur for the signal handler to function properly.
The declare() statement, amongst other things, installs a callback function that handles the dispatching of the signals received since its last call, by calling the function pcntl_signal_dispatch() which in turn calls the signal handlers you installed.
Alternatively, you can call the function pcntl_signal_dispatch() yourself when you consider it's appropriate for the flow of your code (and don't use declare(ticks=1) at all).
This is an example program that uses declare(ticks=1):
declare(ticks=1);
// Install the signal handlers
pcntl_signal(SIGHUP, 'handleSigHup');
pcntl_signal(SIGINT, 'handleSigInt');
pcntl_signal(SIGTERM, 'handleSigTerm');
while(true) {
echo 'loop';
sleep(1);
}
// Reset the signal handlers
pcntl_signal(SIGHUP, SIG_DFL);
pcntl_signal(SIGINT, SIG_DFL);
pcntl_signal(SIGTERM, SIG_DFL);
/**
* SIGHUP: the controlling pseudo or virtual terminal has been closed
*/
function handleSigHup()
{
echo("Caught SIGHUP, terminating.\n");
exit(1);
}
/**
* SIGINT: the user wishes to interrupt the process; this is typically initiated by pressing Control-C
*
* It should be noted that SIGINT is nearly identical to SIGTERM.
*/
function handleSigInt()
{
echo("Caught SIGINT, terminating.\n");
exit(1);
}
/**
* SIGTERM: request process termination
*
* The SIGTERM signal is a generic signal used to cause program termination.
* It is the normal way to politely ask a program to terminate.
* The shell command kill generates SIGTERM by default.
*/
function handleSigTerm()
{
echo("Caught SIGTERM, terminating.\n");
exit(1);
}
This may have some really useful information, it looks like they're utilizing the same things you've attempted but seemingly with positive results? Perhaps there's something here you haven't attempted or maybe missed.
Automatically Restart PHP Script on Exit
Check this: connection_aborted()
http://php.net/manual/en/function.connection-aborted.php
Here's an example of how to use it to achieve what you want:
http://php.net/manual/en/function.connection-aborted.php#111167

Force PHP to skip "exit"

Is there a short solution to avoid the execution of the "exit" language construct? Otherwise I'll have to execute my function with another PHP-request.
Sample: main.php
echo 'hello world';
doSomething();
updateSomething();
Sample: doSomething.php
function doSomething()
{
$a = 1+1;
exit;
}
Sample: updateSomething.php
function updateSomething()
{
$b = 3+5;
exit;
}
But the second function will never be executed ...is there a simple solution? I've found two existing topics, without a real solution. But maybe there is smart trick for my example?
It's not a real example, in my case the functions represents some complex methods, which are called with ajax reqests without a return statement. Because the method are from a external library, I can't modify the code. And I want to call two methods of this library in one single script, if it's possible :)
And ... I know what return is doing, but in my case I can't modify the code, because it's not my code - it's code from an existing software and I just want to call these existing methods in a custom script. But I can just call one of the methods because of the exit-function in every method and it would be great to call them multiple times.
It's not possible to skip or disable an "exit" command.
If you want to do something before the script stops, you can use shutdown-functions or object-destructors ... but I don't think that's something you're up to, is it?
http://www.php.net/manual/en/function.exit.php
Simple. Remove exit; from the functions.
exit() will terminate execution from the script. You probably want to use return which returns back to the calling code.
The year is 2020 and a way to do what question author wanted to now exists - you can install uopz extension from PECL (version 5.0.2 or above), and use uopz_allow_exit() function to disable exit()'s, like this:
echo 'hello world';
uopz_allow_exit( false );
doSomething();
updateSomething();
uopz_allow_exit( true );
Note, however, that you will need to add this config option into your php.ini file:
uopz.exit = 1
If you dont, then all exit() calls will be disabled by default everywhere unless explicitly enabled by uopz_allow_exit().
It is also worth noting that this config option appeared only in version 6.0.1, which, for me at least, failed to install on PHP 7.0, and worked only on 7.2
Oh, and according to the documentation this extension is supposed to be used for unit testing, not production. Then again, nothing in it says that using this extension in production is discouraged, so it's your call.
From the manual:
Terminates execution of the script.
exit (alias of die) is the final thing the script does. Switch to an alternative use
function doSomething()
{
$a = 1+1;
return true;
}

PHP - Releasing resultset

I've been looking online and every article I've come across seems to say something slightly different. If I'm working with a resultset and the program exits before releasing the results what exactly happens?
Is there a better way to ensure that it always happens? The same goes for database connections.
if($statement->fetch()) {
exit("Result!");
}
$statement->free_result();
The free_result tells the database engine it can release the result set.
It only needs to be called if you are concerned about how much memory is being used for queries that return large result sets. All associated result memory is automatically freed at the end of the script's execution.
You can use register_shutdown_function to make sure your results are freed from memory
function shutdown(){
global $statement;
if($statement){
$statement->free_result();
}
}
register_shutdown_function('shutdown');
You can use register_shutdown_function in PHP to make sure that a function / a lot of functions fire on exit. In your example, you could also change the if statement to the following:
$exitResult = null;
if($statement->fetch()) {
$exitResult = "Result!";
}
$statement->free_result();
if ($exitResult !== null) exit($exitResult);
It should be noted though, that most plugins/extensions (that connects to databases etc.) clean up after themself after shutdown of the PHP process (on the end of a script).
If you use this in a class, you can save the reference to $statement and then use a __destruct function in the class.

Categories