In this post we are going to learn how to use supervisord to handle the execution of a symfony command. Basically, supervisord will allow us to:
Sometimes we resort to the unix crontab to automate the execution of processes. This may work most of the time but there can be situations where it can cause problems.
Let's imagine we have a database table which logs users' notifications. The table stores the following information:
On the other hand, we have coded a command whose execution follows the next steps:
We set this command in the linux crontab to run every so often (1 minute, 2 minutes etc). So far so good.
Now let's imagine that the current process has queried 500 notifications and when it has sent 400, a new process starts. This means that the new process will query the 100 notifications that have not been updated yet by the last process plus the new ones:
This can cause those 100 notifications to be sent twice since both processes have queried them.
As a solution, we can resort to using supervisor. It will keeps our process running and will restart it when required. This way, we only keep one process and avoid overlap. Let's analyze how the command should looks like:
#[AsCommand( name: 'app:notification' )] class NotificationCommand extends Command { private bool $forceFinish = false; protected function configure(): void { $this ->addOption('time-limit', null, InputOption::VALUE_OPTIONAL, 'Max time alive in seconds') ->addOption('time-between-calls', null, InputOption::VALUE_OPTIONAL, 'Time between every loop call') ; } protected function execute(InputInterface $input, OutputInterface $output): int { $this->forceFinish = false; pcntl_signal(SIGTERM, [$this, 'signalHandler']); pcntl_signal(SIGINT, [$this, 'signalHandler']); $timeLimit = $input->getOption('time-limit'); $timeBetweenCalls = $input->getOption('time-between-calls'); $dtMax = (new \DateTimeImmutable())->add(\DateInterval::createFromDateString("+ {$timeLimit} seconds")); do{ // Here we should execute a service to query and send notifications // ...... sleep($timeBetweenCalls); $dtCurrent = new \DateTimeImmutable(); }while($dtCurrent < $dtMax && !$this->forceFinish); return Command::SUCCESS; } public function signalHandler(int $signalNumber): void { echo 'Signal catch: ' . $signalNumber . PHP_EOL; match ($signalNumber) { SIGTERM, SIGINT => $this->forceFinish = true, default => null }; } }
Let's explain the command step by step:
The configure method declares to input options:
The execute method behaves as follows:
The signalHandler function catches the SIGTERM and SIGINT Unix signals. SIGINT is the signal sent when we press Ctrl+C and SIGTERM is the default signal when we use the kill command. When the signalHandler function detects them, it sets the forceFinish variable to true so that, when the current loop finishes, the command will finish since the forceFinish variable is no longer false. This allows users to terminate the process without having to wait until the max date is finished.
So far, we have been created the command. Now it's time to setup supervisor so it can handle it. Before starting with the configuration, we must install supervisor. You can do it running the following command:
sudo apt update && sudo apt install supervisor
After installing, you can ensure supervisor is running by executing the next command:
sudo systemctl status supervisor
Supervisor configuration files are placed in the following folder: /etc/supervisor/conf.d. Let's create a file named notif.conf and paste the following content:
command=php <your_project_folder>/bin/console app:notifications --time-limit=120 --time-between-calls=10 user=<your_user> numprocs=1 autostart=true autorestart=true process_name=%(program_name)s_%(process_num)02d
Let's explain each key:
With this configuration, the app:notifications command will be running for a maximum of 120 seconds and, it will sleep during 10 seconds after every loop. After passing 120 seconds or caching a unix signal, the command will exit the loop and finish. Then, supervisor will start it again.
We have been learned how to use supervisor to keep a command running without having to use the crontab. This can be useful when the processes launched by the crontab may overlap, causing data corruption.
In the last book I wrote, i show how to use supervisor to keep the symfony messenger workers running. If you want to know more, you can find the book here: Building an Operation-Oriented Api using PHP and the Symfony Framework: A step-by-step guide
The above is the detailed content of Using Supervisor to handle a Symfony Command execution. For more information, please follow other related articles on the PHP Chinese website!