diff --git a/docs/portal/build/failures-retries.md b/docs/portal/build/failures-retries.md index 82c4f3d3..a837e67c 100644 --- a/docs/portal/build/failures-retries.md +++ b/docs/portal/build/failures-retries.md @@ -20,7 +20,8 @@ next: | `NonDeterministicWorkflow` | Code no longer matches committed history. | Restore compatible code; do not blindly retry. | | `WorkflowFailed` | The execution closed as failed. | Surface the recorded failure to the caller. | | `WorkflowTimedOut` | A Server deadline closed the run. | Start a new workflow only if business policy permits. | -| `WorkflowCancelled` / `WorkflowTerminated` | Both close the Server run immediately, with distinct terminal reasons. | Do not expect workflow-code cleanup after either command. | +| `WorkflowCancelled` | The run closed as Cancelled, after terminal cancellation or successful cooperative cleanup. | Inspect the cancellation lifecycle and cleanup outcome. | +| `WorkflowTerminated` | Termination closed the run immediately. | Do not expect workflow-code cleanup after termination. | ## Make activity retries safe @@ -51,12 +52,16 @@ from history. Discarding a suspended replay Fiber does not schedule cleanup or complete the workflow; PHP may still unwind local `finally` code, so put external effects in activities rather than directly in the workflow body. -Do not use `finally` as a guarantee against terminal `cancelWorkflow()`, -`terminateWorkflow()`, process death, or an already closed run. Service-mode -Server does not yet expose the separate cooperative cancellation request -available to embedded Laravel. When business cancellation requires -compensation to finish, signal that intent and let the workflow complete its -cleanup before closing the run. +Use `requestCancellation()` with cooperating workers when cancellation requires +bounded workflow cleanup. The request preserves one original deadline across +children, activities, duplicates and worker replacement. Shielded durable +cleanup replays after worker loss and successful cleanup closes the run as +`Cancelled`. See the [cooperative cancellation guide](https://durable-workflow.com/docs/2.0/polyglot/cancellation/) +for worker opt-in, operation policies and inspection. + +Terminal `cancelWorkflow()` and `terminateWorkflow()` still close the run +immediately. They do not resume workflow cleanup, and an already closed run +cannot schedule compensation through `finally`. ## Heartbeat long attempts @@ -69,6 +74,11 @@ foreach ($batches as $index => $batch) { Heartbeat details make slow work observable and provide a cancellation checkpoint. Catch `ActivityCancelled` only to release local resources, then rethrow it so the runtime records cancellation correctly. +Cooperative workers supervise callbacks independently of application +heartbeats. A stop receipt proves callback stop, while attempt fencing rejects +a stale result. Downstream side effects still need application idempotency or +reconciliation. + ## Preserve terminal distinctions at the client ```php diff --git a/docs/portal/build/workflows-activities.md b/docs/portal/build/workflows-activities.md index e30b1a6a..1c6703be 100644 --- a/docs/portal/build/workflows-activities.md +++ b/docs/portal/build/workflows-activities.md @@ -168,6 +168,10 @@ try { `ActivityContext::heartbeat()` records progress and throws `ActivityCancelled` when the Server requests cancellation. Heartbeat before and during long calls that can be safely divided into chunks. +Workers that opt into [cooperative cancellation](https://durable-workflow.com/docs/2.0/polyglot/cancellation/) +supervise callbacks and observe cancellation independently of application +heartbeats. Prepared local activities use supervised callback processes. + ## Use durable commands for workflow time The context provides these replay-aware commands: