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()
Related
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();
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
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
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.
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).