Blocks are currently generated directly in block_page_build() (via block_get_blocks_by_region() / block_list() / _block_render_blocks()), which prevents the implementation any kind of caching strategy via the #cache property.

We need to delay the heavy work of block generation until render time if we want to be able to leverage the full power of the render cache on blocks.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Damien Tournoud’s picture

Status: Active » Needs review
FileSize
3.85 KB
6.96 KB

It's actually pretty easy to do so.

There are some whitespace changes required here, that makes the patch harder to read then necessary. Attaching the full version and a review version with ignored whitespace changes.

Status: Needs review » Needs work

The last submitted patch, 918808-block-delay-render.review.patch, failed testing.

manarth’s picture

sub

Damien Tournoud’s picture

Status: Needs work » Needs review
FileSize
4.04 KB
6.9 KB

This accidentally broke the rendering of titles. This version should pass all tests.

Status: Needs review » Needs work

The last submitted patch, 918808-block-delay-render.review.patch, failed testing.

carlos8f’s picture

subscribing

Damien Tournoud’s picture

Status: Needs work » Needs review

#4: 918808-block-delay-render.patch queued for re-testing.

Damien Tournoud’s picture

Ok, the last failure is actually legitimate: because we are rendering the blocks *after* template_preprocess_page(), the messages generated in the blocks will only display on the next page load.

I need opinions on the way forward here, given that I see two options:

  1. Delay the rendering of the messages themselves until they are actually displayed (ie. change the direct $variables['messages'] = theme('status_messages') by a $variables['messages'] = array('#theme' => 'status_messages') and call render($messages) in the page.tpl.php templates). The only real drawback of this approach is that messages generated *after* the main content region is generated will still be displayed on the next page load
  2. Acknowledge that block implementations should not trigger side-effects like drupal_set_messages(). This will not work with caching *anyway*, but this would make debugging slightly harder for block authors
EvanDonovan’s picture

Would option #1 mean that themers would have to remember to add render($messages) to their page.tpl.php or status messages would not get displayed? If so, that might be a bit of a burdensome change.

With option #2, would this mean that I couldn't use drupal_set_messages() or dsm() to debug a block? Could I still use kprint_r()? I think that as long as kprint_r() or the like was available that wouldn't be too burdensome.

Damien Tournoud’s picture

Would option #1 mean that themers would have to remember to add render($messages) to their page.tpl.php or status messages would not get displayed? If so, that might be a bit of a burdensome change.

Yes, option #1 means changing all the echo $messages with echo render($messages) or the messages will not be displayed at all.

With option #2, would this mean that I couldn't use drupal_set_messages() or dsm() to debug a block? Could I still use kprint_r()? I think that as long as kprint_r() or the like was available that wouldn't be too burdensome.

Yes, kprint_r() should still work anywhere on the page.

EvanDonovan’s picture

@DamZ:

For #1:
Actually now that I looked at http://api.drupal.org/api/drupal/themes--bartik--templates--page.tpl.php..., I realized there's already something like if($messages) { print $messages; } in page.tpl.php. So this wouldn't be too big of a change. It's just the new render() syntax, but people moving up from D6->D7 will have to learn that anyway.

As someone who missed that whole party, it seems kind of random when it's render() and when it's print/echo, so it might even be good to have one more use of render() for consistency's sake.

For #2:
I agree that blocks shouldn't print messages. I don't see a good reason why they would, anyway. If kprint_r() and the like work, then debugging is still possible.

So I think either alternative would be OK, but probably feedback from some others would be needed, since I'm certainly not an expert.

dww’s picture

Note: the block causing these test failures is actually update status. It uses hook_help() to conditionally generate status messages. That was long before someone got the bright idea to turn hook_help() output into a block. So, it's not intentional that a block from update status is generating messages, that's a side effect of other changes to D7 core. ;)

So, I'd be happy to generate those messages in update status via some other means, if that's what it takes to make this sane. I agree that caching blocks makes generating side effects via blocks pretty dangerous. That was never the intention in update status. Kudos to DamZ for fleshing out this problem...

Damien Tournoud’s picture

This is basically option #2: we move the drupal_set_message() from update_help() to update_init().

dww’s picture

Status: Needs review » Needs work

DamZ asked me to review the update.module part of this patch. Mostly looks fine. I agree hook_init() is safer than hook_help() given that hook_help() is now a cachable block. I only saw this:

if (arg(1) == 'apparance'

typo. It's 'appearance'...
I'll look more closely, but so far, so good.

dww’s picture

We also need an } else { to avoid hitting this case:
// Otherwise, if we're on *any* admin page and there's a security
// update missing, print an error message about it.
if we're on admin/modules or admin/appearance...

Damien Tournoud’s picture

Status: Needs work » Needs review
FileSize
10.26 KB

Erm. The logic in update_help() was really convoluted. I think this is the correct implementation.

dww’s picture

Yup, the update.module hunk of this patch is now RTBC from my eyes. I haven't thought about the rest of this issue to comment meaningfully on the rest of the patch, but the update.module hunk should go in regardless. So, if this patch gets derailed for D7, we should split that hunk out into a separate issue, since those update.module changes are irrefutable.

dww’s picture

Well, actually, except there's a minor typo in one of the code comments. ;) Try this.

moshe weitzman’s picture

Status: Needs review » Closed (won't fix)

Er, forum_block_view() already uses render cache successfully. So it is possible for blocks to use render cache.

I think you want to be able to use #cache on blocks that were not designed for it. I don't think that’s needed now and perhaps not desireable either. When all blocks use #pre_render, you make it hard to alter blocks using hook_page_alter(). Further, you can always make a new block which uses this technique and calls the existing hook_block_view of from some other module.

IMO this is won't fix. Willing to discuss that some more. Please elaborate on when you want this.

Damien Tournoud’s picture

Status: Closed (won't fix) » Needs review

We definitely need that if we want to be able to implement external cache strategies via Edge Side Includes / Server Side Includes, as I presented during my talk at Drupalcon CPH.

Damien Tournoud’s picture

When all blocks use #pre_render, you make it hard to alter blocks using hook_page_alter().

That's the only valid point in #19. True. But it's the same for all the other elements we render via #pre_render, including the toolbar and shortcut bar. It's easy to use hook_page_alter() to add an additional #pre_render to the target block and modify it from there.

Damien Tournoud’s picture

To elaborate on why this is needed, here is how the ESI module could very cleanly implement ESI / SSI for blocks in Drupal 7 (after this patch):

  • Use hook_page_alter() to add a #cache to each block that you want to render via ESI (not the block content, as in the forum module, the block element itself)
  • Implement a DrupalESICache implementation that stores the rendered block in Memcache and returns an ESI snippet instead (<!--# include file="esi/[cachekey]" -->). This is possible because #markup is passed to cache_set() by reference in drupal_render_cache_set() (this was done specifically for this use case by #651902: Allow ESI tie in).
  • Configure Nginx to map the esi/[cachekey] directly to Memcache, or use a simple wrapper for Varnish
  • Enjoy.

The only flaw in that plan is that blocks are currently rendered very early, even before the page structure is generated. This issue is meant specifically to fix that.

moshe weitzman’s picture

Thanks for the detail.

Could you not do that ESI plan today with the forum blocks? If so, then you can do it for your custom blocks. For *expensive* blocks provided by contrib modules, you should submit patches asking them to do the #pre_render thing. I really think thats preferable to obscuring all blocks, even simple ones, during hook_page_alter(). This patch neuters hook_page_alter(). I'm all about deferring work, but this patch makes goes too far IMO.

dww’s picture

Split off the fixes to update_help() into #931284: Update status admin UI shouldn't rely on hook_help() in case this gets derailed/won't fixed, since that really is a bug fix.

dww’s picture

Status: Needs review » Needs work

Sweet, #931284: Update status admin UI shouldn't rely on hook_help() already landed, so #18 needs a re-roll if the rest of this change is still going to happen...

dww’s picture

Status: Needs work » Needs review
FileSize
6.92 KB

Not my code, and I don't have a strong opinion on if this should happen, but here's the re-roll to keep things moving.

sun’s picture

+++ modules/block/block.module	2 Oct 2010 23:58:34 -0000
+++ modules/block/block.module	2 Oct 2010 23:58:34 -0000
@@ -330,8 +330,11 @@ function _block_get_renderable_array($li

I think that both Damien and moshe are right on their own. It's a conflict of interests and highly depends on whether site builders are willing to do performance optimizations that basically eliminate the usefulness of hook_page_alter(), or not.

So we need a compromise. I think that a module like http://drupal.org/project/esi would just need a way to swap out _block_get_renderable_array() with a custom callback, so core uses that by default (and builds blocks early), but highly optimized sites are free to swap that callback out in order to perform delayed rendering of blocks.

One possible option to achieve that would be to set a #block_render_callback on the region, which defaults to _block_get_renderable_array(), but prior to invoking the callback, a drupal_alter() allows modules to swap that out (and eventually do other neat things).

Basically, this proposal would bring us back to #778476: It's impossible to alter theme regions, resp. #908116: hook_block_region_alter() is missing in block_get_blocks_by_region(), and some other related issues in the queue.

Powered by Dreditor.

catch’s picture

Subscribing.

moshe weitzman’s picture

One more thing - block.module isn't really designed for high performance sites. I would recommend a fresh start in Contrib land. Lets leave block.module as is (easy to alter) in core for D7.

EclipseGc’s picture

So, working through the ctools/panels upgrades, it's become obvious we need something along these lines, as we have had, and would like to continue to have, the ability to kill a region before it renders its content. Currently we have to rely on hook_page_alter() in which case we've already spent all the processing time rendering blocks we had no intention of displaying. Where we go from there in terms of what core is actually going to do, I don't know, but I know the idea of disabling that particular feature in panels has been discussed.

Eclipse

moshe weitzman’s picture

@EclipseGC - my conclusion is in #29. Performance sensitive sites should disable block module. Its nearly deprecated IMO. Panels subsumes it for some sites, context for others, and there will be more.

merlinofchaos’s picture

#29: One of the features of Panels -- and I'm not talking about Panels Everywhere -- is that right now you can for just one page, use a panel and not use blocks on that page. This is very common for a front page, or just one or two landing pages for example, where it has a different look than all other pages.

Right now, it's possible for people to do hybrid blocks/panels solutions. Saying that you can no longer accomplish that (or rather, that doing so is now a giant performance screw-you) is a major regression, and I don't think it is the right call.

moshe weitzman’s picture

Status: Needs review » Closed (won't fix)

I see your point. I'm really just opposed to the delay solution proposed by Damien. If you want to fiddle with the block list retrieval (block_list() or thereabouts) it looks like http://api.drupal.org/api/function/hook_block_list_alter/7 is your friend. Its docs are a bit misleading it says "Act on blocks prior to rendering.". It would be even better to day "Act on blocks prior to *building*".

If you don't want to user that hook, then the query above it has a tag and could be query altered.

moshe weitzman’s picture

Title: Blocks are generated too early » Standardize block cache as a drupal_render() #cache
Version: 7.x-dev » 8.x-dev
Assigned: Unassigned » moshe weitzman
Category: bug » feature
Priority: Major » Normal
Status: Closed (won't fix) » Needs review
FileSize
10.49 KB

OK, here is a healthy compromise, plus some icing. I've taken the last patch here and enhanced it slightly so that blocks that do no caching get rendered immediately, while cached blocks go through #pre_render. I think that's sane, especially given that ...

This patch preserves the block cache API but changes its implementation from custom to a standard drupal_render #cache. This should help the Butler project, as it has to deal with one less special case.

In D8, we hope to immensely increase our cache lifetime and hit rates if we can get cache tags in as articulated recently at #636454-30: Cache tag support. With that, we can have #cache items like these blocks declare their #attached['cache tags'] and have these cache tags aggregated up the render tree like css/js/library items are now (was added at #859602: #cache does not record the #attached declared in child elements). Thus, we can have a block like 'recent content in term x' that can survive a cache_clear_all(). We can also enable block cache for sites that use node access modules.

This patch also removes the admin checkbox 'Enable block caching' since thats really a devel level pref and has no business being in our standard UX IMO. Core assumes blocks behave as they are declared. Folks wishing to disable block cache universally can use hook_block_info_alter or assign the DrupalFakeCache backend for the cache_block bin.

Status: Needs review » Needs work

The last submitted patch, 0001-Block-cache-is-now-implemented-as-a-standard-drupal_.patch, failed testing.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
10.49 KB

Fixed one test failure. 2 others are eluding me. simpletest debugging is a special section of hell.

Status: Needs review » Needs work

The last submitted patch, 0001-Block-cache-is-now-implemented-as-a-standard-drupal_.patch, failed testing.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
10.55 KB

Ugh. Uploaded wrong file.

Status: Needs review » Needs work

The last submitted patch, 0001-Block-cache-is-now-implemented-as-a-standard-drupal_.patch, failed testing.

chx’s picture

Moshe, congratulations on finding a core bug! If in pristine HEAD you force $conf['block_cache'] to be TRUE at least the node recent test fails. Working on it.

chx’s picture

There is no bug. This is a feature. A feature Moshe asked for http://drupal.org/node/115319#comment-1031039 here :D see what happens is that the block is rendered on the block admin screen and when $node4 = $this->drupalCreateNode($default_settings); happens then the caches are not cleared! Solutions are obviously many: add a cache_clear_all() to dCN or just document you need one and add a cache_clear_all to testRecentNodeBlock

chx’s picture

After a brief discussion with webchick, cache clears in dCN would make test performance even worse. So let's add a cache_clear_all to testRecentNodeBlock and the other one.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
11.82 KB

Added a cache_clear_all() call in each test.

catch’s picture

Category: feature » task
Status: Needs review » Needs work

Patch looks like great cleanup. Have discussed with Moshe the possibility of removing the block cache altogether in favour of the render cache, but that's no reason to not do this patch which is just consolidating code.

+++ b/modules/block/block.module
@@ -330,8 +330,40 @@ function _block_get_renderable_array($list = array()) {
+    /* Block caching is not compatible with node_access modules. We also
+     * preserve the submission of forms in blocks, by fetching from cache
+     * only if the request method is 'GET' (or 'HEAD'). User 1 being out of the
+     * regular 'roles define permissions' schema, it brings too many chances of
+     * having unwanted output get in the cache and later be served to other
+     * users. We therefore exclude user 1 from block caching.

Even though this is a large code block, it should use // comments. Also two comment lines exceed 80 chars.

@@ -801,96 +825,54 @@ function block_block_list_alter(&$blocks) {
+      // Blocks without contents should emit no markup at all.

Should be "without content".

We should add an update here to remove the block_cache variable, didn't see one in the patch.

Also the cache_clear_all() in the test points to a d.o issue, I'd rather a comment that just explains that drupalCreateNode() does not automatically flush content caches unlike manually creating a node from a node form.

Otherwise looks great.

moshe weitzman’s picture

Category: task » feature
Status: Needs work » Needs review
FileSize
12.57 KB

Addressed all of catch's items in #44.

moshe weitzman’s picture

catch’s picture

Category: feature » task

Issue summary:

Drupal 7 introduced the render cache, which amongst other things correctly handles stuff like #attached from cached items.

This is used in contrib/custom code, but not so much in core.

The patch converts the block cache, which is currently entirely custom to use the render cache - this allows it to piggy back off helpers like drupal_render_cache_key() etc.

Additionally, it removes the UI for block caching settings - module authors already determine whether their block should be cached or not in hook_block_info(), and there is a contrib module to override this, having a setting for it doesn't really make sense.

6 files changed, 99 insertions(+), 113 deletions(-)

Has an update in it, would be nice to have the boilerplate upgrade tests issue fixed before we start committing those, but a variable_del() does not need tests.

However I am wondering a bit if we need an _update_8000_variable_del() function - variable_del() doesn't fire hooks, but that function may not exist by the time this update runs, however as long as the database table does a straight delete would still work.

catch’s picture

Status: Needs review » Reviewed & tested by the community

Moving to RTBC. If we wanted to be strict, we'd wait for upgrade path testing, but it's also just variable_del(), and the patch doesn't need anything extra IMO.

catch’s picture

Status: Reviewed & tested by the community » Postponed

Spoke to webchick in irc, to make sure there's no confusion, I'm moving all D8 patches with updates to 'postponed' on #1182290: Add boilerplate upgrade path tests, while this one has pretty much zero chance of breaking anything to do with the upgrade path, we should be consistent on requiring code coverage for updates at all, so doing it to this issue too.

catch’s picture

Status: Postponed » Reviewed & tested by the community

That issue is now RTBC, unpostponing this.

catch’s picture

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 0001-Block-cache-is-now-implemented-as-a-standard-drupal_.patch, failed testing.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
12.23 KB

This one should apply.

Status: Needs review » Needs work

The last submitted patch, blockcache.diff, failed testing.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
12.23 KB

Another try.

Status: Needs review » Needs work

The last submitted patch, blockcache.diff, failed testing.

moshe weitzman’s picture

Status: Needs work » Needs review
FileSize
13.39 KB

Uploading right diff ... and removed --no-prefix.

catch’s picture

Status: Needs review » Reviewed & tested by the community

Looks good. The only external change here is the removal of the ui for block caching, everything else is just updating block module internally to the new pattern.

moshe weitzman’s picture

FileSize
13.38 KB

Modernized the bin name from cache_block => block in #cache array. catch approves for leaving RTBC.

webchick’s picture

Can someone test this with some Google AdSense code or similar thing that *has* to show uniquely on every page? Just want to make sure we don't have any behaviour regressions, and I'm not sure how to write unit tests for that.

webchick’s picture

Status: Reviewed & tested by the community » Needs review
moshe weitzman’s picture

We test for that and more in BlockCacheTestCase (see block.test). Back to RTBC?

webchick’s picture

Status: Needs review » Reviewed & tested by the community

Oops. Carry on. :D

catch’s picture

Assigned: moshe weitzman » Dries

Since I RTBCed this I'm not going to also commit it. Putting in Dries' queue for that reason.

However if you can find someone to do their own review and confirm the RTBC before Dries gets to it, unassign him and I'll be happy to commit after that.

chx’s picture

Assigned: Dries » Unassigned

My concern was variable_del('block_cache'); but that can be tended from contrib. Another concern is the uid 1 check but that can't be fixed without turnng this into a huge meow, by changing significantly how our access controls work.So this is good to go for now.

catch’s picture

#59: blockcache.diff queued for re-testing.

catch’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs change record

Thanks! Committed and pushed to 8.x

This needs a change notification.

- the block cache setting has been removed, if you want to disable block caching use a Null cache inc for cache_block - this is more an administrator notification than a contrib developer one but I think we can do that now.

- I don't think we need a note about the private function that dashboard module is calling, naughty dashboard module, but that was the only thing that remotely looked like an API change to me so just flagging up for the record.

webchick’s picture

Title: Standardize block cache as a drupal_render() #cache » API change notification for Standardize block cache as a drupal_render() #cache
Priority: Normal » Critical
Status: Needs work » Active
webchick’s picture

Issue tags: +Novice

And since adding that summary is relatively easy, tagging as Novice.

webchick’s picture

Thanks to the magic of git bisect, I discovered that this patch caused #1297370: Default installation of D8 shows help region, which is a pretty nasty user-facing bug.

moshe weitzman’s picture

Priority: Critical » Major
Status: Active » Needs review
FileSize
1.16 KB

Fixed help region and a book block that was showing itself despite having no content.

chx’s picture

Priority: Major » Critical
Status: Needs review » Active

moshe, that's the wrong issue. Edit: i copied the patch in the other one and credited the comment to you :)

David_Rothstein’s picture

aspilicious’s picture

Status: Active » Needs review

Don't know anything about caching but made a notification anyway. I know it's not that good but at least others just have to edit now.

http://drupal.org/node/1339672

xjm’s picture

Status: Needs review » Fixed

Added detail to the change notice (with chx's help).

dww’s picture

Title: API change notification for Standardize block cache as a drupal_render() #cache » Standardize block cache as a drupal_render() #cache
Issue tags: -Novice, -Needs change record

Thanks, xjm! Setting these back to normal for posterity...

Status: Fixed » Closed (fixed)

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