Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Aborting and resuming a Symfony Console command

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.

like image 891
Jeroen De Dauw Avatar asked May 05 '14 18:05

Jeroen De Dauw


People also ask

What is bin console in Symfony?

The Symfony framework provides lots of commands through the bin/console script (e.g. the well-known bin/console cache:clear command). These commands are created with the Console component. You can also use it to create your own commands.

How do I run console commands?

Easily open Command Prompt by running Windows Run by holding the Windows button and hitting the R button on your keyboard. You can then type "cmd" and press enter, opening Command Prompt. If you're unsure of what commands to use, you can type "Help" into Command Prompt.

What is Symfony Console component?

The Console component eases the creation of beautiful and testable command line interfaces. The Console component allows you to create command-line commands. Your console commands can be used for any recurring task, such as cronjobs, imports, or other batch jobs.


2 Answers

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;
    }
}
like image 130
Jeroen De Dauw Avatar answered Sep 25 '22 04:09

Jeroen De Dauw


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
    }
}
like image 43
kix Avatar answered Sep 23 '22 04:09

kix