Problem/Motivation

In #3492453: Memory leak in DrupalKernel when installing modules, we fixed a massive memory leak for container rebuilds, but only for the installer, because it broke tons of tests.

#2066993: Use magic methods to sync container property to \Drupal::getContainer in functional tests is now actually fixed, so I think we could move this up and see what still breaks.

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3575642

Command icon 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

berdir created an issue. See original summary.

berdir’s picture

Status: Active » Needs review

Pushed the change to an MR.

I did stumble into this again because we're running into issues when our current large update, which installs and uninstalls several modules through update functions, gets killed due to excessive memory. In local testing in ddev without memory limit, I observed up to 666MB (yes, exactly, really). With this change, it did grow to 250-300MB but then suddenly dropped again to < 200MB and never went above 300.

berdir’s picture

Status: Needs review » Needs work

Tests are still failing pretty hard, to be investigated.

catch’s picture

Quite a lot of these:

  ├ Symfony\Component\DependencyInjection\Exception\RuntimeException: You have requested a synthetic service ("keyvalue"). The DIC does not know how to construct this service.
     ├ Exception: Symfony\Component\DependencyInjection\Exception\RuntimeException: You have requested a synthetic service ("kernel"). The DIC does not know how to construct this service.
       ├ Symfony\Component\DependencyInjection\ContainerBuilder->createService()() (Line: 1099)
berdir’s picture

Yes, one problem is the uninstallValidators in ModuleHandler. That isn't being reattached in \Drupal\Core\Extension\ModuleInstaller::updateKernel, so it references the old container. The problem is that accessing it is deprecated:

The "module_installer.uninstall_validators" service is deprecated in drupal:11.1.0 and is removed from drupal:12.0.0. Inject "!tagged_iterator module_install.uninstall_validator" instead. See https://www.drupal.org/node/3432595

Not sure how to solve that :-/

another, more common one is the mime type guesser, a static call that happens through \Drupal\Core\File\MimeType\MimeTypeGuesser::registerWithSymfonyGuesser(), this currently only on preHandle(). I tried moving it to rebuildKernel as well, but didn't fix the test fail yet.

godotislate’s picture

Yes, one problem is the uninstallValidators in ModuleHandler. That isn't being reattached in \Drupal\Core\Extension\ModuleInstaller::updateKernel, so it references the old container.

Off the cuff thought, no idea if actually workable: loop through uninstallValidators and use the reverse container to get their service IDs before the container is rebuilt. (Unless it's already keyed by service ID? That'd be super convenient.)

After the container is rebuilt, reconstitute each uninstall validator using their service ID.

Maybe this is something that DependencySerializationTrait can do, and then do something like the reinjectMe() method like ConfigImporter has.

berdir’s picture

Status: Needs work » Needs review

@godotislate: Thanks, that helped. Not sure if what I came up is the best approaach, but it works. At first it didn't, but then I noticed that that failing test was a config importer, so that also hit #3564735: Container / service exceptions while installed from existing config. added the same change here which I came up with separately.

Also, as usual with these changes, there are rabbit holes within rabbit holes. The failing package manager tests took me a while to track down. Noticed that the event dispatcher gets persisted there, which I finally tracked down to \Drupal\Tests\package_manager\Kernel\SandboxManagerBaseTest::register(). That's not ok, because it has injected service locators and other callbacks that keep references to the old container around. Disabled for now, lets see what really fails without that and then we probably want a separate issue to handle that differently, or do it here depending on how big that gets.

berdir’s picture

Status: Needs review » Needs work

Still a ton of test fails here. Lots of additional package tests doing weird stuff with persisted services. Various one-off tests having pseudo-DI properties that become outdated. updating is broken, request stack is empty. Maybe #2613044: Requests are pushed onto the request stack twice, popped once will help with that, maybe not. More likely that the update kernel doesn't do things properly.

andypost’s picture

there's few things hardcoded so #2719315: Remove unnecessary system.module path setup in installer and related trying to simplify, maybe it will help

berdir’s picture

I debugged this a bit, my guess is that this happens in updates when a container rebuild happens, then the controller and injected dependencies are still on the old container.

I honestly don't know if this is feasible like this. Interestingly, when running updates through drush, I didn't have issues like this, I'm not sure how to resolve this. We could try to reinject in Drupal\system\Controller\DbUpdateController::handle() after running the updates.

I wonder if we should resort to a different mechanism to fix the memory issue. Maybe we could could have a soft reset, that only resets non-synthentic services?

alexpott’s picture

This change looks like it should result in quite a few changes to \Drupal\Core\DrupalKernel::resetContainer() as that is re attaching synthetic services like the kernel. Don't think it needs to with this change.

berdir’s picture

> This change looks like it should result in quite a few changes to \Drupal\Core\DrupalKernel::resetContainer() as that is re attaching synthetic services like the kernel. Don't think it needs to with this change.

I don't quite get what you mean with that.

I didn't write a new comment, but I did implement that soft-reset approach that only resets non-synthentic services, which is working a lot better. So yes, with that, we possibly no longer need some of the changes, like the module installer as it would still kind of work, it's still wrong though. Same with the package manager test, I can probably undo that tag, but those tests and how they change the container are really weird.

mark_fullmer’s picture

If it's useful for test coverage, I'm cross-referencing #3601236: Drupal web installer crashes with "synthetic service kernel" error when pathauto is enabled. The PathAuto module adopted OOP-based hooks and specifically uses dependency injection in one of the new hook classes. The instantiation of the config_factory service via dependency injection triggered the Symfony exception mentioned above (You have requested a synthetic service ("kernel")).

This is explained in depth at https://www.drupal.org/project/pathauto/issues/3601236#comment-16663842

So perhaps test coverage for this issue should include the use of dependency injection in an OOP hook class.

tlo405’s picture

I've been running into this problem on a D11 upgrade... The webform and pathauto modules seem to be the culprit in my case. I did come from the pathauto issue queue and ended up finding this issue. I found some discussions on slack as well regarding this... but sounds like this issue here is the preferred fix? I've found a few other patches in some of these other issues, but it seems like this one is the correct way forward.

I also read that this apparently isn't a problem in 11.4? I'm trying to update to 11.3 (I'm already going from 10.6.x to 11.3.x, so I would prefer not to have to make another jump up to 11.4.x). I don't like to pull in patches to my project that have no chance of ever getting merged, but it seems like this one here is my best bet.

I'll say, the latest MR does work for me, but @berdir it looks like there is still more to be done here? I don't want to move it to RTBC yet if that's the case.

berdir’s picture

Status: Needs work » Needs review

I figured out what caused the test to fail, it was that I didn't reset private services.

It's so hard to follow all this, but I feel like doing this could allow us to simplify and remove some overlapping bits in \Drupal\Core\DrupalKernel::attachSynthetic() and \Drupal\Core\DrupalKernel::resetContainer(), I don't entirely get why they're two different methods and both are called during the installer and the reset only if more than one module is being installed?

Currently, the reset() method only keeps synthetic services. But then there's also the persist_ids parameter and services tagged with that, which is set in resetContainer(). If we'd also respect that directly inside reset() then we wouldn't need that anymore, I think? We still need to reinitialize some services though.

But I think that could go into follow-ups.

tlo405’s picture

Status: Needs review » Reviewed & tested by the community

This issue is a bit difficult to follow, and based on other Drupal.org issues and Slack discussions, there appear to be several ways to trigger the "You have requested a synthetic service ("kernel"). The DIC does not know how to construct this service" error. That said, I can confirm that the latest merge request resolves the issue for me in my project.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Tests look like they are failing... so this definitely needs work.

berdir’s picture

Status: Needs work » Needs review

My last cleanup there didn't handle the weird request_stack service correctly (non-synthetic service but must not be reset)

tlo405’s picture

Status: Needs review » Reviewed & tested by the community

I've tested this one out again, looks good to me.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

I left some review comments on the MR.

One thing that's not explained is the move of MimeTypeGuesser::registerWithSymfonyGuesser($this->container); - looking at this I have two thoughts:

  1. It's great that is available even earlier - so I'm not concerned about the move.
  2. We should consider a follow-up to fire an event here so we can move this code out of DrupalKernel and decouple things a bit
berdir’s picture

Status: Needs work » Reviewed & tested by the community

I thought I replied to @21 but can't seem to find it anymore.

That call is in the super-critical-path (pre-page-cache-hit). Triggering an event there is not an option.

That said, the reason it's there isn't that it is earlier, it is that rebuilding a container does re-register that, which currently is inconsistent (still on the old container).

Setting back to RTBC as I just accepted some suggestions.

catch’s picture

Status: Reviewed & tested by the community » Needs review
That call is in the super-critical-path (pre-page-cache-hit).

I think this means we should add Drupal\Core\File\MimeType\MimeTypeGuesser to the core/composer.json classmap entry which is for very early bootstrap classes. That list may be out of date for other reasons, but we could add the one class here and open a follow-up to review it overall?

berdir’s picture

We have a follow-up: #3612177: Update autoload classmap for Symfony runtime and etc.. I can add the MimeTypeGuesser, will try my patch on core and see what it reports, we are likely missing a lot.

  • catch committed 21a48b63 on 11.5.x
    task: #3575642 Always free up old container on rebuild, not only in the...

  • catch committed dac0667b on 11.x
    task: #3575642 Always free up old container on rebuild, not only in the...

  • catch committed 02e77a7b on 12.0.x
    task: #3575642 Always free up old container on rebuild, not only in the...

  • catch committed 73361685 on main
    task: #3575642 Always free up old container on rebuild, not only in the...
catch’s picture

Version: main » 11.5.x-dev
Status: Needs review » Fixed

Let's just add it in that issue.

Committed/pushed to main, 12.0.x, 11.x and 11.5.x, thanks!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

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

ressa’s picture

Thanks for fixing this, just adding a possibly related issue.