Skip to content

Suspend and pause preserve concurrent actor crashes and report failure - #2707

Merged
Zoe Zhao (zoez7) merged 2 commits into
agent-substrate:mainfrom
cloudonly:fix-finalize-crashed
Oct 16, 2026
Merged

Zoe Zhao (zoez7) merged 2 commits into
agent-substrate:mainfrom
cloudonly:fix-finalize-crashed

Conversation

@cloudonly

@cloudonly Luomu (cloudonly) commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #2676.

If worker deletion or another crash writer marks an actor CRASHED after checkpoint succeeds but before finalize re-reads it, SuspendActor currently overwrites that crash with SUSPENDED, while PauseActor reports success for the CRASHED actor.

Both finalize steps now require the re-read actor to be SUSPENDING before validating snapshots, releasing the worker, or writing the final state. Otherwise they return FailedPrecondition and preserve the existing state, crash reason, and snapshot records. The existing version precondition still rejects a concurrent crash after the read with Aborted. Completed suspend and pause calls retain their existing success paths at workflow entry.

The full-workflow regression now checks FailedPrecondition and preservation of the original crash in both modes, updating the behavior pinned by #2688. The already-crashed pause fixture also clears WorkerAssignment, matching a real crash; it previously reached FailedPrecondition only through the missing-node branch.

Validation of the production change on Linux with CI=true and real PostgreSQL test containers:

  • RED: before the production fix, the corrected already-crashed pause test and both modes of the full-workflow regression fail because the calls incorrectly return OK.
  • GREEN: go test -race -count=1 -json ./... passes: 158 test-bearing packages and 9,670 test entries, including all 763 controlapi entries and 169 API functional entries.
  • go build ./cmd/ateapi ./cmd/atelet ./cmd/kubectl-ate, hack/verify-all.sh, and the tools/apitool race tests pass. The verifier leaves the source tree unchanged.
  • The full suite skips 41 tests requiring root privileges, IPv6 loopback, or configured external object storage. The affected PostgreSQL regressions do not skip.

E2E follow-up:

The original CI passed both gVisor suites but failed both microVM suites in TestGracefulWorkerTerminationSuspend. That test unconditionally expected SuspendActor to succeed after worker deletion, even when deletion had already crashed the actor.

The test now ties the RPC result to the stored state. OK requires SUSPENDED with no crash record. FailedPrecondition or Aborted requires CRASHED, a crash reason, and the previous completed durable snapshot. Both outcomes require the worker and its assignment to be removed. All other errors fail the test.

Follow-up validation on Linux with CI=true and real PostgreSQL and envtest:

  • The four affected controlapi regressions pass with -race -count=3.
  • All 763 controlapi test entries and all 169 functionaltest entries pass, with no skips. The functional package was rerun after initializing the isolated source directory's Git metadata, which envtest setup requires.
  • go test -race -c ./internal/e2e/suites/demo and go vet ./internal/e2e/suites/demo pass. Native compilation, gofmt, and whitespace checks also pass.
  • Real runtime E2E has not run at this head. The new pr-workflow run is action_required and needs maintainer approval.

Draft #2694 refactors these same workflows and currently preserves an out-of-band state while returning success. This focused fix also reports FailedPrecondition, as required by #2676; that behavior should carry forward into the refactor.

  • Local validation passes; runtime E2E awaits workflow approval.
  • Documentation reviewed; finalize and regression-test comments describe the corrected behavior. No guide change is needed.

ensureSuspendedFinalized and ensurePausedFinalized re-read the actor so
that an out-of-band transition is not overwritten, but only the version
precondition guarded that, and it covers a crash landing after the read,
not before it. A worker delete or the syncer crashes an actor without
taking the actor lease, and clears its worker assignment doing so. When
that landed between the atelet call and finalize's read, suspend
committed SUSPENDED over the crash and left the stale crash on the
record, and pause found no assignment to free and reported success for
a crashed actor.

Both steps now require SUSPENDING and otherwise return
FailedPrecondition without touching the record, as the mark steps do,
so each workflow ends CRASHED and the caller is told.

Cover it through the full workflows: flip the test that pinned the old
outcome, and give the already-crashed pause test the shape a real crash
leaves (assignment cleared) instead of one that only reached
FailedPrecondition through the node-unknown branch.
@zoez7

Zoe Zhao (zoez7) commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Luomu (@cloudonly) Please fix the e2e test failures below

Worker deletion can crash the actor before suspend finalization. Check that the RPC result matches the persisted state and that a reported crash retains its completed snapshot and reason.
auto-merge was automatically disabled October 15, 2026 22:33

Head branch was pushed to by a user without write access

@cloudonly

Luomu (cloudonly) commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor Author

Zoe Zhao (@zoez7) Fixed the E2E test failures, please review again.

@zoez7
Zoe Zhao (zoez7) added this pull request to the merge queue Oct 16, 2026
Merged via the queue into agent-substrate:main with commit a1471f0 Oct 16, 2026
23 of 27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Suspend and pause finalize don't check the actor is still SUSPENDING after an out-of-band crash

2 participants