Problem/Motivation
Discovered by #2429617: Make D8 2x as fast: Dynamic Page Cache: context-dependent page caching (for *all* users!), postponed on #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed.
This is blocking critical #2429617: Make D8 2x as fast: Dynamic Page Cache: context-dependent page caching (for *all* users!)
Quoting Wim Leers in #2493021: Remove unused & useless services from HtmlRenderer:
that is already happening in
BlockAccessControlHandler
:// This should not be hardcoded to an uncacheable access check result, but // in order to fix that, we need condition plugins to return cache contexts, // otherwise it will be impossible to determine by which cache contexts the // result should be varied. // @todo Change this to use $access->cacheUntilEntityChanges($entity) once // https://www.drupal.org/node/2375695 is resolved. return $access->setCacheMaxAge(0);
Excellent!
But, then
\Drupal\block\BlockRepository::getVisibleBlocksPerRegion()
causes a lot of sadness:foreach ($this->blockStorage->loadByProperties(array('theme' => $this->getTheme())) as $block_id => $block) { /** @var \Drupal\block\BlockInterface $block */ // Set the contexts on the block before checking access. if ($block->setContexts($contexts)->access('view')) { $full[$block->getRegion()][$block_id] = $block; } }
i.e. access results' cacheability metadata for blocks never makes it into the render arrays of blocks. I did some git history debugging; this was just never tested; this max-age = 0 thing originates in the original "access result cacheability metadata" issue, which just updated all code, and didn't add test coverage for every particular use of it (#2287071: Add cacheability metadata to access checks). Because that was out of scope.
Proposed resolution
Fix it.
Remaining tasks
TBD
User interface changes
None.
API changes
TBD
Comments
Comment #1
Wim Leers#2429617-100: Make D8 2x as fast: Dynamic Page Cache: context-dependent page caching (for *all* users!) contains a rough initial patch by Fabianx.
Comment #2
Fabianx CreditAttribution: Fabianx for Acquia commentedHere is a patch that would work, but that I am not happy with overall ...
I think better would be if #access would be set in the BlockViewBuilder() and Renderer supported an object for #access.
Comment #3
Fabianx CreditAttribution: Fabianx for Acquia commentedComment #4
Fabianx CreditAttribution: Fabianx for Acquia commentedLets see what testbot says ...
Comment #5
Wim Leers+1
@dawehner was working on making
#access
support that. Can't find the issue ATM.Comment #6
Wim LeersShould be
!$access->isAllowed()
.Comment #7
Wim LeersRE #5: this is the issue: #2487600: #access should support AccessResultInterface objects or better has to always use it.
Comment #8
Fabianx CreditAttribution: Fabianx for Acquia commentedComment #9
tim.plunkettgetVisibleBlocksPerRegion() is now an inaccurate name, since we return all blocks per region and the caller is responsible for checking access.
Comment #10
Wim Leers#9: I had the exact same remark. :)
Comment #11
tim.plunkettThis effectively reverts #2367579: Move retrieval of visible blocks to a standalone service, which @chx opened for what I can only assume is Mongo:
Comment #12
Wim LeersI don't see how, because access checking will always invoke the access checkers, regardless of where block config entities are stored?
Comment #15
Wim LeersTestbot failed:
FAILED: [[SimpleTest]]: [PHP 5.4 MySQL] Setup environment: failed to clear checkout directory.
. Hence retesting.Comment #17
chx CreditAttribution: chx commentedThe BlockRepository stuff was meant to significantly reduce the number of blocks that are even possible. A theoretical implementation could read the user from CurrentUserContext, get the roles for that user and run it against visibility.user_role and then remove that visibility rule from the result to avoid a slow PHP check. Of course I have no idea how that would work in actual code because I never actually got around to implement it nor do I know how it fits in the crazy caching world of Drupal 8.
Comment #18
tim.plunkettSo we can probably continue on with this, just renaming the method and being clear that it does NOT run access checking, and then page_manager/panels will have to update.
Comment #19
chx CreditAttribution: chx commentedSeems then the only speedup possible will be querying per theme. It's way less useful but I will work with what I can (in theory. if someone picks up mongodb)
Comment #20
Berdir@chx: That already (and still with this patch) happens, just inside the patch context. It's a loadByProperties(), but that's just a shortcut for an entity query. An entity query that uses a config entity query index now, so it's really fast (one key value lookup to get the config entity ID's).
Comment #21
YesCT CreditAttribution: YesCT commentedComment #27
lauriiiThis should at least fix the fatal error on the tests
Comment #29
Wim LeersThe remaining failures are likely at least partially broken tests: tests that are assuming a block is render cached with a certain cache ID, but that CID is now different because they now take cache contexts from access results into account.
Comment #30
BerdirLooking at the test fails.
Comment #31
BerdirUhm. I don't think we can do this without #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed because what it actually means is that block render caching is dead without having fixed the other issue first?
Comment #32
BerdirI've verified that at least the BlockCacheTest is green with the patch from #2375695-24: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed. So pretty sure we need that before we can continue here.
Comment #33
Fabianx CreditAttribution: Fabianx for Acquia commentedPostponing on #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed, which I had to make critical.
Berdir is right that issue is a hard blocker for this one.
Comment #34
Fabianx CreditAttribution: Fabianx for Acquia commentedComment #35
Fabianx CreditAttribution: Fabianx for Acquia commentedComment #36
Wim LeersI don't understand why this is blocked on #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed. Note that the title of this issue explicitly mentions , not or .
You're absolutely right that #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed is necessary for blocks to be cacheable when using visibility conditions, and that many blocks will effectively not be cacheable if that is missing. But that's a pure performance/cacheability problem. This issue here also presents a security problem, because blocks not intended for one user may be served to another user, because the access results' cacheability metadata never makes it onto the render array.
Back to NW to either still finish this patch independently, or to get clarity on why it is indeed blocked by #2375695.
Comment #37
BerdirNo, it is not only about visibility conditions.
*every* block is set to max-age 0 even if it doesn't have visibility conditions. As soon as we respect that, then blocks are never cached, because max-age prevents that.
See the mentioned BlockCacheTest test. That is very simple, it just tests if a block is cached at all or not. And with this change, the block is no longer cached.
There is no security issue without smartcache because we don't cache the access results.
Comment #38
Wim LeersRiiiight. Blocking this again. Thanks for the clarification!
Comment #39
BerdirCombined patch, want to see how many test fails are left then.
Comment #41
BerdirFixing those test fails to better understand what is needed for test coverage. Quite some changes necessary in unit tests because the place where access checks happen moved. Also again annoyed at static methods calling service methods :)
We could possibly extend them for more unit test coverage but this kind of proves already that we are calling those methods.
PageCacheIntegrationTest shows again that we should filter out the single block cache tags when we have the list cache tag around as well. Would save a ton of cache tags that we need to check in the page cache.
Also fixed an annoying and confusing bug in AssertPageCacheContextsAndTagsTrait, the Unwanted/Missing labels are inverted ;)
Comment #44
EclipseGc CreditAttribution: EclipseGc commentedIt's disheartening to see this much effort duplication from #2377757: Expose Block Context mapping in the UI. Whatever the case, it's probably worth looking at in light of this issue. Most of this patch seemed pretty sensible to me and would further simplify what needs doing in 2377757. ++ from that perspective.
Eclipse
Comment #45
EclipseGc CreditAttribution: EclipseGc commentedThese two things together conspire to output render array data that will result in regions rendering when there's nothing in them to display. This is functionally #953034: [meta] Themes improperly check renderable arrays when determining visibility which is why that access check before adding it to the render array is there in the first place. The problem in 953034 persists regardless because even simply adding #access => FALSE into the render arrays somewhere will cause this problem. Still those checks mitigate the problem manifesting with blocks which is the lion share of what most people are likely to see.
Must we apply the cacheable metadata to empty render array elements? If so, we're going to have to rework region rendering in some way.
Eclipse
Comment #46
Fabianx CreditAttribution: Fabianx for Acquia commented#45: We have to do that anyway. With placeholders the guarantees that a region is empty or not are not very strong anyway.
What if your block is *SI'd in, etc.
There is a Element::isEmpty() in the works however that could be used for now partially at least.
--
Downgrading to major and merging with the other issue for now per berdir's comment.
Comment #47
Berdir@EclipseGc: I'm confused, I don't think it overlaps with that? The combined patch is this on top of #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed, if anything then that issue overlaps with yours?
And about those two lines, yes that's why the last test there is failing. I don't think we have to do add it inside an otherwise empty block element, we can also add it higher up, but so far, that didn't solve the test fail.
As discussed, will move the merged patch over to #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed
Comment #48
Wim Leers#46:
Element::isEmpty()
has already landed.Comment #49
Wim LeersCan
Element::isEmpty()
not be used to solve the last test failure?Comment #50
BerdirI'm pretty sure this isn't postponed but duplicate of #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed. Everything I did here is part of the other issue. We do need a follow-up issue to try and get rid of the max-age 0 in case of missing contexts but that's a different isuse.
Comment #51
Fabianx CreditAttribution: Fabianx for Acquia commentedAs long as that other issue has test coverage, that should be fine.