Background

Features has a directory /includes/ with PHP files for different modules, e.g. /includes/features.ctools.inc for ctools.
The function features_include() includes all of those files for those modules that are currently enabled.
It has a static variable $once to make sure that it is called only once in a typical request.

The hook implementations features_modules_enabled() and features_modules_disabled() call features_include() with a $reset parameter, to make sure the files for newly enabled modules are also included.
When more than one modules are enabled at once, the hook fires only once in module_enable().

However, some caches are refreshed after the enabling of every individual module.
Some of these cache clears might trigger features_include() without the $reset parameter.

Problem/Motivation

When bulk activating modules (eg by using modules_enable()) the hook_modules_enabled() is only called at the very end after activating all modules. Since other other caches (especially `module_implements()`) are cleared after every module this means features sometimes produces “Call to undefined function …” errors.

Steps to reproduce

I don’t have a minimal set of steps to reproduce at the moment, but enabling a feature that pulls in ctools as dependency on a fresh install seems to be a good candidate.

Proposed resolution

  1. Use drupal_static() in features_include(). (patch in the comments)
  2. Hope that #1891356: D7: Reset drupal static caches when a module is enabled or disabled. gets resolved at one point.
  3. Remove the cache flushes in features_modules_enabled() and features_modules_disabled() as Drupal core takes care of it.

Remaining tasks

  • Review the patch
  • Get the Drupal core issue fixed.

User interface changes

None

API changes

None

Data model changes

None

Comments

torotil created an issue. See original summary.

torotil’s picture

Status: Active » Needs review
StatusFileSize
new388 bytes
donquixote’s picture

Quick question: Is this a regression since the last stable 7.x-2.11, or was it already broken then?

Status: Needs review » Needs work

The last submitted patch, 2: features-3176448-drupal-static-2.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

torotil’s picture

I don’t think it’s a regression, no.

donquixote’s picture

The usage of drupal_static() seems a bit funny compared to how other modules use it.
Also, the subsequent comparison should check for FALSE instead of isset().
And we should use === NULL instead of !isset(), if the variable is known to exist.

Overall I don't see how the patch should solve the problem.
It only makes a difference if features_include() is called without the $reset parameter.
But where would this be the case? Which of the calls to features_include() happens after a module is enabled, which does not already have the $reset parameter?

donquixote’s picture

Issue summary: View changes

I am adding a "Background" section to the issue summary, please review.
I think I understand better now.

donquixote’s picture

Hope that #1891356: D7: Reset drupal static caches when a module is enabled or disabled. gets resolved at one point.

If not, we could abuse another hook, e.g. hook_registry_files_alter(), to trigger a features_include(TRUE).

torotil’s picture

Status: Needs work » Needs review
StatusFileSize
new468 bytes

Ok, I was trying to use the static fast pattern. But I don’t think this function is even called often enough to warrant that. Here is a patch with the simple drupal_static() pattern.

torotil’s picture

Your analysis is spot on. Nice idea to abuse hook_registry_files_alter() this seems to be called exactly once and early after each module is enabled. The only thing that I’m afraid of is that too many modules start to include such workarounds for #1891356: D7: Reset drupal static caches when a module is enabled or disabled..

donquixote’s picture

Ok, I was trying to use the static fast pattern.

I don't mind some static fast, but I had the impression it was not done correctly. Or was I mistaken?

The new patch seems ok.
But I assume this will only do anything after core is fixed, right?

Also we should be aware that with this patch (plus core fix) some operations will run more than once in a request when enabling new modules.
E.g. ctools_features_declare_functions().
I think it is ok, because this function does call function_exists() before it declares new functions. But we should at least have a closer look at this and make sure this won't cause problems.

torotil’s picture

@donquixote A profile install via drush already calls module_enable() for every single module it enables so I don’t think there will be problems with that.

I don't mind some static fast, but I had the impression it was not done correctly. Or was I mistaken?

Yes, the condition was done wrongly, but I’d prefer not to use static fast for something that’s not called very often for the typical request anyway.