Updated: Comment #N
Problem/Motivation
Cache::invalidateTags() is called from ThemeSettingsForm::submitForm()
, PerformanceForm::submitForm()
, etc. However, that doesn't get run when the theme settings are changed from an API call, for example, within UserPictureTest::testPictureOnNodeComment()
. In general, cache invalidation needs to be triggered by configuration or content changes, regardless of whether it's a form submission that triggers the change.
Other examples can be found by searching HEAD for where Cache::invalidateTags() or cache_invalidate_tags() is called.
Proposed resolution
A cache tag per configuration name, like config:system.performance
.
Remaining tasks
None.
User interface changes
None.
API changes
- Added
AccessResult::cacheUntilConfigurationChanges(ConfigBase $configuration)
(a sister method ofpublic function cacheUntilEntityChanges(EntityInterface $entity)
). - Added
ConfigBase::getCacheTags()
(just likeEntityInterface::getCacheTags()
). It ensures that each cache tag is of the formconfig:<config object name>
, so e.g.config:system.performance
for the performance settings configuration object. When a configuration object is updated or deleted, this is used to invalidate the appropriate cache tags, as you'd expect. - Added
StaticMenuLinkOverridesInterface::getCacheTags()
.
Because of point 2, all cache tags of config entities have changed accordingly, e.g. menu:<menu name>
has become config:system.menu.<menu name>
. However, you should have been using the getCacheTags()
method on configuration entities already anyway, in which case this cache tag change will be picked up by your existing code automatically.
Beta phase evaluation
Issue category | Bug because Config System imports/syncs today do not result in the necessary cache clears, making it seem as if the import/sync didn't work correctly. On top of that, changing simple config through the UI does not clear any caches whatsoever, and hence simple config that affects e.g. render cached output, doesn't cause that render cached output to be invalidated. |
---|---|
Issue priority | Critical because this breaks cacheability, makes Drupal seem broken. |
Prioritized changes | The main goal of this issue is security/performance. |
Disruption | Zero disruption. |
Comment | File | Size | Author |
---|---|---|---|
#87 | simple_config_cache_tags-2040135-87.patch | 89.33 KB | Wim Leers |
Comments
Comment #1
effulgentsia CreditAttribution: effulgentsia commentedtagging
Comment #2
xjmComment #3
Wim LeersAFAIK the only solution is config event subscribers. Updated the issue summary with the proposed solution.
I think this at least major.
Comment #4
effulgentsia CreditAttribution: effulgentsia commentedSubscribers might be good for other reasons, but is it possible / would it make sense to solve this issue purely with a cache tag per config file, and have the config system itself invalidate that tag when the file is changed? Not sure if that's a workable idea, just throwing it out there.
Comment #5
Wim Leers#4: That would be turning things around from how they're typically done right now, but that could also work.
Right now e.g. the
UserLoginBlock
depends on theuser.settings.register
configuration. A block has its own cache tag. So the event subscriber listens for the config change event and invalidates that block's own cache tag.What you propose implies that each things should be tagged with the configuration entity they depend on. Which implies that there would be significantly more cache tags being used, that propagate to all cache entries (including the page cache), which potentially leads to an exponential explosion in the number of cache tags.
Both are possible, we just have to determine what we prefer. I could see either an
events.yml
file (#2023613-59: Move event registration from services.yml to events.yml) or a follow-up to #2080823: Create API to discover config entities' soft dependencies and use this to present a confirm form on module uninstall be super useful to declaratively indicate which config entities something depends on.Comment #6
Wim LeersRenaming for clarity.
Comment #7
Wim LeersComment #8
effulgentsia CreditAttribution: effulgentsia commentedAlso adding non-entities to the title, for config objects like
system.performance
andmenu_link.static.overrides
.Comment #9
Wim LeersFor config entities, I've opened #2342651: Cache tags for *all* config entities (with an initial patch). That makes this solely about config non-entities.
Without proper cache tag bubbling and test coverage, it's extremely likely that many bug reports will be filed about things "not working", due to necessary cache tags not being associated.
Worse, yet: without proper cache tag invalidation, it will seem like CMI deployments are broken! When changing config ("things in CMI") through the UI, the invalidation is performed by the UI, not by the config system!
Comment #10
catchComment #11
xjmDiscussed with @alexpott, @effulgentsia, and @dawehner. Retitling to focus on the bug we need to fix, instead of a potential implementation.
Comment #12
xjmComment #13
BerdirDiscussed this with @WimLeers a bit.
We have multiple options and directions to fix this.
The mentioned example in this issue says to create a specific event listener for a specific config. I'm worried that we end up with a lot of event listeners that way that have to run on every config save and it is also a pretty complex solution that contrib/custom probably won't do.
So we said that instead, we'd prefer an automated cache tag clearing when simple config is saved, either:
a) A global "simple_config" or something cache tag
b) A cache tag per configuration name, like config:system.performance
c) Find a way to attach "cache tag X must be cleared when config object N is saved
a) and b) have the problem that we don't know where the code that does that would live (Config? does have to know about simple/entity config...) and how it identifies simple config, do we have a reliable way for that?
c) was my idea that we didn't actually discuss yet, there I'm not sure how that cache tag-tagging would work.
On performance of those approaches:
a) has the advantage that we only have to check one cache tag additionally, and our cache invalidation optimization would also keep the cache tag deletions as low as possible on multiple config saves. Downside is that we might be clearing a lot every time simple config chances, but that's not something that happens often.
b) would require more cache checks and deletions but we could do more specific cache invalidations
c) depends on how the invalidation would work, the advantage could be that we can use cache tags that are already in use, at least in some cases. c) is still very theoretical :)
Comment #14
catchSo for a) the more items reference the simple_config cache tag, the more will be invalidated whenever any simple config is invalidated.
This performs best if we end up with lots of different cache items referencing lots of different files.
For b) the more items reference the cache tags of different simple config files, the more invalidations we have to check. This will be best if several items check the cache tag of the same configuration file (then they don't get unnecessarily cleared and don't require lots of invalidation checking). It gets worse if lots of different simple config files get referenced.
b) seems like it'd be OK for most sites to me.
c) would be useful for something like the system.performance page so it can just clear the already-existing render cache tag.
For the system performance page though, I think we should remove the option from there, and add it to Settings instead though - then it could become part of the cache key (since we'd have the setting available on cache get too). This is such an obscure setting to provide a UI for.
Comment #15
effulgentsia CreditAttribution: effulgentsia commentedHow about a d) simple_config:PROVIDER?
Comment #16
effulgentsia CreditAttribution: effulgentsia commented#15 might not be very different from #13.b. In core, I think only system and user modules define more than one simple config per module.
Would it need to? Or, can ConfigEntityBase implement a getCacheTags() that returns the same format as b), so that whatever in the config system does the clearing could apply the same logic for both simple and entity?
Comment #17
Wim LeersNow working on this.
Comment #18
Wim LeersI'm making good progress on this. Calling it a day now. Should have a patch ready tomorrow around noon. I'm using the approach effulgentsia proposed in #16, which makes it surprisingly elegant :)
Comment #19
Wim LeersAnd here's the first iteration. Very unlikely to be green. But pretty complete already.
Comment #21
swentel CreditAttribution: swentel commentedLol, nice comment, but I guess we should make this a little less harsh :)
Besides that, isn't there a way for contrib or custom themes to not have to care about this function, because it seems likely that this can be missed easily.
Comment #22
olli CreditAttribution: olli commentedIn #2373017: No delete link when editing a menu, only reset links I found a @todo that links here and I'm not sure what to do with it. The @todo is not removed in #19.
Comment #23
Wim Leers#21: Yes, that's just there temporarily until e.g. catch and Fabianx have had a chance to look at it and either +1 the direction (in which case I'll clean it up) or propose an alternative solution (in which case it'll go away).
#22: Indeed, that's one thing I missed in this initial patch, I'll fix that todo in the next reroll :)
Comment #24
Wim LeersAnd green. Tests were already added in #19.
Comment #25
Wim LeersOops, still forgot to do #22 in #24. Done now.
Comment #26
Wim LeersNoticed a flaw in #25 after uploading.
Comment #28
moshe weitzman CreditAttribution: moshe weitzman commentedAn abomination indeed! I bet that many preprocess functions are going to forget to call add theme_add_setting_cache_tag() after referencing a setting. FYI, Dries recently opened the door toward removing theme settings at #1256994-37: Allow breadcrumbs and feed icons to be toggled in admin UI.
In any case, this solves the problem at hand and we can discuss removing theme settings in a follow-up.
Comment #29
Wim LeersThanks :) Cleaning up that comment though, it was a bit too harsh to be committed like that.
Solely docs changes, so leaving at RTBC.
Comment #30
catchWhile we might be able to get rid of theme settings, if we don't we're stuck with the strange function in preprocess in contrib themes.
Could we not special case that instead to be a listener and clear the render cache tag when the settings are saved? Then the weirdness is self-contained at least.
Comment #31
Wim LeersI like that idea.
Comment #32
Wim LeersComment #33
Wim LeersNoticed a small mistake.
Comment #36
BerdirLooks great, really like the unification.
With the cache tag invalidation on rendered when a theme settings changes, do we really still need the per-theme and global theme cache tags? What are they used for then?
documentation? :)
Maybe document that we are doing this to match the cache tag of the underlying config object, with a @see?
So what we actually care about here is when the config file is *deleted*. We should also make sure that $config->delete() clears the cache tag.
Discussion about which of those cache tags we really use here doesn't belong here, we already started discussing them in the profiling issue..
Comment #37
BerdirAlso, we have optimization in place to ignore duplicate cache tag invalidations but it might still be worth to avoid the call there and override the relevant methods in ConfigEntityBase like invalidateTagsOnSave() (we still need the list cache tags there...)
Speaking of list (and _view, if we use that for them..) cache tags, do we keep the separate pattern there or would we want to unify them somehow? I'm perfectly OK with keeping them, just asking.
Comment #38
Wim LeersFixed.
Unified the list cache tags. The view cache tags are unchanged, but they are less closely related to the entities themselves anyway; they're associated with the view builder.
Looking at the fails, having implemented catch's suggestion (
) has a consequence I didn't consider, and I think catch also didn't consider.The problematic bit is this code in
\Drupal\block\Entity\Block
:We had this to ensure that if the block configuration changes, all cached pages that use that theme are invalidated. But this is from a time where we didn't have "list" cache tags yet… and thus:
config:block_list
) instead of atheme:<theme name>
cache tag, then we're golden.So I implemented that approach, and it works beautifully.
EDIT: Only after I did all this, I saw that Berdir wrote
— this is absolutely correct, and would've been necessary in any case to get the tests to pass. The patches in #32/#33 failed because I didn't do that yet :)Comment #39
BerdirI meant a @see to ConfigBase::getCacheTags(),getConfigDependencyName() is already there.
Shouldn't this be conditional, for those entity types that explicityl want to define a different tag? We could set it conditionally before calling parent::__construct() I think?
Comment #40
Wim LeersComment #41
catchLooks much better with the event listener, and that's a self-contained workaround we can just remove without an API change later if we end up not needing it.
Comment #44
Wim LeersEasy fix.
I consider this RTBC'able.
Comment #46
Wim LeersAdded beta evaluation
Comment #47
Wim LeersForgot to include the sole new file in the patch. #44 interdiff is still valid; but this is the right patch.
(Just learned about
git add -u
, so I used that instead of of thegit add --all core
that I usually do after applying a patch. Looks like I should go back to what I used before.)Comment #48
BerdirTrying to review this with the same level of nitpick... details as you do. Initially wanted to fix them and RTBC it, but there is at least one real problem I think.
Missing type.
Should be cache tags I think but other examples are the same, so not sure about changing it here.
It would make more sense for me to make . the glue instead of an empty string.
This was an else if before, now it is just an if, I don't think that is correct?
#2393577: Access issue with default settings set to disabled should provide the test coverage for this.
Missing .?
Should this say "untilConfigurationChanges()" ?
Comment #49
dawehnerWe have a CacheTest, let's expand the test coverage for buildTags to include $glue in there.
Its a little bit sad to couple the existance of menu entities now with the menu tree here, it used to be independent, and we had a fight about this earlier.
The underlying question is whether its okay to add an implicit dependency of \Drupal\Core to the system module.
I wonder whether it is a more sane idea to simply foreach over the existing themes and just not care about the regex? This would at least make the code a little bit more readable.
Happy christmas!
Comment #50
BerdirRe-roll and fixed #48 1, 4 (confirmed that the new test failed on this patch), 5 and 6.
Added a test for #49.1 and changed 3 as suggested.
Comment #52
BerdirMessed up the merge and a weird typo.
Comment #53
Wim LeersAddressing the parts of #48 and #49 that haven't been fixed yet (Berdir's changes look great):
I agree it's sad. But when was it independent? It's been like this for a long, long time? The dependency is already there before this patch at least; this patch has only made it explicit.
If you have a counterproposal, I'm all ears :)
Comment #55
BerdirI think there were ~3 lines that called buildTags() with empty $glue, you only updated one. I guess the cache tags refactoring patch conflicted with this.
Comment #56
Wim LeersRebased, and updated those two other instances.
Comment #58
Wim LeersComment #59
BerdirSee #2398085: Inject cache_tags.invalidator instead of using Cache::invalidateTags(), what about updating this to already inject the invalidator? Then we can also drop the global container from the unit tests...
I think the event stuff is now optimized enough to only create this service when we actually need it, so we wouldn't have a performance issue...
Comment #60
Wim LeersDone! For every class were >=1
Cache::invalidateTags()
is touched by this patch.Comment #62
Wim LeersComment #64
Wim LeersDone.
Comment #66
Wim LeersAnd this will be green again, at last!
Comment #67
Wim LeersForgot the interdiff for #66 in my enthusiasm.
Comment #68
BerdirI don't think my point from #37 was addressed, we are still clearing config cache tags twice, once by saving the config and once in invalidateTagsOnSave(). As mentioned, I believe that config entity base should override that and not return the single cache tag there.
The only other point that was not "fixed" is #49.2 with the menu dependency, but I agree with @WimLeers that we are not actually changing anything here, the new name makes it just a bit more obvious. We already were adding that specific cache tag that matches with the old entity cache tag.
Comment #69
BerdirThinking about that method, that has logic to only clear the cache tag on update. Should we do the same here, no point in clearing a cache tag for a new config ... right?
Comment #70
Wim LeersThanks for the review! Will fix those points.
Comment #71
Wim LeersFixed that; this uncovered an instance of
self::method()
instead ofstatic::method()
; i.e. it wasn't using late static binding while it should be.Comment #72
dawehnernitpick: Remove the line without the "8"
It is still sad to couple menu trees to menu entities, even if indirectly.
Comment #73
Wim LeersComment #74
BerdirCan we make this a bit more clear for insert/update? At least document that we only expect a single call, even better if we could test it separately, e.g. to separate test methods, where one explicitly ensures that invalidateTags() is never called. This unit test would also pass if only saving a new config would clear the tag :)
Comment #75
Wim LeersDone.
Comment #76
Wim LeersFrom IRC:
Comment #79
Wim LeersRebased. 3-way merge FTW.
Comment #80
Wim LeersCR created. IS updated.
Comment #81
Wim LeersAlex Pott remarked in IRC that
LanguageConfigOverride
had not yet been updated. After a discussion with Alex Pott and Berdir on how that should work, this is what I ended up with.Comment #82
Berdirsuper-nitpick: $this doesn't need a description.
Feel free to update, but I think this is finally RTBC.
Change record looks good as well, if maybe a bit too detailed for a change record :)
Comment #83
Wim LeersYay!
Fixed the super nitpick.
Comment #84
alexpottI think we need to do something in ConfigFactory::rename() too since this does not use the Config object.
Comment #85
Wim LeersFixed, and added a unit test for it.
Comment #86
BerdirCan you add another mock for the storage->rename() call, then we have that fully covered, and not just 50% of the method :)
Comment #87
Wim LeersSure, done.
Comment #88
BerdirWe discussed in IRC if the cache tag invalidation should be moved to the config factory, we were not sure, but decided to keep it there for now.
This looks good to me again, rename is properly addressed, I can't think of anything else that would be missing.
Comment #89
alexpottCommitted 5859ca2 and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation to the issue summary.
Comment #91
Wim LeersFollow-up: #2407765: Wherever simple config is used to render output to end user, associate their cache tags.
Comment #93
Wim LeersThis CR wasn't published yet. https://www.drupal.org/node/2406455 is now live.