Conversation
Two problems in dumpAll, three lines apart.
dumpAll reads Set.globals with no lock. Every other access takes gmx:
AddGlobal writes under Lock (set.go:220), LookupGlobal reads under RLock
(set.go:228), and the template execution path reads under RLock
(eval.go:201). dump() is the one that does not, so rendering a template
containing {{ dump() }} while another goroutine calls AddGlobal races on
the map, both at the SortedKeys range and at the index that follows:
WARNING: DATA RACE
Write at ... by goroutine 10:
jet.(*Set).AddGlobal() set.go:220
Previous read at ... by goroutine 12:
jet.VarMap.SortedKeys() exec.go:31
jet.dumpAll() dump.go:27
Reproduced on every run. Copy the map under RLock and format outside it,
so the lock is held only for the copy.
Separately, dump() panics when Execute is called with nil data. Runtime.context
is only assigned when data != nil (exec.go:66), so ctx is a zero
reflect.Value and ctx.Type() fails with "reflect: call of
reflect.Value.Type on zero Value". Guard on IsValid.
The existing suite is clean under -race; neither path is covered by it,
and CI does not pass -race.
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.
Two problems in
dumpAll, three lines apart1.
globalsis read without the lock that guards itSet.globalsis mutex-protected everywhere else:set.go:218-222—AddGlobalwrites undergmx.Lock()set.go:226-231—LookupGlobalreads undergmx.RLock()eval.go:201-203— the template execution path reads undergmx.RLock()and
set.go:21names the field's purpose:gmx *sync.RWMutex // global variables map mutex.dump.go:26does not:So rendering a template containing
{{ dump() }}while another goroutine callsAddGlobalraces on the map — at the range and again at the index:A second report in the same run points at
dump.go:31(val := vars[name]). It reproduced on every run I tried.Note
dump("x")is already safe — that path goes throughrnt.resolve, which takes the RLock. Only the no-argument and depth forms bypass it.2.
dump()panics on a nil contextRuntime.contextis only assigned whendata != nil(exec.go:66-68), soExecute(w, vars, nil)leaves it a zeroreflect.Valueanddump.go:19calls.Type()on it:Deterministic, no concurrency needed. This is also what masked the race at first — the panic short-circuits execution before line 26 is reached.
The change
Copy the map under
RLockand format outside it, so the lock is held only for the copy rather than for the whole formatting pass. And guard the context read withIsValid(), printing<nil>when there is no context.Tests
TestDumpGlobalsWhileAddGlobalrunsAddGlobalandExecuteconcurrently;TestDumpWithNilContextcovers the nil-data path.Reverting only
dump.goand keeping the tests gives the race report back under-race, and the nil test fails with thereflect.Value.Typepanic.go test -race ./...is green across all packages with the change. The existing suite is already clean under-race— neither of these paths is covered by it, and CI does not pass-race(.travis.ymlandappveyor.ymlboth run plaingo test -v ./...), which is why they have stayed hidden.One note:
gofmtflagsdump.goat HEAD over the doc-comment bullet indentation (the Go 1.19 comment reformat). I left that alone rather than mixing an unrelated reformat into this diff.Disclosure: this patch was prepared with AI assistance. The race reproduction, the red/green check and the suite runs above were executed against this branch; happy to adjust anything on request.