Catching terminal terminations/exits with Symfony Console (CTRL+C)
Asked Answered
S

2

7

I have built a command which triggers file downloads from over the internet, however since these files need to be processed by another component, we need to make sure that every file that has been downloaded and has not been modified in the last 10 seconds, is a proper video and not corrupted/partially downloaded.

For this reason, we need to find a way to catch CTRL+C or command terminations and cleanup any applicable file that has not been successfully downloaded.

This is what I tried so far by using symfony/console and symfony/event-dispatcher:

#!/usr/bin/env php
<?php

require_once(__DIR__ . '/../vendor/autoload.php');

use Symfony\Component\Console\Application;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventDispatcher;
use ImportExport\Console\ImportCommand;
use Monolog\Logger;

$dotenv = new Dotenv\Dotenv(__DIR__ . '/../');
$dotenv->load();

$logger = new Logger('console');

$dispatcher = new EventDispatcher();
$dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) {
    // gets the command that has been executed
    $command = $event->getCommand();

    var_dump($command);
});

$application = new Application("Import-Export System", 'v0.1.0-ALPHA');
$application->add(new ImportCommand($logger));
$application->setDispatcher($dispatcher);
$application->run();

However the var_dump() is never shown in the console, if I do CTRL+C.

Suggestions?

Skiffle answered 27/2, 2018 at 8:59 Comment(1)
You might want to check this question also #23479607Kansas
M
7

When you do CTRL+C it is actually SIGINT that is being sent, not SIGTERM. The best way I can think of is to register handler with http://php.net/manual/en/function.pcntl-signal.php and dispatch the signal with pcntl_signal_dispatch, example:

pcntl_signal(SIGINT,'sigIntHandler');

function sigIntHandler() {
  // Do some stuff
}

Of course you need to adjust it to your needs. Note that you can also defer to class methods inside your commands, so you could for example create an AbstractCommand with generic sigIntHandler() and register it in the constructor:

pcntl_signal(SIGINT, [$this, 'sigIntHandler']);

Then use pcntl_signal_dispatch() for example in the loop of your command (it needs to be called in each iteration).

Managua answered 27/2, 2018 at 9:23 Comment(4)
This prevents me to actually use CTRL+C though. The command keeps running even though I interrupt it.Skiffle
It is a handler, so it should actually handle the signal in the way you want. If you want to do something and then still stop the execution, you can use exit() or die() function.Managua
pcntl_signal_dispatch() needs to be ran after every "task" to trigger signal handlers, that means if a SIGINT command is sent, handlers will not be executed until pcntl_signal_dispatch() is ran. That means I am not able to interrupt a process in-the-middle-of-something.Skiffle
It depends on what you are doing in the task, but if your operations block IO then of course you need to wait for the task to be finished before the handler is called.Managua
L
2

Since Symfony 5.2, it's a native feature: https://symfony.com/blog/new-in-symfony-5-2-console-signals

Implement an interface (SignalableCommandInterface), subscribe to signals, and handle them:

public function handleSignal(int $signal)
{
    if (SIGINT === $signal) {
        // ...
    }

    // ...
}
Lesbos answered 25/10, 2021 at 14:1 Comment(1)
I'm guessing this requires that you're using the full Symfony framework. It's not working for me and we're on Symfony 6 components.Graham

© 2022 - 2024 — McMap. All rights reserved.