feat: add CmdState.Wait/ExitCode to read a command's exit code race-free#149
Merged
Conversation
Extensions reap their command in a goroutine (go cmd.Wait()) and then read cmdState.Cmd.ProcessState.ExitCode() from their status/stop handlers — a data race, since cmd.Wait() writes cmd.ProcessState concurrently. Give CmdState ownership of the process result: Wait() reaps the process and is the sole reader of ProcessState, recording the exit code into an atomic; ExitCode() returns it (-1 while running). Callers replace their hand-rolled goroutine with 'go cmdState.Wait()' and read cmdState.ExitCode().
|
Claude finished @joshiste's task in 2m 16s —— View job Claude finished reviewing this PR
ReviewThis is a clean, well-scoped fix. I verified the implementation in
Tests in CI ( No bugs, security issues, or race conditions found. No inline comments needed. |
This was referenced Jun 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Several extensions run a command non-blocking and reap it in a goroutine:
then read the result from their Status/Stop handlers:
cmd.Wait()setscmd.ProcessState, so this is a data race on that field (flagged bygo test -race, and able to yield torn reads).extcmd.CmdStatealready owns the*exec.Cmd, the output buffer and its mutex, but exposedCmddirectly and synchronized only the output buffer — so every consumer hand-rolled the same racy pattern. The identical race currently exists in extension-gatling, extension-jmeter, extension-k6 and extension-postman.What
Give
CmdStateownership of the process result:CmdState.Wait() error— reaps the process (cmd.Wait()) and records the exit code into an internalatomic.Int32. It is the sole reader ofcmd.ProcessState. Returns theexec.Cmd.Waiterror for logging.CmdState.ExitCode() int— returns the recorded exit code, or-1while the process is still running (or was signal-terminated), matchingos.ProcessState.ExitCode(). Safe to call concurrently with theWaitgoroutine.Consumers replace
go func(){ cmd.Wait() }()withgo cmdState.Wait()and readcmdState.ExitCode()instead ofcmdState.Cmd.ProcessState.Cmdstays exported (callers still needStart(),Process, env, etc.); theWait()doc steers callers toExitCode()rather than readingProcessStatedirectly.Follow-up
Once this is released, the four extensions above will be migrated to
go cmdState.Wait()+cmdState.ExitCode()(extension-gatling#138 will be repointed to this API).Verification
go build ./...,go vet ./extcmd/, andgo test -race ./extcmd/all pass. New tests cover non-zero exit, zero exit, and concurrent readers racing theWaitgoroutine under-race.