The biggest reason block cache isn't used on sites is because it's very easy for it to break things. Any calls to drupal_add_js or drupal_add_css in a block callback won't happen when the cached version is served, so the css/js you needed for that block is missing. Although blocks can avoid this by using #attached, it's not used widely enough yet to rely on.
While digging into the views cache plugin I noticed that it saves the output of drupal_add_css, drupal_add_js, and drupal_add_html_head before and after the view is rendered. It then stores the differences with the cached item so it can restore them on a cache_hit.
I see no reason why we shouldn't use this pattern for the block cache, which the attached patch implements. To keep the complexity added to _block_render_blocks minimal, I added three helper functions (drupal_restore_css/js/html_head) for restoring saved headers.
Also, this patch is for D7 because it appears that D8 already moved blocks to using render cache, not to mention all of the changes planned surrounding blocks and globals like drupal_add_*, so it doens't relaly make sense to add this there.
While this solution may not be perfect, I'd ask reviewers to keep in mind that the current situation is that you turn on block cache and parts of your site break.
Comment | File | Size | Author |
---|---|---|---|
#45 | save_and_restore-1460766-45.patch | 45.85 KB | Fabianx |
#45 | save_and_restore-1460766-45--tests-only.patch | 43.92 KB | Fabianx |
#45 | save_and_restore-1460766-45--interdiff-do-not-test.patch | 1.92 KB | Fabianx |
Comments
Comment #1
pwolanin CreditAttribution: pwolanin commentedThis looks like a clever and functional approach, though obviously not what one would call elegant.
Seems like if one was desperate one could achieve the same thing within an override of the cache class, but that would be a rather dire abuse of the cache API.
Comment #2
pwolanin CreditAttribution: pwolanin commentedGiven that using #attached is not fully adopted or enforced, it seems like this is more of a bug than a feature request.
It would be helpful to have a test case that demonstrates the bug, especially for a core module.
some possible candidates:
http://api.drupal.org/api/drupal/modules!locale!locale.module/function/l...
any block that calls theme('table') might?
Comment #4
msonnabaum CreditAttribution: msonnabaum commentedI agree, not elegant, but the way we add css and javascript globally is inelegant, so unfortunately this is what is required.
I also agree that this needs a test. Will try to do that soon unless someone wants to beat me to it.
Comment #5
Anonymous (not verified) CreditAttribution: Anonymous commentedlooks good to me. definitely worth a test that illustrates the problem.
Comment #6
msonnabaum CreditAttribution: msonnabaum commentedShould hopefully take care of the test warnings.
Comment #7
xjmJust some docs cleanup; no functional changes.
Comment #8
xjmComment #9
msonnabaum CreditAttribution: msonnabaum commentedNew patch with a test for drupal_add_js().
Still haven't written many simpletests, so please let me know if I'm doing anything wrong there. That said, I can confirm the new test fails without the patch and passes with.
Comment #10
msonnabaum CreditAttribution: msonnabaum commentedHere's a version that doesn't include the change from #917490. Should be functionally identical.
Comment #11
xjmThe test looks good to me. Two minor cleanups:
t()
on assertion message texts. (Reference: http://drupal.org/simpletest-tutorial-drupal7#t and #500866: [META] remove t() from assert message)I've also provided a separate patch with only the test to expose the failure on testbot for other reviewers.
Comment #13
xjmCrossposted. Similarly without #917490: Performance patch for block_list() (D6) and _block_render_blocks() (D7) functions, for real this time. Test is not affected by this change.
Comment #14
catchUsing drupal_add_js() inside a block callback is a bug, that's why we allow blocks to return renderable arrays. It's not a core bug that lots of modules aren't properly updated to Drupal 7 yet.
Comment #15
pwolanin CreditAttribution: pwolanin commentedThe alternative is to fix core block module? The problem is that hook_block_view_alter() doesn't reliably get a render array.
Comment #16
xjmWell, whether we call it a bug or a feature request or an antelope, there's a patch!
Comment #17
pwolanin CreditAttribution: pwolanin commentedso, either this patch is right, because core block module returns a strong instead of a render array:
http://api.drupal.org/api/drupal/modules!block!block.module/function/blo...
or we need to dig around a lot deeper and wider to fix this.
Given that the patch is basically a fairly simple work-around of an inconsistent core behavior and API, I think that's the better approach for 7.
Comment #18
xjmHas tests.
@pwolanin: Er, which is the better approach? :)
Comment #19
pwolanin CreditAttribution: pwolanin commentedI think the current patch is a better option than trying to fix broad parts of D7 core.
Comment #20
moshe weitzman CreditAttribution: moshe weitzman commentedThe function Peter links to above is the view operation for custom blocks. Those return a string and thats fine since an admin can't exactly create a complex render structure using of a textarea on the web.
I still don't understand what what 'fix broad parts of drupal core' refers to.
Comment #21
effulgentsia CreditAttribution: effulgentsia commentedIs there a core module that calls drupal_add_*() in a way that breaks with block cache? If so, that's a core bug we need to fix. Otherwise, this issue is about coddling broken contrib modules. And should we be adding something like that to D7 core at this point? Can this be reformulated as a contrib module instead?
Comment #22
effulgentsia CreditAttribution: effulgentsia commentedSorry. Didn't mean to change status.
Comment #23
catchYep, that's why I changed this to a feature request.
I've not tried it, but hook_block_list_alter() very likely allows you to swap out the block cache for your own version - since you can set the block content in that hook which prevents block module's own code from running later.
Comment #24
Damien Tournoud CreditAttribution: Damien Tournoud commentedAs mentioned, we do have
#attached
for precisely this reason. I don't see a reason to break encapsulation and promote bad practices, especially now that we promote rigid object-oriented designs.This is won't fix for me. At any rate, this belongs in 8.x anyway.
Comment #25
xjmPer the summary, the issue is resolved in 8.x because blocks are already using the render cache, unless I've misunderstood?
Comment #26
catchBlock caching is based on the render cache, but blocks in Drupal 8 can still return a string for $block['content'], http://api.drupal.org/api/drupal/core%21modules%21block%21block.module/f...
Comment #27
nod_so, fixing
block_block_view
to make him return a render and mark this won't fix could work?Comment #28
David_Rothstein CreditAttribution: David_Rothstein commentedI don't think we should change the return value of block_block_view() in Drupal 7. It's not a bug that it's using a string rather than a render array, and changing it would affect hook_block_view_alter() implementations.
If a contrib module needs to add #attached to it, I think it could turn it into an array (and add #attached at the same time) in hook_block_view_alter() itself.
Comment #29
effulgentsia CreditAttribution: effulgentsia commentedFor D8, I could envision a separate issue for ensuring that all page and block callbacks return render arrays instead of strings. But purely on consistency grounds, not something that's sufficiently related to this issue. However, if we did open such an issue for D8, I don't think it would get much traction, given that D8 will at some point possibly begin a process of converting page and block callbacks to some other WSCCI/Layouts initiative construct.
I'm marking this "won't fix", since I don't really see a path forward here. If someone wants to reopen it though, go ahead. Do we have a list of which popular contrib modules are incompatible with block/render cache? Perhaps the way forward is to post issues in their queues to use #attached correctly?
Comment #30
msonnabaum CreditAttribution: msonnabaum commented@effulgentsia - Views. I don't think we need a list when views is the prime example. I also don't see views changing anytime soon, so without this patch, block cache still breaks the majority of sites.
Comment #31
pwolanin CreditAttribution: pwolanin commentedI'm guessing Mark meant to re-open this? Clearly if it breaks Views to use the block cache, this should be fixed in core.
Here's a re-roll since the patch doesn't apply to 7.24. I'm pretty sure it's been resolved in other ways in 8.x, so leaving this issue for 7.x.
Comment #32
msonnabaum CreditAttribution: msonnabaum commentedI never re-opened because I got tired of arguing for what seems like such an obvious core issue. Happy to see others continue. We should at least have a recent patch that applies here for those that are forced to hack core so that block cache can be useful.
Comment #33
serg2 CreditAttribution: serg2 commentedI just wanted to check to make sure I was understanding correctly, and that i am in the right place.
When I enable block caching (in any way) the JS within that block fails. This occurs with Masonry and Flexslider. For flexslider, rather than slides it produces a list and masonry doesn't fill it's allotted area.
Setting 'do not cache' fixes the problem, but then, obviously the view is not cached.
Is the consensus that it is the way the JS is built that is causing this issue or is it an issue in views/block? Or do changes have to be applied to both?
Sorry for the simplistic question, I am not much of a developer and trying to get my head round where the issue lies and how to start understanding how to fix it.
Comment #34
gg4 CreditAttribution: gg4 commentedRe-roll and auto-merged successfully.
This and #1534490: Make block cache cids alterable would be awesome additions to Core.
Comment #35
davemybes CreditAttribution: davemybes commentedJust tested this patch (#34) myself and a previously messed up Views Slideshow now displays correctly with the patch and block caching enabled. Nice work. Thanks.
Comment #36
David_Rothstein CreditAttribution: David_Rothstein commentedWith this patch applied I get all sorts of PHP notices about array-to-string conversion when running tests. (I am not sure why the testbot didn't give them, probably a PHP 5.3 vs 5.4 thing.)
It's valid since hook_block_view() does not always return an array (example) although it's documented as doing so. But other code in the same function checks for this.
I suspect this test will still pass/fail as expected if it returns a render array and uses #attached for the JS? (A lot of the discussion above of render arrays + #attached as a solution to this issue was based on Drupal 8, I think, not Drupal 7.)
If so, it should change to use that, since it's the recommended way to return data from hook_block_view() so it's better to be testing that scenario. (Also it would prove that this issue is definitely valid for Drupal 7.)
Is this wrapper really worth defining?
Aren't these multidimensional arrays? If so, array_diff_assoc() is unlikely to be the right thing here (see #1850798: Add a recursive version of array_diff_assoc()).
(minor) Incorrect indentation.
Comment #37
maximpodorov CreditAttribution: maximpodorov commentedMaybe this code can help:
http://cgit.drupalcode.org/cachedblock/tree/cachedblock.module#n68
Comment #38
nchar CreditAttribution: nchar at Netstudio commentedI have tested the latest patch (34) and it works, but I get some notices. I replaced array_diff_assoc with drupal_array_diff_assoc_recursive and it worked perfectly.
Comment #39
jamesharv CreditAttribution: jamesharv at Technocrat commentedThe patch in #38 solves my block caching issue (javascript not getting added correctly) and I'm not seeing any PHP Notices.
Comment #40
Jonathan Webb CreditAttribution: Jonathan Webb at Acquia commentedNoting that the patch uses $key in the foreach it adds to _block_render_blocks, overwriting the $key defined by the outer loop.
$key isn't used by the outer loop afterward, so it technically works, but the new foreach should probably use a safer variable name (e.g $k, which sibling loops in that function are using). Updated patch attached.
Comment #41
Jonathan Webb CreditAttribution: Jonathan Webb at Acquia commentedComment #42
tterranigma CreditAttribution: tterranigma commented#40 did load all the scripts and css files but there were times that it changed their order, which could lead eg to js errors. I added some lines that reset the weight of these files/snippets/etc to their original value before being saved in the cache. I also added a check before doing the array_diff for js settings so as not to unnecessarily bloat the cached blocks.
Comment #43
tterranigma CreditAttribution: tterranigma commentedIn 42, I wrongly assumed that drupal_add_html_head() initializes a #weight even if the original block had no such key, as drupal_add_js and drupal_add_css do. I was wrong and the path in 42 resulted in some warnings. Here is an updated patch with a simple check.
Comment #44
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedThis is a partial duplicate of #2754245: [META] Explore how to backport render context and #cache properties from Drupal 8 to empower contrib, which solves the same problem space in a more generic way (same way as in D8).
Enabling the new drupal_render_use_render_context() option should make it possible to fix this in an easier way, however the blocks need to use the #pre_render / #cache pattern.
The problem with asset diff'ing is that the stores in drupal_add_js() / drupal_add_css() are not idempotent, which means e.g if blockA already uses foo.css, then it will never be detected for blockB - as core would think it is already loaded.
The views diff'ing ran into that problem again and again ... And it is very hard to track down as it depends on the rows being present and even the caching state of the items ...
I will discuss with stefan, if we want to add a quick follow-up to fix the block cache for the new option already in 7.50 (or wait for the next bug fix release) as it should be a two-liner with #2754245: [META] Explore how to backport render context and #cache properties from Drupal 8 to empower contrib applied now ...
It would definitely provide great value!
Tentatively marking as a Drupal 7.50 target - even if we only would fix it for the new experimental option.
Comment #45
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedHere is a patch rolled on top of #2754245: [META] Explore how to backport render context and #cache properties from Drupal 8 to empower contrib and a single patch, which just is the interdiff.
Comment #46
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedAnd even if catch said 4 years ago that modules just should fix their code to use #attached and not drupal_add_*, now in 2016 this still has not been fully changed and drupal_add_* is a supported API in Drupal 7.
Therefore marking major and approving as Drupal 7 framework manager.
Comment #49
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedRandom test failure (CI error). We do have a race condition in the D7 tests setup - it seems. I presume two tests get the same prefix somehow OR a prefix that still has tables gets re-used.
Comment #51
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedTestbot really does not like this patch ...
Comment #52
Wim LeersExcept this patch will then only fix it for people who modify their
settings.php
to opt in to the new behavior. See #2754245-19: [META] Explore how to backport render context and #cache properties from Drupal 8 to empower contrib and -20 for more details about my concerns.Comment #53
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commented#52: That is correct, but we can have a handbook page for it.
If we change the default behavior, someone might have relied on the wrong behavior (e.g. an asset not being added as the block was cached most of the time).
e.g. a site displaying a JS error, but just on cold cache, combine with something to prevent the cron cache clearing of blocks (happens in the wild) and the site "mostly works", but not when the fix would be applied. Then it would fail 100% of the time (same as if the setting was enabled).
Comment #54
stefan.r CreditAttribution: stefan.r commentedI wouldn't be against eventually removing experimental status (in 7.60 or such) and exposing this as a checkbox on the performance screen (and recommending advanced users to enable it in release notes).
For 7.x we'll have to make certain bugfixes opt-in anyway, so as to not risk breaking sites.
Comment #55
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commentedMaking this a bug and it probably is a good idea to get feedback from the people using the experimental option first.
The patch is small enough that I would put it on a production site myself.
Comment #56
Jim.M CreditAttribution: Jim.M as a volunteer commentedHey,
I had the problem with blocks created by Display Suite. It's described here https://www.drupal.org/node/1820942.
Once I applied the patch from the comment #45, the problem's gone. So, I can confirm that this patch works for me.
Tested on Drupal 7.44 and Display suite 7.x-2.13
Comment #57
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting commented#56: Thank you for testing. I will probably have an updated patch within the next week - or so.
Comment #58
Fabianx CreditAttribution: Fabianx as a volunteer and at Tag1 Consulting for Pantheon commentedPostponed on #2820757: Add Attachment Collector to ensure #attached assets are not lost during render caching.
Comment #59
Fabianx CreditAttribution: Fabianx at Tag1 Consulting for Pantheon commentedComment #60
yonailo CreditAttribution: yonailo commentedHello Fabianx
Can you clarify why this patch has been postponed ? is because if we get to commit #2820757 this patch should be rewritten to use the Attachment Collector API right ?