tpl/tplimpl: Rework template management to get rid of concurrency issues

This more or less completes the simplification of the template handling code in Hugo started in v0.62.

The main motivation was to fix a long lasting issue about a crash in HTML content files  without front matter.

But this commit also comes with a big functional improvement.

As we now have moved the base template evaluation to the build stage we now use the same lookup rules for `baseof` as for `list` etc. type of templates.

This means that in this simple example you can have a `baseof` template for the `blog` section without having to duplicate the others:

```
layouts
├── _default
│   ├── baseof.html
│   ├── list.html
│   └── single.html
└── blog
    └── baseof.html
```

Also, when simplifying code, you often get rid of some double work, as shown in the "site building" benchmarks below.

These benchmarks looks suspiciously good, but I have repeated the below with ca. the same result. Compared to master:

```
name                              old time/op    new time/op    delta
SiteNew/Bundle_with_image-16        13.1ms ± 1%    10.5ms ± 1%  -19.34%  (p=0.029 n=4+4)
SiteNew/Bundle_with_JSON_file-16    13.0ms ± 0%    10.7ms ± 1%  -18.05%  (p=0.029 n=4+4)
SiteNew/Tags_and_categories-16      46.4ms ± 2%    43.1ms ± 1%   -7.15%  (p=0.029 n=4+4)
SiteNew/Canonify_URLs-16            52.2ms ± 2%    47.8ms ± 1%   -8.30%  (p=0.029 n=4+4)
SiteNew/Deep_content_tree-16        77.9ms ± 1%    70.9ms ± 1%   -9.01%  (p=0.029 n=4+4)
SiteNew/Many_HTML_templates-16      43.0ms ± 0%    37.2ms ± 1%  -13.54%  (p=0.029 n=4+4)
SiteNew/Page_collections-16         58.2ms ± 1%    52.4ms ± 1%   -9.95%  (p=0.029 n=4+4)

name                              old alloc/op   new alloc/op   delta
SiteNew/Bundle_with_image-16        3.81MB ± 0%    2.22MB ± 0%  -41.70%  (p=0.029 n=4+4)
SiteNew/Bundle_with_JSON_file-16    3.60MB ± 0%    2.01MB ± 0%  -44.20%  (p=0.029 n=4+4)
SiteNew/Tags_and_categories-16      19.3MB ± 1%    14.1MB ± 0%  -26.91%  (p=0.029 n=4+4)
SiteNew/Canonify_URLs-16            70.7MB ± 0%    69.0MB ± 0%   -2.40%  (p=0.029 n=4+4)
SiteNew/Deep_content_tree-16        37.1MB ± 0%    31.2MB ± 0%  -15.94%  (p=0.029 n=4+4)
SiteNew/Many_HTML_templates-16      17.6MB ± 0%    10.6MB ± 0%  -39.92%  (p=0.029 n=4+4)
SiteNew/Page_collections-16         25.9MB ± 0%    21.2MB ± 0%  -17.99%  (p=0.029 n=4+4)

name                              old allocs/op  new allocs/op  delta
SiteNew/Bundle_with_image-16         52.3k ± 0%     26.1k ± 0%  -50.18%  (p=0.029 n=4+4)
SiteNew/Bundle_with_JSON_file-16     52.3k ± 0%     26.1k ± 0%  -50.16%  (p=0.029 n=4+4)
SiteNew/Tags_and_categories-16        336k ± 1%      269k ± 0%  -19.90%  (p=0.029 n=4+4)
SiteNew/Canonify_URLs-16              422k ± 0%      395k ± 0%   -6.43%  (p=0.029 n=4+4)
SiteNew/Deep_content_tree-16          401k ± 0%      313k ± 0%  -21.79%  (p=0.029 n=4+4)
SiteNew/Many_HTML_templates-16        247k ± 0%      143k ± 0%  -42.17%  (p=0.029 n=4+4)
SiteNew/Page_collections-16           282k ± 0%      207k ± 0%  -26.55%  (p=0.029 n=4+4)
```

Fixes #6716
Fixes #6760
Fixes #6768
Fixes #6778
This commit is contained in:
Bjørn Erik Pedersen
2020-01-15 15:59:56 +01:00
parent 8585b388d2
commit c6d650c8c8
46 changed files with 1332 additions and 1446 deletions

View File

@@ -17,10 +17,9 @@ import (
"regexp"
"strings"
"github.com/gohugoio/hugo/identity"
template "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate"
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
"github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate/parse"
"github.com/gohugoio/hugo/common/maps"
@@ -41,25 +40,21 @@ type templateContext struct {
visited map[string]bool
templateNotFound map[string]bool
identityNotFound map[string]bool
lookupFn func(name string) *templateInfoTree
lookupFn func(name string) *templateState
// The last error encountered.
err error
typ templateType
// Set when we're done checking for config header.
configChecked bool
// Contains some info about the template
parseInfo *tpl.ParseInfo
id identity.Manager
t *templateState
// Store away the return node in partials.
returnNode *parse.CommandNode
}
func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
func (c templateContext) getIfNotVisited(name string) *templateState {
if c.visited[name] {
return nil
}
@@ -76,13 +71,11 @@ func (c templateContext) getIfNotVisited(name string) *templateInfoTree {
}
func newTemplateContext(
id identity.Manager,
info *tpl.ParseInfo,
lookupFn func(name string) *templateInfoTree) *templateContext {
t *templateState,
lookupFn func(name string) *templateState) *templateContext {
return &templateContext{
id: id,
parseInfo: info,
t: t,
lookupFn: lookupFn,
visited: make(map[string]bool),
templateNotFound: make(map[string]bool),
@@ -90,79 +83,36 @@ func newTemplateContext(
}
}
func createGetTemplateInfoTreeFor(getID func(name string) *templateInfoTree) func(nn string) *templateInfoTree {
return func(nn string) *templateInfoTree {
return getID(nn)
}
}
func (t *templateHandler) applyTemplateTransformersToHMLTTemplate(typ templateType, templ *template.Template) (*templateContext, error) {
id, info := t.createTemplateInfo(templ.Name())
ti := &templateInfoTree{
tree: templ.Tree,
templ: templ,
typ: typ,
id: id,
info: info,
}
t.templateInfoTree[templ.Name()] = ti
getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
return t.templateInfoTree[name]
})
return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
}
func (t *templateHandler) applyTemplateTransformersToTextTemplate(typ templateType, templ *texttemplate.Template) (*templateContext, error) {
id, info := t.createTemplateInfo(templ.Name())
ti := &templateInfoTree{
tree: templ.Tree,
templ: templ,
typ: typ,
id: id,
info: info,
}
t.templateInfoTree[templ.Name()] = ti
getTemplateInfoTree := createGetTemplateInfoTreeFor(func(name string) *templateInfoTree {
return t.templateInfoTree[name]
})
return applyTemplateTransformers(typ, ti, getTemplateInfoTree)
}
type templateInfoTree struct {
info tpl.ParseInfo
typ templateType
id identity.Manager
templ tpl.Template
tree *parse.Tree
}
func applyTemplateTransformers(
typ templateType,
templ *templateInfoTree,
lookupFn func(name string) *templateInfoTree) (*templateContext, error) {
t *templateState,
lookupFn func(name string) *templateState) (*templateContext, error) {
if templ == nil {
if t == nil {
return nil, errors.New("expected template, but none provided")
}
c := newTemplateContext(templ.id, &templ.info, lookupFn)
c.typ = typ
c := newTemplateContext(t, lookupFn)
tree := getParseTree(t.Template)
_, err := c.applyTransformations(templ.tree.Root)
_, err := c.applyTransformations(tree.Root)
if err == nil && c.returnNode != nil {
// This is a partial with a return statement.
c.parseInfo.HasReturn = true
templ.tree.Root = c.wrapInPartialReturnWrapper(templ.tree.Root)
c.t.parseInfo.HasReturn = true
tree.Root = c.wrapInPartialReturnWrapper(tree.Root)
}
return c, err
}
func getParseTree(templ tpl.Template) *parse.Tree {
templ = unwrap(templ)
if text, ok := templ.(*texttemplate.Template); ok {
return text.Tree
}
return templ.(*htmltemplate.Template).Tree
}
const (
partialReturnWrapperTempl = `{{ $_hugo_dot := $ }}{{ $ := .Arg }}{{ with .Arg }}{{ $_hugo_dot.Set ("PLACEHOLDER") }}{{ end }}`
)
@@ -215,7 +165,7 @@ func (c *templateContext) applyTransformations(n parse.Node) (bool, error) {
case *parse.TemplateNode:
subTempl := c.getIfNotVisited(x.Name)
if subTempl != nil {
c.applyTransformationsToNodes(subTempl.tree.Root)
c.applyTransformationsToNodes(getParseTree(subTempl.Template).Root)
}
case *parse.PipeNode:
c.collectConfig(x)
@@ -263,7 +213,7 @@ func (c *templateContext) hasIdent(idents []string, ident string) bool {
// on the form:
// {{ $_hugo_config:= `{ "version": 1 }` }}
func (c *templateContext) collectConfig(n *parse.PipeNode) {
if c.typ != templateShortcode {
if c.t.typ != templateShortcode {
return
}
if c.configChecked {
@@ -295,7 +245,7 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
c.err = errors.Wrap(err, errMsg)
return
}
if err := mapstructure.WeakDecode(m, &c.parseInfo.Config); err != nil {
if err := mapstructure.WeakDecode(m, &c.t.parseInfo.Config); err != nil {
c.err = errors.Wrap(err, errMsg)
}
}
@@ -304,10 +254,10 @@ func (c *templateContext) collectConfig(n *parse.PipeNode) {
// collectInner determines if the given CommandNode represents a
// shortcode call to its .Inner.
func (c *templateContext) collectInner(n *parse.CommandNode) {
if c.typ != templateShortcode {
if c.t.typ != templateShortcode {
return
}
if c.parseInfo.IsInner || len(n.Args) == 0 {
if c.t.parseInfo.IsInner || len(n.Args) == 0 {
return
}
@@ -321,7 +271,7 @@ func (c *templateContext) collectInner(n *parse.CommandNode) {
}
if c.hasIdent(idents, "Inner") {
c.parseInfo.IsInner = true
c.t.parseInfo.IsInner = true
break
}
}
@@ -351,8 +301,9 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) {
}
partialName = "partials/" + partialName
info := c.lookupFn(partialName)
if info != nil {
c.id.Add(info.id)
c.t.Add(info)
} else {
// Delay for later
c.identityNotFound[partialName] = true
@@ -361,7 +312,7 @@ func (c *templateContext) collectPartialInfo(x *parse.CommandNode) {
}
func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
if c.typ != templatePartial || c.returnNode != nil {
if c.t.typ != templatePartial || c.returnNode != nil {
return true
}
@@ -381,3 +332,18 @@ func (c *templateContext) collectReturnNode(n *parse.CommandNode) bool {
return false
}
func findTemplateIn(name string, in tpl.Template) (tpl.Template, bool) {
in = unwrap(in)
if text, ok := in.(*texttemplate.Template); ok {
if templ := text.Lookup(name); templ != nil {
return templ, true
}
return nil, false
}
if templ := in.(*htmltemplate.Template).Lookup(name); templ != nil {
return templ, true
}
return nil, false
}