Problem/Motivation
We currently try to rebuild the router in kernel.terminate so that it only gets rebuilt once per request. This is an optimization for when multiple modules (or other route rebuilding operations) are triggered in a single request.
However this makes module enabling non-transactional (see related issues) - the state of the installed modules and their routes gets out of sync, which can lead to race conditions and fatal errors.
Additionally, when enabling modules via the UI, it should move into a batch anyway (see related issues), so would get rebuilt every batch request anyway.
Proposed resolution
When enabling a module, just immediately rebuild routes instead of setting rebuildNeeded.
Remaining tasks
User interface changes
API changes
Data model changes
Comment | File | Size | Author |
---|---|---|---|
#55 | 2589967-2-55.patch | 13 KB | alexpott |
#55 | 51-55-interdiff.txt | 3.78 KB | alexpott |
#52 | 2589967-2-51.patch | 12.1 KB | alexpott |
#52 | 50-51-interdiff.txt | 759 bytes | alexpott |
#50 | 2589967-2-50.patch | 12.2 KB | alexpott |
Comments
Comment #2
damiankloip CreditAttribution: damiankloip commentedLet's start simple.
I still think we want #2572293: Race condition triggerable by a single user due to router rebuild in kernel.terminate and maybe some extension to destructable to allow similar things to happen in finish_request and not terminate.
Comment #4
dawehnerOh yeah this basically now requires all kernel tests to have the table in place and would slow them down a bit :(
Comment #5
catchIt will slow them down a bit but I think we have to prioritize production site stability over test suite performance here.
Or possibly we could look at a null router storage by default for kernel tests.
Comment #6
damiankloip CreditAttribution: damiankloip commentedYeah having a properly working site is way more important than test speed :)
Comment #7
dawehnerYeah sure, just saying. I think one approach would be to just add it to the base class.
Comment #8
BerdirAgreed, either add it to be base class or introduce an in-memory (null might not work, tests possibly rely on it) router storage.
Comment #9
valthebaldMaybe different approach - get router builder as a parameter of module installer, and use it in install()?
Patch attached
Comment #11
dawehnerIs it just me that this issue is more like a routing issue?
Comment #12
catchComment #13
alexpottI think we need to rebuild routing prior to running the install hook so that messages or anything done in there can rely on the module's routes see #2342015: Content Translation module still implements hook_enable
Comment #14
cilefen CreditAttribution: cilefen commentedHow to work around this is in #2663660-2: Aggregator module renders Drupal inoperative as soon as a feed is added.
Comment #15
snufkin CreditAttribution: snufkin at Acquia commentedI can confirm that this solves the issue of using routes in the hook_requirements (#2572459: Route "acquia_connector.setup" does not exist).
The previous test seems to have failed testing due to unrelated issues, so marking it needs review to kick tests off again.
Comment #16
Wim LeersRan into this bug over at #2469431-216: BigPipe for auth users: first send+render the cheap parts of the page, then the expensive parts, in
hook_page_attachments()
.Comment #17
Wim LeersThis is also causing #2679547-4: Fix random fail in ConfigImportAllTest caused by ModuleInstaller not rebuilding routes immediately, which happens to be surfaced by BigPipe.
Comment #18
alexpottI think the route rebuild should happen before hook_installed so at least one hook can rely on the routing system being right.
Comment #19
Wim Leers#2679547-5: Fix random fail in ConfigImportAllTest caused by ModuleInstaller not rebuilding routes immediately, which happens to be surfaced by BigPipe got committed with a work-around until this issue is fixed. This issue should remove the changes made there.
Comment #20
cilefen CreditAttribution: cilefen commentedRelated? #2684189: RouteNotFoundException: Route "dblog.overview" does not exist when uninstalling the dblog module
Comment #21
dawehnerPotentially ...
Comment #22
alexpottWe can't inject the route builder into the module installer because the container is rebuilt during module install so we'd be using the wrong route builder if we do that.
Comment #24
Berdir#2380293: Properly inject services into ModuleInstaller
Comment #25
alexpottHmmm patch doesn't apply to 8.0.x so we need to make this an 8.1.x-dev issue at least.
Comment #26
alexpottComment #27
alexpottWe need explicit tests for install and uninstall..
Comment #28
alexpottComment #29
catchThis isn't compatible alternative routing implementations - we just moved the definition of that table to the implementation too. Can't the router rebuild do that check?
Comment #30
dawehnerThis if is entirely not needed anymore now that we lazy create the table.
Comment #31
dawehnerExpanded the documentation and removed the if.
Comment #32
catchMissing space here, but this is much better thanks. Since that's fixable on commit, RTBC for me.
Comment #33
alexpottSo #31 is going to fail because of the rest module - it has an implicit dependency on the node module. Imo we need make that dependency concrete. Easiest way to see this is to run
Drupal\KernelTests\Config\DefaultConfigTest
with the patch in #31 applied.Comment #34
alexpottComment #35
catchCould use a @todo for #2308745: Remove rest.settings.yml, use rest_resource config entities.
Also that means this will need an upgrade path to enable node module on any site using REST, otherwise the dependency won't be met.
In this case we're not adding a new dependency in the code, just making one that's already there explicit, so it's theoretically safe to newly add the requirement but still not fun.
Comment #37
catchThinking about #35, if there's a workaround other than the explicit dependency on node, that'd save the update function and potential behaviour change for sites with REST but not node enabled. Neither is a good option, but nor is postponing this issue on the REST one.
Comment #38
dawehnerThe workaround would be to skip hal/rest module from the DefaultConfigTest
Comment #39
alexpottWe could just suppress error during installation - that way we still get the default config testing but we not getting the side effect testing of the trigger error in
\Drupal\rest\Routing\ResourceRoutes
.Comment #40
dawehnerMh, I would honestly prefer to just skip rest/hal in the test. Hiding the error hides it for every module, not just rest.
Comment #41
alexpott@dawehner I disagree. The point of this test is that after installing a module its active config matches the default config it provides. Using the
@
symbol doesn't hide anything. All the tests that we expect to occur as part of this test occur.I guess we could just install node if hal or rest is under test but to me skipping a module is worse than doing an
@
.Comment #42
alexpottSo the last issue we have is that people using
\Drupal\Core\Url
in hook_install will still have potentially really inconsistent bugs. Because if they rely on a dependencies routes it'll work if the module was installed in a previous request but if it is installed in the same request it is going to break... it will depend on how boxes are ticked on the module install page.Discuss a bit with @dawehner on IRC. The options seems to be:
Comment #43
alexpottOops... not quite the right interdiff...
Comment #44
dawehnerWhile the discussion in #42 is important it is not in scope of this issue to be honest. The problem already exists ...
Comment #45
catchFor #1 that'd be more or less what we used to have with Drupal 7-style rebuild needed right?
For #2, we could reduce the performance impact of that by combining it with #2501555: Move menu link rebuilding out of route rebuilding - i.e. only do the menu link rebuild at the end of the request, regardless of how many route rebuilds there are.
Comment #46
dawehnerWell, but just during the module installation process. Other processes would still hit the other router table.
Comment #47
catchHmm, so possibly even skip the database altogether then and have an in-memory implementation, closer to what Symfony does itself. Then we don't have to write anything out anywhere at all.
Comment #48
alexpottre #44... from the issue summary:
I think the discussion in #42 is very much in scope.
Comment #49
alexpottHere's a horrible implementation
Comment #50
alexpottSomething a little nicer...
Comment #52
alexpottUninstall updates the container and removes the old service so the swap is not necessary... not sure we need to use the lazy building route provider during uninstall... leaving it in for discussion.
Comment #55
alexpottFixing some docs and making sure we don't do unnecessary rebuilds.
Comment #56
dawehnerI think this is as good as it can get now. In general its though quite sad that we need to do special things for routing, as similar problems could exist for all kind of other subsystems as well.
Comment #57
catchI'm not sure about this yet at all.
The request that's installing the modules gets an up-to-date router when it needs it.
However I'm not at all sure what the state is when a new request comes in during or after the router has been lazy-rebuilt.
Too early in the morning for me to answer those questions myself, so assigning to me to look at later.
Comment #58
catchAlso note to self this looks a lot like the RouteProvider we already have in SimpleTest:
#2589967: Rebuild routes immediately when modules are installed reminded me.
Comment #59
dawehnerYou probably talk about #2605956: Port #2605684 to the new KernelTest but yeah alex certainly was aware of that.
Comment #60
alexpottre #57
This guarantees we've got an up-to-date router table after module installation regardless of whether it has been rebuilt by the lazy_builder service. So the rebuilt router is definitely available earlier than it is in HEAD and there is no danger that it is not available on the request subsequent to installing a module.
Comment #61
alexpottRe the simpletest route provider - yes we can remove it and make it use this one - but I vote for doing that in (a repurposed) #2672762: Move core/modules/simpletest/src/RouteProvider.php to the Drupal\Tests namespace/folder
Comment #62
Novitsh CreditAttribution: Novitsh at Colruyt Group Services commentedTested #55 and still cleanly applies and fixes the issue. I would love to have this fix out fast. Many separate issues are created for this problem from different points of view and mostly in the issue queue of a contrib module. Some have the issue (like me: fastcgi) and others can't reproduce (mamp or so). See Google results also.
Should we start relate issues from contrib to this post, or?
Comment #63
catchI think #57 is better punted to #2572285: Module enabling and router rebuilding should be done in one transaction.
#61 makes sense.
Moving back to RTBC. Also tagging RC target, I think this is worth trying to get in before 8.1.0 if we can.
Comment #64
geerlingguy CreditAttribution: geerlingguy commentedI'm going to also test with a few modules (on PHP-FPM/mod_proxy_fcgi) to see if this patch fixes issues like #2701313: When installing via UI: RouteNotFoundException: Route "facets.overview" does not exist.; I've been running into this all over the place, and also get occasional bug reports from other people installing modules using the UI. Would be nice to get this fixed so the deluge of issues can stop.
+1 on getting this into 8.1.x if at all possible.
[Edit: Tested on Facets, Honeypot, and the rest of the modules I was getting the install WSOD with last Friday—+1 to the RTBC, it makes installing modules actually work.]
Comment #66
geerlingguy CreditAttribution: geerlingguy commentedComment #67
Novitsh CreditAttribution: Novitsh at Colruyt Group Services commentedThanks for the commit and the testing guys!
Comment #68
catchNot in 8.1.x yet...
Comment #69
alexpottI'm definitely +1 to this being an rc target - it solves a whole class of problems the hard to find the cause of.
Comment #71
catchCherry-picked to 8.1.x too.
Comment #72
xjmThis apparently also resolved #2707433: Errors installing from config on 8.1.0-rc1.
Comment #74
cilefen CreditAttribution: cilefen commented#2792293: RouteNotFoundException after enabling the Language module
Comment #75
larowlanLong time ago, but there may be a memory impact/leak from this change on large sites #3356739: Memory usage increases linearly when (un)installing modules via config import