fix: guard the shared log writers and mask list against concurrent access - #6153
Open
McNultyyy wants to merge 1 commit into
Open
fix: guard the shared log writers and mask list against concurrent access#6153McNultyyy wants to merge 1 commit into
McNultyyy wants to merge 1 commit into
Conversation
…cess Three data races that are reachable today, with no behaviour change: * ptyWriter.AutoStop is written by the goroutine running the command once it finished, while the goroutine copying the pty output reads it on every write. It becomes an atomic.Bool. The new test reproduces this reliably under -race. * containerReference.ReplaceLogWriter swaps two fields that the goroutines copying container output read concurrently, and HostEnvironment.ReplaceLogWriter swaps StdOut while a command is running. Both are now guarded, and the readers take a consistent snapshot through an accessor. * RunContext.Masks is appended to by a running step while the log formatter iterates it for every emitted line, and composite run contexts aliased the parent's slice and appended to it. Appends and reads now take a lock, and composite contexts copy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Aug 5, 2026
This was referenced Aug 17, 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 join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Three data races that exist on master today, fixed with no behaviour change.
1.
ptyWriter.AutoStopHostEnvironment.execstartscopyPtyOutputin a goroutine, which callsptyWriter.Writefor every chunk of output and readsAutoStopeach time. Once the command returns, the executing goroutine setswriter.AutoStop = truewhile that reader is still running. It becomes anatomic.Bool.This one is unconditional on
--self-hosted. The newTestPtyWriterAutoStopRacereproduces it reliably: it reportsWARNING: DATA RACEunder-raceon master and passes with this change.2. The log writer slots
containerReference.ReplaceLogWriterswapsinput.Stdout/input.Stderr, whichwaitForCommandand the attach path read while copying container output.HostEnvironment.ReplaceLogWriterswapsStdOutwhile a command may be running. Both swaps are now guarded, and the readers take a consistent snapshot through an accessor rather than reading the fields directly.3.
RunContext.MasksAddMaskappends from a running step while the log formatter iterates the same slice for every emitted line (valueMasker). Composite run contexts made this worse by aliasing the parent's slice header and appending to it. Appends and reads now take a lock, and composite contexts copy instead of aliasing.This is the same family as #2199, which d8506bf only partially addressed — but note it does not fix that issue's most recently reported stack, which is map iteration over shared
modelstructs inNewExpressionEvaluatorWithEnv. Referencing, not closing.Testing
go test -race ./pkg/container/... ./pkg/runner/...Only the
ptyWriterrace has a deterministic reproduction, which is included. The other two need a specific interleaving of a step's output with a writer swap and I could not make them fail reliably, so they are argued from the code rather than from a red test.Trade-offs worth flagging
masksMutexis a package-level global guarding per-RunContextslices, which is coarser than ideal. Happy to move it ontoRunContextif you prefer.valueMaskernow holds a read lock for the duration of the mask loop, which is on the path of every emitted log line. The alternative is copying the slice per line, which allocates instead. Either is defensible; say which you would rather have.🤖 Generated with Claude Code