Failure events and states#

Reference for what Durable records when an activity or a workflow fails. An activity is a unit of side effect in a workflow, such as an HTTP call, a database write or an e-mail. The journal is the append-only record of everything an execution decided and received. See the glossary.

One failed activity writes several events. Reading them tells you whether to compensate, raise an alert or let the workflow fail.

Journal events for one activity#

Event Meaning
ActivityScheduled the workflow asked for the activity
ActivityTaskStarted one attempt began; one row per attempt
ActivityTaskFailed one attempt failed, whether or not another attempt follows
ActivityTaskCompleted only in journals written before #262; a success now writes ActivityCompleted alone
ActivityCompleted final outcome: success
ActivityFailed final outcome: failure
ActivityCancelled final outcome: removed before completing
ActivityCatastrophicFailure the failure itself could not be journaled safely

ActivityTaskFailed is written for every failed attempt, including one that a later attempt recovers from. Before this event existed, an attempt that failed and was followed by a success left no trace in the journal.

retryState of ActivityFailed#

ActivityFailed carries a retryState that names which of four situations ended the retries. Earlier versions recorded all four the same way.

use Gplanchat\Durable\Failure\ActivityRetryState;

$failed->retryState();     // ActivityRetryState
$failed->isStalled();      // true when attempts were exhausted
State Meaning
NonRetryableFailure the exception is declared non-retryable and is never retried
MaximumAttemptsReached every allowed attempt was used
Timeout a schedule-to-start or schedule-to-close bound elapsed
RetryPolicyNotSet no retry policy applied
InProgress not final; another attempt is expected

InProgress records a failure that is not an outcome. It appears when retries are delegated to the Temporal server. By design, it does not count as a terminal outcome, so the next attempt runs.

The state mirrors Temporal’s RetryState, which is also a field on the failure rather than a separate event type.

Attempt counting#

RetryLimit::ofAttempts(3) allows three executions in total, the first one included, as on Temporal.

Without an explicit limit, attempts are unlimited. An activity that always fails is retried forever, and its workflow never fails. See Options.

kind of WorkflowExecutionFailed#

A failure that the workflow code does not catch ends the execution with WorkflowExecutionFailed. Its kind field gives the origin of the failure:

Kind Origin
unhandled_activity_failure an activity failure the workflow did not catch
unhandled_declared_activity_failure a declared business failure the workflow did not catch
unhandled_catastrophic_activity_failure an activity failure that could not be journaled
unhandled_activity_superseded the workflow awaited an activity that had lost a race
workflow_handler_failure the workflow code itself threw
terminated_by_parent a parent closed with ParentClosePolicy::Terminate

On the Temporal backend, the kind is stored in the ApplicationFailureInfo details. Reading the history back rebuilds a typed WorkflowExecutionFailed instead of a bare message, and keeps the name of the failing activity.