I have a Symfony Console command that iterates over a potentially big collection of items and does a task with each of them. Since the collection can be big, the command can take a long time to run (hours). Once the command finishes, it displays some statistics.
I'd like to make it possible to abort the command in a nice way. Right now if I abort it (ie with ctrl+c in the CLI), there is no statistics summary and no way to output the parameters needed to resume the command. Another issue is that the command might be terminated in the middle of handling an item - it'd be better if it could only terminate in between handling items.
So is there a way to tell a command to "abort nicely as soon as possible", or have the ctrl+c command be interpreted as such?
I tried using the ConsoleEvents::TERMINATE event, though the handlers for this only get fired on command completion, not when I ctrl+c the thing. And I've not been able to find further info on making such resumable commands.
This is what worked for me. You need to call pcntl_signal_dispatch before the signal handlers are actually executed. Without it, all tasks will finish first.
<?php
use Symfony\Component\Console\Command\Command;
class YourCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output)
{
pcntl_signal(SIGTERM, [$this, 'stopCommand']);
pcntl_signal(SIGINT, [$this, 'stopCommand']);
$this->shouldStop = false;
foreach ( $this->tasks as $task )
{
pcntl_signal_dispatch();
if ( $this->shouldStop ) break;
$task->execute();
}
$this->showSomeStats($output);
}
public function stopCommand()
{
$this->shouldStop = true;
}
}
You should take a look at RabbitMqBundle's signal handling. Its execute method just links some callbacks via the pcntl_signal() function call. A common case should look pretty much like this:
<?php
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand as Command;
class YourCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output)
{
pcntl_signal(SIGTERM, array(&$this, 'stopCommand', $output));
pcntl_signal(SIGINT, array(&$this, 'stopCommand', $output));
pcntl_signal(SIGHUP, array(&$this, 'restartCommand', $output));
// The real execute method body
}
public function stopCommand(OutputInterface $output)
{
$output->writeln('Stopping');
// Do what you need to stop your process
}
public function restartCommand(OutputInterface $output)
{
$output->writeln('Restarting');
// Do what you need to restart your process
}
}
The answers are more complex than they need to be. Sure, you can register POSIX signal handlers, but if the only signals that need to be handled are basic interrupts and the like, you should just define a destructor on the Command.
class YourCommand extends Command
{
// Other code goes here.
__destruct()
{
$this->shouldStop = true;
}
}
A case where you would want to register a POSIX signal is for the SIGCONT signal, which can handle the resumption of a process that was stopped (SIGSTOP).
Another case would be where you want every signal to behave differently; for the most part, though, SIGINT and SIGTERM and a handful of others would be registered with the same "OMG THE PROCESS HAS BEEN KILLED" operation.
Aside from these examples, registering signal events is unnecessary. This is why destructors exist.
You can even extend Symfony's base Command class with a __destruct method, which would automatically provide cleanup for every command; should a particular command require additional operations, just overwrite it.
Related
I'm writing a daemon in PHP 5.6. So far, it is basically a Daemon class with a mainLoop() method that has an infinite loop. In each iteration, the mainLoop executes a series of steps.
I need it to implement a "graceful kill" mechanism: if a SIGINT or SIGTERM arrive, the daemon must complete the current step of the current iteration before dying.
My idea is to use a static variable Daemon::CONTINUE TRUE by default; when a SIGINT or SIGTERM arrives, it is set to FALSE.
In each iteration, before passing to the next step the daemon checks if self::CONTINUE has switched to FALSE and, if it has, it returns.
I know that the way to do this is to use pcntl_signal. It seems that I can either use it with declare(ticks=1) or with pcntl_signal_dispatch(), but I'm not sure about the difference.
Does declare(ticks=1) make the process check for the arrival of signals after each tick, whereas pcntl_signal_dispatch() explicitly checks the signals only when I call it?
These are snippets of the two ways I described before. Are they both correct? Which one should I use?
Way 1
<?php
declare(ticks=1) {
pcntl_signal(SIGINT, function($signo) {Daemon::CONTINUE = FALSE;});
pcntl_signal(SIGTERM, function($signo) {Daemon::CONTINUE = FALSE;});
}
public class Daemon {
public static $CONTINUE = TRUE;
function mainLoop() {
...
if (self::CONTINUE === FALSE)
return;
...
}
}
Way 2
<?php
pcntl_signal(SIGINT, function($signo) {Daemon::CONTINUE = FALSE;});
pcntl_signal(SIGTERM, function($signo) {Daemon::CONTINUE = FALSE;});
public class Daemon {
public static $CONTINUE = TRUE;
function mainLoop() {
...
pcntl_signal_dispatch();
if (self::CONTINUE === FALSE)
return;
...
}
}
Thanks for your support.
Ok, after some testing and debugging I tried both solutions.
I'll leave here my observations in case somebody encounters my same issues.
It seems that the way 1 with declare(ticks=1) does not work; I can not understand why.
The way 2 with pcntl_signal_dispatch(), on the contrary, seems to work nice.
After deeper research, I think that the way 2 is the best one for my case anyway.
In fact, declare(tick=1), if it worked, would run the pcntl_signal on each tick, roughly corresponding to the execution of each code line.
This can potentially degradate the performances.
On the contrary, apparently pcntl_signal_dispatch) just handles pending signals when it is called, so it should be lighter on performances.
I use Laravel framework in my project.
I need call a function in PHP but I don't need wait for this.
For example:
public function payment($Authority)
{
if (test == 1)
$this -> one($Authority); // don't wait for this call.
return view ("site.payment");
}
private function one($Authority)
{
// php code
// python code
}
Laravel has a queue job system. You could create a job to call that code and have your payment method dispatch the job to the queue for processing. (assuming you don't use the sync driver).
"Queues allow you to defer the processing of a time consuming task, such as sending an email, until a later time. Deferring these time consuming tasks drastically speeds up web requests to your application." - Laravel 5.3 Docs - Queues
public function payment($Authority)
{
if (test == 1) {
// send to queue for processing later
dispatch(new SomeJob($Authority));
}
return view ("site.payment");
}
You can try to use PThreads extension (http://php.net/pthreads):
<?php
// create your own class from Thread
class MyWorkerThreads extends Thread
{
private $workerId;
private $authority;
public function __construct($id, $authority)
{
$this->workerId = $id;
$this->authority = $authority;
}
// main function
public function run()
{
echo "Worker #{$this->workerId} ran" . PHP_EOL;
echo $authority;
// make some long run tasks
$html = file_get_contents('http://google.com?q=testing');
}
}
...
$worker = new WorkerThreads($i, $Authority);
// start new thread with long run task
$worker->start();
...
// You can wait for the job to be finished at any time, using join
$worker->join();
you can call another process to run the code with proc_open. You also can write it into a artisan command, then call the command run in another process.
Here is a example for use proc_open()
I'm trying to catch signals in terminal. I know that I can use pcntl_signal() function but it doesn't work for me.
I am trying with this code:
public function handle() {
pcntl_signal(SIGINT, function ($signo) {
echo "CATCH!\n";
exit;
});
while (true) { echo("!\n"); sleep(2); }
}
When I press Ctrl+C I don't see any result, because program still is working. What do I wrong?
I want to stop program after press Ctrl+C and call __destruct() method.
The pcntl_signal() function won't work without this line in your file:
declare(ticks = 1);
It should probably be at the top of the file, before the class definition.
A tick is an event that occurs regularly during the program execution, and in this case each tick triggers the program to check the signals coming in. I think. I don't know why it's not enabled by default, possibly due to increased CPU load while it's in operation?
I've seen some assertions that ticks were deprecated after PHP 5.3, but I just tested in PHP 7.0.9 and can confirm that pcntl_signal() doesn't work without it.
This can be done with the Laravel SignalableCommandInterface now
class MyCommand extends Command implements SignalableCommandInterface
{
public function handleSignal(int $signal): void
{
echo "CATCH!\n";
exit;
}
// Which signals will be handled
public function getSubscribedSignals(): array
{
return [SIGINT, SIGTERM];
}
}
I had some several scripts that were running for a long time (6+ hours).
They were all containing a main loop that did it's thing, and a registered shutdown function that triggered a mysql query to announce the process as "done".
I decided to use pcntl_fork() inside these main loops, to run each round as a different process, in order to make the entire script complete faster.
It works OK, but, each child process is still registered with the shutdown function.
Therefore, each time a child process is complete it calls that mysql query and announce the script as complete.
How can I disable that shutdown function for the child processes, but keep it alive for the parent?
Sample code to understand what's going on:
common.php
register_shutdown_function('shutdown');
function shutdown()
{ global $objDb,$arg_id ;
echo "\n\n Executing queue process shutdown function.";
$objDb->query("UPDATE queue_args SET done='1' WHERE id='{$arg_id}'");
}
loop.php
include('common.php');
for ($i=1;$i<=200;$i++){
$pid = pcntl_fork();
if (!$pid) {
//child proccess - do something without calling the shutdown function
posix_kill(getmypid(),9);
}
} exit(); //this is when the shutdown function should eventually be called
Thanks
You could register the shutdown function within the if, like this:
if ($pid) {
if(!$registered) {
$registered = true;
register_shutdown_function('shutdown');
}
}else{
//child proccess - do something without calling the shutdown function
posix_kill(getmypid(),9);
}
You can't.
You could set a flag in the child processes after forking and poll it in the shutdown function - if it's set then return early. Or store the parent pid before you fork and, inside the shutdown function, return early if that's not the current pid. Or register the function in the parent process after forking.
if (!$pid) { posix_kill(getmypid(),9); }
This is really bad way to prevent the shutdown function from being invoked in the child process - but has all sorts of other implications - PHP won't shut down cleanly, buffers won't be flushed. Maybe you just need to do this:
#!/usr/bin/php
<?php
$arg_id = exec('task_which_forks.php');
exec("queue_clean_up.php $arg_id");
I am trying to execute a php-cli script from php-cgi
What I got below does not work from cgi.
It does however work when I execute it directly from a shell.
Is there a way to accomplish this?
<?php
if (PHP_SAPI === 'cli')
{
require '../boot.php';
Logger::mailit();
exit;
}
class Logger {
private static $instance = NULL;
private function __construct(){}
public function __destruct()
{
if (PHP_SAPI != 'cli')
{
exec('nohup php '. __FILE__ .' &');
}
}
public static function mailit(){
// Database stuff ...
mail( $row->email, $row->subject, $row->message, $row->headers);
}
}
?>
You have to place the call (Logger::mailit()) of your class after the definition (class Logger) of your class.
Got it actually working, and it was all just a little bug
changed this:
require '../boot.php';
to this:
require __dir__.'/../boot.php';
You can either remove the CLI check if (PHP_SAPI === 'cli'), or include the file and call require '../boot.php'; Logger::mailit(); yourself. Or you can look into exec() (or proc_open(), …) to execute the script as its own process. This might be necessary if you need the sandboxing.
have you tried something like exec('your/script.php &')? (NOHUP)
with proc_open() you can setup the environment for your script (including funky file descriptor stuff). with proc_get_status() you can get the PID of the process you started with proc_open(). With posix_kill() you can send signals to a process.
If I'm not mistaking, your parent process (in your case the php-cgi) keeps running until the child process (in your case the php-cli) finished - even if you setup non-blocking execution.