Problem/Motivation
Installing/uninstalling modules triggers container rebuilds. Mid-request container rebuilds have proven to be the source of weird bugs in the past. Isolating this functionality into a self contained, strictly stateless service will help avoiding this kind bugs in the future.
Proposed resolution
Extract install()
and uninstall()
methods to a separate class/service.
Remaining tasks
Review.
User interface changes
None.
API changes
- Introduce new
module_installer
service (implements\Drupal\Core\Extension\ModuleInstallerInterface
). - Relocate
install()
method fromModuleHandlerInterface
toModuleInstallerInterface
- Relocate
uninstall()
method fromModuleHandlerInterface
toModuleInstallerInterface
Impact on core/contrib: Installation and removal of modules is not something which is commonly performed from within production code. However, developers might need to update their web-tests.
Original report by @dawehner
This seems inconsistent - modules have a handler, themes have a handler and manager. For modules the handler fires hooks and for themes its the manager. I get why this is since the alter only get's fired for the active theme - which is something the ThemeManager knows about but not the ThemeHandler. As @dawehner said in IRC - perhaps we need to refactor ModuleHandler. :(
Comment | File | Size | Author |
---|---|---|---|
#76 | 2324055-76.patch | 139.28 KB | cilefen |
#76 | interdiff-74-76.txt | 717 bytes | cilefen |
#74 | 2324055-74.patch | 138.81 KB | cilefen |
#71 | 2324055-71.patch | 138.8 KB | dawehner |
#71 | interdiff_7998.txt | 2.22 KB | dawehner |
Comments
Comment #1
sunI'm not sure whether this is a (partial?) duplicate of this related issue?
Comment #2
dawehnerProbably not that much, this sounds like a concrete task in contrast to the other one.
Just wanted to open a follow up on the theme init issue.
Comment #3
catchComment #4
dawehnerLet's see how this works out (the installer is broken but beside from that ... ;) )
Comment #6
almaudoh CreditAttribution: almaudoh commentedJust a quick scan through, found one nit:
s/ModuleHandlerInterface/ModuleInstallerInterface/
Comment #7
dawehner@almaudoh
Good catch.
Comment #8
Wim LeersThis patch looks great!
But in IRC, you mentioned this is a potential performance improvement patch. That potential comes only from moving some code into a
module_installer
service, out ofmodule_handler
, and hence needing to load less code, right?While this patch is 120K, "all" it really does, is introduce that
module_installer
service and move ~400 LoC into it.So unless I'm missing something, I think the performance benefit will likely not be noticeable. That being said, this does improve maintainability/understandability.
Comment #10
dawehnerThis could be it.
Comment #11
jibranWhat is the difference between #2004784: Move module install/uninstall implementations into ModuleInstaller and this patch?
Comment #13
andypostComment #14
dawehner@andypost
... This just moves code out of the way for the normal request. Please do the profiling :p
Comment #15
dawehnerMore fixes.
Comment #17
dawehnerAnyone wants to give some feedback?
Comment #18
Fabianx CreditAttribution: Fabianx commentedI assume that tag got missing accidentally?
Comment #19
znerol CreditAttribution: znerol commentedThis is great, it makes a ton of sense to extract the installer/uninstaller logic from module handler.
I guess
module_handler
does not need the@kernel
anymore.Can be removed.
ModuleInstallerInterface
, The module installer.Some lines further down there is a spurious use statement for
ModuleHandlerInterface
.This is dangerous. References to services need to be refreshed after a container rebuild.
ModuleHandlerTest
has a protected method which always fetches a new instance from the container. We should do the same here.Comment #20
dawehnerThank you for the review!
Comment #22
dawehnerFixed it.
Comment #23
znerol CreditAttribution: znerol commentedI think this issue is very important, raising to at least major is appropriate for the following reason: Installing/uninstalling modules triggers container rebuilds. Mid-request container rebuilds have proven to be the source of weird bugs in the past. I expect that moving this functionality to a separate service will help avoiding this kind bugs in the future.
Might be the right place to #2282371: Document the fact that it is not safe to reuse services across container rebuilds.
The
cacheBackend
property does not exist inModuleInstaller
. I'm wondering whether we should refresh themodule_handler
reference here though.Comment #24
catchComment #25
dawehnerI would argue we should, given that the module handler itself, should point to the new cache backend,
which will be initialized, if we update the module handler.
Comment #26
jibranI don't know but this seems related #2372745: KernelTestBase ignores extensions in site-specific directories
Comment #28
larowlan+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -0,0 +1,438 @@
+ // If any modules were newly installed, invoke hook_modules_installed().
+ if (!empty($modules_installed)) {
+ $this->moduleHandler->invokeAll('modules_installed', array($modules_installed));
+ }
Mile23 asked on irc if we fire events for modules being installed - worth adding here while we have a chance or punt to a follow up?
Comment #29
cilefen CreditAttribution: cilefen commentedRaised to critical because it is holding up a critical.
Comment #30
cilefen CreditAttribution: cilefen commentedoops - this one
Comment #31
cilefen CreditAttribution: cilefen commentedComment #32
cilefen CreditAttribution: cilefen commentedComment #35
dawehnerPlease let us keep things in scope ... you know this change for itself is small and self-contained, no internal change of behaviour.
I think the suggestion in #23 turned out to be a bad idea, so what about continue with the patch in #22 and then maybe improve later?
Comment #36
cilefen CreditAttribution: cilefen commented@dawehner Ok. Here is the reroll of #22.
Comment #37
cilefen CreditAttribution: cilefen commentedThere is no such thing as disabled. We may as well change this.
Different usages were not introduced by this patch, but let's make them the same.
Comment #38
znerol CreditAttribution: znerol commentedThe
cacheBackend
property does not exist onModuleInstaller
.Why? The old code refreshed the cache-backend for some reason I guess.
Comment #39
znerol CreditAttribution: znerol commentedOk, I believe I know what is causing the exception in #32. In
ModuleInstaller::uninstall()
the variable$entity_manager
is assigned before the loop. This reference is never updated after the container rebuild. This is also true for the$schema_store
variable. The attached interdiff fixes the problems for me (in theConfigImportAllTest
).Reading through the code again it stands out that all services are always fetched from the container - except
module_handler
, that one is injected into the constructor and needs to be refreshed in theupdateKernel
method. In my opinion there is no reason to special case the module_handler, therefore I suggest to also fetch that from the container when needed.Comment #40
dawehnerlet's apply znerol's patch.
Comment #44
cilefen CreditAttribution: cilefen commentedComment #45
tstoecklerRead through the entire patch, looks great.
This is really an awesome decoupling.
Comment #46
tstoecklerEdit: Wrong issue
Comment #47
tstoecklerComment #48
alexpottWe should be properly injecting all the dependencies now. Also As we can see from the @todo this refactor is correct because I think this gets around the circular dependency between the module handler and the themehandler
Instead of using \Drupal::service() here I think we should get the container from the the kernel. See updateKernel() below.
This code can be improved see the ConfigImporter::reinjectMe() code.
use Drupal\Component\Serialization\Yaml;
anduse Drupal\Core\DrupalKernelInterface;
from moduleHandler.Need to update the docblock
Comment #49
znerol CreditAttribution: znerol commentedRe 1,2,3: This patch is risky enough already, let's please not do this here and instead open a follow-up for cleanup.
Comment #50
znerol CreditAttribution: znerol commentedStray newline.
In order to simplify the review of actual changes to the extracted
install()
anduninstall()
methods, the following command can be used to produce a diff with copy-detection (the low threshold is necessary because of the huge amount of code which still remains inModuleHandler
):git diff -C40% 8.0.x core/lib/Drupal/Core/Extension
Attached is the result with irrelevant hooks removed.
Comment #51
alexpottHmm why is the current patch risky? I think the risks of the patch are only exposed if we properly inject it. If we don't then there is not real difference between this patch and the current state of HEAD. Part of proving the patch is worthwhile is injecting everything.
Also not sure why this patch as the "needs profiling" tag since it does not make any changes that would impact performance. The issue summary does nothing to describe the change, the solution or why we should be doing this.
Comment #52
znerol CreditAttribution: znerol commentedI disagree. The patch changes the behavior of the
install()
anduninstall()
functions. In HEAD, the same instance ofModuleHandler
is reused to invoke the various hooks (caches get reset correctly though). With the patch, the instance is renewed after every container rebuild.In addition the patch contains a considerable amount of changes to test-code. This is okay and necessary when performing an Extract Class refactoring. However, in general we should not touch tests when refactoring code.
Cleaning up the methods can be done in a separate, non-critical, non-api-change followup which does not need to touch any tests. That's what we should do.
Updated the issue summary. I cannot see any reason for the "Needs profiling" tag.
Comment #53
dawehnerYeah I think that we should do the injecting later, given that especially it provides 2 okayish to review patches.
Updated the docs and introduced a DependencyReinjectTrait which can use used by both ModuleInstaller and ConfigImporter and maybe more in the future.
Comment #55
effulgentsia CreditAttribution: effulgentsia commented#2278017: When a content entity type providing module is uninstalled, the entities are not fully deleted, leaving broken reference is currently postponed on this, so adding the "D8 upgrade path" tag to this. Also adding the "blocker" tag per the "More ways to help" section of Drupal core updates for November 8, 2014.
Comment #56
znerol CreditAttribution: znerol commentedWe have a working patch in #44, we could go back there and just include #48.4...6 (excluding 1...3 for reasons stated above).
Comment #57
Crell CreditAttribution: Crell commentedActual unit tests are impacted by whether something is injected or not. If not, then you're not doing a unit test.
I'm with Alex on this one. Inject needed services. If they can't be for some reason, better to know now.
Comment #58
znerol CreditAttribution: znerol commented@Crell please no, not in this issue, see #52 why not. Aside from the technical risk, mixing two different refactorings here would make it impossible to create a cross branch diff like #50, and that again would make it difficult to accurately review the patch.
Comment #59
dawehnerYeah this is fragile and there are more reasons to do stuff than: we can inject stuff now properly.
Decided to go with the suggestion of #58.
Comment #61
dawehnerMh :( dawehner--
Comment #63
dawehnerThere we go, maybe ... @see #61
Comment #65
znerol CreditAttribution: znerol commentedNot quite an assignment...
Comment #66
dawehnerDoh!
Comment #68
znerol CreditAttribution: znerol commented#2376039: Undefined property ContainerAwareEventDispatcherTest::results in run-tests.sh
Comment #69
znerol CreditAttribution: znerol commentedThe exception thrown in
ConfigImportAllTest
is the same as in #32, apply (#39) in order to resolve it.Comment #71
dawehnerOh right, thank you @znerol!!!
Comment #73
znerol CreditAttribution: znerol commentedComment #74
cilefen CreditAttribution: cilefen commentedComment #76
cilefen CreditAttribution: cilefen commentedComment #77
znerol CreditAttribution: znerol commentedTested with the browser: Installation works, module installation/uninstallation works.
Tested with drush:
drush site-install
works,drush pm-enable
breaks withPHP Fatal error: Call to undefined method Drupal\Core\Extension\ModuleHandler::install()
.drush pm-uninstall
is not affected because it still uses the procedural wrapper. Pull request on Drush: #1009.The cross branch interdiff is the same as in #50 (hunks have slightly different offset). The changes are still valid. Also did read through the entire diff again and I think that this is ready now.
Minor (maybe fix on commit): The stray newline at the end of
ModuleInstallerInterface.php
is still present.After commit, the following change record needs an update: Module/hook system functions replaced with module_handler service
Follow-up: #2380293: Properly inject services into ModuleInstaller
Comment #78
alexpottThis issue is a critical task and is allowed per https://www.drupal.org/core/beta-changes. Committed 826245d and pushed to 8.0.x. Thanks!
Let's get https://www.drupal.org/node/1894902 updated and add a bit about 8.x to 8.x to that. Thanks.
Comment #80
dawehnerThank you alex!
Updated the CR