Skip to content

Prevent command Kill races from panicking#64

Open
znull wants to merge 3 commits into
znull/stage-v2-cleanfrom
znull/fix-v2-kill-error-store
Open

Prevent command Kill races from panicking#64
znull wants to merge 3 commits into
znull/stage-v2-cleanfrom
znull/fix-v2-kill-error-store

Conversation

@znull

@znull znull commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

A command stage can be killed from more than one goroutine. For example, a pipeline context can expire while another stage wrapper notices a resource limit and also calls Kill with its own error.

Those errors can have different concrete types. Storing them directly in atomic.Value makes the second store panic with "sync/atomic: store of inconsistently typed value into Value", crashing the process instead of returning a pipeline error to the caller.

So, use a small wrapper type to ensure that doesn't happen.

A command stage can be killed from more than one goroutine. For example,
a pipeline context can expire while another stage wrapper notices a
resource limit and also calls Kill with its own error.

Those errors can have different concrete types. Storing them directly in
atomic.Value makes the second store panic with "sync/atomic: store of
inconsistently typed value into Value", crashing the process instead of
returning a pipeline error to the caller.

So, use a small wrapper type to ensure that doesn't happen.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@znull znull self-assigned this Jun 18, 2026
@znull znull marked this pull request as ready for review June 18, 2026 17:04
@znull znull requested a review from a team as a code owner June 18, 2026 17:04
Copilot AI review requested due to automatic review settings June 18, 2026 17:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens commandStage.Kill behavior when multiple goroutines attempt to kill the same command with different concrete error types, preventing atomic.Value from panicking and crashing the process. It does so by wrapping kill reasons in a stable concrete type before storing.

Changes:

  • Wrap kill reasons in a dedicated commandKillError struct before storing in atomic.Value.
  • Route Unix/Windows Kill implementations through a shared recordKillError helper.
  • Add a unit test intended to ensure different error concrete types can be recorded without panicking.
Show a summary per file
File Description
pipe/command.go Introduces the commandKillError wrapper and centralizes kill-error recording; updates error substitution logic to unwrap.
pipe/command_windows.go Switches kill-reason recording to the shared helper to ensure consistent stored type.
pipe/command_unix.go Switches kill-reason recording to the shared helper to ensure consistent stored type.
pipe/command_test.go Adds a test meant to validate storing different concrete error types without atomic.Value type panics.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment thread pipe/command_test.go Outdated
znull and others added 2 commits June 18, 2026 19:13
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
When multiple goroutines race to kill the same command, the first kill
reason is the one that caused the command to terminate. Preserve that
reason instead of allowing a later cleanup or context-cancellation kill
to overwrite it.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment thread pipe/command.go
Comment on lines +294 to 298
ctxErr := s.ctxErr.Load()
if ctxErr != nil {
// If the process looks like it was killed by us, substitute
// `ctxErr` for the process's own exit error. Note that this
// doesn't do anything on Windows, where the `Signaled()`
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.

2 participants