Page MenuHomePhabricator

Mustache template rendering issues - leaking templates
Closed, ResolvedPublic

Description

We should get to the bottom of these lightncandy issues...
These issues only seem to appear on HHVM
TemplateParser code for reference: http://git.wikimedia.org/blob/mediawiki%2Fcore.git/e37ba0aa2cc74692e2c0cab5bbc5fbb89bb13ae8/includes%2FTemplateParser.php

Issue 1:
When rendering {{tags}} in the browse experiment, sometimes tags won't show up. Putting a dummy 'if' statement above fixes the problem. See [1] for more info:

[1] https://gerrit.wikimedia.org/r/#/c/217161/

it seems this happens when a mixed array is passed in format:

array(
'str' => 'String',
'items' => array(
array( 'key'=> 'foo', 'bar' => 'bar' ),
),
);

Only @bmansurov can currently replicate this so we should check if this is can be replicated outside MediaWiki and file a bug upstream in core or Lightncandy so this doesn't trip us up again!

Issue 2:
Also there was another issue with Array being printed inside of content on T102558

Screen Shot 2015-06-15 at 3.32.54 PM.png (362×412 px, 31 KB)

Reverting https://gerrit.wikimedia.org/r/#/c/218535/ fixed this.

Issue 3:
A third issue has this rendering non-template values (list-thumb-y) despite these not being in template or data passed into it:

Screen Shot 2015-06-16 at 1.13.43 PM.png (368×923 px, 128 KB)

The problem seems to be with the {{#owner}} tag
http://git.wikimedia.org/blob/mediawiki%2Fextensions%2FGather.git/66ca5ddc95db61365ae1c9e14630fc76a5a9f680/templates%2FCollectionsListItemCard.mustache#L9
Removing this or replacing it with {{#if owner}} which is invalid Mustache makes it work.

Event Timeline

bmansurov raised the priority of this task from to Needs Triage.
bmansurov updated the task description. (Show Details)
bmansurov subscribed.
Jdlrobson renamed this task from mustache rendering issue to Identify what is causing mustache rendering issue.Jun 10 2015, 9:42 PM
Jdlrobson set Security to None.
Jdlrobson updated the task description. (Show Details)
Jdlrobson updated the task description. (Show Details)

The problem doesn't seem to be with LightnCandy. I tried this:

in x.php

<?php
$php_inc = "result";

require('lightncandy/src/lightncandy.php');

$template = '<aside class="browse-tags"><h4>{{headerMsg}}</h4><ul class="browse-tags-list">{{#tags}}<li class="browse-tag"><a href="{{link}}">{{msg}}</a></li>{{/tags}}</ul></aside>';
$phpStr = LightnCandy::compile($template);  // compiled PHP code in $phpStr

file_put_contents($php_inc, $phpStr);
$renderer = include($php_inc);

echo $renderer(Array('headerMsg' => 'Tags', 'tags' => Array(Array('link' => '/wiki/Special:TopicTag/abc', 'msg' => 'abc'), Array('link' => '/wiki/Special:TopicTag/heh', 'msg' => 'heh'))));

And ran php x.php which resulted in:

<aside class="browse-tags"><h4>Tags</h4><ul class="browse-tags-list"><li class="browse-tag"><a href="/wiki/Special:TopicTag/abc">abc</a></li><li class="browse-tag"><a href="/wiki/Special:TopicTag/heh">heh</a></li></ul></aside>

as expected.

After more debugging, I think the root cause is the call to render the banners:

echo $templateParser->processTemplate( 'banners', $data );

That said, tags have started rendering fine again, so I can no longer re-produce the issue. I'll take a look at it tomorrow, when it breaks again hopefully.

It may be due to the fact that the banner template uses dot-notation, which I think lightncandy doesn't support:
{{#banners}}{{{.}}}{{/banners}}

Actually, it looks like LightnCandy should support dot notation as well (but maybe it's buggy).

I'm unable to reproduce the bug, but banners may not be the root cause. It was just an (not so) educated guess.

Jdlrobson renamed this task from Identify what is causing mustache rendering issue to Identify what is causing mustache rendering issues.Jun 16 2015, 12:17 AM
Jdlrobson updated the task description. (Show Details)

This looks to be a problem with eval in HHVM. Enabling the zend role in vagrant outputs the correct thing. Additionally adjusting TemplateParser to write the code out to disk in TemplateParser and using include instead of eval also makes this work correctly.

A tiny proof-of-concept patch:

diff --git a/includes/TemplateParser.php b/includes/TemplateParser.php
index 3de70fa2..5c8c584 100644
--- a/includes/TemplateParser.php
+++ b/includes/TemplateParser.php
@@ -126,7 +126,9 @@ class TemplateParser {
                        $code = $this->compileForEval( $fileContents, $filename );
                }
 
-               $renderer = eval( $code );
+               $hack = '/tmp/' . md5($code) . '.php';
+               file_put_contents( $hack, "<?php\n" . $code );
+               $renderer = include $hack;
                if ( !is_callable( $renderer ) ) {
                        throw new RuntimeException( "Requested template, {$templateName}, is not callable" 
                }
Jdlrobson renamed this task from Identify what is causing mustache rendering issues to Identify what is causing server side templates mustache rendering issues.Jun 16 2015, 8:25 PM

FWIW i tried installing hhvm 3.1.0, 3.3.6 and 3.7.2. All showed the same issue. So var_exported the code generated by LightnCandy along with the variables passed to TemplateParser::processTemplate() and eval'd that in a script by itself, unable to reproduce. Some odd hocus pocus is happening but I have no clue what.

I've asked the maintainer of Lightncandy to get involved in this thread if possible: https://github.com/zordius/lightncandy/issues/161

Additionally adjusting TemplateParser to write the code out to disk in TemplateParser and using include instead of eval also makes this work correctly.

You could try using [[ http://php.net/manual/en/function.create-function.php | create_function ]] instead of eval; the results of that are preserved as global functions so whatever closure uniqid tracking is involved here probably does not get reset between calls. Probably increases memory usage but has no impact on performance (while with writing to disk caching it would be the other way around).

I'm a bit surprised though that templates are not cached to disk by default. It seems like enabling that would fix this bug and also improve performance in general.

10:22 AM <jdlrobson> ebernhardson: why are we using eval ? why was not using eval rejected?
10:22 AM <ebernhardson> joakino: getting the failure is difficult, i'm not exactly sure what the conditions are that cause hhvm to re-use the internal closure instance names.
10:22 AM <ebernhardson> jdlrobson: i wonder if i can dig up the rfc notes ... basically krinkle didn't think disk was an appropriate place to put a cache and it needed to be in memcache
10:23 AM <ebernhardson> jdlrobson: if we have to pull the code from memcached, then eval is the only reasonable way to execute the code
10:23 AM <joakino> ebernhardson: what about keeping the object memcache cache but loading the templates with LightnCandy::prepare from disk? Disk will be only accessed the first time
10:23 AM <joakino> ebernhardson: oh that's true, ignore me
10:23 AM <jdlrobson> Krinkle: do you remember? ^
10:24 AM <ebernhardson> joakino: joakino the problem is Lightnandy::prepare will generate a new name every time its executed
10:24 AM <ebernhardson> (a new file on disk i mean)
10:24 AM <joakino> aha
10:25 AM <Krinkle> The larger problem is that it's totally unnecessary for it to produce PHP code.
10:25 AM <Krinkle> I was on the edge of rejecting the library based on that alone. It doesn't speak confidence.
10:25 AM <ebernhardson> i still disagree that writing an interpreter inside another interpreter is a good solution :P
10:26 AM <Krinkle> The library already has a data model. It's not that bad.
10:26 AM <Krinkle> It just rejects it early on in favour of recursively substituting the functions.
10:27 AM <ebernhardson> Krinkle: well, the problem now is that hhvm has a bug with eval where the internal name of a closure can be reused. This means mobile's recent code doesn't work: https://github.com/facebook/hhvm/issues/5502
10:28 AM <ebernhardson> it doesn't happen every time, but under some unknown set of circumstances you get a previously generated closure instead of the one you intend
10:30 AM <Krinkle> That's a nice bug catch.
10:30 AM <Krinkle> Limited to eval() ?
10:30 AM <ebernhardson> yea
10:31 AM <jdlrobson> Krinkle: nice bug catch but causing chaos in mobile land :-/
10:31 AM <Krinkle> My solution would be to change Lightncandy to shortcircuit its prepare tree and output the tokens in a pure data (and we can store the array as either serialised php or json). And then when consuming the data, call the relevant functions, just like it does now.
10:31 AM <Krinkle> It'll cut out a whole slew of problems, including the need for the security key.
10:31 AM <Krinkle> It will also allow HHVM to optimise the code paths in the long term.
10:32 AM <Krinkle> instead of each template being its own function.

Jdlrobson renamed this task from Identify what is causing server side templates mustache rendering issues to Mustache template rendering issues - leaking templates.Jun 17 2015, 8:12 PM
Jdlrobson triaged this task as High priority.

Switching to create_function from eval didn't make the problem go away for me.

Ignore that last message - I was working on the wrong code.

I doubt allow_url_include is enabled on any WMF server.

phuedx claimed this task.

I'm closing this as Resolved.

The underlying issue (T102937) was worked around in T102941. @EBernhardson's fix was recently merged upstream and will, hopefully, be deployed soon. When that is done we can revert our workaround at our leisure (T104427).