Problem/Motivation

After #2579847: /node/add is lacking cacheability metadata, causes problems when cached by Dynamic Page Cache and "Use admin theme when editing or creating content" is turned off I have no choice but to file this as a critical. Despite all warnings, we went ahead with default block cache and smart cache. We can not release Drupal 8 like this because everything will break in really mysterious ways.

Proposed resolution

As per dawehner in #2579847-33: /node/add is lacking cacheability metadata, causes problems when cached by Dynamic Page Cache and "Use admin theme when editing or creating content" is turned off require #cache on every controller which returns a render array.

Remaining tasks

User interface changes

API changes

Data model changes

CommentFileSizeAuthor
#58 interdiff.txt4.95 KBdawehner
#58 2579941-58.patch26.49 KBdawehner
#55 interdiff.txt8.59 KBdawehner
#55 2579941-55.patch22.17 KBdawehner
#49 interdiff.txt2.03 KBWim Leers
#49 2579941-48.patch14.3 KBWim Leers
#35 2579941-35.patch12.35 KBWim Leers
#32 interdiff.txt4.91 KBWim Leers
#32 2579941-32-do-not-test.patch12.35 KBWim Leers
#32 2579941-32.patch15.18 KBWim Leers
#28 interdiff.txt4.51 KBWim Leers
#28 2579941-26-do-not-test.patch8.75 KBWim Leers
#28 2579941-26.patch11.58 KBWim Leers
#2 2579941_2.patch660 byteschx
#4 2579941_3.patch956 bytesWim Leers
#4 interdiff.txt1.42 KBWim Leers
#5 2579941_5.patch3.63 KBchx
#11 2579941-11.patch6.33 KBWim Leers
#11 2579941-11-do-not-test.patch3.49 KBWim Leers
#11 interdiff.txt4.5 KBWim Leers
#19 2579941-19.patch9.98 KBWim Leers
#19 2579941-19-do-not-test.patch7.14 KBWim Leers
#19 interdiff.txt4.74 KBWim Leers
#25 2579941-25.patch10.92 KBWim Leers
#25 2579941-25-do-not-test.patch8.08 KBWim Leers
#25 interdiff.txt1000 bytesWim Leers
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

chx created an issue. See original summary.

chx’s picture

Status: Active » Needs review
FileSize
660 bytes
dawehner’s picture

Let's see how many fails we have

Wim Leers’s picture

FileSize
956 bytes
1.42 KB

#2 will come back at least mostly green. It's in the wrong place.

chx’s picture

Wim's patch fails at node/add (expected after the parent critical) so here's the two rolled together to see how many we have after node/add is fixed.

Wim Leers’s picture

I think it's a great idea to nudge/force people to think about cacheability, always.

OTOH, this approach means that even on _admin_route: TRUE routes, no_cache: TRUE routes and POST requests, where Dynamic Page Cache doesn't do anything, you'll still be forced to add cacheability metadata. Personally, I think that's great, but that means we'll be forcing people to think about cacheability metadata even in cases where it doesn't make sense to do caching.

Fabianx’s picture

+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -223,6 +223,9 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
+          if (!isset($main_content['#cache'])) {
+            throw new \LogicException('Must have cacheability metadata.');
+          }

Do we allow to return an empty array?

I assume yes, just asking.

Fabianx’s picture

#6:

Can we check the $request->data('smart_cache_cacheable') attribute, e.g. the request policies?

The last submitted patch, 4: 2579941_3.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 5: 2579941_5.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
6.33 KB
3.49 KB
4.5 KB

This moves the exception to Dynamic Page Cache, so it is only thrown when Dynamic Page Cache actually may cache the response. This means all admin routes and no_cache routes will work as expected again.

(The do-not-test patch is the actual patch, the other one includes #2579847: /node/add is lacking cacheability metadata, causes problems when cached by Dynamic Page Cache and "Use admin theme when editing or creating content" is turned off, just like #5.)

moshe weitzman’s picture

Looks good. Lets have the exception also link to the relevant handbook or API page.

The last submitted patch, 4: 2579941_3.patch, failed testing.

The last submitted patch, 5: 2579941_5.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 11: 2579941-11.patch, failed testing.

Fabianx’s picture

Overall I am +1 to making this explicit; however some caveats:

We decided in the smart cache / dynamic_page_cache policy issue that we wanted to make this enabled by default for everything for now and just fix critical core fails with a simple no_cache: TRUE.

It is quite astonishing that we only found one real critical problem so far - which is also a real edge case as that should have been marked no_cache: TRUE already since a while if there were not this strange dynamic adding of admin_route.

So lets not get into a panic situation, where all the world is blowing up. We knew there would be some criticals following from smart cache / dynamic_page_cache and we knew we could simply mark the routes as uncacheable and be done.


I think throwing an Exception is a good developer tool, however it also means a HUGE BC break as a developer now will have to decide:

- opt-in to smart-cache
- opt-out of smart-cache

explicitly.

It means that whoever is returning a render array will either opt-in to dynamic_page_cache or opt-out of dynamic_page_cache or needs to disable dynamic_page_cache locally.

So the previous version of not doing anything is no longer allowed and maybe that is a good thing.

Berdir told me anecdotically that they have not yet enabled smart_cache for the existing production sites, which limits the BC break for existing sites to contrib authors.


I like the idea of checking for #cache, it is what I would have originally wanted to see happen - but did not know how.


To the patch itself:

#11: The event view subscriber should mainly set a flag on the request that the render array was not having '#cache', then the check should be done in smart cache itself after the real response policy check.

The fake response policy check will not work and again lead to false positives.

Wim Leers’s picture

Status: Needs work » Needs review

Per #2556889: [policy, no patch] Decide if SmartCache is still in scope for 8.0 and whether remaining risks require additional mitigation:

The "delay release" risk can be mitigated by adding the "no_cache: TRUE" option to any route whose controller's cacheability metadata is found to be incorrect.

This issue is then about

  1. warning the developer that a certain controller is missing cacheability metadata — this hugely helps with pushing contrib in the right direction
  2. adding no_cache: TRUE to all routes in core that aren't yet providing correct cacheability metadata; core can gradually add the necessary cacheability metadata to those routes over time, so that by e.g. Drupal 8.1.0 or 8.2.0, all of the core routes that can be cached, also are, and provide the necessary cacheability metadata.

Working on doing point 2. Some of the test controllers that are super trivial to fix, I will just fix.

Wim Leers’s picture

Heh, I cross-posted with Fabianx. Similar thoughts.

The fake response policy check will not work and again lead to false positives.

Without that, we will still end up complaining on _admin_route: TRUE and no_cache: TRUE routes, because those are in response policies. Yes, we should actually have "route policies" too, besides just request & response policies.

Wim Leers’s picture

Working on doing point 2. Some of the test controllers that are super trivial to fix, I will just fix.

Here's a first (small) round. It's getting late here, and it's been a long day. I will continue in the morning.

Fabianx’s picture

Status: Needs review » Needs work

#18: Yes, but what I mean is change the code to do:

+++ b/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php
@@ -149,6 +152,44 @@ public function onRouteMatch(GetResponseEvent $event) {
+  public function onViewRenderArray(GetResponseForControllerResultEvent $event) {
+    $request = $event->getRequest();
+    $result = $event->getControllerResult();
...
+    // Check if MainContentViewSubscriber would render this render array, i.e.
+    // if this render array will in fact be converted into a response.
+    if (is_array($result) && ($request->query->has(MainContentViewSubscriber::WRAPPER_FORMAT) || $request->getRequestFormat() == 'html')) {
+      // If #cache is not set, complain.

Just here do:

   // If #cache is not set, complain later.
  $request->attributes->set(self::ATTRIBUTE_CONTROLLER_RESPONSE_HAS_CACHE, isset($result['#cache']);

Then when actually having passed the Response and Request policies in DPC onResponse listener do:

  if ($request->attributes->has(self::ATTRIBUTE_CONTROLLER_RESPONSE_HAS_CACHE) && $request->attributes->get(self::ATTRIBUTE_CONTROLLER_RESPONSE_HAS_CACHE) === FALSE) {
    throw new Exception('...');
}

Just when we are about to cache the response we can decide properly on the data that the controller provided no cacheability metadata.

catch’s picture

No exception please. trigger_error() should do.

chx’s picture

Actually isn't this what asserts are made for? Smells like one, isn't it?

Edit:

assert(isset($result['#cache']), 'The controller returned a render array without cacheability metadata. See https://www.drupal.org/developing/api/8/render/arrays/cacheability.');

The last submitted patch, 19: 2579941-19.patch, failed testing.

Fabianx’s picture

I am fine with assert() or trigger_error() also makes the BC break less.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
1000 bytes
8.08 KB
10.92 KB

@catch Which should it be: trigger_error() or assert()? I'll leave the choice to you.


This updates the 4XX controller's render arrays. This should also significantly reduce the number of failures.

The last submitted patch, 11: 2579941-11.patch, failed testing.

The last submitted patch, 19: 2579941-19.patch, failed testing.

Wim Leers’s picture

Implemented #20, which is indeed much better.

dawehner’s picture

Ah this is much better

Fabianx’s picture

Just some nits:

+++ b/core/modules/dynamic_page_cache/src/EventSubscriber/DynamicPageCacheSubscriber.php
@@ -241,6 +232,13 @@ public function onResponse(FilterResponseEvent $event) {
+    if ($request->attributes->get(self::ATTRIBUTE_CONTROLLER_RESULT_RENDER_ARRAY_HAS_CACHEABILITY) === FALSE) {

I think adding the ->has() check is better / cleaner as a controller can just return a CacheableResponse and then this is NULL.

We also use ->has() for some other attributes - in the rest of the code.

The last submitted patch, 25: 2579941-25.patch, failed testing.

Wim Leers’s picture

FileSize
15.18 KB
12.35 KB
4.91 KB

Let's provide more useful information to developers. And let's add test coverage.

For now, I started using trigger_error(). Easy enough to change.

The last submitted patch, 28: 2579941-26.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 32: 2579941-32.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
12.35 KB

The last submitted patch, 25: 2579941-25.patch, failed testing.

dawehner’s picture

+++ b/core/modules/dynamic_page_cache/src/Tests/DynamicPageCacheIntegrationTest.php
@@ -127,6 +127,12 @@ public function testDynamicPageCache() {
+    $this->assertResponse(200);
+    $this->assertRaw("The controller for the route 'dynamic_page_cache_test.html_without_cacheability' returned a render array without cacheability metadata. See https://www.drupal.org/developing/api/8/render/arrays/cacheability.");
   }

Are you sure assertRaw is the right way to check that? Don't we have a error handler which detects those errors on test time?

Wim Leers’s picture

Title: Require #cache on every controller which returns a render array » Let Dynamic Page Cache warn developers when a controller's render array does not have #cache set

More accurate title.

Wim Leers’s picture

#37: I'm certain this is not 100% correct :) That test still fails because that error is triggered; I don't know how to test properly. I know I've fought this in the past too, and there are at most a handful of WebTestBase tests dealing with this. Pointers (or reroll) welcome.

The last submitted patch, 28: 2579941-26.patch, failed testing.

The last submitted patch, 32: 2579941-32.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 35: 2579941-35.patch, failed testing.

The last submitted patch, 35: 2579941-35.patch, failed testing.

pwolanin’s picture

Does this really meet the definition of critical (release blocking)?

chx’s picture

I absolutely have no idea any more. If we were applying criteria I am familiar with we would have never gotten here: you'd would've never committed so much code that made Drupal slow (when did you see the performance gate stopping any core initiatives?) and then you would never have added such a significant change this extremely late to mitigate the performance catastrophe you (you, this time I won't say we, I refuse to take even partiial responsibility) brought on yourselves. As far as I am concerned, yes, this is critical because if core breaks something as often visited as node/add then what hope contrib / custom has for writing bug free code? None, I believe. But, this is Drupal 8 and what I see and what others see fundamentally differs. So: I do not know.

dawehner’s picture

Priority: Critical » Major
Issue tags: +Security improvements

Let's try to be objective. This issue certainly hardens security by making it harder for people to specify non cacheablity information at all.
On the other hand this issue also doesn't solve any actual existing critical security issues, given which places had to be covered. I kinda assume that giving people
more information about filter formats isn't a security issue given that we check access to them on forms.

chx’s picture

Unfollowed.

Fabianx’s picture

If we can get this in as major, sure fine with me ...

But it is a little big behavior change.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
14.3 KB
2.03 KB

This will fix another whole bunch.

catch’s picture

It should be E_DEPRECATED then it can go in a minor I think.

We don't know that routes without #cache have actual cacheability problems, they might be fine. Also ones that have it might not be.

So it is not an error to not have it, we just prefer you to have it or set no_cache. Deprecation seems about right.

Status: Needs review » Needs work

The last submitted patch, 49: 2579941-48.patch, failed testing.

Wim Leers’s picture

We don't know that routes without #cache have actual cacheability problems, they might be fine. Also ones that have it might not be.

Exactly! This is what I'd been pondering yesterday night too: it's not a perfect signal, and so we shouldn't actually hard force this. It should only be a nudge towards developers.

Do you think this can go in a future RC release? AFAICT it could.

catch’s picture

If we think it is actual security hardening, then it could probably go in during RC - the patch itself looks low risk.

Whether it's actually helping anything to have empty #cache in controllers to suppress whatever error we add (like the patch adds) is a different question though.

The last submitted patch, 49: 2579941-48.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
22.17 KB
8.59 KB

This is hopefully everything.

dawehner’s picture

Status: Needs review » Needs work

The last submitted patch, 55: 2579941-55.patch, failed testing.

dawehner’s picture

Studiographene’s picture

Status: Needs work » Needs review

The last submitted patch, 55: 2579941-55.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 58: 2579941-58.patch, failed testing.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Wim Leers’s picture

Status: Needs work » Closed (cannot reproduce)

More than a year after this was filed, the fear has fortunately not become reality. I think it's safe to close this.

Not sure what the best status is.