mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-29 23:20:49 +03:00
parent
99fff2997d
commit
5643e663e7
7 changed files with 68 additions and 38 deletions
|
@ -213,7 +213,7 @@ func (l configLoader) applyDefaultConfig() error {
|
||||||
"disableAliases": false,
|
"disableAliases": false,
|
||||||
"debug": false,
|
"debug": false,
|
||||||
"disableFastRender": false,
|
"disableFastRender": false,
|
||||||
"timeout": "30s",
|
"timeout": "60s",
|
||||||
"timeZone": "",
|
"timeZone": "",
|
||||||
"enableInlineShortcodes": false,
|
"enableInlineShortcodes": false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -850,7 +850,7 @@ func (c *cachedContentScope) contentPlain(ctx context.Context) (contentPlainPlai
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if herrors.IsTimeoutError(err) {
|
if herrors.IsTimeoutError(err) {
|
||||||
err = fmt.Errorf("timed out rendering the page content. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file: %w", err)
|
err = fmt.Errorf("timed out rendering the page content. Extend the `timeout` limit in your Hugo config file: %w", err)
|
||||||
}
|
}
|
||||||
return contentPlainPlainWords{}, err
|
return contentPlainPlainWords{}, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,13 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/bep/lazycache"
|
"github.com/bep/lazycache"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/constants"
|
"github.com/gohugoio/hugo/common/constants"
|
||||||
"github.com/gohugoio/hugo/common/hashing"
|
"github.com/gohugoio/hugo/common/hashing"
|
||||||
"github.com/gohugoio/hugo/identity"
|
"github.com/gohugoio/hugo/identity"
|
||||||
|
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/tpl"
|
"github.com/gohugoio/hugo/tpl"
|
||||||
texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
|
"github.com/gohugoio/hugo/tpl/tplimpl"
|
||||||
|
|
||||||
bp "github.com/gohugoio/hugo/bufferpool"
|
bp "github.com/gohugoio/hugo/bufferpool"
|
||||||
"github.com/gohugoio/hugo/deps"
|
"github.com/gohugoio/hugo/deps"
|
||||||
|
@ -109,7 +109,7 @@ func (c *contextWrapper) Set(in any) string {
|
||||||
// A string if the partial is a text/template, or template.HTML when html/template.
|
// A string if the partial is a text/template, or template.HTML when html/template.
|
||||||
// Note that ctx is provided by Hugo, not the end user.
|
// Note that ctx is provided by Hugo, not the end user.
|
||||||
func (ns *Namespace) Include(ctx context.Context, name string, contextList ...any) (any, error) {
|
func (ns *Namespace) Include(ctx context.Context, name string, contextList ...any) (any, error) {
|
||||||
res := ns.includWithTimeout(ctx, name, contextList...)
|
res := ns.include(ctx, name, contextList...)
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
return nil, res.err
|
return nil, res.err
|
||||||
}
|
}
|
||||||
|
@ -121,49 +121,36 @@ func (ns *Namespace) Include(ctx context.Context, name string, contextList ...an
|
||||||
return res.result, nil
|
return res.result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ns *Namespace) includWithTimeout(ctx context.Context, name string, dataList ...any) includeResult {
|
func (ns *Namespace) include(ctx context.Context, name string, dataList ...any) includeResult {
|
||||||
|
v, err := ns.lookup(name)
|
||||||
|
if err != nil {
|
||||||
|
return includeResult{err: err}
|
||||||
|
}
|
||||||
|
return ns.doInclude(ctx, v, dataList...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *Namespace) lookup(name string) (*tplimpl.TemplInfo, error) {
|
||||||
if strings.HasPrefix(name, "partials/") {
|
if strings.HasPrefix(name, "partials/") {
|
||||||
// This is most likely not what the user intended.
|
// This is most likely not what the user intended.
|
||||||
// This worked before Hugo 0.146.0.
|
// This worked before Hugo 0.146.0.
|
||||||
ns.deps.Log.Warnidf(constants.WarnPartialSuperfluousPrefix, "Partial name %q starting with 'partials/' (as in {{ partial \"%s\"}}) is most likely not what you want. Before 0.146.0 we did a double lookup in this situation.", name, name)
|
ns.deps.Log.Warnidf(constants.WarnPartialSuperfluousPrefix, "Partial name %q starting with 'partials/' (as in {{ partial \"%s\"}}) is most likely not what you want. Before 0.146.0 we did a double lookup in this situation.", name, name)
|
||||||
}
|
}
|
||||||
// Create a new context with a timeout not connected to the incoming context.
|
v := ns.deps.TemplateStore.LookupPartial(name)
|
||||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), ns.deps.Conf.Timeout())
|
if v == nil {
|
||||||
defer cancel()
|
return nil, fmt.Errorf("partial %q not found", name)
|
||||||
|
|
||||||
res := make(chan includeResult, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
res <- ns.include(ctx, name, dataList...)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case r := <-res:
|
|
||||||
return r
|
|
||||||
case <-timeoutCtx.Done():
|
|
||||||
err := timeoutCtx.Err()
|
|
||||||
if err == context.DeadlineExceeded {
|
|
||||||
//lint:ignore ST1005 end user message.
|
|
||||||
err = fmt.Errorf("partial %q timed out after %s. This is most likely due to infinite recursion. If this is just a slow template, you can try to increase the 'timeout' config setting.", name, ns.deps.Conf.Timeout())
|
|
||||||
}
|
|
||||||
return includeResult{err: err}
|
|
||||||
}
|
}
|
||||||
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// include is a helper function that lookups and executes the named partial.
|
// include is a helper function that lookups and executes the named partial.
|
||||||
// Returns the final template name and the rendered output.
|
// Returns the final template name and the rendered output.
|
||||||
func (ns *Namespace) include(ctx context.Context, name string, dataList ...any) includeResult {
|
func (ns *Namespace) doInclude(ctx context.Context, templ *tplimpl.TemplInfo, dataList ...any) includeResult {
|
||||||
var data any
|
var data any
|
||||||
if len(dataList) > 0 {
|
if len(dataList) > 0 {
|
||||||
data = dataList[0]
|
data = dataList[0]
|
||||||
}
|
}
|
||||||
v := ns.deps.TemplateStore.LookupPartial(name)
|
|
||||||
if v == nil {
|
|
||||||
return includeResult{err: fmt.Errorf("partial %q not found", name)}
|
|
||||||
}
|
|
||||||
|
|
||||||
templ := v
|
info := templ.ParseInfo
|
||||||
info := v.ParseInfo
|
|
||||||
|
|
||||||
var w io.Writer
|
var w io.Writer
|
||||||
|
|
||||||
|
@ -212,6 +199,20 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
|
||||||
Variants: variants,
|
Variants: variants,
|
||||||
}
|
}
|
||||||
depsManagerIn := tpl.Context.GetDependencyManagerInCurrentScope(ctx)
|
depsManagerIn := tpl.Context.GetDependencyManagerInCurrentScope(ctx)
|
||||||
|
ti, err := ns.lookup(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent := tpl.Context.CurrentTemplate.Get(ctx); parent != nil {
|
||||||
|
for parent != nil {
|
||||||
|
if parent.CurrentTemplateInfoOps == ti {
|
||||||
|
// This will deadlock if we continue.
|
||||||
|
return nil, fmt.Errorf("circular call stack detected in partial %q", ti.Filename())
|
||||||
|
}
|
||||||
|
parent = parent.Parent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r, found, err := ns.cachedPartials.cache.GetOrCreate(key.Key(), func(string) (includeResult, error) {
|
r, found, err := ns.cachedPartials.cache.GetOrCreate(key.Key(), func(string) (includeResult, error) {
|
||||||
var depsManagerShared identity.Manager
|
var depsManagerShared identity.Manager
|
||||||
|
@ -221,7 +222,7 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
|
||||||
depsManagerShared = identity.NewManager("partials")
|
depsManagerShared = identity.NewManager("partials")
|
||||||
ctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, depsManagerShared.(identity.DependencyManagerScopedProvider))
|
ctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, depsManagerShared.(identity.DependencyManagerScopedProvider))
|
||||||
}
|
}
|
||||||
r := ns.includWithTimeout(ctx, key.Name, context)
|
r := ns.doInclude(ctx, ti, context)
|
||||||
if ns.deps.Conf.Watching() {
|
if ns.deps.Conf.Watching() {
|
||||||
r.mangager = depsManagerShared
|
r.mangager = depsManagerShared
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,7 +256,6 @@ func TestIncludeTimeout(t *testing.T) {
|
||||||
files := `
|
files := `
|
||||||
-- config.toml --
|
-- config.toml --
|
||||||
baseURL = 'http://example.com/'
|
baseURL = 'http://example.com/'
|
||||||
timeout = '200ms'
|
|
||||||
-- layouts/index.html --
|
-- layouts/index.html --
|
||||||
{{ partials.Include "foo.html" . }}
|
{{ partials.Include "foo.html" . }}
|
||||||
-- layouts/partials/foo.html --
|
-- layouts/partials/foo.html --
|
||||||
|
@ -271,7 +270,7 @@ timeout = '200ms'
|
||||||
).BuildE()
|
).BuildE()
|
||||||
|
|
||||||
b.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
b.Assert(err.Error(), qt.Contains, "timed out")
|
b.Assert(err.Error(), qt.Contains, "maximum template call stack size exceeded")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIncludeCachedTimeout(t *testing.T) {
|
func TestIncludeCachedTimeout(t *testing.T) {
|
||||||
|
@ -284,6 +283,8 @@ timeout = '200ms'
|
||||||
-- layouts/index.html --
|
-- layouts/index.html --
|
||||||
{{ partials.IncludeCached "foo.html" . }}
|
{{ partials.IncludeCached "foo.html" . }}
|
||||||
-- layouts/partials/foo.html --
|
-- layouts/partials/foo.html --
|
||||||
|
{{ partialCached "bar.html" . }}
|
||||||
|
-- layouts/partials/bar.html --
|
||||||
{{ partialCached "foo.html" . }}
|
{{ partialCached "foo.html" . }}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -295,7 +296,7 @@ timeout = '200ms'
|
||||||
).BuildE()
|
).BuildE()
|
||||||
|
|
||||||
b.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
b.Assert(err.Error(), qt.Contains, "timed out")
|
b.Assert(err.Error(), qt.Contains, `error calling partialCached: circular call stack detected in partial`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// See Issue #10789
|
// See Issue #10789
|
||||||
|
|
|
@ -160,6 +160,7 @@ type CurrentTemplateInfoCommonOps interface {
|
||||||
// CurrentTemplateInfo as returned in templates.Current.
|
// CurrentTemplateInfo as returned in templates.Current.
|
||||||
type CurrentTemplateInfo struct {
|
type CurrentTemplateInfo struct {
|
||||||
Parent *CurrentTemplateInfo
|
Parent *CurrentTemplateInfo
|
||||||
|
Level int
|
||||||
CurrentTemplateInfoOps
|
CurrentTemplateInfoOps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -484,13 +484,24 @@ func (t *TemplateStore) ExecuteWithContext(ctx context.Context, ti *TemplInfo, w
|
||||||
|
|
||||||
templ := ti.Template
|
templ := ti.Template
|
||||||
|
|
||||||
|
parent := tpl.Context.CurrentTemplate.Get(ctx)
|
||||||
|
var level int
|
||||||
|
if parent != nil {
|
||||||
|
level = parent.Level + 1
|
||||||
|
}
|
||||||
currentTi := &tpl.CurrentTemplateInfo{
|
currentTi := &tpl.CurrentTemplateInfo{
|
||||||
Parent: tpl.Context.CurrentTemplate.Get(ctx),
|
Parent: parent,
|
||||||
|
Level: level,
|
||||||
CurrentTemplateInfoOps: ti,
|
CurrentTemplateInfoOps: ti,
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = tpl.Context.CurrentTemplate.Set(ctx, currentTi)
|
ctx = tpl.Context.CurrentTemplate.Set(ctx, currentTi)
|
||||||
|
|
||||||
|
const levelThreshold = 999
|
||||||
|
if level > levelThreshold {
|
||||||
|
return fmt.Errorf("maximum template call stack size exceeded in %q", ti.Filename())
|
||||||
|
}
|
||||||
|
|
||||||
if t.opts.Metrics != nil {
|
if t.opts.Metrics != nil {
|
||||||
defer t.opts.Metrics.MeasureSince(templ.Name(), time.Now())
|
defer t.opts.Metrics.MeasureSince(templ.Name(), time.Now())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1229,3 +1229,19 @@ layouts/list.html
|
||||||
b.AssertFileContent("public/p1/index.html", "layouts/single.html")
|
b.AssertFileContent("public/p1/index.html", "layouts/single.html")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplateLoop(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- hugo.toml --
|
||||||
|
-- layouts/_partials/p.html --
|
||||||
|
p: {{ partial "p.html" . }}
|
||||||
|
-- layouts/all.html --
|
||||||
|
{{ partial "p.html" . }}
|
||||||
|
|
||||||
|
`
|
||||||
|
b, err := hugolib.TestE(t, files)
|
||||||
|
b.Assert(err, qt.IsNotNil)
|
||||||
|
b.Assert(err.Error(), qt.Contains, "error calling partial: maximum template call stack size exceeded")
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue