Repository navigation
Scope worker cancellation and closure acknowledgements to executions #163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9321285
e38237c
0369f4c
06dc1e8
f1d7bfe
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -169,15 +169,27 @@ | |
| return executeError(newBackendFailure(metrics.TaskFailurePhaseBackend, metrics.TaskFailureReasonWorkspaceSetup, fmt.Errorf("invalid task ID for workspace path: %w", err))) | ||
| } | ||
|
|
||
| // Determine working directory: shared target dir or per-task workspace. | ||
| // Determine working directory: shared target dir or isolated workspace. | ||
| var workspaceDir string | ||
| usingTargetDir := b.config.TargetDir != "" | ||
|
|
||
| if usingTargetDir { | ||
| workspaceDir = b.config.TargetDir | ||
| } else { | ||
| // Create per-task workspace directory. | ||
| // Legacy assignments use a per-task workspace. | ||
| workspaceDir = filepath.Join(b.config.WorkspaceRoot, taskID) | ||
| if params.ExecutionID != "" { | ||
| if err := validateTaskIDForPath(params.ExecutionID); err != nil { | ||
| return executeError(newBackendFailure(metrics.TaskFailurePhaseBackend, metrics.TaskFailureReasonWorkspaceSetup, fmt.Errorf("invalid execution ID for workspace path: %w", err))) | ||
| } | ||
| var err error | ||
| // Give each execution a fresh workspace so overlapping executions | ||
| // cannot share files or remove each other's workspace during cleanup. | ||
| workspaceDir, err = os.MkdirTemp(b.config.WorkspaceRoot, taskID+"-"+params.ExecutionID+"-") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we're incorporating
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, will add a comment |
||
| if err != nil { | ||
| return executeError(newBackendFailure(metrics.TaskFailurePhaseBackend, metrics.TaskFailureReasonWorkspaceSetup, fmt.Errorf("failed to create execution workspace: %w", err))) | ||
| } | ||
| } | ||
| if err := os.MkdirAll(workspaceDir, 0700); err != nil { | ||
| return executeError(newBackendFailure(metrics.TaskFailurePhaseBackend, metrics.TaskFailureReasonWorkspaceSetup, fmt.Errorf("failed to create workspace directory: %w", err))) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -67,6 +67,7 @@ type DockerBackend struct { | |
| 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can this grow without bound?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| } | ||
|
|
||
| // NewDockerBackend creates a new Docker backend, connecting to the Docker daemon. | ||
|
|
@@ -181,24 +182,33 @@ func (b *DockerBackend) ExecuteTask(ctx context.Context, params *TaskParams) Exe | |
| } | ||
|
|
||
| doneContainerStart := params.SetupEvents.startPhase(ctx, SetupEventContainerStart) | ||
| key := taskExecution{params.TaskID, params.ExecutionID} | ||
| // Transport and server failures cannot prove that the daemon created no container. | ||
| b.taskContainers.Store(key, "") | ||
| resp, err := dockerClient.ContainerCreate(ctx, client.ContainerCreateOptions{ | ||
| Config: containerConfig, | ||
| HostConfig: hostConfig, | ||
| }) | ||
| if err != nil { | ||
| if cerrdefs.IsInvalidArgument(err) || cerrdefs.IsNotFound(err) || cerrdefs.IsConflict(err) { | ||
| b.taskContainers.Delete(key) | ||
| } | ||
| doneContainerStart(true) | ||
| return executeError(newBackendFailure(metrics.TaskFailurePhaseBackend, metrics.TaskFailureReasonContainerCreate, fmt.Errorf("failed to create container: %w", err))) | ||
| } | ||
|
|
||
| containerID := resp.ID | ||
| b.taskContainers.Store(key, containerID) | ||
| log.Debugf(ctx, "Created Docker container: %s", containerID) | ||
|
|
||
| defer func() { | ||
| if containerID != "" && !b.config.NoCleanup { | ||
| cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), BackendShutdownTimeout) | ||
| defer cleanupCancel() | ||
| if _, removeErr := dockerClient.ContainerRemove(cleanupCtx, containerID, client.ContainerRemoveOptions{Force: true}); removeErr != nil { | ||
| if _, removeErr := dockerClient.ContainerRemove(cleanupCtx, containerID, client.ContainerRemoveOptions{Force: true}); removeErr != nil && !cerrdefs.IsNotFound(removeErr) { | ||
| log.Debugf(ctx, "Container %s already removed or removal failed: %v", containerID, removeErr) | ||
| } else { | ||
| b.taskContainers.Delete(key) | ||
| } | ||
| } | ||
| }() | ||
|
|
@@ -221,6 +231,11 @@ func (b *DockerBackend) ExecuteTask(ctx context.Context, params *TaskParams) Exe | |
| } | ||
| case status := <-waitResult.Result: | ||
| log.Debugf(ctx, "Container exited with status code: %d", status.StatusCode) | ||
| if b.config.NoCleanup && status.Error == nil { | ||
| // Preserve the exited container for debugging, but not its tracking. | ||
| // Later cancellation intentionally leaves this stopped container alone. | ||
| b.taskContainers.Delete(key) | ||
| } | ||
|
|
||
| if params.Logs == nil && (zerolog.GlobalLevel() <= zerolog.DebugLevel || status.StatusCode != 0) { | ||
| logOutput, logErr := b.getContainerLogs(ctx, dockerClient, containerID) | ||
|
|
@@ -272,9 +287,22 @@ func dockerResourcesForShape(shape *types.InstanceShape) container.Resources { | |
| return res | ||
| } | ||
|
|
||
| // CancelTask is a no-op: cancelling the ExecuteTask context fully stops a | ||
| // Docker-backend task. | ||
| func (b *DockerBackend) CancelTask(context.Context, *CancelParams) error { return nil } | ||
| // CancelTask removes the captured execution container, including when normal cleanup failed. | ||
| func (b *DockerBackend) CancelTask(ctx context.Context, params *CancelParams) error { | ||
| key := taskExecution{params.TaskID, params.ExecutionID} | ||
| containerID, ok := b.taskContainers.Load(key) | ||
| if !ok { | ||
| return nil | ||
| } | ||
| if containerID == "" { | ||
| return fmt.Errorf("container creation outcome is unknown for execution %s", params.ExecutionID) | ||
| } | ||
| if _, err := b.dockerClient.ContainerRemove(ctx, containerID.(string), client.ContainerRemoveOptions{Force: true}); err != nil && !cerrdefs.IsNotFound(err) { | ||
| return err | ||
| } | ||
| b.taskContainers.Delete(key) | ||
| return nil | ||
| } | ||
|
|
||
| // Shutdown closes the Docker client. | ||
| func (b *DockerBackend) Shutdown(ctx context.Context) { | ||
|
|
||
There was a problem hiding this comment.
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.