Skip to content

Stop() timeout error swallowed by SIGKILL EPERM on Darwin #3522

@mattsu2020

Description

@mattsu2020

What happened

On Darwin (macOS), State.Stop() in pkg/internal/testing/process may return "unable to kill process ..." instead of the expected "timeout waiting for process ... to stop" error when StopTimeout expires.

This causes test failures in process_test.go where the test asserts the error contains "timeout":

Expect(processState.Stop()).To(MatchError(ContainSubstring("timeout")))

What you expected to happen

Stop() should always return a timeout error when StopTimeout expires, regardless of whether the subsequent SIGKILL succeeds or fails.

How to reproduce it

On macOS, run:

go test ./pkg/internal/testing/process/ -run "Stop.*cannot be stopped" -count=100

The test is flaky — it passes when SIGKILL succeeds, but fails when the process has already exited between the timeout and the SIGKILL attempt.

Root cause

In process.go, the Stop() method's timeout case:

case <-timedOut:
    if err := signalProcess(ps.Cmd.Process, syscall.SIGKILL); err != nil {
        return fmt.Errorf("unable to kill process %s: %w", ps.Path, err)
    }
    return fmt.Errorf("timeout waiting for process %s to stop", path.Base(ps.Path))

When the timeout fires:

  1. SIGTERM was already sent, and the process is shutting down
  2. SIGKILL is sent via syscall.Kill(-process.Pid, syscall.SIGKILL) (in signal_unix.go)
  3. On Darwin, if the process is already in its termination sequence, syscall.Kill returns EPERM
  4. The "unable to kill process" error is returned instead of the "timeout waiting for process to stop" error

The SIGKILL after timeout is a best-effort escalation — its failure should not override the timeout error that is the primary signal.

Environment

Suggested fix

The timeout error should always be returned when StopTimeout expires. The SIGKILL error can be included as supplementary information but should not replace the timeout error.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions