-
-
Notifications
You must be signed in to change notification settings - Fork 7.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix missing page data for alternative formats #9342
Conversation
Thanks for this. I think I understand the problem. I think it would be good, however, if we could get one code path doing both of these.
I would suspect that the first option would be best/simplest/easiest to understand ... |
@bep Thanks for the review. I've implemented the second solution and got the tests to pass. Here are my notes on the first solution. I tried making the following changes to my first commit: diff --git a/hugolib/page.go b/hugolib/page.go
index f35865cf..59031f93 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -940,40 +940,16 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
panic(fmt.Sprintf("pageOutput is nil for output idx %d", idx))
}
+ if z, ok := p.pageOutput.ContentProvider.(*page.LazyContentProvider); ok {
+ z.Reset()
+ }
+
// Reset any built paginator. This will trigger when re-rendering pages in
// server mode.
if isRenderingSite && p.pageOutput.paginator != nil && p.pageOutput.paginator.current != nil {
p.pageOutput.paginator.reset()
}
- if isRenderingSite {
- cp := p.pageOutput.cp
- if cp == nil {
- // Look for content to reuse.
- for i := 0; i < len(p.pageOutputs); i++ {
- if i == idx {
- continue
- }
- po := p.pageOutputs[i]
-
- if po.cp != nil && po.cp.reuse {
- cp = po.cp
- break
- }
- }
- }
-
- if cp == nil {
- var err error
- cp, err = newPageContentOutput(p, p.pageOutput)
- if err != nil {
- return err
- }
- }
- p.pageOutput.initContentProvider(cp)
- p.pageOutput.cp = cp
- }
-
return nil
}
diff --git a/hugolib/page__output.go b/hugolib/page__output.go
index 7f1140e7..7bb3089c 100644
--- a/hugolib/page__output.go
+++ b/hugolib/page__output.go
@@ -64,10 +64,29 @@ func newPageOutput(
// will be assigned. In the case of an unanticipated request, we lazily
// initialize a pageContentOutput.
ContentProvider: page.NewLazyContentProvider(func() (page.ContentProvider, error) {
- cp, err := newPageContentOutput(ps, ps.pageOutput)
- if err != nil {
- return nil, err
+ cp := ps.pageOutput.cp
+ if cp == nil {
+ // Look for content to reuse.
+ for _, po := range ps.pageOutputs {
+ if po == ps.pageOutput {
+ continue
+ }
+
+ if po.cp != nil && po.cp.reuse {
+ cp = po.cp
+ break
+ }
+ }
+ }
+
+ if cp == nil {
+ var err error
+ cp, err = newPageContentOutput(ps, ps.pageOutput)
+ if err != nil {
+ return nil, err
+ }
}
+ ps.pageOutput.initContentProvider(cp)
return cp, nil
}),
TableOfContentsProvider: page.NopPage,
diff --git a/resources/page/page_lazy_contentprovider.go b/resources/page/page_lazy_contentprovider.go
index d8d92d7c..c1878a46 100644
--- a/resources/page/page_lazy_contentprovider.go
+++ b/resources/page/page_lazy_contentprovider.go
@@ -49,6 +49,12 @@ func NewLazyContentProvider(f func() (ContentProvider, error)) *LazyContentProvi
return &lcp
}
+// Reset returns the LazyContentProvider to its pre-initialization state
+func (lcp *LazyContentProvider) Reset() {
+ lcp.init.Reset()
+ lcp.cp = NopPage
+}
+
func (lcp *LazyContentProvider) Content() (interface{}, error) {
lcp.init.Do()
return lcp.cp.Content() After this,
However, if we make this change to the test, it passes: diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go
index f1c27d51..4b0ed3d3 100644
--- a/hugolib/content_render_hooks_test.go
+++ b/hugolib/content_render_hooks_test.go
@@ -462,6 +462,7 @@ RSTART:Hook Heading: 2:REND
// https://github.com/gohugoio/hugo/issues/6882
func TestRenderStringOnListPage(t *testing.T) {
renderStringTempl := `
+{{ .Summary }}
{{ .RenderString "**Hello**" }}
`
b := newTestSitesBuilder(t) It turns out that if we add the content reuse logic to LazyContentProvider, and a template does not call any of LazyContentProvider's methods, |
@ptgott I will pull this branch and see if I understand it. |
OK; I had a look -- I think we can safely ignore my suggestion earlier. I added a commit to this PR which adjusts the logic slightly to save some mem allocs -- the test still passes and it looks correct to me. Let me know if you agree and I'll merge. |
@bep Looks good to me, thanks! |
When a template calls the .Translations function and a Hugo environment is using multiple output formats, a template that calls methods like .Summary and .Len on each translation will unexpectedly show empty return values for these methods. This is because each pageOutput's ContentProvider is assigned to a page.NopPage in newPageOutput. When *HugoSites.render assigns pageContentOutputs to pageOutputs in *pageState.shiftToOutputFormat, it reuses pageContentOutputs from other pageOutputs, leaving some pageContentOutputs as NopPages. While this approach conserves resources, sometimes it means that a template will unexpectedly call a method on a pageContentOutput that is actually a NopPage. In the case of ContentProvider methods called on translations for alternative output formats, the methods were called on NopPages. This change introduces LazyContentProvider, which performs late initialization when one of its methods is called. This way, we can reuse content in "normal" cases but ensure that ContentProvider methods work as expected when a pageOutput is not assigned a pageContentOutput during the initial pre-render phase. Fixes gohugoio#8919
Which saves a fair amound of allocations: ``` gobench --package ./hugolib --bench "SiteNew/Regular_D" --base master ``` Before: ``` name old time/op new time/op delta SiteNew/Regular_Deep_content_tree-10 40.7ms ± 3% 41.2ms ± 1% ~ (p=0.343 n=4+4) name old alloc/op new alloc/op delta SiteNew/Regular_Deep_content_tree-10 27.7MB ± 0% 28.8MB ± 0% +3.76% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Regular_Deep_content_tree-10 304k ± 0% 329k ± 0% +8.07% (p=0.029 n=4+4) ``` After: ``` name old time/op new time/op delta SiteNew/Regular_Deep_content_tree-10 34.2ms ± 1% 34.7ms ± 1% ~ (p=0.114 n=4+4) name old alloc/op new alloc/op delta SiteNew/Regular_Deep_content_tree-10 27.7MB ± 0% 28.1MB ± 0% +1.38% (p=0.029 n=4+4) name old allocs/op new allocs/op delta SiteNew/Regular_Deep_content_tree-10 304k ± 0% 314k ± 0% +3.03% (p=0.029 n=4+4) ``` Updates gohugoio#8919
PR gohugoio#9342 introduced a regression in which calling .Translations in a template and calling RenderString on a translated Page caused a nil pointer dereference. The issue was that not all Pages returned from .Translations had initialized ContentProviders at the time the calling template was being executed. While PR gohugoio#9342 had attempted to ensure that all ContentProviders were initialized for translations at build time, it only performed the initialization for receivers of ContentProvider methods such as .Summary. However, the ContentProvider's *pageState.pageOutput.cp would remain uninitialized, causing the nil pointer dereference. This change edits the .Translations and .AllTranslations methods to ensure that all of a page's translations have an initialized content provider in time for a template to be executed. Since LazyContentProvider is no longer needed with this approach, this change also reverts the following commits: - cdcd15b - 25d645f Fixes gohugoio#9383
PR gohugoio#9342 introduced a regression in which calling .Translations in a template and calling RenderString on a translated Page caused a nil pointer dereference. The issue was that some Pages returned from .Translations had a nil cp field at the time the calling template was being executed. While PR gohugoio#9342 had attempted to ensure that all ContentProviders were initialized for translations at build time, it only performed the initialization for receivers of ContentProvider methods such as .Summary. However, the ContentProvider's *pageState.pageOutput.cp would remain uninitialized, causing the nil pointer dereference. This change edits the .Translations and .AllTranslations methods to ensure that all of a page's translations have an initialized content provider in time for a template to be executed. Since LazyContentProvider is no longer needed with this approach, this change also reverts the following commits: - cdcd15b - 25d645f Fixes gohugoio#9383
PR gohugoio#9342 introduced a regression in which calling .Translations in a template and calling RenderString on a translated Page caused a nil pointer dereference. The issue was that some Pages returned from .Translations had a nil cp field at the time the calling template was being executed. While PR gohugoio#9342 had attempted to ensure that all ContentProviders were initialized for translations at build time, it only performed the initialization for receivers of ContentProvider methods such as .Summary. However, the ContentProvider's *pageState.pageOutput.cp would remain uninitialized, causing the nil pointer dereference. This change edits the .Translations and .AllTranslations methods to ensure that all of a page's translations have an initialized content provider in time for a template to be executed. Since LazyContentProvider is no longer needed with this approach, this change also reverts the following commits: - cdcd15b - 25d645f Fixes gohugoio#9383
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
When a template calls the .Translations function and a
Hugo environment is using multiple output formats,
a template that calls methods like .Summary and .Len on
each translation will unexpectedly show empty return
values for these methods.
This is because each pageOutput's ContentProvider is
assigned to a page.NopPage in newPageOutput. When
*HugoSites.render assigns pageContentOutputs to
pageOutputs in *pageState.shiftToOutputFormat, it
reuses pageContentOutputs from other pageOutputs,
leaving some pageContentOutputs as NopPages. While this
approach conserves resources, sometimes it means that
a template will unexpectedly call a method on a
pageContentOutput that is actually a NopPage.
In the case of ContentProvider methods called on
translations for alternative output formats, the methods
were called on NopPages.
This change introduces LazyContentProvider, which
performs late initialization when one of its methods is
called. This way, we can reuse content in "normal" cases
but ensure that ContentProvider methods work as expected
when a pageOutput is not assigned a pageContentOutput
during the initial pre-render phase.
Fixes #8919