| Project: | Drupal core |
| Version: | 8.x-dev |
| Component: | base system |
| Category: | bug report |
| Priority: | critical |
| Assigned: | Unassigned |
| Status: | active |
| Issue tags: | API clean-up, data integrity, data loss, DrupalWTF, Usability |
Issue Summary
Background
- #1776830: Installation and uninstallation of configuration provided by a module that belongs to another module's API
- #1881630: [meta] Determine how to respond to invalid plugins (plugin dependencies)
Problem
- Disabled modules cause data to be lost or get stale/unmaintained.
- Data of disabled modules cannot be staged.
- References from or to data in disabled modules are broken.
Details
- The key concept here is data integrity.
- All data that isn't exclusively owned by a disabled module needs to go through APIs of other modules, and those modules have to make hard decisions in order to keep the system running.
-
If you disable a module, data integrity is no longer maintained; i.e., disabling a module means to uninstall it step by step under the hood:
- Field types disappear; all field values get stale.
- Entity types disappear; all references are unresolvable, data is lost.
- Plugin types disappear, and along with it, all plugin IDs; all unresolvable plugin references get orphaned.
- ...
- Configuration, content, and data of disabled modules cannot be staged. The system is unable to know how to deal with the data, since it is impossible to serialize/export and import it.
- The current transitional disable » uninstall state in itself means that the API of a module is not invoked when its data is deleted, which consequently means that data integrity gets broken in enabled modules.
- Users are currently required to use the dangerous facility of disabling modules and the system makes it look as if it was a safe and sane thing to do.
History
- Disabled modules worked well in a time when every module was an atomic, isolated isle, mostly doing its own thing, with its own data, and its own functionality. Today, the world of Drupal modules is too abstract, too modular, and too integrated, in order for that concept to work.
-
Today, a "disabled module" means two totally different things:
- The module's functionality disappears from the user interface.
- The module's functionality disappears from the API (the code isn't loaded, its hook implementations aren't invoked, etc).
By merging 1) and 2) in the concept of disabled modules, instead of just doing 1), we've created a high number of critical/major bugs in the past couple of years. Some of those include:
#943772: field_delete_field() and others fail for inactive fields
#1029606: Regression: Not loading the .module file causes a fatal error when uninstalling some modules (as does loading it)
#1115510: Entity providing modules must call field_attach_delete_bundle() in hook_uninstall()
#773828: Upgrade of disabled modules fails
#895014: All fields of a node type are lost on module disable
#1017672: D6 to D7 update process permanently deletes comment bodies and other data, and throws fatal SQL errors
#1451072: Deleting a comment author while the Comment module is disabled leads to an EntityMalformedException error after it's reenabled
#1227966: Dynamically-defined blocks are never removed from the site, even when they disappear from hook_block_info()
#1887904: update_retrieve_dependencies() only considers enabled modules
#737816: user_role_grant_permissions() throws PDOException when used for a disabled module's permission or with non-existent permissions
Proposed solution A
-
Remove the concept of disabled modules, including its UI. All installed modules are always installed/enabled and always loaded.
-
hook_enable(),hook_disable(),hook_modules_enabled(),hook_modules_disabled()are removed, without replacement.Note:
hook_install(),hook_uninstall(),hook_modules_installed(), andhook_modules_uninstalled()remain to exist. -
Consider to introduce a new facility that allows site builders/developers to temporarily remove (!= disable) a module from the system via Devel module, or even a core module.
Removing a module is piece of cake: Remove its entry from the
system.module:enabledconiguration. Flush all caches.Make it clear in that new facility's UI that this is a dangerous operation, which may involve data loss. Educate users that modules should only be removed temporarily, for a very short time, for the sake of debugging. Clarify that users might have to restore their site to a backup created before modules got removed.
Proposed solution B
Note: Mostly obsolete, since Drupal 8 is no longer abouts hooks only. There are services, classes, plugins, etc in addition to hooks now, so this approach would have to be baked into the entire infrastructure.
- All installed modules are always loaded, not just enabled ones.
-
All modules can declare hook names that are essential for maintaining data integrity via
hook_hook_info().These hooks are e.g.
hook_field_info(),hook_entity_info(),hook_entity_load(), etc. - Basically, anything relating to CRUD and providing "building blocks." -
When invoking a hook via
module_hook(),module_invoke(), andmodule_implements(), hooks are invoked in enabled modules, and if the hook is declared as "essential", it is invoked in disabled modules as well.So when a module is disabled, its
hook_form_alter(),hook_menu(),hook_views_data()and other "UI level" hooks don't fire, it disappears from the UI. However, if it implements some of the essential hooks, then those hooks still run.
Potential follow-ups
-
Introduce a new "Disable module" facility that means to disable its user interface aspects only.
This means:
- All code of disabled modules remains to be loaded.
- Services of disabled modules remain to be functional.
- Routes, plugins, info hooks, etc. remain to be available.
- Access to all data items that support any notion of access is denied for all users.
-
#1503314: Remove the concept of active / inactive (field types, storage) from Field API
...since it might no longer be necessary to maintain the active status, if all fields and storage engines would be active?
Comments
#1
So either
or
Pick one.
#2
#1052692: New import API for major version upgrades (was migrate module) is option 1 of those two choices.
#3
Good luck with that.EDIT: After actually reading the linked issue -- this is not a "you can never upgrade" solution. This is a "the only way to upgrade is to export, then re-import your data".
Which is really the only way to handle a moderately complex site anyway, because it's nearly certain that at least one of the d6 modules used will not have a smooth d7 upgrade path.
So...
#4
"It would be very tempting to change the states we support to these:
Uninstalled
Installed"
I don't see how we can do that... the ability to disable a module temporarily is important when troubleshooting. Uninstalling is destructive.
#5
Can disable module temporarily by just moving it out of webroot. I've done that, frequently. :-)
Also, change "disable / re-enable" to "export data / uninstall / re-install / import data".
Does make troubleshooting more difficult, but not impossible.
#6
Right, my position on that issue is that we currently pretend that you can install Drupal, then when it comes time to update it to the next major release, just run update.php and come out the other side. This is only true if you have an extremely basic site, otherwise we are lying to ourselves and everyone else by trying to maintain that fallacy. You can trace the history of that issue at least back to #194310: Check / run updates of disabled modules and #80526: Core modules need their updates in their own files.
Extremely simple sites will have extremely simple migrations, and this would also enforce that people don't run upgrades on their actual live database ever (which lots of people do with simple sites, then end up with unrecoverable breakage when they hit a bug - even the upgrade instructions recommend 'backing up' instead of actually running the upgrade on a copy). It will not be simple to implement the migrate issue but it may be a pre-requisite for this one to be resolved properly.
However it is the same overall issue for disabling and re-enabling modules without updates even - if you have a module that doesn't do much, and especially doesn't interact wi th other modules much, then it's fine to disable it or enable it as you like, but if it's even moderately complex or dealing with dynamic things like fields, anything that affects actual content, then it quickly becomes a nightmare where we are piling hacks upon hacks to support it being in that state.
If the module is simple, then disabling is not really going to be much more destructive than uninstalling - you might lose some configuration options you set up - but config should be exportable in Drupal 8 so even that is not going to be such a big deal.
If it has it's own non-configuration data, or other modules and data are in the system that depend on the module, then if you just disable it with current D6/7/8 your site is in some kind of inconsistent state already. Currently we run somewhere between allowing people to shoot themselves in the foot, implementing ugly workarounds, and one-offs that prevent modules being disabled when 'other stuff in the system' depends on them (as opposed to a listed dependency in a .info file for another module). That goes back to #232327: Deleting node type leaves orphan nodes and older issues as well.
#7
@webchick: disabling modules when existing data in the system depends on them is often destructive at the moment. The only data we can guarantee (in reality, not the current pretence) won't be completely destroyed or irreparably out of sync is the actual configuration data that the module itself provides (if another module depends on that, then it probably registers an explicit dependency - that way works at least).
If that's the only thing being destroyed, then it's either not a great loss, or we can do import/export of the config.
#8
"If the module is simple, then disabling is not really going to be much more destructive than uninstalling - you might lose some configuration options you set up - but config should be exportable in Drupal 8 so even that is not going to be such a big deal."
No, this isn't accurate. Take Aggregator module, for example. That table has 400+ rows of data in it in Drupal.org's database. Disable it, and nothing's likely to happen since nothing else depends on it (unless Aggregator items get made entities in D8, I suppose), but uninstall it and bye-bye Drupal Planet.
I kind of liked the system we introduced in one of those issues where basically if there are any X left (where X is some kind of shared data type other modules depend on; in this case field data iirc), don't allow uninstallation unless they're manually removed first. That's a pattern that's pretty easy to enforce throughout core and contrib, if we can get agreement on it.
#9
If we allowed export and re-import of aggregator feed configuration, then the aggregator case would be partly covered (and it'd be a big fat warning to the person who decides to uninstall it). If aggregator feeds become entities then taking the 'reverse dependency' approach from that field_delete_field() issue, it'd not be allowed to be disabled until something happened to those entities and fields anyway. Aggregator module is rapidly becoming the exception rather than the rule.
It's not entirely the case that "nothing's likely to happen" on Drupal.org right now if you disable aggregator module.
Drupal Planet will disappear, the dashboard will be broken since it displays items from that field, and many thousands of RSS feed readers (personal and people's own aggregator modules on other sites) will start getting 404s. This is a very different situation to if you install aggregator module then immediately disable it again, but we don't do any kind of warning at all to people if they're disabling modules that will hide/render unusable or destroy data (whether data they store themselves, or data stored by other modules). The position in the past has been "if you're disabling /uninstalling a module, you know what you're doing", but with most modules you can no longer make that assumption, since the module may not be in any way directly related to the feature it provides.
So aggregator is a good case for whether we can get this right or not, but I don't agree it's a counter-example to changing the distinction to just installed/uninstalled.
#10
For debugging:
Add functionality similar to http://drupal.org/project/environment_modules to devel (or even a special admin page in core).
Basically we'd remove the disabled vs. uninstall distinction in the modules screen, but allow people to actually disable module hooks from being run and/or code loaded at all from somewhere else. That somewhere else could make it clear this should only be used as a temporary measure and that modules should not be left in that state for long on a production site.
For update.php:
- have module_implements() thrown an exception if it's called during update.php - this would enforce use of the _update_1234_foo_bar() functions for updates. Either that or just have it return an empty array on there.
Another example where this is completely broken #1204820: Entity providing modules must call field_attach_delete_bundle() in hook_uninstall() - entity module provides various stuff centrally for other modules that can specify it as a dependency. However if you disable all those modules and also disable entity module, then entity_modules_uninstalled() won't get run when you disable the dependent modules.
#11
subscribing
#12
Tagging with framework, since this is primarily about resolving interdependencies.
#13
Subscribe. This issue will be intetesting in a world of “plugins“. What happens when the module implementing a plugin instance is unavailable ?
Although, as catch points, fields are a painful kind of plugin, because unlike views handlers, they hold user data.
#14
Subscribe. Tricky problem.
#15
This issue and #1052692: New import API for major version upgrades (was migrate module) are part of a new trend I'm noticing. We are acknowledging that we have tried but ultimately failed at robustly providing a few services in drupal core. It doesn't feel good to remove disabled modules. It doesn't feel good to remove one-click major upgrades. But I agree that we should. The core dev team can't keep fighting the same family of critical bugs forever. They will quit in frustration. I really hope the community does not reply with "Sorry, try harder".
This issue is wise.
#16
This problem can be fixed by imposing a few rules:
1) Modules can't implement more than one of hook_field_storage_info(), hook_field_info() or hook_entity_info() (and their _alter counterparts). This means refactoring storage and fields as separate modules, and modules using these fields must declare them as dependencies.
2) Any module which can create content (entity, field, node) must clean up content after itself. 'Cleaning up after itself' means at least marking its content as deletable without having to call a module-based hook, and at best doing the actual deed.
3) Modules should be able to block their own disabling and uninstallation: hook_can_disable(), hook_can_uninstall(). These hooks would live in the install file, and be called while creating the disable/uninstall module forms, not during the act. These hooks would return messages explaining to the user what needs to happen before the module can be disabled or uninstalled (or TRUE, because it can be disabled/uninstalled). A 'why can't I uninstall this?' link would be added to the form, similar to what Coder does with the 'code review' link, and following this link would show the explanation.
Thoughts? :-)
#17
At the risk of making Drupal development dangerously user-friendly, may I see a patch to add this check to the status report? Something like:
$requirements = array();$storages = array_merge(module_implements('field_storage_info'), module_implements('field_storage_info_alter'));
$storages = array_combine($storages, $storages);
$fields = array_merge(module_implements('field_info'), module_implements('field_info_alter'));
$fields = array_combine($fields, $fields);
$entities = array_merge(module_implements('entity_info'), module_implements('entity_info_alter'));
$entities = array_combine($entities, $entities);
foreach (array_intersect_key($storages, $fields) as $module) {
$requirements[] = array(
'title' => t('Module %module should not define both a storage_info and a field_info hook.', array('%module' => $module)),
'severity' => REQUIREMENT_WARNING,
);
}
foreach (array_intersect_key($fields, $entities) as $module) {
$requirements[] = array(
'title' => t('Module %module should not define both a field_info and an entity_info hook.', array('%module' => $module)),
'severity' => REQUIREMENT_WARNING,
);
}
foreach (array_intersect_key($entities, $storages) as $module) {
$requirements[] = array(
'title' => t('Module %module should not define both an entity_info and a storage_info hook.', array('%module' => $module)),
'severity' => REQUIREMENT_WARNING,
);
}
return $requirements;
#18
I was just thinking the other day (#1268974-22: For disabled node types with 'base' != 'node_content', _node_types_build() does not return data about the type.) about how to migrate toward cleaner uninstallation without breaking all sorts of existing modules. I'd love to see different uninstall policies that one could implement (e.g. hook_full_uninstall(), hook_metadata_uninstall(), etc.) with hook_uninstall() marked for deprecation. I liked the idea because the UI could be extended for finer administrative control and for certain sites I might like to filter out modules that don't implement hook_full_uninstall(), for instance.
#19
I went a little ways this direction by writing a tiny braindead module that shows you the orphaned fields that still reside in your database: http://drupal.org/sandbox/Mile23/1171894
#20
Subscribing
#21
How about the following compromise:
We allow modules to be disabled, but we do not allow update.php to run in this state. The admin must 'shit or get off the pot' before running updates. This way, we get rid of the need to run updates for disabled modules. Thats the biggest problem here IMO.
Temporary disabling of modules for debugging purposes is still allowed.
#22
So in order to upgrade from D7 to D8, or even from 8.1 to 8.2, it will be necessary to first remove all non-core modules and purge all associated data from the database.
This is a lovely idea which I am sure will quickly gain widespread community support.
#23
I'm assuming that major version upgrades are handled by #1052692: New import API for major version upgrades (was migrate module). You mocked that approach in #3 and then seemed to support it later. Minor upgrades would require you to enable or uninstall only those modules which are disabled. There is no distinction between core and contrib there. Minor version upgrades do not require that you disable all contrib modules. See UPGRADE.txt
catch points out a scenario where you have 3 disabled modules and want to uninstall one but can't properly cleanup the disabled module. This suggests that uninstall is another operation which can't be performed with additional modules in a disabled state.
@pillarsdotnet - I think you have some good ideas but your sarcasm is very hard to work with. Please try to keep collaborative and positive.
#24
Sorry; I did correct myself. When upgrading from d6 to d7, I wound up manually importing the data anyway. As I said in #3 and you appeared to confirm in #15, that's probably the way to go. Newbies and managers who are considering Drupal will be disappointed to hear that they will have to start virtually from scratch at every major version upgrade, but it's probably better to admit failure in the beginning than to apologize for it later.
Okay, noted; thanks.
I suspect that you didn't find any humor in my dangerously user-friendly comment, either. But seriously, if I took the time to format and submit such a patch, would I be wasting my time? Or do such checks belong in Coder rather than
system_status()?At any rate, we need to provide some kind of feedback when people break our implicit don't do that rules.
#25
"How about the following compromise: We allow modules to be disabled, but we do not allow update.php to run in this state."
Is that a compromise? Between what and what? There are a number of problems, and they do not represent any kind of dichotomy for which there is a compromise. There are only solutions.
Some of these problems are: The update/upgrade problem you mention, the fact that your site breaks if you uninstall a needed field or entity/node type, and orphaned data left behind because there's no D in Drupal CRUD.
The obvious solution is to require dependencies between fields, entities, and the modules that use them, and require any module-generated content type (entity, field) to demolish its own data if Drupal isn't going to do it.
For Drupal updates, disallowing update.php if there are disabled modules isn't such a bad idea. However, even uninstalling these modules will in many cases leave behind orphaned data in the database, leaving behind a trail of error-generating tables in an undefined update state.
So please tell pillarsdotnet that it's OK to write a real patch. :-)
#26
The compromise is between #0 which says 'No more disabled modules' and webchick's 'We need to briefly disable code for debug purposes and keep data around in the meanwhile'.
Yes, there are a lot of related issues here. I won't change the title of this issue because I smile every time I see it. But perhaps we could narrow the scope to 'disallow update.php when any module is disabled'. Thats a nice win, and pretty easily accomplished. IMO that does not have to wait until #1052692: New import API for major version upgrades (was migrate module) which probably won't land until we are much closer to release.
As for your last request - anyone is welcome to write any patch they wish. system_requirements() checks do seem like a (small) piece of the solution. Now that i downscoped this issue, this isn't the best place IMO.
#27
Posted: #1288276: The status page should warn when two different types of field hooks are implemented by the same module.
#28
I've opened #1330472: clean up entity-related data on module uninstall.
Short version: The only solution I came up with requires us to enforce *no* other modules are disabled when an entity-providing module is uninstalled.
#29
#23/26:
So, um ... how could this possibly work for a multisite installation where you have multiple sites running off a superset of shared modules, but each site only uses some subset of those? Enabling all modules for a given site could be extremely problematic.
#30
If a module has never been installed on a site there is no problem. The issue here is about (dropping support for) enabling a module, disabling it, then leaving it in that state for any period if time and expecting everything to magically work which is what core attempts now. Just having a module in the file system doesn't count as installed.
#31
Gotcha, thanks, that's clearer. The docs would need to make that distinction clear I think.
Perhaps if modules can still be disabled temporarily yet remain visible (ie not just temporarily removing the module folder) there needs to be some explicit distinction on the module admin page between a module that is available but not installed vs one that is installed but disabled. Seems like a 2-checkbox job, that?
#32
If the module is in the filesystem but not enabled or installed, it will show up as an unchecked listing on the modules page.
If the module is in the filesystem, was enabled at some point, is now disabled, but was not uninstalled, it will show up in the Uninstall tab of the modules page.
Note the awkwardness of that last sentence, and behold the UX problem for disabled modules. :-)
#33
Yes, exactly, a big fat UX problem. Hiding a crucial part of the current module status in the Uninstall tab also strikes me as dangerous, not just the fact that there is a third "disabled" state.
Devil's advocate: why is there a separate Uninstall tab anyway, if simply disabling a module can be potentially as dangerous as uninstalling it?
#34
Disabling the module isn't that dangerous any more, since it will warn you that you can't uninstall it before its content is gone.
Modules should probably look more like themes, which can be enabled/disabled and default/not default.
#35
Disabled modules are broken, and it's time we gave up on them.
The bandaids we put on Field API are not nearly enough, and there will always be more issues as modules become more complex.
So I agree with catch. Let's only have installed / uninstalled.
Of course, there are things to consider:
1) Updating. There is already wide consensus for moving major core updates (between branches, such as D7 -> D8) to a Migrate-like solution.
This means installing D8 & contrib modules, then pointing towards the old site to get the data, and convert it into a new suitable form.
We already force contrib to write update code, this is just a new and less problematic way of doing it. And "disabled" doesn't matter in that case.
2) Debugging
We can all agree that it is sometimes useful to disable a module (when it can be disabled, for example a module that doesn't provide entity types or field types).
I think the solution for that is the ability to prevent certain hooks from running.
Basically, we'd have something like a special callback in module_hook() (I know, Inception) that would allow a module to say "nothing to see here, move along".
Then you have something like #1387776: Move some rebuilds outside of Drupal in core or contrib, a script that scans the files, builds a list of all hooks, and allows the user to say
"Disable just this hook." or "Disable all hooks of this module" or "Disable all hooks of this type (form_alter)" or "Disable all non-core hooks".
The possibilities are very interesting, and far wider then what we have currently. I've also added a comment to the issue I linked with further thoughts on what is essentially a hook registry.
#36
Ugh, there is no way in heck we could possibly explain to 99% of Drupal's users what to do with checkboxes about "do and do not run X hook." :(
#37
99% of the users won't care about debugging at all.
What I'm talking about is a developer's tool.
Plus, it's not like our current modules page, or the disabled / uninstalled duality isn't pure, non-functional hell (uninstall is still pretty much impossible in D7 for ordinary users because of Field API and that comes down to "disabled' again).
Our modules are barely disable-able as it is. Try telling a user to disable Commerce, or anything more complex than Aggregator.
But let's give users a thought...
We could have some simple version that allows the user to disable all hooks for a certain module (a column in the modules page or whatever),
but I'm worried that we'd be back to the same level of breakage again.
Plus, when users do debugging, they often do it after someone else told them to (a maintainer on the queue, someone on support). And in that case, having the ability to disable a certain hook would allow very precise investigation (I know that VBO can be affected by hook_form_alter, don't really care about hook_init, etc).
EDIT: Expanded my points a bit.
#38
We could even have the ability to mark certain hooks as not possible to disable (hook_entity_info(), hook_field_info()).
So that way even if the user clicks "disable", that leaves a few hooks alive, so less chance of breakage.
Not sure if any of this makes sense, just trying to offer a way to slay the dragon.
#39
All hooks should go through module_implements() (not the case now).
devel module (or even system module if bash the crocodile debugging is such a core feature) can implement hook_module_implements_alter()), there is a list of checkboxes for each module and clicking one + saving disables all their hooks from running. While any module is disabled, a hook_requirements() or similar screams bloody murder at the admin until they enable a module again. It would be possible to do this for individual hooks as well but we don't have to, to keep the same level of UI for this as there is now.
That's easy enough to solve at least to the level of UI-only debugging easiness we have now, compared to the various issues listed here which are impossible to solve with the ability to indefinitely disable modules and expect to switch them on again months or even years later as if nothing had happened, or alternatively uninstall them after all. These have caused actual user data loss in the wild let's not forget. I can't believe this issue has been stalled since June on this.
#40
We came up with a couple easy first steps with decent UX in IRC:
update.phpbe run when there are disabled modules. Inform the user that the modules should be either enabled or uninstalled before proceeding, with links.#41
Also, have a list of certain hooks that must always run, for starters hook_entity_info() and hook_field_info().
So even if a user disables a module for debugging, parts important for the rest of the system continue running.
And then we can kill the whole concept of "activate / inactive" fields.
#42
I have a slightly different perspective on this issue. I don't think the problem is with the concept of disabled modules itself, but rather with the specific way we've chosen to define them in Drupal. So before we throw out the concept entirely (or add warnings to the UI about the peculiar consequences of our current definition), maybe we should rethink the definition?
In Drupal right now a "disabled module" means two totally different things:
The only problems I'm aware of are with #2, not #1. So, strawman proposal: Is it possible to get rid of #2 only? In other words, when a module is "disabled", maybe its code should still be there running in the background and managing whatever data it still needs to manage. But it would be responsible for staying out of the UI.
The naive way to do that would just be to continue invoking all hooks, but the module itself uses statements like "if module_exists('me')" to avoid running the UI-related code that shouldn't be run. That's not great DX, though, and to be robust we probably need the module invoking the hook to make the decision sometimes also. So we'd have to think a lot more about the consequences of this, and whether it would require a cleaner separation between hooks that affect the UI and hooks that don't, etc. But I think it's worth thinking about.
One obvious consequence would be performance. Under this plan, disabling a module wouldn't improve the performance of your site anywhere near as much as it does now, since most of the code would still be running. But I think that's fine. The best practice could be "to improve performance, uninstall all modules you aren't using." That's already the best practice now anyway to some extent (since uninstalling removes cruft from the variables table that gets loaded on every page, etc). It would just become more of a best practice going forward.
#43
Big points to David for creativity. I guess we would rename 'disabled' state to 'hidden'. I'll mull it for a bit.
#44
#42 doesn't make sense to me really. What happens if you "hide" a node access module? Its interface goes away, but it continues to affect access control? Am I misunderstanding? How would you "hide" access control without turning it off?
#45
Node access would definitely be something that should turn off. The goal would be that to a user of the site, the module must appear like it's not there and therefore has no effect.
The things that have to stay would really be mostly housekeeping hooks. E.g., if your hook_node_delete() implementation removes stuff from your module's database table every time a node is deleted, that needs to keep running so that your data is always up to date for the next time the module gets turned back on.
"Hidden" may or may not be the right term... it's tricky.
#46
So yeah, the way I described it originally (turning off the user interface vs. turning off the API) may have been a bit simplistic. It's not quite along those dividing lines really.
I'd like to suggest a new (half-serious) name for this concept: "hibernation"
When a module is put into hibernation, it only does the minimum things necessary to stay alive, so that when it wakes up later it will be happy and functional. (By contrast, what we have now is more like "zombie modules". When we disable a module, we kill it almost entirely, but then expect it will be fine when we bring it back to life later... Instead, it just feasts on our brains.)
So, does this concept of "hibernated" modules makes sense? I think it could really work, but I'm a little worried about whether the DX tradeoff is worth it. No matter how it's implemented, it will necessarily add more things for module developers to think about. And if they don't do it right, we'll have lots of "I turned this module off but parts of it are still on" bug reports.
#47
Well, the real question is how to mark the hooks that are "essential" and must always run. Approaching it from this angle since that's the shortest list of hooks.
hook_hook_info()?
Core should do it for itself (as I said, hook_entity_info(), hook_entity_info_alter(), hook_field_info(), etc), and then contrib can do it for itself (commerce hooks for bundles, etc).
As far as I'm concerned, if we switch to installed / uninstalled (therefore removing the "enabled" word, and going directly from installed to uninstalled without the zombie state in between), we can keep the "disabled" term using the new concept. Or does it have too much baggage?
#48
I'm really not convinced by this 'partially disabled' notion. It won't help with things like debugging, since parts of the module still run.
Having encountered a problem with Commerce and fields from disabled modules yesterday, I was thinking about this problem as I wonder whether we can't take an idea from Views: the 'broken' handler. When I build a View and add a plugin or a handler that's from a contrib module, and later disable that module, the view still broadly works, but the specific handler is marked as being broken. I can remove it, or re-enable the module and in both cases it's ok.
The main problems with disabled modules are that there are things in the database that need code to work, and the code is now absent. These are mainly: entity types, node types, field types, field widgets, and field formatters. Could we provide a 'broken' type or handler for each of these to step in when the expected code is missing?
I'm thinking these would do things like:
- for node types, show the node in strikethrough on the node admin page, and prevent it being edited
- for field components, show the field as 'disabled' on entity display and forms
#49
@#48 by joachim:
That would be a major paradigm shift. Traditionally, Drupal handles buggy code by failing spectacularly rather than gracefully. Core developers see this as an advantage for several reasons:
#50
> Traditionally, Drupal handles buggy code by failing spectacularly rather than gracefully
A 'broken' handler doesn't mean buggy code. It means a fallback component that does pretty much nothing apart from tell the user that the actually required component is missing.
#51
re: 'broken' handler : that works well for views, because a view is only runtime code, that doesn't carry db data.
"field widget" and "field formatter" are a non issue : missing widgets and formatter types are already replaced by the 'default' widget or formatter at runtime.
For "field type", the problem is that, unlike a View, a field has to do housekeeping on actual db data.
If a node is deleted while a field type is 'absent/disabled', there's just no way we can reliably clean the corresponding field data, and a genereic 'broken' handler won't help here.
Same kind of argument applies to "missing entity type" , "missing node type".
#52
So could we disallow deleting of entities that are in that state?
I don't think we're expecting a site with disabled modules to be fully operational, just not keeling over and usable for debugging and upgrades.
#53
This has been debated at length in #943772: field_delete_field() and others fail for inactive fields (that ended up doing this opposite : prevent disabling field types in use).
Preventing deletion of stuff that include disabled field types is just not trackable - includes preventing node deletion, node type deletion...
#54
There was an interesting suggestion in #16 that no one followed up on:
This is something that would be extremely useful. It doesn't solve the whole problem but at least it provides a way for a module to communicate that it shouldn't be disabled. And that takes care of another consideration -- some modules CAN safely be disabled. So modules that should not be disabled invoke hook_can_disable() to refuse to allow that to happen, other modules do not and users can still disable them.
Also I like the creativity have having a module still available while disabled but just hidden from the user in some way, but I can think of hundreds of ways the code would have to be tweaked to make sure it behaves correctly both in the enabled and disabled state, and I have no confidence I would even know how to ensure it works correctly both ways, even with a good faith effort to do so. If we think things are broken now, I can't even imagine how much more broken it would be when module maintainers try to write code that should work both ways, but doesn't.
#55
I fully agree with both Karen's points in #54.
- "Modules should be able to block their own disabling and uninstallation" is already possibly with hook_system_info_alter(), that's what we did in #943772: field_delete_field() and others fail for inactive fields. Official hook_can_disable() / hook_can_uninstall() hooks would ease the process and make it an official pattern.
[edit: we have an opportunity to introduce a hook named _disableable, let's not miss it :-p]
- The 'active but hidden' approach sounds like an implementation/debugging hell in practice. Too many chances this is done wrong.
#56
> Preventing deletion of stuff that include disabled field types is just not trackable - includes preventing node deletion, node type deletion...
It seems that the main problem is that the arrival of FieldAPI has changed the way dependent and required modules interact with one another: it's turned it upside down in a way.
In D6 days, module B would depend on module A, and use hook_form_alter to change its form. Module A is completely ignorant of what module B is doing, and if B goes away A can keep on working as normal. There is even then a slight problem of data not being handled (eg disable taxonomy module, then delete a node with tags) but it's just database cruft and nothing you'd notice.
D7 fields turns this on its head by making module A go and ask for its dependents with field_attach_form().
Given we've already made that change, I don't see why we can't also require modules that use FieldAPI to first ask field_attach_is_my_form_ok() and block operations such as deletion if it gets back a FALSE.
#57
What we did was a mistake that solved nothing. It basically tells users "You know what? Forget it, you can never uninstall".
It was a (bad) bandaid for D7 and not something that should be thought about for D8.
That code created circular dependencies for Commerce and required an alter hook that basically undid everything core did for Commerce modules, allowing modules to just be disabled (and then on uninstall disabled fields would get re-enabled, which would delete them but keep them in the db. So you have junk in the database but at least Commerce was uninstalled and can be installed again). See #1403972: Make uninstallation possible again if you want the full story.
I've spent countless hours just getting something as large as Commerce to actually be uninstallable.
And that is after helping the unfortunate #943772: field_delete_field() and others fail for inactive fields get written.
I feel like we've lost focus here, entertaining irrelevant ideas.
David gave us a good summary in #42.
Here's a plan:
1) The "disabled' concept gets removed from Field API and all other parts of core apart from the status column in {system} and a link on the Modules page to flip that. This means, for example, that modules get uninstalled while still being enabled. This is what catch originally proposed.
2) We then, for debugging purposes, add a new "hibernation / disabled" (reuse the old word or not, I don't care) concept that disables all non-essential hooks.
This requires hooks to be sorted into two groups. It is done by core (and contrib modules) by declaring which hooks are essential, through
hook_hook_info() for example. That list is consulted by module_implements(), so if a module has status = 0 and the hook is not in the essential list, don't run it.
So if a module goes into hibernation, it still provides the data structures it needs, it just disappears from the menu, the alter hooks, etc.
The list of essential hooks should be fairly small once we stop misusing hooks in core (so field types become plugins, for example. And plugins are always provided, they are in the essential category).
The end result of this is that a module can be enabled / disabled, and installed / uninstalled. These two are not connected. No more disable -> pray -> uninstall -> curse.
I could even imagine the whole hibernation concept being provided by contrib (Devel). Core just needs to make sure it uses module_implements() always.
#58
If we only want disabled/hibernated for debugging purposes only, I don't see what's wrong with #10, if the 'essential hook' is the one that's hosing your site then you want disabling the module to switch everything off - the main thing is having it as a completely separate state to installed/uninstalled which bojanz's post nicely summarized.
#59
#54 won't solve the problem we have, as still you can disable modules and enable them afterwards. Consider module B storing information for an entity provided by module A. If module B is uninstalled while module A is disabled, the entity type of module A is not there and thus the data module B stores related to the entity type of module A won't be removed during uninstall - it is going to remain in the database, forever.
That said, #10 sounds like a good solution to me.
#60
I don't understand why core does anything with disabled modules at all? To me, "disabled" means "completely ignore this bit of code but don't remove the data because I might still need it". If you disable a module that holds data and has interactions with other modules and continue to run the site that way for a while and the data gets out of synch, that seems more like user error than anything. I can see providing a warning on disable that you may have problems when you re-enable but, beyond that, why should core care? We can't keep users from doing stupid things in every case. If we try, they just get more creatively stupid. :)
So my proposal is:
This doesn't address the "disable on major upgrade" bit but I think the Migrate option is a better route for that, anyway.
Michelle
#61
We've been talking on IRC and I thought of an idea:
1) Get rid of disable.
2) Add a hook or something to let modules define what sorts of uninstall they support.
Options are:
#1 would be the normal uninstall we have now. #2 would save information along with uninstalling. If the module supports saving it would also support importing. When the module is installed again, it has the option of importing the saved off data. It's up to the module to know how to handle this data, determine if it's out of synch, stale, whatever. Core shouldn't need to worry about that (except for core modules).
This is also very similar to the whole config in code / migrate instead of update idea and likely could use much, if not all, of the same code. So, basically, when you uninstall you do a module dump similar to if you are doing a major upgrade. That way you can turn off modules temporarily and just suck the data back in when you turn them on or use that to do an upgrade.
That would get rid of the whole disabled modules problem completely because it only allows uninstall but it also opens the door for uninstall not necessarily meaning losing all your data.
Michelle
[Edit: just a typo]
#62
So to sum it up, it would be "put all of the module's data into a hibernation in a cave that nobody else goes into".
There's no reason we can't call this 'disabled' in the UI, as suggested in #57.
> and likely could use much, if not all, of the same code
Yup, but this is something that's broken on D7 too. Or is it too late to do anything to help D7 at all?
> Core shouldn't need to worry about that (except for core modules).
Be nice to provide an API though. Something akin to cache_set/cache_get buckets but without any cron clean-up obviously.
#63
I wanted to chime in a bit, because this has a deep impact on the UX. We have been working on a module page redesign and did explore how we could allow for uninstall to have more prominence.
I would say that the current model is not so broken, other than uninstall basically not doing much. The idea of disabling a module, and uninstalling when you really want to remove the data, to me feels solid.
I am deeply troubled with the proposal, to basically have two states - that are to be communicated on the module page. I would prefer a workflow, that handles data removal after on/off of a module. Even though, that is still difficult from a UX perspective, it at least doesn't require two different operations/models to be explained.
#64
I read over in the redesign modules page that there was "building consensus" for removing the disabled state of modules. I would like to state categorically that this is a terrible, awful, no good, very bad idea that I will fight with my very last breath. For an end user's sake, there *has* to be a state called "disabled." What actually happens when that state is achieved is up to core developers, of course, but you cannot possibly explain to an end user "enabled, uninstalled, or uninstalled, with caveats."
#65
Before we all panic, I think we actually have an idea that's viable for everyone if we bring together the different threads:
1. there is still a 'disabled' state, caused by unticking a module's ticky box
2. disabling a module causes all its data to go into hibernation in a bucket.
3. re-enabling / waking up a module requires that module to re-introduce its hibernated data into the DB in a way it knows best
4. uninstalling a module wipes its data entirely. You can only uninstall disabled modules -- BUT! this is not because of the architecture, it's a designed UX pathway to guide the user.
#66
@webchick
The point of that comment and remark was to get UX people involved while there's still no final plan or even a first patch, because UX is a big factor in how this is implemented.
Still, I fail to see how installed / uninstalled are such an unnatural concept. The fact that there's a middle step for uninstalling is never something I found clear.
We've just had a huge IRC discussion (catch, Bojhan, Michelle, merlinofchaos)
Bojhan has the opinion that the flow should be disabled -> uninstalled, even if the "disabled' step is not needed or might not mean anything.
It is still up for discussion whether that should be one step (disable & uninstall) or two. It's a matter of how the modules page is (re)designed.
However, we still agree that only enabled modules can be uninstalled. So catch had the idea of silently re-enabling the module just prior to uninstall.
The icky part here is hook_enable / hook_disable (and hook_modules_enabled / hook_modules_disabled). Those hooks will fire like crazy.
catch and I agreed that it might not be a bad idea to just kill them. They are only misused in contrib anyway.
So, plan of acton could be:
1) Make disabling non-destructive, the idea I mentioned previously
2) Remove hook_enable / hook_disable (and hook_modules_enabled / hook_modules_disabled)
3) Kill the active / storage_active Field statuses. Since hook_field_info() runs for disabled modules, the field type always exists. Same for storage
4) Make uninstall enable the module before continuing.
I would also like batch api / queue api powered field deletion when the field type module is being uninstalled.
What I dream of is being able to go to the Modules page, check 10 checkboxes, and click uninstall. I get a progress bar, the deed gets done, I rest.
No more reloading the modules page 20 times, running cron, seeing what depends on what.
Bojhan can modify that plan to satisfy the UX gods, but the point still stands.
There would be side effects to that plan: disabling the PHP module would still keep the filter it provides, making it kinda useless.
[EDIT: Added the plan of action]
#67
Of course, the problem with partial disabling where some hooks run is exactly that; some hooks run.
So people would probably argue that hook_entity_load( ) should always run, and if there's a bug there, it will stay present even if the module is disabled.
I don't see any way of going around that. The fact is that disabled modules are a fundamental problem at the heart of Drupal, and fixing that while keeping everyone happy will lead to a few WTFs, even if it succeeds.
#68
We need all disabled modules to be active to be able to properly uninstall. We cannot fake "disabled", because this would lead even more WTFs (why does it still break my site??). ouch.
Still, silently re-enabling them for uninstall seems to be the only way out.
#69
re @bojanz #66:
The issue is not whether hook_field_info() will run for disabled modules. It is that hook_field_delete() must be reachable when the field data is purged - which can take an arbitrary amount of time.
#70
> It is that hook_field_delete() must be reachable when the field data is purged - which can take an arbitrary amount of time.
We have a queue system in core already. Why not use that?
Situation: module A is deleting entities, which affect a field marked as inactive and belonging to disabled module B.
Stick an operation in the queue that says 'Hey module B, when you wake up out of hibernation (if ever), you need to perform this task'.
#71
"this task" being "do your job purging those N field values, that belonged to nodes that got deleted 5 weeks ago".
That means we're able to preserve the actual field values somewhere and hand them out later on at a time when the nodes themselves (and possibly the node type itself) are long gone.
The logic for maintaining that 'in between' state is really not something I'm willing to write or maintain within Field API. Just too much hair pulling and possible race conditions.
I don't even think that the part that does "on field delete, extract the field values out of the main storage and push them somewhere out of the way but available for values purging at an arbitrary later date" is doable with Mongo field storage.
#72
Well, it's either remove the disabled state (something which webchick said she is explicitly against) or soften the problem by making the state less "disabled" :) It's a compromise I'm willing to make.
True. As I said in #66:
This would solve it nicely.
#73
Yes, it would be a better DX to haver on top of the current D7 state (field type module un-disableable until there are field data) - a D7 patch is completely possible
but -
No, it doesn't solve the fact that hook_field_delete() needs to be present (and therefore the module enabled) during all the time data is being purged.
#74
Well, we already said that we want to make uninstall work on enabled modules. That's the basic point of this issue.
So purging happens at the beginning of uninstall, the hook is still present.
#75
Here's an idea which won't address everything but which I think could be useful for some of the problems we have here:
Introduce the concept of a clean-up jobs queue, and the concept of the site being in a 'dirty' state while clean-up jobs remain outstanding.
- any module may add jobs to the clean-up queue
- cron runs will gradually empty the clean-up queue
- the clean-up queue can be completely dealt with in a batch operation, similar to going to the updates page.
- while the site is dirty, certain tasks may not be performed. These would include disabling/hibernating further modules, or enabling new modules, or certain entity operations. The admin UI would explain why these actions can't be carried, with a link to a help page and to the page to run the clean-up batch.
In essence, this is a generalization of the situation we currently have where deleting things leaves the modules page telling you that you still have dependencies, which then silently vanish on cron runs.
So for example, deleting an entity with inactive fields logs a clean-up job for deleting the row for (entity_type, id) in that field's table.
#76
Just came across another fun bug that reminded me of this issue: #1451072: Deleting a comment author while the Comment module is disabled leads to an EntityMalformedException error after it's reenabled
#77
to follow David_Rothstein I also have another funny one :-)
#1458338: Media file widget causes unrecoverable error after disabling and re-enabling media module
I think it marks out, what webchick and catch are saying in #4 and #7. I think both are right. I can agree with webchick that we should keep in the options for disabling a module since we shouldn't force users to reinstall or move folders for a short -off- time, but as catch also says: the only data we can guarantee atm after re-enabling as -kept-, is configuration.
xjm: thanks for pointing me to this issue. But I would like to vote for keeping focus on the issue title to find a better solution for disabled modules, instead of discussing module uninstallation and reinstallation as an option for this. Why not adopting the method how configuration is kept, even if it would be for keeping at least module depending fields and its recognition afterwards? I dunno if possible, didn't took a look into it yet. ... Just another thought ...
#78
Okay, time to get this moving again.
Talked this over with merlinofchaos (and later chx as well) at DrupalCon, and we narrowed down the fix a bit, concentrating on the important point.
This is a strictly code change, with no UI changes.
The crux of the matter is what David said in #42, quoting:
The key concept here is Data integrity. Right now, if you disable a module, all hell breaks loose because data integrity isn't maintained, field types and entity types disappear, etc.
The proposed fix is this:
1) All installed modules get loaded / included at bootstrap, not just enabled ones.
2) Modules declare hooks that are essential to maintaining data integrity, by using hook_hook_info(). These hooks are hook_field_info(), hook_entity_info(), hook_entity_load(), etc. Basically, anything relating to CRUD and providing "building blocks".
3) When invoking a hook (module_hook() / module_implements()), hooks are invoked in enabled modules. Also if the hook is declared as "essential", it is invoked in disabled modules as well.
So, when a module is disabled, its hook_form_alter, hook_menu, hook_views_data and other "UI level" hooks don't fire, it disappears from the UI.
However, if it implements some of the essential hooks, then those hooks still run.
I have a half-broken patch prepared (as always, the installer is the one creating a problem. Sad, sad code).
Gonna hack on it some more tonight, then post it.
The issue we need to tackle first is: #1503224: Cleanup module_list(). Those cleanups are necessary if we want to do this correctly.
A followup issue is #1503314: Remove the concept of active / inactive (field types, storage) from Field API (since it would no longer be necessary to maintain the active status, all fields and storage engines would be active).
Removing the usability tag since there are no UX changes here (although I'd love to see us separate enabled / disabled and installed / uninstalled as concepts in the UI, eventually).
#79
Rolled against #1503224: Cleanup module_list().
Still broken (install fails).
The "essential" parameter can be called something else.
Needs some code in entity_hook_info() that registers entity type specific hooks (such as hook_node_load()) -> Can we call hook_entity_info() from hook_hook_info()?
Also remember that some field hooks actually function as callbacks (hook_field_load, hook_field_insert, etc), so they are always called when the module is included, no need to declare them. Hoping that whole mess gets fixed when Field API moves to plugins.
In a plugin world, I see us declaring certain plugin types as essential. So a field type would continue to exist, but a field widget doesn't need to.
#80
The last submitted patch, 1199946-fix-disabled-modules.patch, failed testing.
#81
"Field API --> Plugins" is in progress, relying on the current (in-progress too...) state of the Plugins API.
In that approach, it will all depend on the "Plugins discovery mechanism" whether Plugin Implementation classes (i.e. field type handlers) provided by disabled modules can be located, instanciated and have their methods invoked.
That discovery method (who holds a registry of available plugins and how is this list collected) is very much in discussion right now - CMI, info() hooks, class annotations... See #1497366: Introduce Plugin System to Core, more specifically comments #5, #7, #12.
Also, think the recent "PSR-0 loader for module-provided classes" registers namespaces on disabled modules right now.
#82
As it is now, when I have an installed (as in placed & extracted in the file structure - not necessarily enabled) module that is known to cause issues, I simply leave it disabled in the server. Even if I can't get to the modules page to disable it, I've learned to do it straight from the db and the system table (to get around WSODs for example). I don't have to worry as to which part of the module was broken - be it the UI or API code it now simply doesn't load at all. The reason I leave it disabled is because I want to be notified of any future version updates and try to see if the problem I was having is still there simply by upgrading and (re)enabling the module.
What does this proposed change here mean for user like me that are in this habit of keeping their problematic modules disabled, but still under
/sites/all/modules? Do I have to completely remove them (as in also delete them from the file system) in order to be sure that it's not them causing any other issues?There's also the category of modules that serve for a single purpose (migration, import, troubleshooting etc) and that it is recommended to disable them when not needed. Until now, we used to simply disable them but still keep them around just in case we need them again. Will we now have to also make sure we deleting the actual files from the server too?
#83
> Do I have to completely remove them (as in also delete them from the file system) in order to be sure that it's not them causing any other issues?
That means that either:
a) you have to run the uninstall procedure on them. Your data is lost.
b) you just move them out of the way and hope for the best. So our troubleshooting procedure now involves manually moving files, and we've reinvented 'disabling' a module to be more complicated and just as bad.
#84
@yched
Yes, I'm aware of that discussion. Doesn't affect this issue much. I'm personally for using annotations. Extending that to hooks would allow explicit registration and metadata above the hook itself, which would be awesome.
@klonos, joachim
The major implication is that the module file is always included, and present in the memory of each request. So you can no longer gain a performance boost by just disabling a module. Generally, it would behave the same as before, most hooks don't run, so most problems would disappear by disabling the module. There's always a slight chance that the bug is actually in one of the essential hooks (entity_info, or whatever), so there is a possibility that disabling wouldn't solve it, won't deny it. Still, it's really small.
That said, I've been looking into solving that as well.
Here's an alternative patch. It introduces a "hibernation" state ({system}.status == 2). When a module is being disabled, we check if it implements one of the essential hooks, and if it does, we put it into hibernation, which works as I described above (module file included, only essential hooks run). If it doesn't implement any essential hooks, it gets completely disabled and isn't included, same as before.
Compared to my previous patch, this one actually works in my testing.
So, I now need reviews (about everything from docs to key naming, approach taken, etc).
1) Which approach should I continue? Hibernation seems like a less invasive change. Wondering about the terminology, and whether it is good enough.
2) If I continue with hibernation, there's a matter of:
+ // Certain hooks should be invoked in hibernated modules, but shouldn't+ // trigger hibernation themselves.
+ // @todo Make this generic? Another key in hook_hook_info()?
+ $ignore = array(
+ 'schema',
+ );
I also need help on getting #1503224: Cleanup module_list() to pass tests.
#85
You could also mark them as uninstalled (i.e. status = -1) in the database without actually running the uninstall hooks. I think that would achieve pretty much the same thing as disabled modules do now (the difference would be that the module's update functions don't run but that's probably more in line with what you want anyway).
That kind of thing would not be "officially" supported any more - since as we know, for a significant number of modules the concept is broken anyway. However, I could imagine a Drush command that does it? .... So people in the know could still do old-style disabling of modules for debugging purposes, if they're willing to assume the risk.
#86
Oops, crosspost.
Interesting, need to think about that some-modules-can-be-disabled-and-some-can-only-be-hibernated approach!
#87
I can't actually do a patch review, but the logic behind the "hibernation"/"-1 status" seems the best approach to me. Thanx for coming up with it ;)
#88
...sorry for being the reason to bury this for so long. So many x-posts :/
#89
While you guys are speaking about data integrity, you may want to have a look at #911352: Document that foreign keys may not be used by all DB drivers.
#90
#773828: Upgrade of disabled modules fails seems like a sub-issue or follow-up to this, so I've demoted it.
#91
As you can see, this issue won't ever happen, so there's no point in demoting other issues for it.
#92
Now that #1503224: Cleanup module_list() was fixed and in, I think the next thing... is for someone to layout some detailed next steps to provide some direction.
#93
Countless of issues are currently bumping into the problem space of disabled modules. This needs to end.
Thinking through the problem space based on D8's architecture leaves me with only one possible conclusion that is a radical change compared to now. Instead of duct-taping the current bogus architecture and making it even more complex, we need to revise the concept entirely. I did not want to hi-jack this issue with my proposal, so I've created a new issue:
#1883658: Remove the concept of disabled modules
Feedback welcome. (Again, please over there, so in case that issue won't fix, we can return to here.)
#94
postponing this while we give that a go.
#95
Marked #1883658: Remove the concept of disabled modules as duplicate, re-titling this one to describe the problem rather than any proposed solution.
Quoting fago from the other issue since that sums up my current position (which is the same as 18 months ago):
People arguing for disabled modules please read #1017672: D6 to D7 update process permanently deletes comment bodies and other data, and throws fatal SQL errors and #895014: All fields of a node type are lost on module disable if you haven't already, this isn't a theoretical issue.
#96
Answering webchick from the other issue:
There's two reasons why modules can end up disabled and cause problems, which have nothing to do with how much people love the feature or not:
1. Upgrade instructions explicitly tell people to disable modules, they don't have a choice or they're "doing it wrong", see previous comment for the kinds of fun that causes.
2. The uninstall tab is relatively obscure and hard to find (and not even an option for modules that don't define hook_uninstall() which I bet many field/plugin-defining modules don't since they're not maintaining their own schema), so plenty of sites end up with a bunch of 'disabled' modules that have long since been removed from the code base.
#97
I'm with @Jose Reyero in the other thread. Data integrity checks like #1451072: Deleting a comment author while the Comment module is disabled leads to an EntityMalformedException error after it's reenabled should be handled in hook_enable() / hook_disable(). We could also go with hook_info changes that flag data integrity issues in system_modules_disabled() so that we can give better context to end users. For instance, "Hey, you're about to delete Commerce X, doing so will delete the following data....".
But running data "integrity" code in the background is a misnomer. If I don't want the functionality, I don't want its data. (Remember, there are cases where we disable and remove modules because they cause database bloat and kill performance.) We just need to be more clear from an API standpoint how to guard against integrity violations and what a module author's responsibilities are in terms of:
* Preventing data integrity failure.
* Rebuilding data when necessary.
* Warning the end user about potential data loss.
In the node access world, we've had to deal with this problem forever, and it is the module's duty to make one's data enabled, disabled, and rebuildable.
Plugin dependencies are a separate issue, and, presumably introduce hard dependencies in module .info files.
Fixing the upgrade process is also a separate issue.
#98
If you want disabled modules, then you expressively opt out of staging. Which means both content and configuration.
The two facilities are fundamentally incompatible. In pretty much the same way they're incompatible with updates.
Truth is, by disabling a module, you're enabling plethora of code consisting of dirty hacks that are currently scattered across the entire system, which normally does not run. The majority of that code involves brutal stop-gap fixes, because the system cannot reasonably know how to resolve conflicts when there's data for functionality that does not exist.
In an as abstract and modular system as Drupal is today, disabling a module factually means to uninstall it step by step under the hood, since all data that isn't exclusively owned by the module needs to go through APIs of other modules, and those modules have to make hard decisions in order to keep the system running.
Unless you record and replay every single change that happens to your site, it is impossible to replay the exact history that would be required to maintain data integrity of disabled modules. The more data changes, the more unrecoverably broken is the data of disabled modules. That's why disabled modules represent data loss. If you don't lose it immediately, then you lose it over time.
The problem with all of this is that we're putting this logically and architecturally broken facility in front of innocent regular users. Worse, we even try to "support" it — even though there's factually nothing that can be supported.
As mentioned in #1883658-8: Remove the concept of disabled modules, there's nothing wrong with Devel (or even a devel-alike module in core) allowing you to remove a module from the system without uninstalling it. A matter of minutes to implement. But it needs to be crystal clear that you're effectively hacking the system and that there's absolutely no guarantee that the configuration and data of the removed module will remain to exist or be functional.
The problem is that essentially this hack is the built-in, default user interface for modules in core today and worse, we're even forcing users to use the facility, and worst, we make it look like as if it was a safe and sane thing to do.
The problem is that even the transitional disable » uninstall state in itself breaks APIs across the board, and consequently, data integrity in other, enabled modules.
Disabled modules worked well in a time when every module was an atomic, isolated isle, mostly doing its own thing, with its own data, and its own functionality. Today, the world of Drupal modules is too abstract, too modular, and too integrated, in order for that concept to work.
It's five minutes to midnight to get rid of it.
#99
At the risk of sounding like a marketing droid, if the other big systems in our market space let you disable things and we remove that capability, that's not going to look good. "Pfft, why can the WP developers figure this out but those oh-so-smart Drupal people can't?" (You know that's not a fair comparison, and I know that's not a fair comparison, but that doesn't mean the comparison won't get made.)
#100
Is that even the case though? Last time I looked at WP (admittedly some time ago), various widgets required hard-coding function calls into the template. You can't disable that without hacking the code out of your template again.
Also like sun says (and apparently me in July 2011 in this very issue) we can still provide the facility for this, just hidden away in devel with a warning instead of baked into the entire system.
#101
With the approach proposed by @sun in #1883658: Remove the concept of disabled modules can we get any replacement for the troubleshooting ability we lose? Everytime a non-developer opens an a bug report I can't reproduce, I ask him to disable contrib modules one at a time until she figures out which one is the cuplrit. This won't work with the new proposal.
#102
> Unless you record and replay every single change that happens to your site
I still like the sound of this. We already record actions for future playback with the core job queue. This would just need to be able to handle invoking a given module and hook.
#103
@plach I think your choices would be: ask the person to make a backup, and then A) uninstall the contrib modules or B) ask them to install devel and use it to disable modules. I'm guessing we would put up a good doc page on how and why to do that.
For PR: I wonder if we could rename "uninstall" to "disable" so we would still have disabling... and then call removing the code either uninstalling or removing the module.
Sounds like we might have a concrete sub task: do a survey of a couple other systems and see what terms they use, and what they mean.
#104
Ok, this would work for me.
#105
I've revised, merged, re-structured, and clarified the issue summary, in the hope to clarify the overall problem space for everyone who hasn't been neck-deep involved in all the critical bugs of the past years, nor any of the major configuration system and plugin system issues that are completely stuck right now.
The current issues are of primary concern here, as we're running into conflicts with disabled modules being officially supported, but it is factually impossible to support them, because finding a solution is identical to trying to solve the equation of
0x = 1. The only possible way would be to introduce tons of code that consists of lots of dirty conditional hacks that need to be re-executed at runtime, and which would not actually help or improve the situation of data integrity and data loss in the end - all it'd do is to prevent fatal errors.In short, we're trying to make sense and improve the new architectural features of D8, but the facility of disabled modules presents a major blocker in various issues. If we want the new D8 features to be feature-complete and architecturally sane, then we need to do something about this issue. And quickly, because time is running out.
So far, pretty much all resolution proposals involved to replace the current notion with something else, because the current concept is utterly broken.
Therefore, the most logical first step would be to remove the current facility. Second, reinvent and redesign it from scratch.
At least that's what we'd normally do for any other facility in the system, if it would show the same track record of critical bugs and current unresolvable problems. It would especially make sense in this case, since neither the intended audience nor the actual use-cases of disabled modules is clearly defined, so all we're really doing currently is to duct-tape a preexisting and broken concept of which no one really knows why it exists in the way it exists, and everyone who wants to retain the current implementation blatantly ignores its brokenness. We can do better. :)
#106
This have been a pain for so long and I hoped we could fix this in D8. I see no other way of fixing this then ripping the functionality out of core. Let's get out of this mess.
- Turning of the module will remove settings and data and clean up any run states.
- Uninstall remove the modules file from local system. This might fail if webserver is missing access.
#107
Okay, I'm going to ask a question here.
Known bad scenario:
1. Disable module
2. Make significant changes to entities on the site
3. Re-enable module: stale data may be inconsistent, causing critical bug reports
Proposed scenario:
1.
Export module dataBack up entire site2. Uninstall module
3. Make significant changes to entities on the site
4.
Re-install module5.
Re-import module data.Restore entire site to backup at known good stateAt this point, the stale data may still be inconsistent with the new state of the site, depending on the nature of the changes made. My question is, will the data-import code for modules be better equipped to detect and repair inconsistencies that may have developed since it was exported, compared to what modules can do today with their existing data during hook_enable?If the answer is yes, then I am all in favor of the proposed changes. If the answer is no, then we have only moved and slightly obscured the problem, not solved it. Mind you, it might still be an improvement to obscure the problem if it cannot be fixed, but we should consider the cost vs. the benefit in that instance.Edit: Sorry, I missed an important part of the proposal. I am in favor of option A.
#108
@@ -0,0 +0,0 @@-If the answer is no, then we have only moved and slightly obscured the problem, not solved it.
+If the answer is no, then we have removed the problem, and we don't try to solve it. We purposively remove an architecturally flawed design. We allow us to rethink and come up with new ideas and design proposals.
And the answer is no. Let there be some Einsteins to think different, think big, and beyond imagination. :) As long as we meanwhile unblock progress for others and keep the system's architecture clean.
#109
Patch in #108 "Needs work": :)-If the answer is no, then we have only moved and slightly obscured the problem, not solved it.+If the answer is no, then we have removed the problem, and we don't try to solve it. We purposively remove an architecturally flawed design. We require the use of contrib tools to re-introduce the flawed concept at users' own risk.
I think this is a plausible strategy, and probably an improvement over the current situation. The data import problem is going to remain a difficult problem that will be difficult to document and describe, but perhaps that is a discussion for a separate issue.Edit: My mistake. I am in agreement with #111.
#110
At the risk of duplicate content, I'm syndicating this once more for architectural review. Apparently, some people missed that the other issue was marked as duplicate.
#111
Actually, this seems like a pretty reasonable suggestion to me. I'm not sure we could safely expose it in the UI, but it is something that's very easy to write a handbook page for. (Yay CMI.) So I'd say documenting this troubleshooting technique clearly in the handbook would resolve my earlier concerns about removing the disabled state. I think proposed solution A in the current summary is the way to go.
#112
d.o--
#113
This is strategically important enough that we should run it past Dries.
#114
Here's another one #1887904: update_retrieve_dependencies() only considers enabled modules, related to an old favourite #194310: Check / run updates of disabled modules.
#115
By reading many of the comments in this case I have the impression that some are thinking that users are disabling/uninstall a module for no good reason. But in real world we must more expect that a module is totally broken piece of crab that has only or at least very many malfunctions and does not behave as intended. Additionally to this users like to get rid of this broken module and remove all data from Drupal to get back to a stable Drupal installation. This is why Drupal need to remove all data related to a module if it's uninstalled.
I do not like to disable a module and keep it on my disk until the 8.x get's upgraded to 9.x. I just like to remove all this stuff from my disk, including everything saved in the database by this broken module.
We have so many modules that are not properly maintained and so many people who cannot code, nor debug and so on. A module may discontinued because of security issues or lack of developers... I guess we have all of this, but maybe a proper uninstall.
#116
@hass, no-one's talking about removing the ability to uninstall modules, only the ability to leave them disabled without either unintalling or re-enabling (in fact we're not even talking about removing that necessarily, just making it much less prominent, slapping a warning on it that it's for temporary troubleshooting only, and not continuing to have runtime code attempt to support modules being in that state).
#117
It sound like we agree and might be at a point to start with a patch. Shall we try a patch for proposal A, or do we now want to continue discussing not if disabling is broken beyond compare, but now: what solution to pursue?
#118
Oh sorry. I missed that we are waiting for feedback from @Dries.
#119
Dries, ask bjaspan to recall his memories of the big discussion in Coppenhagen about upgrading disabled modules. When he chases you out of his office, that might be enough to convince you that disabled modules are a problem.
#120
I just now skimmed this entire thread, and what stands out to me is that the only identified use case for disabling a module while retaining its data and configuration (so that when the module is re-enabled, you don't need to redo all that data entry) is for temporary troubleshooting. And that for anything other than temporary troubleshooting, fully uninstalling the module is what the site administrator actually desires (or should desire), but doesn't always do, because it's an extra bunch of clicks with the current UI.
If we believe this to be true, then I think the issue summary's option A is clearly the best choice.
But I'm concerned that real site administrators are under-represented in this issue thread, and that we haven't considered real world use cases for when hibernating (#46) a module is strongly wanted. Without knowing what and how common those use cases are, it's hard (for me) to evaluate whether option B is better. Perhaps Dries magically has a feel for this, but in case not, how can we gather this information?
#121
In addition to troubleshooting/debugging, some use cases for temporarily disabling a module include:
a) Suppressing errors during infrequent conditions unanticipated by the module (e.g., network communication problems for a module not built to handle those gracefully).
b) Improving performance during a heavy traffic spike (e.g., disable comment.module or something like that to get load under control, but without deleting all existing comments).
All things considered, I'm leaning towards option A (i.e. remove enable/disable). If we went with A, I believe we'd see (1) solutions that help people preserve their configuration files upon uninstallation and provide instant re-configuration upon installation, (2) maybe even a module-level kill-switch setting in a module itself, and (3) versioning of configuration. We could discuss how important something like (1) is and if it should be part of core.
Option B (i.e. required hooks vs cosmetic hooks) is not a complete solution; it still can't guarantee data integrity. Plus, I think to a large extent we can already do some of this through permissions and existing configuration options. I feel option B is a non-starter.
There may be an option C? The only time it may be safe to disable a module is when you are in maintenance mode and no content is created unexpectedly. Maybe you can only disable a module when in maintenance mode, and you can only get out of maintenance mode when no modules are disabled? It still offers no true data integrity, but it certainly provides a safer environment for people to disable modules. It would work for troubleshooting, but not for use cases (a) and (b). This solution isn't perfect either but may be worth exploring a bit more.
#122
Another data point: In some exciting client work this week, we had a site-killing "function not found" error that we were able to cure by disabling and re-enabling one of our custom modules. There may have been another way to do it, but disable/enable is one way out of an ugly circular dependency situation.
Only allowing disabling of modules in maintenance mode sounds like an interesting possibility, since that's already the "stand back, I'm breaking things!" mode.
#123
OK so to get started on this, I think the following are relatively uncontroversial at the moment?
1. At the API level, allow a module to be directly uninstalled (i.e. no intermediary disabled state).
2. Build a separate UI for disabling and enabling modules - we can then make the decision about whether this is possible outside maintenance mode (I quite like this idea since it enforces the warning).
3. Remove the ability to disable modules by unchecking them on the admin/modules page - we still might want to leave uninstall in a tab for now.
#124
There are a lot of use cases for a site administrator to disable a module (and not just temporarily). Also, most modules actually can be safely disabled and turned on again at an arbitrary later time. The bug being discussed in this issue is not a widespread problem that many modules face; it's only a serious bug because when it does happen, the consequences can be catastrophic.
Given that, removing the functionality altogether seems like an unfortunate solution, even as an "intermediary" step.
If we don't want to fix this directly (and I don't really understand the specific criticisms of option B that a couple people mentioned above, but I do think option B is complex and possibly not worth the effort) then it seems we should look at how to work around it. Making the UI for uninstalling modules more prominent and the UI for disabling modules less prominent definitely sounds like a good idea to me, but simply slapping a warning on all modules that you shouldn't disable them except temporarily is crying wolf (and likely a warning that people will ignore exactly when they shouldn't).
Earlier I think someone suggested just making it a flag in the .info file or whatever, to allow a module to declare that it is not safe to disable. This does put some burden on module developer, but not really a whole lot, and it can be addressed with documentation. I don't think most modules would need this flag anyway.
So what I would think might work is:
Hopefully this is an achievable (if not perfect) solution, with some usability wins in that it makes it easier for administrators to uninstall a module rather than simply disable it (which in most cases is probably what they wanted anyway).
Actually as of Drupal 7, that's no longer true anymore; as of #151452: Uninstalling modules does not follow dependencies and can lead to WSOD any module can be uninstalled via the user interface. But it is definitely hard to find the tab and a pain to do, so most people in my experience don't bother doing it.
#125
I've come up with the following kinds of "data" that modules need stored, along with a representative module for each one. Of course, some modules store multiple kinds of data.
@David: for which of these kinds of data/modules do you consider disabling to be catastrophic vs. not?
I wonder if contrib (e.g., http://drupal.org/project/deploy or something like it) could even do this for entity data, given the serialization/deserialization work being added to D8. That work has been primarily motivated by web services, but no reason contrib can't leverage that to serialize/deserialize to/from files.
#126
I already started writing a module that will export entities to serialized files, then re-import them into a fresh site. I'll be using this for a demo of replicating an entire drupal site, content and all, into a freshly installed Drupal instance. So yeah, there's a lot of potential there honestly.
#127
Another example from [#1822048:1], note that in Views' case it goes to great lengths to handle the case of disabled modules, since a view has myriad dependencies across multiple modules which currently may disappear at any time.
If the access plugin work gets in (i.e. access to blocks and potentially router paths governed by configurable sets of plugins which can be provided by multiple modules), then that's going to be a much more widespread case.
#128
Rules in 7.x has the same problem to solve. What I did there was maintaining a list of modules the configuration depends on and one of the module gets disabled, a) the "plugin" gets skipped when dealing with rule and b) the rule is marked as broken in the interface + skipped from evaluations + a warning message is shown. When the module is re-enabled, the rule is re-enabled and the admin notified with a message.
Imo, being more conservative with checks here is a good thing. In fact, I even ran into the situation on a client site where views cache got broken and so a view was executed without an (possibly important) filter! We should better fail pro-actively.
Still, I'm not sure how this relates to the "disabled modules" feature and so to this issue. For this problem it does not matter whether the module is just disabled or uninstalled?
#129
I think that the question of
is a non-obvious, secondary goal of the proposal here. Essentially, the hope is:
Or in other words:
#130
sun: I don't think that removing the "disabled but not purged" state will do anything to make it easier to "purge" data. If you remove a views argument plugin module, you still need to either
1) Magically edit every view to remove the reference to that plugin, possibly causing it to break in exciting and possibly data-exposure-security-issue-causing ways.
2) Gracefully handle in Views the possibility of "OMG where did that plugin go?"
And read "Field", "Rule", "text format", or various other systems above as needed. Just saying "We don't support disable but not purge" doesn't make "purge" any easier.
#131
One advantage for the purge situation is that the module is still enabled when you uninstall it, so plugins can still be discovered, info hooks are still executed etc. Right now when a module is uninstalled, Views (or whichever module consuming plugins) doesn't have a decent way to know which plugins are provided by the modules being uninstalled since it's not really supposed to load that dead code and execute it itself.
#132
Thats a pretty major improvement. I had not thought of the benefits that uninstall will see. Lets proceed.
#133
Also when disabled modules are uninstalled it can get a lot more complicated than that, the above was only the most simple example:
For example module x provides a plugin used by a View, then Views module is disabled, then module x is uninstalled. hook_modules_uninstalled() should still run for Views module, but then Views module can't even rely on its own API/Services whatever being available at that point.
Bundle-providing modules are required to run field_attach_delete() bundle in hook_uninstall() for their bundles, but if field module is optional + disabled then that function isn't available.
#134
I think #123 is fairly uncontroversial, and I would perhaps add to it:
4. Do not allow modules to be uninstalled or update.php to be run while any modules are disabled (#21).
5. Further discuss whether to allow some modules to flag themselves as "safe to disable" (#124), and what such a flag should allow.
@David: what do you think? With these 5 steps, do we still leave open the possibility to have your concerns addressed?
#135
Perhaps we also should keep a track of module that just got removed without getting uninstalled, the user being stupid and deleted the module directory, and lists these as broken modules? Then recommend the user to restore the module and to uninstall it properly or be ready to face a sea of pain.
#136
Doesn't need to be on Dries' plate currently.
#137
This would be anything in the system config but not in the file system, should be very doable to present that. At the moment it causes quite serious issues leaving a site in that state, i.e. #1081266: Avoid re-scanning module directory when multiple modules are missing.
#138
No hard and fast rule, just any module which depends on responding to another module's events and can't (or isn't willing to) deal with the possibility that it failed to respond to some, cannot be safely disabled. Basically, you just have to look at your code and say "what would happen if these hooks don't get invoked for a few months"? Comment module, for example, probably shouldn't ever be disabled (at least currently) not because it defines an entity type, but rather because the entity type it defines is heavily tied to nodes and the code isn't really written to deal with the possibility that the comment and node records are out of sync.
Core modules tend to be complicated and to depend on each other a lot and so I would expect them to be more likely than average to have these problems.
For modules like Field, Views, etc. (which store data provided by other modules and don't want those other modules to ever be disabled either), this could be achieved with documentation, or perhaps hook_system_info_alter() to edit the flag and prevent all of them from being disabled at once.
Somewhat, though I don't understand the idea of having two separate UIs... this would involve adding yet another independent page to the admin interface which also lists modules similar to how admin/modules already does? That's why I suggested just doing it as a checkbox on the confirmation form (seemed a lot simpler).
#139
From my point of view, it's way simplier than all that.
If data of disabled module is prone to corruption, module should clearly notify about that and suggest deinstallation of data.
If freshly enabled module finds it's old data to be corrupted, it should repair it. If repair is impossible, ask admin to uninstall old data. If it's unsafe, ask if admin wants to try repair or not.
If module fails to do that, it should be considered a bug in that very module.
No work on architecture needed, maybe except making sure that needed hooks are there.
Seriously, if I disable a module all I really want is it's hooks not firing, none of them. If module have UI that makes sence to be turned off when the hooks are there, UI should be a submodule (see views approach). And if you will remove true module disabling, ppl will rename / copy out module's code to stop it from working when they need so. Way less safe. Way less convenient. So removing "disable module" option from Drupal provides none at all improvements of data integrity, just encourages risky workarounds.
Long story short:
1) Disabling modules works pretty OK
2) If module can't take care for it's data, if it can't it's module's bug
3) Be careful not to encourage dangerous hacks
#140
I know little about Drupal 8 and just skimmed through this topic, but maybe some of my suggestions will be of use (hopefully I'm not just repeating what has already been brought up).
It seems like there are 2 main concerns here:
1) Disabled modules are broken beyond repair.
2) Disabling modules is a useful feature and should not be completely removed especially since no. 1 does not apply to every module.
This leads me to think that every module should deal with the problem themselves while core provides the necessary means to do so. The way I see it handled roughly is that disabling and uninstalling are split apart: you can do either separately from the other. Every module that can handle being disabled (either no chances of any issues arising or they can be fixed when the module gets re-enabled via hook_enable for example) can be disabled. Modules that would break one way or another could tell Drupal that they cannot be disabled at all (only uninstalled). Maybe there could also be a possibility for module X to tell that modules Y and Z must be disabled or uninstalled before this module can be disabled.
Modules could also block certain tasks from being performed when disabled if necessary (forcing users to either uninstall or enable them before proceeding). Since code of disabled modules shouldn't be run there could be a possibility to mark this in the .info file. Another approach could be to to introduce additional hooks and/or a "modulename.disabled" file for code that will be executed in disabled state. This file should be exclusive for the disabled state when no other module files are included so that a module can perform the minimum tasks that are needed for it to not break. Being exclusive it might be possible to duplicate necessary hook implementations there to just handle disabled state.
Might be too early or the wrong place for this, but in case it's not and the suggestion or something similar goes through it might be nice to have the module page reorganized as well:
*) 3 separate tabs: first for enabled, second for disabled and third for not installed modules.
*) every tab has only the relevant buttons (disable/uninstall, enable/uninstall, enable) with empty checkboxes by default that allow the user to select the modules the action should be performed on.
*) confirmation page for the action would show the necessary messages (for example "module X will not be disabled, because [some message the module itself provides]" or "disabling module Z requires modules Y and Z to be disabled as well - do you wish to continue").
#141
Wouldn't it be enough to just make hook_disable() required, make Drupal to flatly refuse enabling anything not implementing it, and report bugs every time when re-enabled module finds it's data corrupted?