Skip to content

Scope worker cancellation and closure acknowledgements to executions - #163

Merged
seemeroland merged 5 commits into
mainfrom
roland/execution-scoped-worker-cancellation
Oct 15, 2026
Merged

seemeroland merged 5 commits into
mainfrom
roland/execution-scoped-worker-cancellation

Conversation

@seemeroland

@seemeroland seemeroland commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Scope cancellation to an execution so delayed cleanup cannot stop a replacement execution of the same run. Treat successful backend cancellation as an accepted shutdown request; do not wait for or acknowledge confirmed physical closure.

Review guide

Start with internal/worker/worker.go and the message types, then review execution targeting in the Docker, Kubernetes, Direct, and Command backends. Focus on delayed cancellation/completion, provisioning races, and retries of failed backend operations.

What changed

  • Advertise the optional execution-scoped-cancellation-v1 capability and carry execution identity in assignment, claim, completion/failure, and cancellation handling.
  • Track work by run and execution. An old completion cannot remove its successor's tracking, and scoped cancellation cannot target a different execution.
  • Wait for provisioning/execution setup to return before backend cancellation, so cleanup cannot incorrectly run before resource creation finishes.
  • Retry returned backend cancellation errors locally with capped exponential backoff and per-attempt timeouts. Stop retrying once cancellation is accepted; shutdown stops and joins outstanding retry work.
  • Deduplicate in-progress and accepted cancellation requests, retain tracking across WebSocket reconnects, and bound the completed-execution cache.
  • Isolate Direct execution workspaces and fence Kubernetes deletion with execution identity and resource UID.
  • No execution_closed message, closure-verification API, or physical-closure polling.

Compatibility and limits

The protocol remains compatible with legacy run-only cancellation requests. This capability is an optional improvement, not a prerequisite for deploying the server change. The server supports older worker deployments through guarded run-ID cancellation and preserves early handoff.

A successful cancellation does not prove all descendants or customer-managed runtime resources have exited; completion is delegated to the backend/operator. Tracking and failed-operation retry state survive WebSocket reconnects, not worker-process restarts. Legacy run-ID cancellation cannot provide the same execution isolation as scoped cancellation.

Validation

  • go test ./...
  • go test -race ./...
  • Repeated cancellation/shutdown/cache race tests (20 runs)
  • Go formatting, go vet ./..., golangci-lint run --allow-serial-runners (0 issues), go build -v ./..., and diff whitespace checks

All passed for the simplified accepted-request implementation. Backend tests use fakes; these results do not establish live customer-infrastructure closure.

Co-Authored-By: Warp agent@warp.dev

@cla-bot cla-bot Bot added the cla-signed label Oct 5, 2026
Comment thread internal/worker/direct.go
return executeError(newBackendFailure(metrics.TaskFailurePhaseBackend, metrics.TaskFailureReasonWorkspaceSetup, fmt.Errorf("invalid execution ID for workspace path: %w", err)))
}
var err error
workspaceDir, err = os.MkdirTemp(b.config.WorkspaceRoot, taskID+"-"+params.ExecutionID+"-")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a false positive. Both IDs are validated as single path components before reaching os.MkdirTemp. The validator rejects /, , empty values, and ./.., and failures return immediately. The IDs affect only the temporary directory’s filename pattern; the parent is the operator-configured WorkspaceRoot. They cannot introduce an absolute path or parent-directory traversal.

@seemeroland
seemeroland marked this pull request as ready for review October 7, 2026 22:14
@seemeroland
seemeroland requested a review from vorporeal October 8, 2026 22:06
Comment thread internal/worker/direct.go
return executeError(newBackendFailure(metrics.TaskFailurePhaseBackend, metrics.TaskFailureReasonWorkspaceSetup, fmt.Errorf("invalid execution ID for workspace path: %w", err)))
}
var err error
workspaceDir, err = os.MkdirTemp(b.config.WorkspaceRoot, taskID+"-"+params.ExecutionID+"-")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're incorporating ExecutionID here so that, if a worker gets assigned a second execution for the same task, it starts in a fresh directory?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, will add a comment

Comment thread internal/worker/docker.go
platform string // Docker daemon platform (e.g., "linux/amd64" or "linux/arm64")
platformSpec ocispec.Platform
sidecarVolumes sync.Map // volume name -> *sidecarVolumeState
taskContainers sync.Map // taskExecution -> container ID; retained after cleanup failures

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this grow without bound?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under normal cleanup, entries are removed when the container is removed. There’s an avoidable accumulation with NoCleanup; I’ll drop tracking once the container is confirmed exited, while preserving the container itself.

Failed cleanup and uncertain creation outcomes can still accumulate entries—we retain those so cancellation doesn’t silently lose track of potentially running containers. But this isn't the only example of potential unbounded growth in the worker and is uncommon

Comment thread internal/worker/worker.go
}
key := taskExecution{cancellation.TaskID, cancellation.ExecutionID}
if cancellation.ExecutionID == "" {
// A task-only request is explicitly legacy run-wide cancellation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this legacy logic something that we'll be able to remove once the server support for execution-scoped cancellation rolls out?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, added a todo

@seemeroland
seemeroland merged commit 266a4be into main Oct 15, 2026
7 of 8 checks passed
@seemeroland
seemeroland deleted the roland/execution-scoped-worker-cancellation branch October 15, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants