Add a key to the partialCached deadlock prevention

Fixes #13889
This commit is contained in:
n1xx1
2025-08-07 11:17:14 +02:00
committed by GitHub
parent ecc3dd1f53
commit 2216028620
4 changed files with 40 additions and 6 deletions

View File

@@ -126,7 +126,7 @@ func (ns *Namespace) include(ctx context.Context, name string, dataList ...any)
if err != nil { if err != nil {
return includeResult{err: err} return includeResult{err: err}
} }
return ns.doInclude(ctx, v, dataList...) return ns.doInclude(ctx, "", v, dataList...)
} }
func (ns *Namespace) lookup(name string) (*tplimpl.TemplInfo, error) { func (ns *Namespace) lookup(name string) (*tplimpl.TemplInfo, error) {
@@ -144,7 +144,7 @@ func (ns *Namespace) lookup(name string) (*tplimpl.TemplInfo, error) {
// 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) doInclude(ctx context.Context, templ *tplimpl.TemplInfo, dataList ...any) includeResult { func (ns *Namespace) doInclude(ctx context.Context, key string, 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]
@@ -170,7 +170,7 @@ func (ns *Namespace) doInclude(ctx context.Context, templ *tplimpl.TemplInfo, da
w = b w = b
} }
if err := ns.deps.GetTemplateStore().ExecuteWithContext(ctx, templ, w, data); err != nil { if err := ns.deps.GetTemplateStore().ExecuteWithContextAndKey(ctx, key, templ, w, data); err != nil {
return includeResult{err: err} return includeResult{err: err}
} }
@@ -198,6 +198,8 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
Name: name, Name: name,
Variants: variants, Variants: variants,
} }
keyString := key.Key()
depsManagerIn := tpl.Context.GetDependencyManagerInCurrentScope(ctx) depsManagerIn := tpl.Context.GetDependencyManagerInCurrentScope(ctx)
ti, err := ns.lookup(name) ti, err := ns.lookup(name)
if err != nil { if err != nil {
@@ -206,7 +208,7 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
if parent := tpl.Context.CurrentTemplate.Get(ctx); parent != nil { if parent := tpl.Context.CurrentTemplate.Get(ctx); parent != nil {
for parent != nil { for parent != nil {
if parent.CurrentTemplateInfoOps == ti { if parent.CurrentTemplateInfoOps == ti && parent.Key == keyString {
// This will deadlock if we continue. // This will deadlock if we continue.
return nil, fmt.Errorf("circular call stack detected in partial %q", ti.Filename()) return nil, fmt.Errorf("circular call stack detected in partial %q", ti.Filename())
} }
@@ -214,7 +216,7 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
} }
} }
r, found, err := ns.cachedPartials.cache.GetOrCreate(key.Key(), func(string) (includeResult, error) { r, found, err := ns.cachedPartials.cache.GetOrCreate(keyString, func(string) (includeResult, error) {
var depsManagerShared identity.Manager var depsManagerShared identity.Manager
if ns.deps.Conf.Watching() { if ns.deps.Conf.Watching() {
// We need to create a shared dependency manager to pass downwards // We need to create a shared dependency manager to pass downwards
@@ -222,7 +224,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.doInclude(ctx, ti, context) r := ns.doInclude(ctx, keyString, ti, context)
if ns.deps.Conf.Watching() { if ns.deps.Conf.Watching() {
r.mangager = depsManagerShared r.mangager = depsManagerShared
} }

View File

@@ -299,6 +299,32 @@ timeout = '200ms'
b.Assert(err.Error(), qt.Contains, `error calling partialCached: circular call stack detected in partial`) b.Assert(err.Error(), qt.Contains, `error calling partialCached: circular call stack detected in partial`)
} }
// See Issue #13889
func TestIncludeCachedDifferentKey(t *testing.T) {
t.Parallel()
files := `
-- config.toml --
baseURL = 'http://example.com/'
timeout = '200ms'
-- layouts/index.html --
{{ partialCached "foo.html" "a" "a" }}
-- layouts/partials/foo.html --
{{ if eq . "a" }}
{{ partialCached "bar.html" . }}
{{ else }}
DONE
{{ end }}
-- layouts/partials/bar.html --
{{ partialCached "foo.html" "b" "b" }}
`
b := hugolib.Test(t, files)
b.AssertFileContent("public/index.html", `
DONE
`)
}
// See Issue #10789 // See Issue #10789
func TestReturnExecuteFromTemplateInPartial(t *testing.T) { func TestReturnExecuteFromTemplateInPartial(t *testing.T) {
t.Parallel() t.Parallel()

View File

@@ -161,6 +161,7 @@ type CurrentTemplateInfoCommonOps interface {
type CurrentTemplateInfo struct { type CurrentTemplateInfo struct {
Parent *CurrentTemplateInfo Parent *CurrentTemplateInfo
Level int Level int
Key string
CurrentTemplateInfoOps CurrentTemplateInfoOps
} }

View File

@@ -485,6 +485,10 @@ func (s *TemplateStore) FindAllBaseTemplateCandidates(overlayKey string, desc Te
} }
func (t *TemplateStore) ExecuteWithContext(ctx context.Context, ti *TemplInfo, wr io.Writer, data any) error { func (t *TemplateStore) ExecuteWithContext(ctx context.Context, ti *TemplInfo, wr io.Writer, data any) error {
return t.ExecuteWithContextAndKey(ctx, "", ti, wr, data)
}
func (t *TemplateStore) ExecuteWithContextAndKey(ctx context.Context, key string, ti *TemplInfo, wr io.Writer, data any) error {
defer func() { defer func() {
ti.executionCounter.Add(1) ti.executionCounter.Add(1)
if ti.base != nil { if ti.base != nil {
@@ -502,6 +506,7 @@ func (t *TemplateStore) ExecuteWithContext(ctx context.Context, ti *TemplInfo, w
currentTi := &tpl.CurrentTemplateInfo{ currentTi := &tpl.CurrentTemplateInfo{
Parent: parent, Parent: parent,
Level: level, Level: level,
Key: key,
CurrentTemplateInfoOps: ti, CurrentTemplateInfoOps: ti,
} }