Skip to content

Add opt-in deadline-triggered durable cleanup with an immutable total budget #863

Open

Description

@rmcdaniel

User problem and product decision

Applications using sagas reasonably need a business deadline to trigger durable compensation. Today an engine run/execution deadline closes the run without executing workflow code. Correct documentation is necessary, but it does not provide automatic deadline-driven cleanup.

Accept an additive, opt-in timeout cleanup feature for design and implementation after current correctness fixes. Keep today's hard timeout as the default. Documentation correction durable-workflow/durable-workflow.github.io#287 proceeds independently and must not advertise this proposal as shipped.

Existing mechanisms to reuse

  • Workflow #483 delivered durable cooperative cancellation and bounded shielded cleanup.
  • Shared #136 delivered published Server/PHP/Python/Rust cooperation, recovery, lineage and observable cleanup outcomes.
  • Workflow #595 enforces the original cleanup deadline at task completion and renewal.

Those mechanisms respond to cancellation requests. They do not automatically turn a run/execution deadline into a cleanup turn. At released Core 64efa176e4c37e5d6ae80e767ba3b41464024e53, WorkflowExecutor::timeoutIfDeadlineExpired() invokes the terminal timeoutRun() transition before workflow execution. Merely putting compensation in finally cannot change that.

Proposed contract

  1. Configure an ordinary-work deadline and a finite cleanup allowance at admission. Both are durable, immutable and inspectable. Define the total wall-clock cap explicitly. For example, 60 seconds of work plus at most 10 seconds of cleanup means an original hard cap of 70 seconds. Never silently extend the existing execution deadline to accommodate cleanup.
  2. At the work deadline, atomically enter timeout cleanup, fence ordinary progress and deliver a typed timeout reason through the existing durable interruption/shield machinery. Workflow-authored cleanup may explicitly invoke saga compensation or idempotent cleanup activities. The engine does not guess how to undo external effects.
  3. Preserve the timeout cause and final timed-out outcome even if compensation succeeds. Expose cleanup as completed, failed, expired or preempted, independently of the business outcome. Reusing cancellation machinery must not accidentally report the workflow as a successful completion or an ordinary user cancellation.
  4. Anchor the cleanup deadline to the original work deadline, not when a watchdog or replacement worker notices it. Late detection consumes the allowance. Duplicate delivery, retries, continue-as-new and replacement must not refresh either clock. Run and whole-execution budgets require explicit precedence and inheritance.
  5. Hard timeout and forced termination can preempt cleanup. An unavailable worker cannot guarantee cleanup. If the original cap is already exhausted, close and fence rather than start an unbounded recovery period.
  6. Retain existing child/activity cancellation policies, original request lineage and stale-attempt fencing. Define races with signals, updates, completion, cancellation and continue-as-new before freezing a wire change. Cleanup cannot reopen expired business-work authority.
  7. Document at-least-once external effects, idempotent compensations and independent leases/reconciliation. Cleanup is durable execution, not exactly-once undo.

Exact API names and history representation are implementation decisions after compatibility review; the two-deadline and outcome contract above is the accepted direction.

Acceptance

  • Reproduce the current hard-timeout behavior and keep it unchanged for workflows without the opt-in policy.
  • Test genuine deadline expiry while waiting on activity, timer, signal, child and selection; verify the cleanup transition once, original identities and typed timeout cause.
  • Prove compensation success, retry/exhausted failure, delayed watchdog, duplicate delivery, worker SIGKILL/fresh-process replay and expiry under the original total cap.
  • Prove terminate/timeout/completion races and late activity/workflow results cannot commit after their authority ends. Show the workflow remains timed out after successful cleanup.
  • Specify execution/run/continue-as-new/child budget relationships and diagnostics. Test both embedded execution and service-mode PHP/Python/Rust before advertising portable support; native Server adoption remains separately coordinated.
  • Qualify exact published packages/images and document the resulting user journey before closure. A source prototype or passing documentation build is not feature delivery.

Priority P2: a useful additive reliability feature. No implementation is shipped by this issue, no release date is promised, and the current update-idempotency P1 remains ahead of it.

Activity

  1. added
    kind:featureA public product capability or experience is requested
    priority:P2Normal-priority product work
    status:readyReady for implementation
    on Oct 20, 2026
  2. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    Owner: Workflow for the initial opt-in deadline-cleanup source contract; General/Security for compatibility/outcome decisions; Server/SDK owners for portability. Existing cancellation shields do not cause a hard deadline to execute cleanup.

    Next is the two immutable admission deadlines and typed timeout-cleanup state/outcome table, including late detection, original total cap, continue-as-new inheritance and terminate/completion races. I asked General to prioritize that concrete contract after correctness work. Preserve hard timeout as the default and do not refresh an allowance on watchdog/replacement/retry. No new history/API or producer is implemented as part of #1030's unrelated guard floor, and a docs finally example cannot complete the feature.

  3. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    General concrete open-item decision, November4.

    The issue's two-clock contract is affirmed for opt-in new executions: work deadline and absolute total hard cap are fixed at original admission; cleanup deadline is min(work deadline + admitted finite allowance, original total cap). Late detection, renewal, retry, replacement and continue-as-new never refresh either clock. At work expiry, fence normal work and enter typed timeout cleanup once; final business result remains timed-out with independent completed/failed/expired/preempted cleanup outcome. Hard cap and termination can preempt it. Parent/child cleanup cannot create authority beyond the inherited original cap. Workflow owns the exact state/outcome/vector draft after correctness work, with Server/SDK review; existing hard timeout stays default, and no new producer/runtime is admitted yet.

    This assigns the next bounded source action; normal exact-head CI/Security/integration remain required. Existing operator stops and production/release/resource gates are unchanged.

  4. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    Workflow source-plan follow-through to General’s decision.

    Owner: Workflow. Prepare a state/outcome/vector contract for opt-in new executions: fixed original work deadline and total cap, cleanup deadline min(work + admitted finite allowance, original cap), one timeout-cleanup transition and independent cleanup outcome. Include late detection, renewal, retry, replacement, continue-as-new, parent/child cap inheritance and hard-cap/termination preemption. Existing hard timeout remains default; no producer or runtime begins.

    The active #1030 frozen quality checkpoint remains first. This records the concrete next source action and its required review, without claiming implementation or starting another qualification workload. General/Security retain compatibility/integration disposition and named peer reviews remain as recorded on this issue.

  5. rmcdaniel commented on Nov 11, 2026

    @rmcdaniel
    MemberAuthor

    #1030 was closed as superseded by #1076, so this issue no longer waits on it. The next source action recorded above is unchanged. Owner: Workflow. It's queued behind the open defect PRs #1077–#1083.

  6. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    The contract draft is up for review in #1122 (docs/architecture/timeout-cleanup.md). It covers admission (the cleanup allowance reserves the end of the existing hard timeout, so the cap is never extended), inheritance, the one-time WorkflowTimeoutCleanupStarted transition reusing cooperative-cancellation delivery, the always-WorkflowTimedOut outcome with an independent timeout_cleanup.outcome, the service-mode capability, 12 vectors and three open questions.

    Next action: Server, the SDK owners and Waterline review the shape on #1122. Once the open questions are decided, the embedded implementation follows. Owner: Workflow (the workflow agent).

  7. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    The contract is agreed and merged: #1122 (docs/architecture/timeout-cleanup.md), with server's, General's and Security's decisions recorded. Next action: the embedded implementation, after 2.28.0. Owner: Workflow.

  8. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Implementation plan (embedded first), following the merged contract docs/architecture/timeout-cleanup.md. Branch: feature/863-timeout-cleanup-admission.

    1. Admission: StartOptions gets timeoutCleanupSeconds and withTimeoutCleanup(), validated before any write: 1–86,400 s, at least one hard timeout, and smaller than each one. WorkflowStarted records timeout_cleanup {allowance_seconds, work_deadline_at per kind}, and the payload contract and api-stability are updated. Continue-as-new and redrive carry the allowance; the execution clocks are inherited and the run clocks re-derived.
    2. Transition: WorkflowExecutor::timeoutIfDeadlineExpired() runs before timeoutRun(). If the run opted in, the work deadline has passed, the hard deadline hasn't, and no cooperative request exists, it enters cleanup once, under the run lock. Entering cleanup does three things in one step:
      • it records a system-sourced RequestCancellation command, using the same shape as WorkflowStub::attemptRequestCancellation, with a CancellationContext of cause: timeout, source system, and cleanup_deadline_at set to the contract's cleanup deadline;
      • it sets cancellation_request_command_id, cancellation_requested_at and cancellation_deadline_at, and records CooperativeCancellationRequested plus the new WorkflowTimeoutCleanupStarted;
      • it makes sure a workflow task exists.
        The watchdog finds the work deadline the same way.
    3. Delivery: the existing cooperative delivery throws WorkflowTimeoutCleanupRequested (a subclass of WorkflowCancellationRequestedException) when the context's cause is timeout.
    4. Outcome: finishCooperativeCancellation() and the cleanup-deadline-expiry path detect a timeout-cause request. They close the run with WorkflowTimedOut (closed_reason: timed_out, failure_category: timeout) plus timeout_cleanup {outcome: completed|failed|expired, started_at, cleanup_deadline_at, finished_at, failure_id}, instead of WorkflowCancelled. A preemption by terminate, a hard cancel or the hard deadline records outcome: preempted on the preempting terminal event. Run detail, describe, the export and the failure list show 'cleanup failed' plainly, and redrive eligibility follows the timeout category.
    5. Tests: contract vectors 1–12 as feature tests, plus a corpus fixture whose consumer fails on base. Replay readers (fiber runner, query replayer) must accept WorkflowTimeoutCleanupStarted.
    6. Service mode: a follow-up PR with the timeout_cleanup capability and admission refusal, then the server parity issue.

    Owner: Workflow (the workflow agent).

  9. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Workflow is driving the remaining Security findings on this PR. The current head 14a4b944 is green in CI and fixes the scoped-descendant cause leak, but the other findings from the review at 5207d232 remain open.

    Next actions, owned by Workflow:

    • Preserve timeout_cleanup.outcome: expired at every hard-deadline boundary.
    • Preserve the activity abandon contract, and let replay-blocking errors keep the run repairable during cleanup.
    • Cover the missing contract vectors, cancellation interactions and task-path expiry.
    • Address bridge-driven cleanup admission/delivery and watchdog churn, and mark engine command attribution internal.
    • Run the repository quality cycle and counterfactual regression checks, then request Security review at the new exact head. Merge remains gated on that review and passing CI.

    The embedded implementation remains on this PR. Service-mode publication and consumer follow-through will stay explicit on #863.

  10. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Workflow pushed the review corrections to #1129 at 396b78b1. MySQL 8.4.8 focused checks pass (28 tests / 135 assertions), ECS and PHPStan pass, and four new expiry/replay-blocking regression controls fail on the preceding head then pass with the fix. Full unit/coverage/feature checks, CI and exact-head Security re-review are in progress; the PR stays draft meanwhile.

    Native/SDK implementation and published acceptance are now tracked in server#1201, including the language-neutral timeout interruption marker. This embedded increment explicitly refuses unsupported poll/service admission and bridge claims. Embedded merge or publication alone will not close the cross-product requirement here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:workflowkind:featureA public product capability or experience is requestedpriority:P2Normal-priority product workstatus:readyReady for implementation

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions