Skip to content

Commit 2216028

Browse files
authored
Add a key to the partialCached deadlock prevention
Fixes #13889
1 parent ecc3dd1 commit 2216028

File tree

4 files changed

+40
-6
lines changed

4 files changed

+40
-6
lines changed

tpl/partials/partials.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func (ns *Namespace) include(ctx context.Context, name string, dataList ...any)
126126
if err != nil {
127127
return includeResult{err: err}
128128
}
129-
return ns.doInclude(ctx, v, dataList...)
129+
return ns.doInclude(ctx, "", v, dataList...)
130130
}
131131

132132
func (ns *Namespace) lookup(name string) (*tplimpl.TemplInfo, error) {
@@ -144,7 +144,7 @@ func (ns *Namespace) lookup(name string) (*tplimpl.TemplInfo, error) {
144144

145145
// include is a helper function that lookups and executes the named partial.
146146
// Returns the final template name and the rendered output.
147-
func (ns *Namespace) doInclude(ctx context.Context, templ *tplimpl.TemplInfo, dataList ...any) includeResult {
147+
func (ns *Namespace) doInclude(ctx context.Context, key string, templ *tplimpl.TemplInfo, dataList ...any) includeResult {
148148
var data any
149149
if len(dataList) > 0 {
150150
data = dataList[0]
@@ -170,7 +170,7 @@ func (ns *Namespace) doInclude(ctx context.Context, templ *tplimpl.TemplInfo, da
170170
w = b
171171
}
172172

173-
if err := ns.deps.GetTemplateStore().ExecuteWithContext(ctx, templ, w, data); err != nil {
173+
if err := ns.deps.GetTemplateStore().ExecuteWithContextAndKey(ctx, key, templ, w, data); err != nil {
174174
return includeResult{err: err}
175175
}
176176

@@ -198,6 +198,8 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
198198
Name: name,
199199
Variants: variants,
200200
}
201+
keyString := key.Key()
202+
201203
depsManagerIn := tpl.Context.GetDependencyManagerInCurrentScope(ctx)
202204
ti, err := ns.lookup(name)
203205
if err != nil {
@@ -206,23 +208,23 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
206208

207209
if parent := tpl.Context.CurrentTemplate.Get(ctx); parent != nil {
208210
for parent != nil {
209-
if parent.CurrentTemplateInfoOps == ti {
211+
if parent.CurrentTemplateInfoOps == ti && parent.Key == keyString {
210212
// This will deadlock if we continue.
211213
return nil, fmt.Errorf("circular call stack detected in partial %q", ti.Filename())
212214
}
213215
parent = parent.Parent
214216
}
215217
}
216218

217-
r, found, err := ns.cachedPartials.cache.GetOrCreate(key.Key(), func(string) (includeResult, error) {
219+
r, found, err := ns.cachedPartials.cache.GetOrCreate(keyString, func(string) (includeResult, error) {
218220
var depsManagerShared identity.Manager
219221
if ns.deps.Conf.Watching() {
220222
// We need to create a shared dependency manager to pass downwards
221223
// and add those same dependencies to any cached invocation of this partial.
222224
depsManagerShared = identity.NewManager("partials")
223225
ctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, depsManagerShared.(identity.DependencyManagerScopedProvider))
224226
}
225-
r := ns.doInclude(ctx, ti, context)
227+
r := ns.doInclude(ctx, keyString, ti, context)
226228
if ns.deps.Conf.Watching() {
227229
r.mangager = depsManagerShared
228230
}

tpl/partials/partials_integration_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,32 @@ timeout = '200ms'
299299
b.Assert(err.Error(), qt.Contains, `error calling partialCached: circular call stack detected in partial`)
300300
}
301301

302+
// See Issue #13889
303+
func TestIncludeCachedDifferentKey(t *testing.T) {
304+
t.Parallel()
305+
306+
files := `
307+
-- config.toml --
308+
baseURL = 'http://example.com/'
309+
timeout = '200ms'
310+
-- layouts/index.html --
311+
{{ partialCached "foo.html" "a" "a" }}
312+
-- layouts/partials/foo.html --
313+
{{ if eq . "a" }}
314+
{{ partialCached "bar.html" . }}
315+
{{ else }}
316+
DONE
317+
{{ end }}
318+
-- layouts/partials/bar.html --
319+
{{ partialCached "foo.html" "b" "b" }}
320+
`
321+
b := hugolib.Test(t, files)
322+
323+
b.AssertFileContent("public/index.html", `
324+
DONE
325+
`)
326+
}
327+
302328
// See Issue #10789
303329
func TestReturnExecuteFromTemplateInPartial(t *testing.T) {
304330
t.Parallel()

tpl/template.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ type CurrentTemplateInfoCommonOps interface {
161161
type CurrentTemplateInfo struct {
162162
Parent *CurrentTemplateInfo
163163
Level int
164+
Key string
164165
CurrentTemplateInfoOps
165166
}
166167

tpl/tplimpl/templatestore.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,10 @@ func (s *TemplateStore) FindAllBaseTemplateCandidates(overlayKey string, desc Te
485485
}
486486

487487
func (t *TemplateStore) ExecuteWithContext(ctx context.Context, ti *TemplInfo, wr io.Writer, data any) error {
488+
return t.ExecuteWithContextAndKey(ctx, "", ti, wr, data)
489+
}
490+
491+
func (t *TemplateStore) ExecuteWithContextAndKey(ctx context.Context, key string, ti *TemplInfo, wr io.Writer, data any) error {
488492
defer func() {
489493
ti.executionCounter.Add(1)
490494
if ti.base != nil {
@@ -502,6 +506,7 @@ func (t *TemplateStore) ExecuteWithContext(ctx context.Context, ti *TemplInfo, w
502506
currentTi := &tpl.CurrentTemplateInfo{
503507
Parent: parent,
504508
Level: level,
509+
Key: key,
505510
CurrentTemplateInfoOps: ti,
506511
}
507512

0 commit comments

Comments
 (0)