Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
Some cache contexts in views plugins are still wrong; for example: some use 'cache.context.user' instead of 'user'.
Proposed resolution
Fix them. But also introduce validation that would've prevented this in the first place.
Remaining tasks
None.
User interface changes
None.
API changes
None. Only API additions:
Cache::validateContexts()
Cache::refresh()
— to reset the static cache used by cache context validation (when installing modules)
Beta phase evaluation
Issue category | Bug because Views' cacheability metadata is broken. |
---|---|
Issue priority | Major because prevents us from depending on Views cacheability metadata, which has a ripple effect on all of core's cacheability. |
Prioritized changes | The main goal of this issue is performance. |
Disruption | Zero disruption (unless you were using wrong cache contexts, in which case Drupal 8 will now inform you of the problem rather than failing mysteriously). |
Comment | File | Size | Author |
---|---|---|---|
#52 | validate_cache_contexts-2451679-52.patch | 25.86 KB | Wim Leers |
#32 | 2451679-32.patch | 31.64 KB | dawehner |
#29 | validate_cache_contexts-2451679-29.patch | 26.56 KB | Wim Leers |
#24 | validate_cache_contexts-2451679-24.patch | 21.92 KB | Wim Leers |
Comments
Comment #1
dawehnerLet's give it a try.
Comment #2
tstoecklerMaking the issue summary more readable. I thought those were two sentences... :-)
Comment #3
tstoecklerSeems @Wim Leers found this as well, in #2451679: Validate cache contexts (+ cache contexts in some views plugins wrong).
Comment #4
Wim LeersYes, I've been seeing and fixing this too, in two places even: #606840-40: Enable internal page cache by default & #2432837-58: Make cache contexts hierarchical (e.g. 'user' is more specific than 'user.roles').
I'm repurposing this issue to fix the general problem: cache contexts are never validated.
Comment #5
Fabianx CreditAttribution: Fabianx commentedComment #6
Wim LeersHrm, turns out we already have validation:
… but it happens only when converting cache contexts to keys. This avoids making
Cache::mergeContexts()
dependent on the container. Should we change that strategy? We're already seeing bubbling being a significant percentage of the page building time, this would make that *much* worse, and impossible to improve significantly.Actually, I think we can do this by using the
cache_contexts
container parameter. Receiving that once and comparing with it is quite cheap.Comment #8
Fabianx CreditAttribution: Fabianx commentedPlease use a static class property instead.
This should probably use a look-up table for performance so that isset() can be used.
Comment #9
Wim Leers#8:
Comment #10
Wim LeersStraight reroll to chase HEAD.
Comment #11
Fabianx CreditAttribution: Fabianx commented#9:
1. I don't think we should mix static in functions and class static.
2. What I meant was a) using array_flip and b) caching already looked up implementations (with parameters)
Comment #12
dawehnerJust some work in the meantime.
... because a static as part of the method makes it impossible to clear, ... in case you have to, or various other reasons, of which one is: make it clear which "dependencies" methods on a class have.
Comment #13
dawehnerNote: Those exceptions should probably be better assertions, see #2451793: [META] Assert Statement Use in Drupal
Comment #15
Wim Leers#12: Thanks, that was almost identical to my first iteration :D
But FabianX expressly meant that to include the parameters of cache contexts that accept parameters. So, doing that too.
Comment #16
Fabianx CreditAttribution: Fabianx commentedCan we array_flip once() and then set
static::$validContextTokens[$context_token] = TRUE;
please?
But yes that is what I meant :).
Comment #19
Wim Leers#16: argh that's what I first was doing, but I thought you wanted #15 :P Will do, unless @dawehner beats me to it. (Calling it a night here — *hungry*! This is my first thing in the morning.)
Comment #20
Wim LeersAddressed #15.
Comment #21
Wim LeersFixing most of the test failures. Many thanks to Fabianx & alexpott to help debug this. (I first thought something was wrong with the module installer.)
Comment #24
Wim LeersThis should fix the majority of the test failures.
Comment #26
dawehnerFixed some of the failures, but I would like to rise a point here. As we have seen in some other profilings, merging the cache metadata is costly.
The additional validation is costly, so I@m curious whether we can move it to a level, at which its just needed once. Maybe on cache set?
If we move it to a different location we might be also be possible to not require the static cache anymore.
It would be nice to explain why we need this here.
Don't we use REQUEST_TIME? ... I see someone, we all know, to scream because of the md5 call, even I think its BS to not use it.
Comment #28
Wim Leers(I worked on this in parallel with #26. Same diff as @dawehner posted, but I also enhanced the debug output. So rerolling just for that.)
It's this change in #12 that's the root cause of the last failures; the tests weren't adjusted accordingly. This caused 2 types of failures:
EntityReferenceRelationshipTest
,FieldEntityTest
,ViewEntityDependenciesTest
) were failing because something goes wrong inDrupal\views\Plugin\views\HandlerBase->getEntityType()
. I got stuck there. Hoping dawehner can fix that.Comment #29
Wim Leers#26:
I considered this too. But:
Cache::mergeTags()
works. I think we should make both work similarly, and then optimize both in a follow-up. (The same performance concern applies to tag merging.)Comment #32
dawehnerThere we go.
Comment #33
Fabianx CreditAttribution: Fabianx commentedThis is no longer true as the ::refresh method shows.
If we want to micro-optimize this can save the result of strpos.
$colon_pos = strpos(...);
if ($colon_pos !=== FALSE) {
$c_id = substr($c_id, 0, $colon_pos);
}
Nice change :).
These are great!
caaching :)
Besides those this looks quite ready, could use a beta evaluation.
Comment #34
Wim LeersFixed all remarks in #33.
Comment #35
Wim LeersAdded beta evaluation.
Comment #36
Fabianx CreditAttribution: Fabianx commentednit - "level ,we"
-- can be fixed on commit.
=> RTBC
Comment #37
Wim LeersFixed the nit.
Also:
Filed #2454643: Optimize cacheability bubbling (Cache::mergeTags(), ::mergeContexts(), BubbleableMetadata) and #2454649: Cache Optimization and hardening -- [PP-1] Use assert() instead of exceptions in Cache::merge(Tags|Contexts).
Comment #38
dawehnerLooks great for me!
Comment #39
alexpottIf I run all the phpunti tests using PHPUnit I get errors.
Comment #40
Wim LeersThe static caching got in the way:
\Drupal\Tests\Core\Cache\CacheTest::testValidateContexts()
ran first, and then\Drupal\Tests\Core\Render\RendererBubblingTest()
runs, both of which set a different set of valid cache contexts.Comment #41
dawehnerSo this test failure clearly has shown that using state information is bad, and results in brittle code.
Let's be honest and put that onto the CacheContext service, where this, if we are honest, belongs to.
Comment #44
Wim LeersHaving looked at #41 — I much, much prefer #41. No hassling with statics at all! I checked with Fabianx, and he also thinks #41 is better.
Just did some clean-up:
CacheContexts::validate()
toCacheContexts::validateTokens()
, so that it matchesCacheContexts::parseTokens()
::parseTokens()
RendererBase
, which was still returninglanguage
instead oflanguages
RendererTestBase
:)Since this was RTBC and these are only cosmetic changes relative to @dawehner's changes in #41, I'm very tempted to already RTBC this, but I guess I better leave it to somebody else.
Comment #45
Fabianx CreditAttribution: Fabianx for Drupal Association commentedThis is wrong now that cache context hierarchy is in.
--
Rest looks fine. I wonder how that one slipped through all tests though.
Comment #46
Wim Leers#45: that could only have slipped through if we have zero integration test coverage for that Views plugin. Fixing that is out of scope for this issue.
Comment #47
dawehnerIt looks great for me.
Comment #48
Fabianx CreditAttribution: Fabianx for Drupal Association commentedI was of the impression it is user.node_grants:view ...
Comment #49
Wim LeersWow, epic fail much. It was very late yesterday — sorry for that stupid mistake.
Comment #50
Fabianx CreditAttribution: Fabianx for Drupal Association commentedNow it is RTBC :).
Thanks!
Comment #51
catchcache contexts no?
Nit but shouldn't exception messages use sprintf, or did I miss something?
Slightly concerned about the following scenario:
1. Enable a module that provides a cache context
2. Some stuff gets cached using that cache context
3. The module is uninstalled
4. The stuff is still cached and this condition gets hit when it's returned from cache.
We're moving towards selectively clearing caches when modules are uninstalled, so this feels not impossible to hit as a condition.
Is this worth thinking about more? If it's worth thinking about, I'd suggest making the exception a trigger_error() call instead, then opening a follow-up to make it an exception. That situation is something a real site could very possibly run into, and a developer probably wouldn't.
Comment #52
Wim LeersFirst, I was going to say: "but installing a module clears all caches!" — but that's no longer true apparently.
Hence: great catch!
This is a pre-existing problem though. See the code that actually converts cache context tokens to cache keys:
As you can see, this is also (already in HEAD) throwing an exception. So the problem already exists; the only difference is that it happens in a slightly later stage.
Therefore I propose to fix this in a follow-up issue entirely, and commit the patch as-is. (Unless I missed something which causes this patch to make HEAD worse.)
Follow-up created: #2460013: Uninstalling modules containing cache contexts or #post_render_cache callbacks may break Drupal if they are cached somewhere.
Comment #53
Fabianx CreditAttribution: Fabianx for Drupal Association commentedI also think 3. is worth of a follow-up instead of doing it here.
We could compare the cache contexts lists before and after or force modules to uninstall properly by clearing the 'rendered' cache tag themselves.
Comment #54
Wim LeersNow that I think about it, there's another reason that not clearing the render cache after uninstalling a module is problematic: render cache items may include
#post_render_cache
callbacks… which may live in uninstalled modules.Comment #55
catchBumped the other issue to critical, but agreed the problem isn't introduced here.
Committed/pushed to 8.0.x, thanks!
Comment #57
bzrudi71 CreditAttribution: bzrudi71 commentedThis seems to introduce some strict warnings in views tests like:
Strict Standards: Declaration of Drupal\views\Tests\Handler\AreaEntityTest::setUpFixtures() should be compatible with Drupal\views\Tests\ViewUnitTestBase::setUpFixtures($import_test_views = true) in /var/www/core/modules/views/src/Tests/Handler/AreaEntityTest.php on line 23
Please see PostgreSQL Bot
Comment #58
Wim Leers#57: I don't see how this patch could've caused that. Are you 100% certain this is the first commit where that happens, and that it can be reproduced?
Comment #59
bzrudi71 CreditAttribution: bzrudi71 commented#58: I see this the first time today and non of the other hand full of commits yesterday seem related to this ;-) I just did a quick views test run again and it's reproducible.
qa.drupal.org shows the same messages while testing this patch in full review log...
Comment #60
Fabianx CreditAttribution: Fabianx for Drupal Association commentedIt seems this change is incompatible, when something inherits the ViewUnitTestBase.
Do we use this argument here anyway?
Comment #61
Wim LeersThat looks like it's a c/p error indeed. Patch posted at #2460731: Strict warning in ViewUnitTestBase.