Forcer le redémarrage d’un worker Symfony
Il arrive de temps en temps que notre worker n’arrive pas à se relever. Aussi fort que vous vous battez, l’erreur a mis PHP ou votre application dans un état instable et il n’y a rien à faire à part l’achever !
Pour pallier ça, vous avez deux options !
Si vous avez la main sur l’exception qui est levée, vous pouvez implémenter StopWorkerExceptionInterface ou lever directement une instance de StopWorker. Symfony laissera alors le worker s’éteindre après le traitement du message.
Cependant, il y a des cas où vous n’avez pas la main sur l’exception. Prenons l’exemple de « RedisException ». Si la connexion entre PHP et Redis est instable, les appels vers Redis échoueront. Et même si la connexion revient à la normale, un certain nombre d’appels seront toujours en échec. Il est possible de wrapper tous les appels à Redis. Mais… c’est long et fastidieux.
À la place, nous vous proposons ce listener, qui permettra d’éteindre le worker, si une exception d’un certain type est levée :
namespace App\Messenger\Listener;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent;
use Symfony\Component\Messenger\Event\WorkerRunningEvent;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
final class RestartWorkerListener implements EventSubscriberInterface
{
private const array EXCEPTIONS_TO_RESTART_WORKER = [
// Redis cannot recover from a connection error.
\RedisException::class,
];
private bool $stop = false;
public function __construct(
private readonly LoggerInterface $logger = new NullLogger(),
) {
}
public function onMessageFailed(WorkerMessageFailedEvent $event): void
{
$e = $event->getThrowable();
if ($this->stopIfNeeded($e)) {
return;
}
if ($e instanceof HandlerFailedException) {
foreach ($e->getWrappedExceptions() as $e) {
if ($this->stopIfNeeded($e)) {
return;
}
}
}
}
public function onWorkerRunning(WorkerRunningEvent $event): void
{
if ($this->stop) {
$event->getWorker()->stop();
}
}
public static function getSubscribedEvents(): array
{
return [
WorkerMessageFailedEvent::class => 'onMessageFailed',
WorkerRunningEvent::class => 'onWorkerRunning',
];
}
private function stopIfNeeded(\Throwable $e): bool
{
if ($this->shouldRestartWorker($e)) {
$this->stop = true;
$this->logger->error('Worker will stop due to an error.', [
'exception' => $e,
]);
return true;
}
return false;
}
private function shouldRestartWorker(\Throwable $e): bool
{
foreach (self::EXCEPTIONS_TO_RESTART_WORKER as $exception) {
if ($e instanceof $exception) {
return true;
}
}
return false;
}
}
Et voilà 🤓 Avec ce listener et quelques modifications de la constante EXCEPTIONS_TO_RESTART_WORKER vous pourrez laisser votre worker re-démarrer en toute sérénité.
Protip : pour tester une instabilité entre PHP et un composant externe et, si vous utilisez docker, vous pouvez mettre en pause votre container :
docker pause app-redis-1
docker unpause app-redis-1
Commentaires et discussions
Nos formations sur ce sujet
Notre expertise est aussi disponible sous forme de formations professionnelles !
Symfony avancée
Découvrez les fonctionnalités et concepts avancés de Symfony
Ces clients ont profité de notre expertise
L’équipe de Finarta a fait appel à JoliCode pour le développement de leur plateforme Web. Basée sur le framework Symfony 2, l’application est un réseau privé de galerie et se veut être une place de communication et de vente d’oeuvres d’art entre ses membres. Pour cela, de nombreuses règles de droits ont été mises en places et une administration poussée…
Nous avons travaillé en étroite collaboration avec Cerfrance pour améliorer la qualité de leurs projets PHP et Symfony, tout en renforçant les compétences de leur équipe. Notre intervention a consisté à mettre en place une intégration continue (CI), à coacher l’équipe pour l’ajout de fixtures et de tests, à dockeriser l’application, et à installer…
Nous avons réalisé la refonte du site de l’agence Beautiful Monday en utilisant nos compétences HTML5/CSS3 côté front-end, et le framework Symfony2 côté back-end. Afin de s’afficher correctement sur n’importe quel appareil, le site est entièrement responsive. La partie intégration a été effectuée avec un grand soin, en respectant parfaitement la maquette…