fix: avoid shared error variable in cycle tag closure - #151
Open
masabni wants to merge 1 commit into
Open
Conversation
cycleTag builds its renderer closure once, at parse time. The closure assigned the result of io.WriteString to the err variable declared by the compiler at tags/iteration_tags.go:45 instead of to a local, so every render of a parsed template wrote to that one captured variable. Rendering a single parsed template concurrently was therefore a data race. Declare the write's error locally. No behavior change.
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.
Fixes #150
cycleTagbuilds its renderer closure once, at parse time. Attags/iteration_tags.go:65the closure assigned the write's error with=rather than:=, so it wrote to theerrdeclared by the compiler at line 45 instead of to a local:The closure is built once per parse and shared by every render, so concurrently rendering one parsed
*Templateraces on that variable. Declaring the error locally fixes it.Only site. Every other returned closure already declares a fresh
errinside itself before assigning (iteration_tags.go:89,include_tag.go:45,render_tag.go:389,404,engine.go:71), so this is one fix rather than a sweep.Behavior unchanged.
erris only ever non-nil on theFRender-to-a-failing-writer path, and the cycle counters were already per-render —cycleMapis created inloopRenderer.renderand published viaforloop[".cycles"]— so rendered output is unaffected.Test.
TestTemplate_Render_cycle_raceparses one template and renders it from 16 goroutines. It sits next to the existingTestTemplate_Parse_raceandTestTemplate_Render_race, which cover concurrent parse and concurrent render of distinct templates; this closes the remaining gap of one shared template. The template is deliberately just{% for %}+{% cycle %}with empty bindings, so it doesn't touch the cachesConfigshares by design. On unfixed code it fails:Checklist
make testpasses.make lintpasses (0 issues).make vetandmake cipass too.