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 = `{{block "a" .}}a{{end}}{{block "b" .}}b{{end}}` + 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())