[11.x] Middleware that calls ModuleHandler->alter() can remove hooks from module_implements cache

Created on 17 April 2023, over 1 year ago
Updated 17 May 2023, over 1 year ago

Problem/Motivation

We had an issue present on a website where after deployments tokens would suddenly stop working. I originally thought this was an issue in the token contrib module as the problem presented itself in the same way as https://www.drupal.org/project/token/issues/3088219#comment-13972171 πŸ› Inconsistent loading of third party hook implementations Fixed

When in a bad state, we could observe that in the module_implements cache had an empty array for the module_implements_alter key of the bootstrap cache.

drush ev "print_r(\Drupal::service('module_handler')->getImplementations('module_implements_alter'))"

Array
(
)

After a lot of debugging I suspect what is happening is a race condition triggered by a module the calls $moduleHandler->alter() in its logger constructor, which is a dependency of some StackMiddleWare.

This resulted in the logger being constructed when the kernel is constructed - which is before DrupalKernel is loading the module files.

On the first request the cache for key hook_info will be empty so the module handler calls buildHookInfo() and saves that to the cache. buildHookInfo has a safety check to reload the module files https://git.drupalcode.org/project/drupal/-/blob/10.1.x/core/lib/Drupal/...

When buildImplementationInfo('module_implements_alter') is called the ModuleHandler has already loaded the module files and so it returns the expected list. This is statically cached and then saved to the cache at the end of the request.

What I think is happening is that a second request starts and is able to load key hook_info from the cache, however if the first request has not completed the cache for key module_implements_alter is still empty - so it will produce an empty result for module_implements_alter.

The first request then writes to the cache, and then the second request completes and writes to the cache again, this time with an empty array for module_implements_alter.

Steps to reproduce

This relies on a combination of things outside of core.

I was able to reproduce it through

  • enabling the cloudflare contributed module (which has middleware that has a dependency on a logging channel)
  • enabled the raven contributed module (which has a logger channel that calls ModuleHandler->alter in its constructor - but note that raven module now works around this issue by only invoking the alter hook if configured to do so)
# Clear the cache
drush cr
# Ensure Drupal has cached hook info
drush ev "\Drupal::service('module_handler')->getHookInfo()"
# Manually drop the module_implements cache to mimic a scenario where the original request started but not complete
drush ev "\Drupal::service('cache.bootstrap')->delete('module_implements')"

After loading the website in the browser I can see that module_implements alter is empty.

Proposed resolution

Should the module handler include a similar safety check to buildHookInfo in buildImplementationInfo to verify that the modules are loaded?

Is this a documentation issue - https://www.drupal.org/docs/drupal-apis/middleware-api β†’ does not set expectations around what services and functionality should be available to middleware.

Remaining tasks

  1. Decide if this is an issue for Core or not

User interface changes

n/a

API changes

-

Data model changes

n/a

Release notes snippet

-

πŸ› Bug report
Status

Closed: duplicate

Version

11.0 πŸ”₯

Component
ExtensionΒ  β†’

Last updated about 22 hours ago

No maintainer
Created by

πŸ‡³πŸ‡ΏNew Zealand ericgsmith

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Production build 0.71.5 2024