Execute code after script abort on CLI - php

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

Related

How do I ensure that a process started from a queue job gets terminated after timeout?

I have a problem with terminating processes started from a queue job.
I use the yii2-queue extension to run some long running system commands that have a total execution time limit controlled by the getTtr method of the RetryableInterface. The command may take anywhere from minutes to hours to fully complete, but I need to kill it after it hits the 60-minute mark.
<?php
use Symfony\Component\Process\Process;
use yii\base\BaseObject;
use yii\queue\RetryableJobInterface;
class TailJob extends BaseObject implements RetryableJobInterface
{
public function getTtr()
{
return 10;
}
public function execute($queue)
{
$process = new Process('tail -f /var/log/dpkg.log');
$process->setTimeout(60);
$process->run();
}
public function canRetry($attempt, $error)
{
return false;
}
}
Now, the problem that I face is that even when queue/listen kills the job, the tail command (it's just an example; in production I need to run a different command) keeps running in the background. Is there any way I can force the system to kill the tail command when the job is killed?
Your script needs to keep checking if the timeout was reached; e.g.
while($process->isRunning()) {
$process->checkTimeout();
usleep(200000);
}
Read more about "Process Timeout" here:
https://symfony.com/doc/current/components/process.html
Run the command with a timeout
$process = new Process('timeout 3600 tail -f /var/log/dpkg.log');
Will limit the process to a maximum of 60 minutes.
If your script kills it first that's fine and if it doesn't then the process will die at the timeout time.
https://linux.die.net/man/1/timeout

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

ReactPHP: What is the difference between terminate process and close process?

Using the reactphp/child-process library,
$loop = React\EventLoop\Factory::create();
$process = new React\ChildProcess\Process(...some long proccess..);
$process->on('exit', function($exitCode, $termSignal) {
// ...
});
$process->start($loop);
$loop->run();
to kill the process should I use $process->close() or $process->terminate() ?
Whats the difference?
terminate() - This method call proc_terminate method, and you can send custom signal. By default proc_terminate send SIGTERM, but you can send another signal, for example SIGSTOP or SIGKILL
close() - This method close descriptors and call proc_close. $this->process set null, and write exit code in $this->exitCode
If you need stop child process - call close()

PHP Cli key event listener function

I am making php cli application in which I need Key Event listener
let's say this is my code
Task To do
for($i=0;$i<=n;$i++){
$message->send($user[$i]);
}
Once I'm done with sending messages, I will have to keep connection alive with following code to receive delivery receipts.
I use $command = fopen ("php://stdin","r"); to receive user commands.
while(true){
$connection->refresh();
}
Connection is automatically kept alive during any activities but on idle i have to keep above loop running.
How can I run the event on pressing any key which will make this event break and execute some function?
PHP is not developed, to handle this kind of problems. The Magic Word would be Thread
The Cleanest way, I can Imagine, is taking a look into the Extension PThreads. With it, you can do Threads like in other Language:
class AsyncOperation extends Thread {
public function __construct($arg){
$this->arg = $arg;
}
public function run(){
if($this->arg){
printf("Hello %s\n", $this->arg);
}
}
}
$thread = new AsyncOperation("World");
if($thread->start())
$thread->join();
The other way would be to do tasks via a queue in a different script. There are some Queue Server out there, but it can be done simple calling shell_execute your Queue Script via PHP.exe. On Linux you need something like ...script.php > /dev/null 2>/dev/null &, Windows start /B php..., to stop waiting on Script is finished.

PHP pcntl_signal callbacks are not called

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).

Categories