Our drupal_render() cache properly records and re-injects any #attached items that are placed at the same level as the #cache element. But it neglects to record any that were set on child elements so that those are not re-injected when we have a cache hit. As a result, the page is broken by missing js or css.

Comments

catch’s picture

subscribing.

catch’s picture

Priority: Normal » Major
Status: Active » Needs review
StatusFileSize
new3.4 KB

Ran into this again with #986084: Add cache for media_gallery_block_view(). I'm upping this to major, since any module using render caching, where something down the line might use #attached, is going to be broken by this - it's equivalent to drupal_add_js() plus the block cache in D6, except that part of introducing render caching was specifically to avoid that bug.

Uploading a patch that works for the main case, where you set #cache on an element, and the child element implements #attached.

It doesn't account for the case where a child element has #pre_render set, and the pre_render callback adds #attached. At this point it starts getting very tricky, since a child element might also implement drupal_render() caching, and if so we'll need to track attached for all the children, at each level where #cache is set, and we only get #attached when the pre_render callback has been executed. I don't have time to work through that fully this afternoon, so uploading progress so far.

catch’s picture

Status: Needs review » Needs work

Definitely needs tests as well.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new4.59 KB

This should now handle handle elements that have children that set #pre_render as well.

No tests yet still but marking needs review since lack of tests shouldn't stop people looking at this.

For testing there are three code paths, only the first is necessary for the original bug, there second two are edge cases which might be nice to test:

1. #cache is set on an element, a child element sets #attached - the css/js should still appear when that element is cached.
2. #cache is set on an element, a child element has #pre_render, the pre_render callback adds #attached somewhere.
3. #cache is set on an element, as well as #theme, elements that get passed through theme() have #attached set on them somewhere.

Status: Needs review » Needs work

The last submitted patch, attached_cache.patch, failed testing.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new4.54 KB

Removing an unused variable, no other changes.

Status: Needs review » Needs work

The last submitted patch, attached_cache.patch, failed testing.

bfroehle’s picture

What about doing the #attached flattening process in drupal_render_cache_set?

bfroehle’s picture

+++ includes/common.inc	23 Dec 2010 17:12:55 -0000
@@ -5804,6 +5823,52 @@ function drupal_render_cache_set(&$marku
+  foreach ($element as $key => $value) {
+    if (is_array($value)) {
+      $element[$key]['#collect_attached'] = TRUE;
+      drupal_render_collect_attached($value);
+    }

This needs to use element_children or similar, to avoid adding #collect_attached unnecessarily.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new2.59 KB

Doing it in drupal_render_cache_set() is a lot simpler, I'd initially been concerned about elements that had already been cached etc. here, but there's no reason since we have attached available for those at this point too (because this patch fixes that bug, doh!), and everything has run at this point. This patch should deal with both of those. Didn't look into the forum test, but if this fixes it will do simpletests here next.

bfroehle’s picture

StatusFileSize
new5.23 KB
new2.79 KB

The forum test was breaking earlier due to the use of is_array instead of element_children.

Tests:
I've drafted up a testing routine which implements the first suggested test in #4:

#cache is set on an element, a child element sets #attached - the css/js should still appear when that element is cached.

It's not the prettiest but does elucidate the issue. Caveats in writing the simpletest:

  1. Sets $_SERVER['REQUEST_METHOD'] = 'GET' so the cache system turns on.
  2. Uses #attached javascript only, and uses drupal_static_reset('drupal_add_js'); to verify that the javascript is still attached after the element is reloaded from cache.
bfroehle’s picture

+++ includes/common.inc	24 Dec 2010 04:05:52 -0000
@@ -5625,6 +5626,51 @@ function drupal_render_cache_set(&$marku
+    foreach (array('library', 'css', 'js') as $key) {
+      if (isset($elements['#attached'][$key])) {
+        if (!isset($attached[$key])) {
+          $attached[$key] = array();
+        }
+        $attached[$key] = array_merge($attached[$key], $elements['#attached'][$key]);
+      }

Why limit this to 'library', 'css', and 'js', instead of doing something like:

foreach ($elements['#attached'] as $key => $value) {
  if (!isset($attached[$key])) {
    $attached[$key] = array();
  }
  $attached[$key] = array_merge($attached[$key], $value);
}
catch’s picture

StatusFileSize
new5.46 KB

Test looks good, thanks!

The 2nd and 3rd test suggestions were for edge cases which don't really apply with the most recent patch, since we know that everything is available by the time we reach drupal_render_cache_set() itself.

Trying to remember why I limited this to the three keys, while drupal_process_attached() has a good reason, there's no reason to anywhere else, so made the suggested change.

Not strictly related to this bug, but drupal_render() #states support likely suffers from a similar bug, we should open a new issue for that I think - will need a similar approach but doesn't affect the code here.

bfroehle’s picture

StatusFileSize
new5.03 KB

Beautified the test routine a bit, a fixed some spelling mistakes. I think this is pretty much ready to go.

Regarding #states, I read quickly through the code and it seems to be tossing everything into #attached which would then get picked up and cached with this patch. Thus we might have solved that problem as well.

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

Well done, folks. Nice to exterminate this bug before release.

sun’s picture

+++ includes/common.inc
@@ -5804,6 +5805,49 @@ function drupal_render_cache_set(&$markup, $elements) {
+    $attached[$key] = array_merge($attached[$key], $value);
+    }

Wrong indentation?

Powered by Dreditor.

bfroehle’s picture

StatusFileSize
new5.03 KB

Thanks sun. I've fixed the indentation.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 859602-18.patch, failed testing.

bfroehle’s picture

Status: Needs work » Needs review

#18: 859602-18.patch queued for re-testing.

The one failed test, somewhere in the Locale testing routines, passed locally on my machine.

bfroehle’s picture

Status: Needs review » Reviewed & tested by the community
paul.lovvik’s picture

I have also tested this patch, while testing http://drupal.org/node/986084#comment-3779090. The code looks good.

effulgentsia’s picture

Subscribe. I haven't reviewed the implementation, but definitely +1 to getting this fixed in D7 (hopefully 7.1). The render cache is pretty much unusable otherwise. From a quick skim, nothing jumps out at me as breaking any BC.

catch’s picture

Issue tags: +Performance
StatusFileSize
new5.03 KB

Looked through the latest changes to the test, all looks fine, I fixed Javascript > JavaScript in several code comments. Since this was the only change, leaving RTBC.

Also just to confirm there's no BC breakage here, all we do is look through the entire element for #attached instead of only the top level.

And yes this makes render caching, and by extension #attached itself with any kind of caching (since #attached won't work for custom cache_get()/cache_set() or the normal block cache either) entirely useless without the patch applied.

dries’s picture

Status: Reviewed & tested by the community » Fixed

This looks like a good change; improves performance and shouldn't affect existing code. Committed to CVS HEAD. Thanks.

moshe weitzman’s picture

Status: Fixed » Patch (to be ported)

Thanks Dries. Marking for backport as this was committed to HEAD only AFAICT. Perhaps RTBC is the right status. Not sure.

effulgentsia’s picture

Status: Patch (to be ported) » Fixed

AFAICT, there is no DRUPAL-7 branch yet, so I think HEAD is still 7.x.

Status: Fixed » Closed (fixed)
Issue tags: -Performance

Automatically closed -- issue fixed for 2 weeks with no activity.