Problem

  • drupal_rebuild() rebuilds + compiles the service container twice, since drupal_flush_all_caches() rebuilds it, too.

Unclear

  • Why does drupal_flush_all_caches() rebuild the container?
  • The only legit use-case for calling drupal_flush_all_caches() was the "Clear all caches" button on the Performance page previously — but today, dfac() is called from a dozen of places :(
  • Is hook_rebuild() still necessary? — If it is, why do some implementations clear their caches in there instead of using hook_cache_flush()?

Release note snippet

Full cache rebuilds have been significantly optimised, making drush cr and similar commands up to 30-40% faster.

CommentFileSizeAuthor
#61 2160091-61.patch15.81 KBalexpott
#61 60-61-interdiff.txt8.37 KBalexpott
#60 2160091-60.patch15.29 KBalexpott
#60 57-60-interdiff.txt1.34 KBalexpott
#57 2160091-58.patch15.12 KBalexpott
#57 55-58-interdiff.txt7.69 KBalexpott
#55 2160091-55.patch17.98 KBalexpott
#55 51-55-interdiff.txt10.57 KBalexpott
#51 2160091-52.patch15.65 KBdawehner
#51 interdiff-2160091-52.txt1.24 KBdawehner
#50 2160091-50.patch14.57 KBalexpott
#50 48-50-interdiff.txt2.96 KBalexpott
#48 2160091-48.patch13.04 KBalexpott
#48 47-48-interdiff.txt3.71 KBalexpott
#47 2160091-47.patch10.89 KBdawehner
#47 interdiff-2160091-47.txt793 bytesdawehner
#43 2160091-42.patch10.21 KBalexpott
#43 41-42-interdiff.txt2.01 KBalexpott
#41 2160091-41.patch9.91 KBalexpott
#41 40-41-interdiff.txt7.31 KBalexpott
#40 interdiff-2160091-40.txt728 bytesdawehner
#40 2160091-40.patch3.03 KBdawehner
#36 2160091-36.patch2.88 KBalexpott
#2 drupal8.rebuild-vs-cache.2.patch9.81 KBsun
#13 2160091-13.patch2.94 KBdamiankloip
#23 interdiff.txt439 bytesswentel
#19 2160091-19.patch3.96 KBdamiankloip
#23 2160091-23.patch3.9 KBswentel
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

moshe weitzman’s picture

Priority: Normal » Major

We need clarity on the appropriate uses of these cache rebuild functions.

sun’s picture

Assigned: Unassigned » sun
Status: Active » Needs review
FileSize
9.81 KB

Perhaps, let's first see how much stuff is going to break with the most blatant approach ;)

Contains snippets of #2159459: Rebuild script triggers errors in error handler, fails to rebuild container

Status: Needs review » Needs work

The last submitted patch, 2: drupal8.rebuild-vs-cache.2.patch, failed testing.

sun’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 2: drupal8.rebuild-vs-cache.2.patch, failed testing.

YesCT’s picture

Assigned: sun » Unassigned
charginghawk’s picture

Reviewing this as part of #2474049: [meta] Major issue triage. The issue still exists, drupal_rebuild rebuilds the container and then calls drupal_flush_all_caches which also rebuilds the container.

However, I'm recommending that release managers downgrade this to "Normal". While it touches a nerve with respect to various functions (including hook_cache_flush and hook_rebuild), it doesn't trigger a PHP error or cause a failure.

There is a need for clarity on how cache flushes and container rebuilds should interact. However, the current situation where both drupal_rebuild and drupal_flush_all_caches do both, and the latter does it a little more cleanly, fits better with current use cases where drupal_flush_all_caches is more prevalent:

https://api.drupal.org/api/drupal/core!includes!common.inc/function/call...
https://api.drupal.org/api/drupal/core!includes!utility.inc/function/cal...

timmillwood’s picture

It looks as though a lot of the code in this patch has already bade it's way into 8.0.x so I'm not sure how relevant some of it is. I think it needs a good review by someone closer to the issue.

iMiksu’s picture

Assigned: Unassigned » iMiksu

Rerolling

iMiksu’s picture

Assigned: iMiksu » Unassigned

Hmm yeah. This rerolling needs bit of insight on caching. It would be helpful to provide some steps how to test the #2 patch against HEAD?

damiankloip’s picture

This is definitely still an issue. Unfortunately, with the current code in HEAD. This is harder to fix that it was in sun's patch in #2.

damiankloip’s picture

New patch, the only way we can really do this at this point, I think, is to just move more of this code into dfac(). Otherwise we have no real way to avoid the container rebuild happening twice. It just means dfac() does slightly more work, but is also a safer method. Really this happens via drupal_rebuild() anyway.

damiankloip’s picture

Status: Needs work » Needs review

The way things currently stand we also iterate over and call deleteAll() on all cache bins twice too.

Alternative is to move the container invalidation stuff into drupal_rebuild() and not have any of that in dfac(). And if people want the full/heavier option - they call drupal_rebuild() instead.

dawehner’s picture

This improves rebuild performance, which is really great! It has a good impact in developer experience, just fixing that is already nice!

Status: Needs review » Needs work

The last submitted patch, 13: 2160091-13.patch, failed testing.

The last submitted patch, 13: 2160091-13.patch, failed testing.

joelpittet’s picture

+++ b/core/includes/common.inc
@@ -1162,6 +1163,23 @@ function element_info_property($type, $property_name, $default = NULL) {
+  // Force kernel to rebuild php cache.
+  PhpStorageFactory::get('twig')->deleteAll();

@@ -1179,9 +1197,6 @@ function drupal_flush_all_caches() {
   // Wipe the Twig PHP Storage cache.
   PhpStorageFactory::get('twig')->deleteAll();

dup call needed?

damiankloip’s picture

Status: Needs work » Needs review
FileSize
3.96 KB

Rerolled, and no, I don't think there is reason for two. Just an artefact of moving code...I hope.

Status: Needs review » Needs work

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

xjm’s picture

Thanks @charginghawk for help triaging this issue! Adding issue credit for the triage. Thanks also @damiankloip for the updated patch.

Given the potential performance implications, I think it makes sense to keep this issue marked as major.

xjm’s picture

swentel’s picture

Status: Needs work » Needs review
FileSize
3.9 KB
439 bytes

removed two lines which made one test at least pass locally (EditorUpdateTest), not sure about the rest, go bot!

damiankloip’s picture

Thanks swentel. I forgot about this one. Yes, seems we probably don't need to be doing that. I think I remember trying to boot() etc... after invalidate for the \Drupal::service('page_cache_kill_switch') call afterwards.

Fabianx’s picture

I am not 100% sure we can do that.

if hook_cache_flush fails due to a problem that needs caches cleared first, then you are locked in.

Maybe we need an aggressive_rebuild() function instead?

---

+++ b/core/includes/common.inc
@@ -1080,11 +1082,25 @@ function element_info_property($type, $property_name, $default = NULL) {
+  // Invalidate the container.
+  // Bootstrap up to where caches exist and clear them.
...
+  // Invalidate the container.

This comment is doubled here.

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.

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

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

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

alexpott’s picture

I think this is just a normal priority because drupal_rebuild() is only called from rebuild.php and it not at all part of the normal running of the site. Double building of the container is unfortunate but I'm not sure even qualifies this as a bug.

Regarding the rest of the issue summary:

  • At this point I don;'t think we can stop drupal_flush_all_caches() from rebuilding the container now - this would be a very difficult behaviour change for contrib to respond too
  • Re usages of drupal_flush_all_caches() in core - it is either update.php, the performance page or tests. Yes it would be great to refactor the tests to not do this but then we'd need a greater separator between runner and system-under-test environments - that I've argued for for a long time but people don't want to do because of performance. The runtime usages look acceptable to me.
  • Re hook_rebuild - yes we should open a separate issue to deprecate that and refactor block_rebuild() to something sensible - I think there might be issues about that already.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.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.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

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

alexpott’s picture

Category: Bug report » Task
Issue tags: +Needs tests

We should definitely add test coverage of rebuild.php too. I don't think this is a bug it's just a task. Nothing is actually broken and the drupal_rebuild() is not on the critical path.

+++ b/core/includes/common.inc
@@ -1080,11 +1082,25 @@ function element_info_property($type, $property_name, $default = NULL) {
+  // Disable recording of cached pages.
+  \Drupal::service('page_cache_kill_switch')->trigger();

I think this should only really be part of the drupal_rebuild() there's no need for it to go here. It can/should be after the dfac().

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.6 was released on August 1, 2018 and is the final bugfix release for the Drupal 8.5.x series. Drupal 8.5.x will not receive any further development aside from security fixes. Sites should prepare to update to 8.6.0 on September 5, 2018. (Drupal 8.6.0-rc1 is available for testing.)

Bug reports should be targeted against the 8.6.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.7.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.6.x-dev » 8.8.x-dev

Drupal 8.6.x will not receive any further development aside from security fixes. Bug reports should be targeted against the 8.8.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.9.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.7 was released on June 3, 2020 and is the final full bugfix release for the Drupal 8.8.x series. Drupal 8.8.x will not receive any further development aside from security fixes. Sites should prepare to update to Drupal 8.9.0 or Drupal 9.0.0 for ongoing support.

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

alexpott’s picture

Version: 8.9.x-dev » 9.1.x-dev
Category: Task » Bug report
FileSize
2.88 KB

This is causing https://github.com/drush-ops/drush/issues/4661 so switching this back to be a bug. I also wonder if this is causing #2985199: Extensions in Multisite Directories Not Registered When Rebuilding Cache

I've tested this with drush and it works perfectly when moving a module.

alexpott’s picture

Issue tags: -Needs tests

rebuild.php has a test - see \Drupal\Tests\system\Functional\UpdateSystem\RebuildScriptTest - it got added in #2575267: Add test coverage for rebuild.php

moshe weitzman’s picture

Thanks. I think we need to add an optional $request param to drupal_flush_all_caches() because Drush no longer sets the globals needed for createFromGlobals() to work.

Status: Needs review » Needs work

The last submitted patch, 36: 2160091-36.patch, failed testing. View results

dawehner’s picture

Status: Needs work » Needs review
FileSize
3.03 KB
728 bytes

I was looking at the failures, while reading your comment @moshe weitzman.

LogicException: Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead.
Drupal\Core\Render\Renderer->doRender()() (Line: 244)

As part of update.php we call drupal_flush_all_caches() right after running normal updates, and before all the post updates are triggered.
This is called, as part of the update controller. In there the previous patch initializes a new request.

The render system itself uses static::$contextCollection keyed by request to track context throughout the rendering process.
As version #36 of drupal_flush_all_caches() creates a completly new request is let's $contextCollection to not longer find the right render context and stops.

The first possible solution to this problem is what @mosh mentioned in #38: Don't create a new request by passing in the request. This works fine for the update system, but I'm wondering, whether it might break drupal_flush_all_caches() in other places.

Another possible solution could be to properly undertand what's happening. drupal_flush_all_caches() initializes the render system as part of \Drupal::service('twig')->invalidate(), which means it has a reference to the request stack of the second created request.
Later in update.php we then render the page using $this->bareHtmlPageRenderer which has a still a reference to the old renderer and old request stack, while the twig subsystem, which itself calls to the renderer, has the new reference.
Lazily initialising $this->bareHtmlPageRenderer solves the problem:


@@ -192,7 +193,7 @@ public function handle($op, Request $request) {
     }
     $title = isset($output['#title']) ? $output['#title'] : $this->t('Drupal database update');
 
-    return $this->bareHtmlPageRenderer->renderBarePage($output, $title, 'maintenance_page', $regions);
+    return \Drupal::service('bare_html_page_renderer')->renderBarePage($output, $title, 'maintenance_page', $regions);
   }
 

Again this solution has the same problem as before, it just fixes update.php, but not potential other usages of the same method.

A third proposal is to use the $request from \Drupal::request(), if available. This way we just create a new request, if we have not initialized the container before. Maybe this could be combined with proposal one as well.

alexpott’s picture

Thanks @dawehner I think preserving the current request - if available makes a lot of sense. Nice sleuthing.

Here's a test to confirm that we are limiting the amount of rebuilds. This is might even make test runs quicker!

andypost’s picture

alexpott’s picture

Seems some tests are still failing. I think we need to use the current kernel if it is available.

The last submitted patch, 40: 2160091-40.patch, failed testing. View results

The last submitted patch, 41: 2160091-41.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 43: 2160091-42.patch, failed testing. View results

dawehner’s picture

The new `drupal_flush_all_caches()` rebuilds the container, so any reference to a service, like the theme manager, is now not longer the same one as before.

alexpott’s picture

Status: Needs work » Needs review
FileSize
3.71 KB
13.04 KB

$this>service in functional tests is always a dangerous this to do - we should remove it from this test and call the test rebuild so everything is correctly on the test too.

alexpott’s picture

For containers and tests see #2066993: Use \Drupal consistently in tests

alexpott’s picture

Let's test hacking in a module via editing core.extension. drupal_flush_all_caches is meant to deal with this.

dawehner’s picture

Posting a small documentation improvement.

The last submitted patch, 48: 2160091-48.patch, failed testing. View results

The last submitted patch, 50: 2160091-50.patch, failed testing. View results

Status: Needs review » Needs work

The last submitted patch, 51: 2160091-52.patch, failed testing. View results

alexpott’s picture

Status: Needs work » Needs review
FileSize
10.57 KB
17.98 KB

Here's a fix for the test and a change that means we don't need to "fix" any tests. So less behaviour change.

joachim’s picture

alexpott’s picture

I've realised that the entire call to \Drupal\Core\DrupalKernel::updateModules() in drupal_flush_all_caches() is obsolete. So we can completely remove it rather than check if it is going to change anything.

Also we can have less complexity if we pass in a DrupalKernel to drupal_flush_all_caches() - if the caller chooses to do this then it is on them to rebuild the container. This allows drupal_rebuild() to minimise container rebuilds.

alexpott’s picture

Here's a drush cache rebuild on minimal showing quite a large performance boost due to one less container rebuild....

https://blackfire.io/profiles/compare/fae29143-3152-4ce1-8b32-40b677af57...

27% quicker - 50% less queries - 94,000 less calls to preg_match() :D

Status: Needs review » Needs work

The last submitted patch, 57: 2160091-58.patch, failed testing. View results

alexpott’s picture

Status: Needs work » Needs review
FileSize
1.34 KB
15.29 KB

Ah of course drupal_flush_all_caches() is used as a batch operation. So sometime the first argument will be an array.

alexpott’s picture

+++ b/core/includes/common.inc
@@ -531,43 +544,26 @@ function drupal_flush_all_caches() {
 
-  // Ensure that all modules that are currently supposed to be enabled are
-  // actually loaded.
-  $module_handler->loadAll();
+  // Rebuild module data that is stored in state.
+  \Drupal::service('extension.list.module')->reset();
 
   // Rebuild all information based on new module data.
-  $module_handler->invokeAll('rebuild');
+  \Drupal::moduleHandler()->invokeAll('rebuild');

I've added some test coverage that proves removing $module_handler->loadAll(); is okay. Essentially it is irrelevant and going to be done twice because when we call \Drupal::moduleHandler()->invokeAll('rebuild'); it'll be done again because we've cleared the caches - see \Drupal\Core\Extension\ModuleHandler::buildHookInfo().

Also I added empty_module_test when I was trying to test this with a kernel test and it could reload code twice in the same request because in PHP you can't unload code. Given we're now using a functional test we can use a regular module and test that the code is loaded as expected.

moshe weitzman’s picture

Status: Needs review » Reviewed & tested by the community

Code looks good, and new tests are most welcome. As an aside, this will speed up drush cache:rebuild as soon as people upgrade their Drupal - no Drush upgrade required.

The last submitted patch, 47: 2160091-47.patch, failed testing. View results

sun’s picture

People, you are awesome. The comments on this issue are amazing. I miss you all is what I'd love to say but that wouldn't be appropriate for an issue comment.

The last submitted patch, 47: 2160091-47.patch, failed testing. View results

  • catch committed 6e84b57 on 9.2.x
    Issue #2160091 by alexpott, dawehner, damiankloip, swentel, sun, moshe...
catch’s picture

Status: Reviewed & tested by the community » Patch (to be ported)

OK this looks really good to me.

We have a patch release scheduled next week, and I'd prefer to give this issue slightly longer than that in the release branch before it goes into a tagged release, so for now just committing to 9.2.x and marking 'to be ported' to 9.1.x, which we can do once the next patch release is out. Since this is likely to fix multiple major bugs in the queue, as well as the performance improvement, I think it's fine to add the optional param in a patch release.

Committed 6e84b57 and pushed to 9.2.x. Thanks!

Wim Leers’s picture

🤩

I forgot this issue even existed! Epic work 👏

Code looks good, and new tests are most welcome. As an aside, this will speed up drush cache:rebuild as soon as people upgrade their Drupal - no Drush upgrade required.

And perhaps stating the obvious but worth stressing: faster Drupal deployments in general, for both custom/manual deployment scripts and drush deploy.

I think this is worth a release note? 🤓

catch’s picture

Issue summary: View changes
Issue tags: +9.2.0 release notes

Yep let's add a release note.

catch’s picture

More of a highlight.

neclimdul’s picture

Oh wow! Exciting!

Miss you to sun! Finding more issues created by you getting closed recently and consider that a sign Drupal 8/9/10 is getting more mature and hard problems are getting solved. :-D

Would have been nice but not sure if this is worth porting at this point since 9.2 is right around the corner. Patch seems to apply cleanly though if maintainers want to commit it.

catch’s picture

Version: 9.1.x-dev » 9.2.x-dev
Status: Patch (to be ported) » Fixed

Yep, moving this to fixed against 9.2.x

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

podarok’s picture

Hi
This change breaks core and contribs in different places
https://www.drupal.org/project/drupal/issues/3222577#comment-14163851

Appreciate any help