Only invoke a given cached partial once

Note that this is backed by a LRU cache (which we soon shall see more usage of), so if you're a heavy user of cached partials it may be evicted and
refreshed if needed. But in most cases every partial is only invoked once.

This commit also adds a timeout (the global `timeout` config option) to make infinite recursion in partials
easier to reason about.

```
name              old time/op    new time/op    delta
IncludeCached-10    8.92ms ± 0%    8.48ms ± 1%   -4.87%  (p=0.016 n=4+5)

name              old alloc/op   new alloc/op   delta
IncludeCached-10    6.65MB ± 0%    5.17MB ± 0%  -22.32%  (p=0.002 n=6+6)

name              old allocs/op  new allocs/op  delta
IncludeCached-10      117k ± 0%       71k ± 0%  -39.44%  (p=0.002 n=6+6)
```

Closes #4086
Updates #9588
This commit is contained in:
Bjørn Erik Pedersen
2023-01-24 20:57:15 +01:00
parent 93ed6e447a
commit 4ef9baf5bd
21 changed files with 346 additions and 203 deletions

View File

@@ -21,6 +21,8 @@ import (
"strings"
"testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/htesting/hqt"
"github.com/gohugoio/hugo/hugolib"
)
@@ -225,7 +227,7 @@ D1
b.Assert(got, hqt.IsSameString, expect)
}
// gobench --package ./tpl/partials
// gobench --package ./tpl/partials
func BenchmarkIncludeCached(b *testing.B) {
files := `
-- config.toml --
@@ -262,7 +264,7 @@ ABCDE
}
builders := make([]*hugolib.IntegrationTestBuilder, b.N)
for i, _ := range builders {
for i := range builders {
builders[i] = hugolib.NewIntegrationTestBuilder(cfg)
}
@@ -272,3 +274,53 @@ ABCDE
builders[i].Build()
}
}
func TestIncludeTimeout(t *testing.T) {
t.Parallel()
files := `
-- config.toml --
baseURL = 'http://example.com/'
timeout = '200ms'
-- layouts/index.html --
{{ partials.Include "foo.html" . }}
-- layouts/partials/foo.html --
{{ partial "foo.html" . }}
`
b, err := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).BuildE()
b.Assert(err, qt.Not(qt.IsNil))
b.Assert(err.Error(), qt.Contains, "timed out")
}
func TestIncludeCachedTimeout(t *testing.T) {
t.Parallel()
files := `
-- config.toml --
baseURL = 'http://example.com/'
timeout = '200ms'
-- layouts/index.html --
{{ partials.IncludeCached "foo.html" . }}
-- layouts/partials/foo.html --
{{ partialCached "foo.html" . }}
`
b, err := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).BuildE()
b.Assert(err, qt.Not(qt.IsNil))
b.Assert(err.Error(), qt.Contains, "timed out")
}