Right now features_modules_enabled() goes through all enabled modules, sees which ones are features, and enables them.
A cache clear (features_cache_flush()) later rebuilds them.

This approach has two major problems:
1) The module's info file needs to be parsed to see if the module is a feature. So for each enabled module, there is an expensive file reading operation, with all kinds of operations layered on top. There are caches to offset the cost, but those caches don't exist during installation, creating performance problems.
Related: #1530386: Avoid unnecessary cache rebuilds and improve installation performance.
2) The enabling / rebuilding happens too late. This means that it is not possible for a feature to depend on another feature.
Imagine I had a "product" feature depending on a "store" feature. I enable them both at the same time. When the product_enable() or product_install() function fires, the store feature hasn't yet been enabled or rebuilt. If the "product" feature wants to add fields to a node type declared by "store", it's not possible.
This is a big problem.

Commerce Kickstart v2 currently has code like this in each feature install file:

function commerce_kickstart_blog_enable() {
  // Rebuild the features static caches.
  features_include(TRUE);

  $module = 'commerce_kickstart_blog';
  $feature = feature_load($module);
  $items[$module] = array_keys($feature->info['features']);
  _features_restore('enable', $items);
  _features_restore('rebuild', $items);
}

What I propose:
1) Start generating features with install files containing the above hook_enable().
2) Modify features_modules_enabled() to only do the parsing / checking / feature enabling if the module doesn't have a hook_enable() implementation.
This will allow "old-style" features to still work.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

bojanz’s picture

Title: Rethink the way features are enabled » Rethink the way features are enabled / disabled
Status: Active » Needs work
FileSize
912 bytes

Of course, disabling should get the same treatment.

Here's a patch that applies on top of #1530386: Avoid unnecessary cache rebuilds and improve installation performance and demonstrates just the features_modules_disabled / features_modules_enabled changes.
One of the effects of this change is that modules implementing hook_enable() won't get their info files parsed, which is a performance improvement.
And if the feature has no hook_enable(), everything still works.

The part that remains to be done is the code that writes the hook_enable() / hook_disable() code to the feature install file.

hefox’s picture

There's been talk about on creating/adding install files to do update functions as needed (don't recall the exact issue), and sounds like a good idea. I hate the features rebuild in cache clear, and would much prefer if update functions/hook_enable/hook_install were able to replace.

(None of this needed for features that do not depend on features/true exportables of course.)

bojanz’s picture

On the topic of removing the features rebuild from features_flush_caches():
Having it in an update function wouldn't cut it, because there would need to be a one-line update function for each change to a feature, which could reach a massive number.

I think the answer to that is making the feature rebuild explicit rather than implicit, the same way the Configuration initiative is approaching the reload problem for D8. This means that instead of all features getting rebuilt on cache clear, the rebuild needs to be triggered by going to a page & pushing a button, or executing a drush command.
It's going to be a bother to re-educate users, but it seems worth it.

mpotter’s picture

It's going to be a bother to re-educate users, but it seems worth it.

You handle that by just setting the option for rebuild on cache clear to be TRUE unless changed. So existing users are unaffected but people who know what they are doing can turn off the auto-build option and rebuild manually. With a module like Features that is so widely used, consideration of existing users is always a bit issue for me.

The part I'm a bit concerned about is the writing of the hook to the install file. Remember that people use features in many different ways. There are cases where a hook_enable or hook_disable might already exist in the feature.module file. There are cases where a feature.install file already exists and you'd need to check to see if the hook_enable has already been written or not and preserve the rest of the install file. Just lots of cases where I can see problems arising, so this is definitely not something that is going to make it into v1.0 at this point in the dev cycle.

An approach such as #658772: Provide way to disable rebuild during features_flush_caches() has a better chance of making it into Features soon since it's much less intrusive and doesn't affect existing installations. So I'd like to see a phased approach where we get that patch improved for now and then work more on a better comprehensive solution for the future.

nedjo’s picture

Yes, fixing this up is a key need, for both performance and dependency handling. As well as the .info file parsing and related issues, there's the problem that on cron multiple features are rebuilt on a single page load, which can easily lead to out of memory errors.

As Mike noted in #4, though, rewriting .install and hook_enable() will quickly get messy. Can we just continue to do our work in hook_modules_enabled()? True, hook_enable() is called for each module in turn while hook_modules_enabled() is only called once for all modules that were enabled. But the modules are sorted first in dependency order, so our dependencies should be fine.

So it looks like there are two changes we'd need to make to features_modules_enabled():

  • Find a less costly way to determine what modules are features modules.
  • Add a rebuild call to the existing enable call. Maybe consider pushing the enable + rebuild calls into a batch operation to avoid out of memory errors.

As hefox said in #2, we're not really talking about all features and all components here--just features that include faux exportables. The only shortcut that occurs to me is testing for the featurename.features.inc file rather than parsing the .info file--but is this file generated for all features with faux exportables?

Why isn't rebuild already being called here? Just to avoid delaying the page load when enabling a feature or features? Maybe a batch would help here too--at least it would provide some feedback.

nedjo’s picture

Here's a first rough take on how to rebuild features on enable via a batch operation.

On manual testing (enable or disable a feature at admin/structure/features) this works for some features but in once case (enabling debut_redhen)I got an infinite loop seemingly related to entity api and ctools.

This will generate an access denied error when enabling with Apps because an Apps redirect will be missed.

Haven't tested at install time or via Drush.

bojanz’s picture

I like this idea.

However, Kickstart features currently have additional install code that places blocks into correct regions, provides additional configuration for provided product types, etc. All that works only after a feature rebuild, which still happens too late (since there is no hook after hook_modules_enabled()).
Should we perhaps provide one, like hook_feature_enabled? Features already has some, but they are per-component, not per-feature (which might be easier to grasp).

nedjo’s picture

@bojanz: Yes, a bit tricky. If we end up taking this direction, rather than introducing a new hook, I guess you could use hook_module_implements_alter() in the install profile to move the features hook_modules_enabled() implementation to the beginning and then implement hook_modules_enabled() rather than hook_enable() in each feature.

nedjo’s picture

I've been experimenting with @bojanz's code from the issue summary and seem to be hitting a couple of issues that will probably also occur in my patch.

1. When features_rebuild() is called (in hook_flush_caches()), components are rebuilt/created in the order that their component types are returned by features_get_components(). This order is important and differs from the alphabetical order that components take in a feature's .info file.

Specifically, user_features_api() returns 'user_role' before 'user_permission', with the result that user roles are - correctly - created before the user permissions that require them. But in a module's .info file alphabetical ordering will put user_permission components before user_role ones, with the result that the role doesn't exist when the permission referencing it is created.

We might need to call features_get_components() and then reorder a feature's components by type.

2. It appears that some data supplied by newly-enabled modules are not yet available when feature components are rebuilt. Specifically, I got the error "SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'module' cannot be null", which may result from newly-enabled modules' permissions not showing up due to a stale cache in module_implements().

We might need at a minimum to call drupal_static_reset('module_implements').

bojanz’s picture

This might be related: #1597186: Do not cache included components in features_include_defaults(). We have it applied on Kickstart, but it's been a while...

nedjo’s picture

Version: 7.x-1.x-dev » 7.x-2.x-dev
Status: Needs work » Needs review
FileSize
1.39 KB

Updated patch with a fix to the first problem noted in #9. The second problem in #9 turned out to be caused by rebuilding a feature that includes a profile2_type in hook_enable(), before the new profile2_type is added in entity_modules_enabled(). So it shouldn't show up in this patch, if only because features comes after entity in hook_modules_enabled(). That said, the workaround I suggested in #8 could then break things again, by moving features before entity....

Switching to the 2.x branch.

nedjo’s picture

Previous patch was missing the added file.

kclarkson’s picture

Has this patch been reviewed ?

I have site on pantheon that is exhausting my memory. Here is what support suggested:

| APR 01, 2013 | 11:27AM PDT

In terms of pinpointing the root cause as this looks feature specific as for the solution there is nothing platform specific. The volume of calls is the problem here and how the features module is handling the jobs being performed.

Features attempts to process everything as a single transaction without concurrency which is not the most efficient way to do this. In this thread the suggestion is to increase the timeout, but that does not really solve the problem. Doing this will be fine until you hit the PHP timeout and could cause the CPU and memory usage to spike.

The suggestion by #3 on that thread is the better way to handle the features management, using batch operations for large feature sets:

https://drupal.org/node/1053608

The thread that it points to is here. However, the status needs review rather than accepted or closed:

https://drupal.org/node/1572578

The worfklow outlined in the description the problem is possibly with how features is managing those operations.

Status: Needs review » Needs work

The last submitted patch, features-restore-batch-1572578-12.patch, failed testing.

hefox’s picture

Status: Needs work » Needs review
FileSize
358 bytes

From a quick refreshier, there's two issues here, when features rebuild happen, and many big features rebuilding use many resources/takes many time (thus the batching)? I'm not totally convinced that should be addressed just in the call from features_module_enabled.

Due to that, adding a simple patch that just adds in the features rebuild

hefox’s picture

Need to refresh what files have been included

hefox’s picture

Proper order for components

hefox’s picture

Oops, can't call features get components till after that cache is cleared in features_include(TRUE)

pfrenssen’s picture

Just stumbled on this issue. I have been tackling the same problem in January for Features 7.x-1.x in #1265168: Rebuild the file list properly when a feature is enabled or disabled. This was the relevant part of my patch:

diff --git a/features.module b/features.module
index 6aeb2d7..42fd679 100644
--- a/features.module
+++ b/features.module
@@ -300,19 +300,29 @@ function features_modules_disabled($modules) {
  * Implements hook_modules_enabled().
  */
 function features_modules_enabled($modules) {
-  // Go through all modules and gather features that can be enabled.
-  $items = array();
+  // Check if any of the enabled modules create new components.
+  $new_components = array_intersect(module_implements('features_api'), $modules);
+
+  // Check if any of the natively supported modules have been enabled.
+  $new_modules = array_intersect(array('features', 'block', 'context', 'field', 'filter', 'image', 'locale', 'menu', 'node', 'taxonomy', 'user', 'views', 'ctools'), $modules);
+
+  // Check if any of the enabled modules contain features.
+  $new_features = array();
   foreach ($modules as $module) {
     if ($feature = features_load_feature($module)) {
-      $items[$module] = array_keys($feature->info['features']);
+      $new_features[$module] = array_keys($feature->info['features']);
     }
   }
 
-  if (!empty($items)) {
-    _features_restore('enable', $items);
-    // Rebuild the list of features includes.
+  // Clear components cache and load new include files if needed.
+  if (!empty($new_components) || !empty($new_modules) || !empty($new_features)) {
     features_include(TRUE);
   }
+
+  // Enable the new features.
+  if (!empty($new_features)) {
+    _features_restore('enable', $new_features);
+  }
 }

Ref. https://drupal.org/files/1265168-36-features-module_enable.patch

kristiaanvandeneynde’s picture

Another problem that we've encountered with Features is that you cannot create nodes during an install.

Features only creates the field bases / field instances on hook_enable(), which occurs after hook_install(). So basically if you have a feature that adds some fields to (for instance) Page, you cannot create a Page during hook_install() of, say, a profile.

I've done some debugging and this is what I came up with:

field_create_instance()           - Creating instance body for bundle: webform
PROFILE_install()                 - Running PROFILE install
field_instance_features_rebuild() - Rebuilding feature field instances for module: feature_inst
field_create_instance()           - Creating instance field_address for bundle: institution
field_create_instance()           - Creating instance field_feedback_introduction for bundle: institution
field_create_instance()           - Creating instance field_mail_feedback for bundle: institution
field_create_instance()           - Creating instance field_mail_invitation for bundle: institution
field_create_instance()           - Creating instance field_type for bundle: institution
field_instance_features_rebuild() - Rebuilding feature field instances for module: feature_sg
field_create_instance()           - Creating instance field_education_type for bundle: survey_group
Ogredude’s picture

Rerolled #18 to remove extra comment

dsnopek’s picture

#1597186: Do not cache included components in features_include_defaults() isn't a duplicate of this issue - see comment #9. There is an issue with installing Panopoly with Features 2.0-rc2 or 2.0-rc3 (version 2.0-rc1 actually works!) that only that patch fixes - not this one.

mpotter’s picture

Status: Needs review » Fixed

This fixed the install issue in OA2 that was causing all features to be built at the end (and exceeded max execution time). Doesn't seem to have any downside or any decrease install time, so I've committed this to 676484f.

Status: Fixed » Closed (fixed)

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

bojanz’s picture

Title: Rethink the way features are enabled / disabled » Features can't depend on other features
Status: Closed (fixed) » Needs work

This issue was closed, but the work that nedjo and I did was not included in any way.
This means that it is still impossible for a feature to depend on another feature (unless I missed something).

bojanz’s picture

Title: Features can't depend on other features » Rethink the way features are enabled / disabled
Status: Needs work » Needs review
FileSize
601 bytes

Actually, the explicit rebuild was added, so the dependencies should be fine. Restoring title.

Two problems remain here:
- Since all rebuilding happens at once, it will easily timeout on any distribution.
- There is no way for a feature's hook_enable / hook_install to operate on data created by Features, because hook_modules_enabled() runs last.

So for Kickstart to be converted to Features 2.x I need a way to disable features_modules_enabled() so I can continue the manual enablings.
Can we commit the applied patch? Perhaps we can document this somewhere as well.

nedjo’s picture

Agreed that we sometimes need a batch operation. But the need for a batch is not specific to a given distribution. The patch applied from #21 helped avoid timeouts only in the limited cases that:

  • features are enabled only one per page load (for instance, through an install-time batch operation), and
  • no given feature on its own exceeds a timeout.

Any time many or complex features are enabled at one time - for example, through the Admin > Structure > Features screen - there may be a need to batch the operation to avoid a timeout.

So it may be better to directly address the problem in Features - as I began to do in #12 - rather than adding a workaround/override.

We could also consider changing the variable_get() call in drupal_set_time_limit() in _features_restore() to a default value of 240 rather than FALSE. (240 seems to be the default used in e.g. https://api.drupal.org/api/drupal/modules!node!node.module/function/node...). [Edit: see #1892296: Allocate sufficient time for migration processes for a similar approach in Migrate.]

  • mpotter committed 676484f on 8.x-3.x
    Issue #1572578 by hefox, nedjo, bojanz, Ogredude: Rethink the way...

  • mpotter committed b43ecaa on 7.x-2.x authored by bojanz
    Issue #1572578 by hefox, nedjo, bojanz, Ogredude: Rethink the way...
mpotter’s picture

Status: Needs review » Fixed

Committed #27 to b43ecaa.

Status: Fixed » Closed (fixed)

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