Repository: gohugoio/hugo (language: go)

Issue:
partialCached regression from 0.92.2 to 0.93.0
With the changes in 0.93 partialCached started causing deadlocks. While the issue with #9519 was fixed, there are still more things causing it.
I figured out my problem was trying to run `partialCached "something"` while rendering a `partialCached "something"`, and I prepared a simple repro here: https://github.com/n1xx1/hugo-0.93.0-deadlock-repro
Of course changing back to 0.92.2 everything works as expected, and also if I change partialCached into partial.
Thank you.

### What version of Hugo are you using (`hugo version`)?

<pre>
$ hugo version
hugo v0.93.0-07469082+extended windows/amd64 BuildDate=2022-02-28T08:30:42Z VendorInfo=gohugoio
</pre>

### Does this issue reproduce with the latest release?
Yes


Discussion / hints:
Hmmm... Thanks for the report.
OK, now I understand what's going on. I need to roll back that lock change and wait for #9570 -- the problem you're seeing is that you don't get the same `context.Context` all the way down, as it's only partially implemented, so when you go `single.html => paritalCached => Content => shortcode => partialCached` you end up in this situation.

I'm sorry about this mess.

Fix patch:
diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go
index 500f5d1a358..b18e280ca4b 100644
--- a/tpl/partials/partials.go
+++ b/tpl/partials/partials.go
@@ -233,21 +233,14 @@ func (ns *Namespace) getOrCreate(ctx context.Context, key partialCacheKey, conte
 		}
 	}()
 
-	// We may already have a write lock.
-	hasLock := tpl.GetHasLockFromContext(ctx)
-
-	if !hasLock {
-		ns.cachedPartials.RLock()
-	}
+	ns.cachedPartials.RLock()
 	p, ok := ns.cachedPartials.p[key]
-	if !hasLock {
-		ns.cachedPartials.RUnlock()
-	}
+	ns.cachedPartials.RUnlock()
 
 	if ok {
 		if ns.deps.Metrics != nil {
 			ns.deps.Metrics.TrackValue(key.templateName(), p, true)
-			// The templates that gets executed is measued in Execute.
+			// The templates that gets executed is measured in Execute.
 			// We need to track the time spent in the cache to
 			// get the totals correct.
 			ns.deps.Metrics.MeasureSince(key.templateName(), start)
@@ -256,21 +249,28 @@ func (ns *Namespace) getOrCreate(ctx context.Context, key partialCacheKey, conte
 		return p, nil
 	}
 
-	if !hasLock {
-		ns.cachedPartials.Lock()
-		defer ns.cachedPartials.Unlock()
-		ctx = tpl.SetHasLockInContext(ctx, true)
-	}
-
-	var name string
-	name, p, err = ns.include(ctx, key.name, context)
+	// This needs to be done outside the lock.
+	// See #9588
+	_, p, err = ns.include(ctx, key.name, context)
 	if err != nil {
 		return nil, err
 	}
 
+	ns.cachedPartials.Lock()
+	defer ns.cachedPartials.Unlock()
+	// Double-check.
+	if p2, ok := ns.cachedPartials.p[key]; ok {
+		if ns.deps.Metrics != nil {
+			ns.deps.Metrics.TrackValue(key.templateName(), p, true)
+			ns.deps.Metrics.MeasureSince(key.templateName(), start)
+		}
+		return p2, nil
+
+	}
 	if ns.deps.Metrics != nil {
-		ns.deps.Metrics.TrackValue(name, p, false)
+		ns.deps.Metrics.TrackValue(key.templateName(), p, false)
 	}
+
 	ns.cachedPartials.p[key] = p
 
 	return p, nil


Test patch:
diff --git a/tpl/partials/integration_test.go b/tpl/partials/integration_test.go
index 446e4711809..bda5ddbd5c6 100644
--- a/tpl/partials/integration_test.go
+++ b/tpl/partials/integration_test.go
@@ -103,6 +103,44 @@ P2
 `)
 }
 
+// Issue #588
+func TestIncludeCachedRecursionShortcode(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- config.toml --
+baseURL = 'http://example.com/'
+-- content/_index.md --
+---
+title: "Index"
+---
+{{< short >}}
+-- layouts/index.html --
+{{ partials.IncludeCached "p1.html" . }}
+-- layouts/partials/p1.html --
+{{ .Content }}
+{{ partials.IncludeCached "p2.html" . }}
+-- layouts/partials/p2.html --
+-- layouts/shortcodes/short.html --
+SHORT
+{{ partials.IncludeCached "p2.html" . }}
+P2
+
+  `
+
+	b := hugolib.NewIntegrationTestBuilder(
+		hugolib.IntegrationTestConfig{
+			T:           t,
+			TxtarString: files,
+		},
+	).Build()
+
+	b.AssertFileContent("public/index.html", `
+SHORT
+P2
+`)
+}
+
 func TestIncludeCacheHints(t *testing.T) {
 	t.Parallel()