Skip to content
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

express-hbs 2.0.2 causes infinite loops and crashes Ghost #10643

Closed
allouis opened this issue Mar 27, 2019 · 1 comment · Fixed by #10750
Closed

express-hbs 2.0.2 causes infinite loops and crashes Ghost #10643

allouis opened this issue Mar 27, 2019 · 1 comment · Fixed by #10750
Assignees
Labels
bug [triage] something behaving unexpectedly

Comments

@allouis
Copy link
Contributor

allouis commented Mar 27, 2019

#10642 reverts the update to express 2.0.2 to avoid this bug.

Issue summary

When using the {{#get}} helper inside of a helper's template file, Ghost seems to end up in an infinite loop and crashes.

Technical details

In an effort to narrow the cause of the problem the following scenarios have been tested (✅=working, ❌=infinite loop):

  • {{#get}} inside a regular partial
  • {{#get}} inside the original navigation.hbs used by {{navigation}}
  • {{#get}} inside of a theme's partials/navigation.hbs override
  • {{#get}} inside the pagination.hbs file or a theme override

Our template-driven helpers all use templates.execute() which compiles a hbs template string if necessary then calls the resulting function. These are non-async helpers, whereas {{get}} is an async helper (returns promise).

I've generated a flamegraph which highlights the calls within express-hbs here and here that appear to be looping.

kevinansfield pushed a commit that referenced this issue Mar 27, 2019
@ErisDS ErisDS added the bug [triage] something behaving unexpectedly label Mar 28, 2019
@allouis allouis self-assigned this Apr 3, 2019
@allouis
Copy link
Contributor Author

allouis commented May 13, 2019

Update

This issue can now be traced back to here: https://github.com/barc/express-hbs/blob/v2.1.2/lib/hbs.js#L364-L366

In some circumstances, the value of this.resolverCache is not the same as context...resolverCache or options...resolverCache.

In this case, the promise will be added to a "resolverCache" which is not shared between helpers, so when express-hbs attempts to re-hydrate the template output with promise data, it will not be found - resulting in an endless loop when trying to replace the __aSyNc_ string.

Fix (possible)

This can be fixed by replacing the highlighted lines from above with:

    var resolverCache = _.get(context, 'data.root.resolverCache') ||
		_.get(options, 'data.root.resolverCache') ||
		this.resolverCache;

This changes forces express-hbs to prefer a resolverCache from either the context or the options, which reads from the root data - meaning that it is shared between all helpers when rendering a template. (I would like to know if it's possible for neither options nor context to have a resolverCache but this does - I suspect this is not a valid case)

Status

I have still not managed to replicate this outside of Ghost, but it seems to be occurring when a block helper is used like {{#helper "smth" key=val}} - Which causes the context to equal "smth" (An example of usage in Ghost is {{#get "posts" include="tags"}}

Handlebars documentation states that a helper will always be called with a this value of context - however when context is a string, this is not the case.

I'm going to continue to try to produce a breaking test for this, though if we can't, I would suggest moving forward with the patch detailed above.

allouis added a commit to allouis/Ghost that referenced this issue Jun 25, 2019
closes TryGhost#10643

The async resolver in express-hbs relies on storing the state of the
promises on the `this` value inside of a helper, which is always set to
the `context`. This patch updates our helpers which render templates, to
use `this` as the context when rendering their templates.
allouis added a commit that referenced this issue Jun 25, 2019
closes #10643

The async resolver in express-hbs relies on storing the state of the
promises on the `this` value inside of a helper, which is always set to
the `context`. This patch updates our helpers which render templates, to
use `this` as the context when rendering their templates.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug [triage] something behaving unexpectedly
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants