Problem/Motivation
NodeController calls $this->renderer->addCacheableDependency with an object that doesn't implement CacheableDependencyInterface, this results in an uncacheable page for DPC.
Steps to reproduce
Visit the node overview page
Notice that the page is uncacheable in dynamic page cache
Proposed resolution
username is a render array, use the correct API for merging cacheability metadata
Steps to test
1. On a fresh install of Drupal 9.3.x, apply the patch.
2. Create a node (can be published or unpublished).
3. Edit the node you created.
4. Open the web developer tools Inspect pane and select the Network tab.
4. View the node and click on its Revisions tab.
5. On the Network tab in your browser's Inspect tab, scroll up to the request for /revisions and select that row. In the adjacent tab, look under Respsonse Headers for the `x-drupal-dynamic-cache` row and note the value (should be MISS). Refresh the page and repeat, noting the new value (should be HIT).
Remaining tasks
1. Verify patch sets the x-drupal-dynamic-cache response header on node/%/revisions, i.e. node/1/revisions.
2. Add test coverage to verify that if the username is updated, the cache metadata that uses the username is also updated.
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|
Issue fork drupal-3227637
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
larowlanComment #3
larowlanComment #6
larowlanAnd we're not adding the right cacheability metadata anyway.
Comment #7
mxr576LGTM
Comment #8
catchIs there a positive assertion we can make here?
Comment #9
larowlanTagging for today's sprint
Comment #11
jibranThank you @larowlan for addressing #8. I left a comment on the MR. Do you think we should address it?
Comment #12
fubarhouse commentedHey there.
Took some time to setup testing for the assertions for state of cache, so here you go :)
Comment #13
jibranThank you for updating the patch @fubarhouse.
I think we have to revisit the page between these two.
Comment #14
amber himes matzI've added a drupalGet request between the MISS and HIT checks in the NodeRevisionsUITest.php file. Patch and interdiff attached. Needs review and testing. (I was unsure how to test.)
Security question: Since a username is a user-entered value, does it need any kind of filtering before being used in a cache tag? Or does the initial username validation suffice?
Comment #15
daffie commentedRemoved the fix from the patch, to see if the added testing tests the fix.
Comment #17
daffie commentedThe fix looks good to me.
The patch without the fix fails, therefor the added test is testing the fix.
The added test also looks good to me.
For me it is RTBC.
Reuploading the full patch to be committed.
Comment #18
larowlanThis is a render array here, so the cache metadata of rendering the username is what we're passing along here, not the username.
However, this makes me think that we may not have test coverage for this metadata being handled properly, because
a) there was no caching before
b) we weren't even passing the node metadata
So I think we should check if we have test coverage that makes sure the cache metadata works properly for the user, by editing the username and making sure the content updates. If not, we should add one.
Comment #19
kristen polThanks for the feedback. Here's some more:
X-Drupal-Dynamic-Cacheis used instead ofx-drupal-dynamic-cacheFor reference, there are a few examples of explicitly checking the dynamic cache in an assert:
core/modules/layout_builder/tests/src/Functional/LayoutSectionTest.phpcore/modules/jsonapi/tests/src/Functional/ResourceTestBase.phpcore/modules/toolbar/tests/src/Functional/ToolbarCacheContextsTest.phpcore/modules/node/tests/src/Functional/NodeBlockFunctionalTest.phpcore/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.phpcore/modules/rest/tests/src/Functional/ResourceTestBase.phpComment #20
kristen polTagging for manual testing of the response headers before and after the patch. If you haven't checked headers before, here are instructions for Chrome:
https://stackoverflow.com/questions/4423061/how-can-i-view-http-headers-...
and one for Firefox:
https://www.websparrow.org/misc/how-to-view-http-headers-in-mozilla-firefox
Comment #21
amber himes matzGoing to attempt a new patch today.
Comment #22
amber himes matzLooking at this in Firefox's Network tab, with the patch from comment #17 applied and the cache cleared, I don't see a response header
x-drupal-dynamic-cachebeing set onnode/%/revisions. (I do see it onnode/%.)Can we verify that the patch to set the
x-drupal-dynamic-cacheonnode/%/revisionsis working before moving on to getting the test coverage (of which I am in progress, but am fine with someone else taking it on as I am a novice at tests)?I also updated the issue summary with steps to test and remaining tasks (please verify that I have the right idea about this).
Comment #23
larowlanComment #24
alexpottWe only need the first assertion here - no? Having both seems unnecessary.
Comment #25
yogeshmpawarAgree with @alexpott. working on it.
Comment #26
yogeshmpawarAddressed #24 & uploaded updated patch.
Comment #27
chetanbharambe commentedHi @yogeshmpawar,
Verified and tested patch #26.
Patch applied successfully but not working as expected.
Testing Steps:
# Goto: Appearance -> Apply Seven theme
# Create a node (can be published or unpublished).
# Edit the node you created.
# Open the web developer tools Inspect pane and select the Network tab.
# View the node and click on its Revisions tab.
# On the Network tab in your browser's Inspect tab, scroll up to the request for /revisions and select that row. In the adjacent tab, look under Response Headers for the `x-drupal-dynamic-cache` row and note the value (should be MISS). Refresh the page and repeat, noting the new value (should be HIT).
Expected Results:
After applying patch #26,
# User should see
x-drupal-dynamic-cache: UNCACHEABLE(currently it's not appearing) and it gets removed# User should see
x-cache: HIT# User should see
x-drupal-cache: MISSActual Results:
# Currently user is able to see
x-drupal-dynamic-cache: UNCACHEABLE# x-cache: HIT is not appearing
# x-drupal-cache: MISS is not appearing
Please refer attached screenshots for the same.
Not working as expected.
Can be a move to Needs Work.
Comment #32
abhaypai commentedCame here from Bug smash initiative. I am still able to replicate this issue on D11 and tried initial level of debugging.
Wondering why
CacheableMetadata::createFromRenderArray($username)->addCacheableDependency($node)orCacheableMetadata::createFromRenderArray($username)are not working as expected after i added this code for closing the issue by reviewing it. Checked type of argument that i am passing and also looked into multiple other ways of defining it, stillx-drupal-dynamic-cacheis not visible on response headers.Comment #33
abhaypai commentedMight need some more insights here from the community / reporter/ maintainer, thats why moving it to Needs review status.
Is it designed that way or needs fix ? note i am aware of related issue https://www.drupal.org/project/drupal/issues/3232018 and from my POV possibly we need to take some action on this issue, since it is affecting in many more pages with
x-drupal-dynamic-cache: UNCACHEABLEin response headers.Comment #34
smustgrave commentedComment #35
catchThis doesn't need more info, the bug has been reproduced in #32 and also previously, it does need a conversion to an MR though at least.
Comment #36
akhil babuBy default,
x-drupal-dynamic-cacheheader is present when revision oveview page is accessed and its value is 'UNCACHEABLE'.Now, If revision overview page is accessed after applying patch #17,
x-drupal-dynamic-cacheheader wont be there. This is due tho the following condition in onResponse() method of DynamicPageCacheSubscriber.$this->responsePolicy->check()calls several policy rules andDrupal\dynamic_page_cache\PageCache\ResponsePolicy\DenyAdminRoutesrule returns 'deny' for revison overview route as its an admin route. (_admin_route: true). Sox-drupal-dynamic-cacheheader is omitted.Comment #38
acbramley commentedRebased against 11.x, removed the
->addCacheableDependency($node)addition (out of scope), and moved testing to a dedicated test case since we need to disable the use_admin_theme setting.Comment #39
smustgrave commentedShows test coverage
Change seems straight forward and gone through a number of reviews. Believe feedback has been addressed.
Comment #40
alexpottCommitted and pushed 44f56c2c1b8 to 11.x and 371936c49aa to 11.1.x. Thanks!
Comment #44
donquixote commentedWe have a failing test in 11.1.x:
Drupal\Tests\node\Functional\NodeRevisionsAllTest::testRevisions()
git bisect reveals the failure was introduced here.
For some reason, it was not failing in the MR when it was merged.
Comment #45
donquixote commentedIf a page was not cached before, it is not enough to just fix the reasons why it was not cached.
We also need to check that all the cache metadata added to the page is complete!
I suspect we have missing cache tags..
Comment #46
donquixote commentedTo be analysed in #3525111: NodeRevisionsAllTest::testRevisions() fails in 11.1.x
Comment #47
catchThe fix for the regression in 11.1.x isn't straightforward and it's been in 11.1.x for over a month, it's also not clear why this is passing on 11.x, given both sides of that are confusing, I'm going to go ahead and revert here. There is the start of a fix in #3525111: NodeRevisionsAllTest::testRevisions() fails in 11.1.x.
Comment #49
acbramley commentedIt looks like the difference is to do with some other unrelated cache metadata making this cacheable on 11.1.x when it's not on 11.x (in the context of NodeRevisionsAllTest)
11.x cache headers:
11.1.x cache headers:
Comment #50
acbramley commentedHere's the crucial difference:
11.x has
node.settings:use_admin_themedefaulting to true https://git.drupalcode.org/project/drupal/-/blob/11.x/core/modules/node/...11.1.x has it defaulting to false https://git.drupalcode.org/project/drupal/-/blob/11.1.x/core/modules/nod...
DPC has a response policy to blanket deny on admin pages via
Drupal\dynamic_page_cache\PageCache\ResponsePolicy\DenyAdminRoutesThis default setting was changed in #3347015: Consider using the administration theme when editing or creating content by default, does that just need to be committed to 11.1.x?
Comment #51
donquixote commentedI would say this is a significant functional change that we would not want to have in 11.1.x.
If all we care is to make a test work, we can change that setting in the test itself.
Or rather, we change that setting in the test to use the front-end theme instead of the back-end theme, if we are interested in the cache behavior.
Then, the more important question is:
Can and should we cache a revision list?
If so, which cache tags make sure that this cache is invalidated when revisions other than the published revision are added, modified or deleted? I don't think such a cache tag exists currently.
Also, what is the use case where caching the revision list actually makes sense? That is, where the revision list is visited significantly more often than it is changed? (I suppose working with revisions in workflow)
Comment #52
acbramley commentedI think in a real world scenario we'd almost never hit the bug that this test is hitting. To reproduce it via a browser I had to:
1. Disable the admin theme setting for node.settings
2. Disable the breadcrumb and primary tab blocks (these add the node cache tag)
Without doing this the node cache tag gets added to the page and when editing a node via the UI to create a new revision, the revisions page is purged.
I think it's perfectly reasonable to have a cacheable revisions page, especially since the reason why it isn't is because of a bugged call to
addCacheableDependency(which I thought we deprecated in #3232018: Trigger a deprecation when using \Drupal\Core\Cache\RefinableCacheableDependencyTrait::addCacheableDependency with a non CacheableDependencyInterface object but that didn't cover the renderer...)Comment #53
catchI don't think we differentiate between default and non-default revisions when invalidating cache tags so it should actually be invalidated correctly. Didn't test this but couldn't find any differentiation.
Whether that's in itself a caching bug (or lack of feature) is a different question though. We could add an explicit test that it gets invalidated, and then if we stop invalidating tags for non-default revisions it would cover that case.
Comment #54
godotislateOne thing I noticed from MR 12158 for #3525111: NodeRevisionsAllTest::testRevisions() fails in 11.1.x is this:
Drupal\Core\Entity\ContentEntityStorageBase::deleteRevision()does not invalidate cache tags, afaict. Looks like all the cache tag invalidation for entity CRUD operations occurs in the entity class and not the storage class, anddelete()is never called on the loaded revision indeleteRevision(). I'm not sure whether this is a bug or as intended.Comment #55
donquixote commentedThe question is which cache tags should be invalidated here.
I suspect we would have to invent a new cache tag that does not exist yet.
(here is what I remember, please correct me)
There is one cache tag for the published / active version of an entity.
This gets invalidated when that published / active version is updated.
There are good reasons why we would not want to invalidate this when other revisions are deleted or updated, because we want the published display to be served from cache as often as possible.
Instead, we could introduce a cache tag for "revision list for entity ", which gets invalidated when any revision of that entity is deleted, created or updated, or there is a workflow change.
We would have to properly test this.
And we need to determine if we really want to fill the cache with rendered entity revisions.
It could be really nasty on storage, for possibly little benefit.
Comment #56
acbramley commentedWe might as well wait until #2334319: {% trans %} does not support render array and MarkupInterface valued expressions is merged before fixing up the conflicts here too since that changes the rendering of $username as well.
Comment #58
oily commentedFix merge conflict MR !1111.
Comment #59
berdirThis adds back code that was removed. Instaed, the @todo above needs to be dealt with now.
In combination with #2334319: {% trans %} does not support render array and MarkupInterface valued expressions, is likely enough to just remove that.