Échecs et réessais#
Une activité qui échoue, ce n’est pas un événement mais plusieurs, et savoir les distinguer est ce qui permet de décider s’il faut compenser, alerter, ou laisser le workflow mourir.
Ce que le journal enregistre pour une activité#
| Événement | Sens |
|---|---|
ActivityScheduled |
le workflow l’a demandée |
ActivityTaskStarted |
une tentative a commencé — une par tentative |
ActivityTaskFailed |
une tentative a échoué, qu’une autre suive ou non |
ActivityTaskCompleted |
une tentative a réussi |
ActivityCompleted |
résultat final : succès |
ActivityFailed |
résultat final : échec |
ActivityCancelled |
résultat final : retirée avant d’aboutir |
ActivityCatastrophicFailure |
l’échec lui-même n’a pas pu être journalisé sans risque |
ActivityTaskFailed compte : sans lui, une tentative qui échouait puis se voyait suivie d’un succès
ne laissait aucune trace. La première erreur disparaissait purement et simplement du journal.
Pourquoi une activité a cessé de réessayer#
ActivityFailed porte un retryState qui dit dans laquelle des quatre situations vous êtes —
elles étaient autrefois indiscernables :
use Gplanchat\Durable\Failure\ActivityRetryState;
$failed->retryState(); // ActivityRetryState
$failed->isStalled(); // vrai quand les tentatives sont épuisées
| État | Sens |
|---|---|
NonRetryableFailure |
l’exception est déclarée non réessayable — elle ne sera jamais retentée |
MaximumAttemptsReached |
toutes les tentatives autorisées ont été consommées |
Timeout |
une borne « planification à démarrage » ou « planification à clôture » s’est écoulée |
RetryPolicyNotSet |
aucune politique de réessai ne s’appliquait |
InProgress |
non final — une autre tentative est attendue |
InProgress est la façon d’enregistrer un échec qui n’est pas un dénouement. Il apparaît quand
le réessai est délégué au serveur Temporal, et il ne compte délibérément pas comme un dénouement
terminal, pour que la tentative suivante ait bien lieu.
Cet état reflète le RetryState de Temporal : là aussi, c’est un champ de l’échec, pas un type
d’événement distinct.
Déclarer une exception non réessayable#
use Gplanchat\Durable\Activity\ActivityOptions;
ActivityOptions::of(5, nonRetryableExceptions: [PaymentRefusedException::class]);Une carte refusée ne s’arrangera pas à la troisième tentative. Sur le backend Temporal, cela devient
le nonRetryableErrorTypes de la politique de réessai : le serveur cesse de réessayer lui
aussi, pas seulement le worker PHP.
Le décompte des tentatives#
RetryLimit::ofAttempts(3) signifie trois exécutions au total, comme sur Temporal — et non
trois réessais après un premier essai.
Sans limite explicite, les tentatives sont illimitées. Une activité qui échoue systématiquement réessaiera indéfiniment et le workflow n’échouera jamais. Voir Options.
Quand c’est le workflow lui-même qui échoue#
Une erreur que le workflow ne traite pas produit un WorkflowExecutionFailed, dont le kind dit
d’où elle vient :
| Genre | Origine |
|---|---|
unhandled_activity_failure |
un échec d’activité que le workflow a laissé s’échapper |
unhandled_declared_activity_failure |
un échec métier déclaré qu’il a laissé s’échapper |
unhandled_catastrophic_activity_failure |
un échec d’activité qui n’a pas pu être journalisé |
unhandled_activity_superseded |
un perdant de course qu’il a attendu quand même |
workflow_handler_failure |
le code du workflow a lui-même levé |
terminated_by_parent |
un parent s’est fermé avec ParentClosePolicy::Terminate |
Sur le backend Temporal, ce genre voyage dans les détails d’ApplicationFailureInfo : relire
l’historique reconstruit donc un WorkflowExecutionFailed typé plutôt qu’un simple message. Le nom
de l’activité fautive survit à l’aller-retour.