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.
Comment | File | Size | Author |
---|---|---|---|
#27 | 1572578-27-allow-rebuild-to-be-disabled.patch | 601 bytes | bojanz |
#21 | 1572578-features-module-enabled-rebuild-21.patch | 810 bytes | Ogredude |
#18 | 1572578-features-module-enabled-rebuild-18.patch | 1.09 KB | hefox |
#17 | 1572578-features-module-enabled-rebuild-17.patch | 1.05 KB | hefox |
#16 | 1572578-features-module-enabled-rebuild-16.patch | 545 bytes | hefox |
Comments
Comment #1
bojanz CreditAttribution: bojanz commentedOf 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.
Comment #2
hefox CreditAttribution: hefox commentedThere'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.)
Comment #3
bojanz CreditAttribution: bojanz commentedOn 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.
Comment #4
mpotter CreditAttribution: mpotter commentedYou 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.
Comment #5
nedjoYes, 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():
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.
Comment #6
nedjoHere'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.
Comment #7
bojanz CreditAttribution: bojanz commentedI 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).
Comment #8
nedjo@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.
Comment #9
nedjoI'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 (inhook_flush_caches()
), components are rebuilt/created in the order that their component types are returned byfeatures_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')
.Comment #10
bojanz CreditAttribution: bojanz commentedThis 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...
Comment #11
nedjoUpdated 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.
Comment #12
nedjoPrevious patch was missing the added file.
Comment #13
kclarkson CreditAttribution: kclarkson commentedHas 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.
Comment #15
hefox CreditAttribution: hefox commentedFrom 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
Comment #16
hefox CreditAttribution: hefox commentedNeed to refresh what files have been included
Comment #17
hefox CreditAttribution: hefox commentedProper order for components
Comment #18
hefox CreditAttribution: hefox commentedOops, can't call features get components till after that cache is cleared in features_include(TRUE)
Comment #19
pfrenssenJust 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:
Ref. https://drupal.org/files/1265168-36-features-module_enable.patch
Comment #20
kristiaanvandeneyndeAnother 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:
Comment #21
Ogredude CreditAttribution: Ogredude commentedRerolled #18 to remove extra comment
Comment #22
helmo CreditAttribution: helmo commentedThis fixes #2080999: WD cron: EntityFieldQueryException: Unknown field: field_oa_date in EntityFieldQuery->addFieldCondition. see https://drupal.org/node/2080999#comment-7898053
#1597186: Do not cache included components in features_include_defaults() might be a duplicate of this issue.
Comment #23
dsnopek#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.
Comment #24
mpotter CreditAttribution: mpotter commentedThis 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.
Comment #26
bojanz CreditAttribution: bojanz commentedThis 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).
Comment #27
bojanz CreditAttribution: bojanz commentedActually, 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.
Comment #28
nedjoAgreed 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:
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 indrupal_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.]Comment #31
mpotter CreditAttribution: mpotter commentedCommitted #27 to b43ecaa.