You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dogfooding a long-running workflow exposed a cleanup limitation: cancelling a waiting run does not resume its workflow code, so durable activities in finally are never scheduled. Applications that need graceful cancellation must currently build an application-specific signal path or rely entirely on external expiry/reconciliation.
This is an API capability gap, not a regression in 2.0.4. The current cancellation contract makes cancellation terminal. #480/#481 fixed ordinary Fiber disposal and correctly documented that terminal cancellation does not guarantee cleanup; that issue should remain closed.
Reproduction
Verified on published durable-workflow/workflow 2.0.4 (f7b910958a186a11f52e916843f9019ea7b85ad8), PHP 8.4.25, Laravel database queue with SQLite, 2026-09-06 UTC:
useWorkflow\V2\Activity;
useWorkflow\V2\Workflow;
useWorkflow\V2\WorkflowStub;
usefunctionWorkflow\V2\activity;
usefunctionWorkflow\V2\timer;
finalclass WaitingWorkflow extends Workflow
{
publicfunctionhandle(): void
{
try {
timer(3600);
} finally {
activity(CleanupActivity::class);
}
}
}
finalclass CleanupActivity extends Activity
{
// Test-only observation; real cleanup belongs in an idempotent activity.publicstaticint$calls = 0;
publicfunctionhandle(): void
{
self::$calls++;
}
}
$stub = WorkflowStub::make(WaitingWorkflow::class, 'cancel-cleanup');
$stub->start();
// Drain the database queue until the workflow is waiting on its timer.$stub->cancel('stop');
// Drain again, including after the timer would have elapsed.
Observed: cancelled terminal run, zero cleanup calls. Positive control without cancellation: completed run, one cleanup call. The focused Laravel test passes these characterizations (1 test / 9 assertions); queue work runs in-process via Artisan, so the static counter is visible. This does not claim fresh-process cancellation recovery or service-mode validation.
The implementation explains the result: WorkflowStub::attemptCancel() closes the run and its pending tasks/timers; RunWorkflowTask does not replay terminal runs.
Accepted Direction
Provide an explicit cooperative cancellation path that can durably execute bounded cleanup before closing the run. Preserve the existing terminal cancel() semantics for stable consumers; decide the additive API and history representation before implementation. Forced termination and process death must never be advertised as guaranteed cleanup.
Acceptance
Cooperative cancellation is durable, idempotent, and observable while cleanup is pending; it is distinct from immediate terminal cancellation/termination.
A suspended workflow can observe the request and execute cleanup activities with normal retry/timer support and a bounded cancellation-safe scope.
Prove success, cleanup failure/retry, duplicate cancellation, termination during cleanup, worker loss and fresh-process replay; no duplicate irreversible effects.
Document the supported cleanup pattern and the need for external resource leases/reconciliation even with cooperative cancellation.
Assess Server and PHP/Python/Rust SDK parity before claiming a portable service-mode capability. This reproduction establishes only the embedded behavior.
Accepted product improvement ahead of discretionary coverage expansion, after the current in-progress recovery safety change. No release date is implied by this issue.
Problem
Dogfooding a long-running workflow exposed a cleanup limitation: cancelling a waiting run does not resume its workflow code, so durable activities in
finallyare never scheduled. Applications that need graceful cancellation must currently build an application-specific signal path or rely entirely on external expiry/reconciliation.This is an API capability gap, not a regression in 2.0.4. The current cancellation contract makes cancellation terminal. #480/#481 fixed ordinary Fiber disposal and correctly documented that terminal cancellation does not guarantee cleanup; that issue should remain closed.
Reproduction
Verified on published
durable-workflow/workflow 2.0.4(f7b910958a186a11f52e916843f9019ea7b85ad8), PHP 8.4.25, Laravel database queue with SQLite, 2026-09-06 UTC:Observed: cancelled terminal run, zero cleanup calls. Positive control without cancellation: completed run, one cleanup call. The focused Laravel test passes these characterizations (1 test / 9 assertions); queue work runs in-process via Artisan, so the static counter is visible. This does not claim fresh-process cancellation recovery or service-mode validation.
The implementation explains the result:
WorkflowStub::attemptCancel()closes the run and its pending tasks/timers;RunWorkflowTaskdoes not replay terminal runs.Accepted Direction
Provide an explicit cooperative cancellation path that can durably execute bounded cleanup before closing the run. Preserve the existing terminal
cancel()semantics for stable consumers; decide the additive API and history representation before implementation. Forced termination and process death must never be advertised as guaranteed cleanup.Acceptance
Accepted product improvement ahead of discretionary coverage expansion, after the current in-progress recovery safety change. No release date is implied by this issue.