-
Notifications
You must be signed in to change notification settings - Fork 0
Signal able commands
macropay-solutions edited this page Jul 22, 2025
·
2 revisions
The commands as opposed to jobs, are not by default signal aware so, in a deploy or scale down scenario the long running commands may be killed before they finish.
Note that this tutorial will check for signals in the middle of the command not after the command has finished like in the job's case.
To make the commands signal aware they must implement Symfony\Component\Console\Command\SignalableCommandInterface.
- Start by adding this class to your project and extend it into all your commands (or only in the long running ones):
<?php
namespace App\Console\Commands;
use App\Exceptions\SignalableCommandException;
use Illuminate\Console\Command;
use Symfony\Component\Console\Command\SignalableCommandInterface;
abstract class SignalableCommand extends Command implements SignalableCommandInterface
{
protected bool $shouldStop = false;
protected string $signalReceived = '';
protected array $subscribedSignals = [
'SIGTERM' => SIGTERM,
'SIGINT' => SIGINT,
'SIGHUP' => SIGHUP
];
public function getSubscribedSignals(): array
{
return $this->subscribedSignals;
}
/**
* @todo If updating from symfony 5.4 update the function definition
*/
// for symfony 6.4 the definition is public function handleSignal(int $signal, /* int|false $previousExitCode = 0 */): int|false
public function handleSignal(int $signal): void
{
$this->signalReceived = (string)\array_search($signal, $this->subscribedSignals, true);
if ('' !== $this->signalReceived) {
$this->shouldStop = true;
}
}
/**
* @throws SignalableCommandException
*/
protected function throwIfShouldStop(): void
{
if ($this->shouldStop) {
throw new SignalableCommandException(
'Signal received ' . $this->signalReceived . '. Stopping artisan command: ' . $this->signature
);
}
}
}- Add this exception class
<?php
namespace App\Exceptions;
class SignalableCommandException extends \Exception
{
}- Add this test command and registed it in \App\Console\Kernel::$commands
<?php
namespace App\Console\Commands;
use App\Exceptions\SignalableCommandException;
use Illuminate\Support\Facades\Log;
class TestBgLongRunningCommand extends SignalableCommand
{
/**
* The name and signature of the console command.
* @var string
*/
protected $signature = 'background:test';
/**
* The console command description.
* @var string
*/
protected $description = 'This command runs for a long period';
public function handle(): void
{
try {
while (true) {
Log::error($this->signature);
\sleep(1);
$this->throwIfShouldStop();
}
} catch (SignalableCommandException $e) {
Log::error($e->getMessage());
}
}
}- Add this in your composer.json
"ext-pcntl": "*"- Use this code in your command's handle function inside a loop for example
try {
$this->throwIfShouldStop();
} catch (SignalableCommandException $e) {
Log::error($e->getMessage());
return;
}or if the loop is outside your command class:
try {
$this->service->doSomething([$this, 'throwIfShouldStop']);
} catch (SignalableCommandException $e) {
Log::error($e->getMessage());
return;
}and in the service:
public function doSomething(callable $throwIfShouldStop): void
{
//loop
throwIfShouldStop();
...
}