aboutsummaryrefslogtreecommitdiff
path: root/src/html
diff options
context:
space:
mode:
authorCaleb Spare <cespare@gmail.com>2016-10-14 00:59:19 -0700
committerRob Pike <r@golang.org>2016-10-17 00:35:20 +0000
commitcd2c9df7612795cad5b56cabe5ec29c7771db5fe (patch)
treec7246b27dc2b0248b4c58d28f157f7e73db9b703 /src/html
parentd8cbc2c918f68e8ca5992e68fed052a0e52a8e67 (diff)
downloadgo-cd2c9df7612795cad5b56cabe5ec29c7771db5fe.tar.xz
html/template: fix Clone so that t.Lookup(t.Name()) yields t
Template.escape makes the assumption that t.Lookup(t.Name()) is t (escapeTemplate looks up the associated template by name and sets escapeErr appropriately). This assumption did not hold for a Cloned template, because the template associated with t.Name() was a second copy of the original. Add a test for the assumption that t.Lookup(t.Name()) == t. One effect of this broken assumption was #16101: parallel Executes racily accessed the template namespace because each Execute call saw t.escapeErr == nil and re-escaped the template concurrently with read accesses occurring outside the namespace mutex. Add a test for this race. Related to #12996 and CL 16104. Fixes #16101 Change-Id: I59831d0847abbabb4ef9135f2912c6ce982f9837 Reviewed-on: https://go-review.googlesource.com/31092 Run-TryBot: Rob Pike <r@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rob Pike <r@golang.org>
Diffstat (limited to 'src/html')
-rw-r--r--src/html/template/clone_test.go35
-rw-r--r--src/html/template/template.go3
2 files changed, 38 insertions, 0 deletions
diff --git a/src/html/template/clone_test.go b/src/html/template/clone_test.go
index d7c62fa399..069064c98b 100644
--- a/src/html/template/clone_test.go
+++ b/src/html/template/clone_test.go
@@ -8,6 +8,7 @@ import (
"bytes"
"errors"
"io/ioutil"
+ "sync"
"testing"
"text/template/parse"
)
@@ -194,3 +195,37 @@ func TestFuncMapWorksAfterClone(t *testing.T) {
t.Errorf("clone error message mismatch want %q got %q", wantErr, gotErr)
}
}
+
+// https://golang.org/issue/16101
+func TestTemplateCloneExecuteRace(t *testing.T) {
+ const (
+ input = `<title>{{block "a" .}}a{{end}}</title><body>{{block "b" .}}b{{end}}<body>`
+ overlay = `{{define "b"}}A{{end}}`
+ )
+ outer := Must(New("outer").Parse(input))
+ tmpl := Must(Must(outer.Clone()).Parse(overlay))
+
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ for i := 0; i < 100; i++ {
+ if err := tmpl.Execute(ioutil.Discard, "data"); err != nil {
+ panic(err)
+ }
+ }
+ }()
+ }
+ wg.Wait()
+}
+
+func TestTemplateCloneLookup(t *testing.T) {
+ // Template.escape makes an assumption that the template associated
+ // with t.Name() is t. Check that this holds.
+ tmpl := Must(New("x").Parse("a"))
+ tmpl = Must(tmpl.Clone())
+ if tmpl.Lookup(tmpl.Name()) != tmpl {
+ t.Error("after Clone, tmpl.Lookup(tmpl.Name()) != tmpl")
+ }
+}
diff --git a/src/html/template/template.go b/src/html/template/template.go
index 063e46d6bf..d5e195ff69 100644
--- a/src/html/template/template.go
+++ b/src/html/template/template.go
@@ -240,6 +240,9 @@ func (t *Template) Clone() (*Template, error) {
ret.set[ret.Name()] = ret
for _, x := range textClone.Templates() {
name := x.Name()
+ if name == ret.Name() {
+ continue
+ }
src := t.set[name]
if src == nil || src.escapeErr != nil {
return nil, fmt.Errorf("html/template: cannot Clone %q after it has executed", t.Name())