Problem/Motivation
The stream wrapper manager currently calls a hook_stream_wrappers_alter() very early in the request and always, which makes it impossible to lazy load modules.
After trying different attempts and debugging this, we discovered the following:
a) Completely broken, as we call a hook before the module system is loaded, so we can never find any hooks to invoke (#2392433: Stream wrappers are registered before page cache will fix that, though)
b) It also breaks hook_module_implements_alter() as those is called on the first invoked hook, which then also doesn't find any implementations.
c) As far as we can see, the alter hook serves no purpose. It was there for system_stream_wrappers_alter(), which was used to unset the private stream wrapper (which as mentioned above was never called and there were no test fails). #2170235: file_private_path should be in $settings, like file_public_path now moved this into a core service provider to dynamically register it. All you can do in the alter hook is remove it or provide a different implementation, all of those things are possible through service providers. A hook/even would only be necessary if we want to make different stream wrappers available dynamically per request. That seems wrong to even consider supporting.
None of the other service-based things provide alter hooks like this, this was just left there because we had it before.
Proposed resolution
Remove the hook.
Remaining tasks
- Write a test that makes sure that private stream wrapper is correctly available or not (through the file system configuration form, for example)
User interface changes
* None.
API changes
Remove hook_stream_wrappers_alter().
Beta phase evaluation
Issue category | Bug because hook_stream_wrappers_alter() is completely broken |
---|---|
Issue priority | Critical performance issue |
Prioritized changes | The main goal of this issue is performance. |
Disruption | No disruption for core/contrib because the current implementation is defect anyway. |
Comment | File | Size | Author |
---|---|---|---|
#61 | stream-wrapper-event-2353357-61-interdiff.txt | 2.96 KB | Berdir |
#61 | stream-wrapper-event-2353357-61.patch | 6.22 KB | Berdir |
#54 | stream-wrapper-event-2353357-54.patch | 4.55 KB | Berdir |
#54 | stream-wrapper-event-2353357-54-interdiff.txt | 1.43 KB | Berdir |
#52 | stream-wrapper-event-2353357-52.patch | 3.11 KB | Berdir |
Comments
Comment #1
dawehnerI am not sure whether we really want to throw an event on container dump time, this could load easily to people injecting services and we end up with potential infinite container rebuilds.
Yes, we already load the previous container on container rebuild time, but its worth to think about it and avoid problems in the first place.
On alternative way to alter stream wrappers is to simply use a
{$module}ServiceProvider::alter()
already. Would that be enough?Comment #2
Fabianx CreditAttribution: Fabianx commentedOh, no I am not talking about throwing an event at container dump time.
I am just talking about tagged services, no clue about the right syntax, but like:
[@streamWrappersAlterSubscriber]
Then (if I do understand the event system correctly) we can just call all services tagged with that tag in the container to alter the stream wrappers.
Similar to how Twig Extensions work.
Unless I am missing something that should allow us to not having to load all module to enable them to be able to alter stream wrappers.
Unrelated:
- Tonight I dreamed of a hook_ "registry" in the container using my updated find_functions code ( similar to drupal_find_theme_functions) that would allow us for defined and specially registered hooks (not all hooks) to lazy load the modules that implement that hook.
Comment #3
Fabianx CreditAttribution: Fabianx commentedComment #4
catchAgreed on critical - if we don't make this change before release, there's no chance to do it after (compared to other performance issues which would just be optimisation with no refactoring). Critical performance issue in its own right vs. performance being critically bad.
Comment #5
BerdirJust playing around a bit, seeing what happens.
Most obvious issue atm is lots of issues about constants not being defined, like DRUPAL_OPTIONAL.
On an empty front page, the call into loadAll() doesn't happen until the first _theme() call.
Comment #7
Fabianx CreditAttribution: Fabianx commentedNice! So a new meta to put all 'define' constants to static properties on classes (which can be autoloaded)?
That could be a lot of nice Novice issues.
/me excited!
Comment #8
BerdirWell, all of those are going to be API changes unless we leave the old ones in place...
More hacks to fix the apparently most common case.
Comment #10
BerdirWe still have lots of random helper/api functions in module files, unsurprisingly. Fixing a few of those with manual load calls, we probably want to identify them and move to services or static methods or something.
Comment #12
BerdirMore fixes.
Comment #14
BerdirMore fixes, killing a few deprecated menu callbacks, loading module files in other cases. Everything very hacky, just trying to get to a point where it is working stable enough to do some performance tests, for example on a ajax route that doesn't invoke hooks.
Comment #16
BerdirMore of the same.
Comment #18
ArlaSince #2028109: Convert hook_stream_wrappers() to tagged services., the hook is not only inconvenient for performance reasons. It is now also broken, and breaks
module_implements_alter
hooks.The
stream_wrappers_alter
hook is now invoked before modules are loaded. Consequently, themodule_implements_alter
hook is, too. This blocks modules from having theirmodule_implements_alter
implementations discovered.Comment #19
catchComment #20
Fabianx CreditAttribution: Fabianx commented#18: Uhm, should there not be a moduleHandler->loadAll() or such before calling hook_stream_wrappers_alter()?
And yes the module_implements_cache corruption is a variation of #496170: module_implements() cache can be polluted by module_invoke_all() being called (in)directly prior to full bootstrap completion ...
I would suggest we get a quick-fix in, in another issue as this one is for the performance parts of this.
Comment #21
BerdirThere is no quick fix. The stream wrapper manager is initalized in DrupalKernel::boot(), the modules are loaded in preHandle() which comes much later.
There are only two options, rolling back the stream wrapper manager (which would be sad) or fixing this.
Comment #22
Fabianx CreditAttribution: Fabianx commentedSo we can't call ModuleHandler()->loadAll() from that stage - before calling alter? - at least temporarily.
OR:
And we can't move the alter hook to preHandle? -- Nope, we can't.
Comment #23
BerdirJust a reroll for now.
Comment #25
BerdirA lot changed since this worked, trying some fixes.
Comment #27
jibranconflicts.
Comment #28
BerdirPosted a reroll of the previous patch at #1905334: Only load all modules when a hook gets invoked.
Fresh start here that converts to an event. Not sure about some things, especially naming. I did move system_stream_wrappers_alter() inline, no point in basically altering our own definitions IMHO when we can do it right in there. There's also an issue to convert that to a setting, would avoid loading config.
I suspect this is a regression at first, but it does fix a critical bug and allows for #1905334: Only load all modules when a hook gets invoked.
It's technically not an API change, because right now, it is impossible to implement the alter hook, as mentioned above :)
Comment #30
BerdirMade the two services optional, fixed kernel tests.
Comment #32
Berdir...
Comment #34
BerdirReroll. Can't reproduce those tests fails locally, let's try again.
Comment #35
BerdirComment #36
chx CreditAttribution: chx commentedI don't get it , why are we using setter method calls instead of a factory method since we are injecting the container anyways?
Comment #37
BerdirI'm using setter injection because I made it optional so we don't have to check/trigger that event in installer/kernel tests.
Comment #38
dawehnerIts really looking good.
One thing I was wondering, whether we need all the methods on the event class. Would you really for example use the "hasStreamWrapper"?
Note: we do use now @Event to mark them as available event.
Can we link to the issue which tries to get rid of the configuration and the switch to the setting?
Comment #39
BerdirI'm not sure about the methods, but I felt like that might be to be a common case when altering, if it exists, get and change it. Also changed back to optional constructor arguments, as discussed with @dawehner.
Added @Event, and added a link to that private file system issue.
Profiled this together with @dawehner. It is hard to do fairly, because in HEAD, the alter hook is broken and we don't call the config. Also, injecting the config still results in loading the module handler internally (through config typed somehow), so we don't actually save on that.
Despite that, it was not slower as executing the no-op event is very fast. Which is what I was worried about and added the tag for.
What we must look into is moving this back into preHandle(). It being in boot() means that we call it for page cache responses and that seems very wrong, especially in combination with the config get.
Comment #40
Wim LeersBerdir already opened an issue for
This is the one: #2392433: Stream wrappers are registered before page cache.
Comment #41
dawehnerGiven that I think we need to add a change record.
Went with @berdir through the patch. I think its ready as it is.
Okay this is fair.
Comment #42
Wim LeersProfiling already happened.
Patch is indeed looking great.
This does not yet bring the big improvement to page cache performance that it enables, because the one
@todo
still needs to happen in a follow-up.Only marking NW for a nitpick, what seems like a genuine bug and a missing follow-up issue that needs to be linked to.
Would be good to document why these are optional? (AFAICT: for the installer.)
You added this @todo, but an issue is still missing. Would be great if you could add one.
The first argument should be the event dispatcher, not the service container?
Why didn't this trigger any test failures?
Comment #43
BerdirThanks for the review, added the documentation, issue link and fixed the wrong KernelTestBase. This has not been tested yet, and it will fail :)
Comment #45
Wim LeersIt passed, actually :)
Adding the linked issue as a related issue here.
That CR is still needed. Keeping at NR to get more reviews hopefully. But IMHO this is RTBC as soon as there is a CR.
Comment #46
BerdirWeird, looks like we never created a change record for #2028109: Convert hook_stream_wrappers() to tagged services., I'll write one tomorrow.
Comment #47
BerdirI mean the previous patch that I cancelled manually :)
Comment #48
Wim LeersAh, that makes more sense :)
Comment #49
chx CreditAttribution: chx commentedActual problem: What if I want to remove a stream wrapper?
Rant to be ignored: this is making a 1 line alter
$info['foo']['bar']
to a get, the change and the set. Also, alterEvent is a fantastic textbook example of how haphazard hooks and events are now. Yes, I know there is no other solution at this point but this issue shows a lot of Drupal 8 problems so clearly that it deserves this tag.Comment #50
Wim LeersGood point. That needs to be supported.
I share reservations about some of the verbosity (dare I say "Javaness"?) we have in Drupal 8. But indeed that ship has sailed, and this is solving a critical performance problem. So indeed, unfortunately to be ignored at this point.
Comment #51
BerdirOk, we decided to change this completely.
Discussed with @alexpott and we can't see a reason to change stream wrappers dynamically. Anything else can be done in a ServiceProvider now, so we are raising #2170235: file_private_path should be in $settings, like file_public_path to critical and will register the private service dynamically there. Then we can just remove the hook here. Postponing on that.
Comment #52
BerdirOk, fresh start, just removing the hook, as explained above.
Need to add tests for the correct behavior of private, as that is broken right now and we should have a test for it.
Comment #53
BerdirUpdated issue summary. Working on extending the tests now.
Comment #54
BerdirUpdated ConfigTest. I removed the check for file_temporary_path, as drupalPost() verifies that for us, I just kept the checks for public/private.
Note that there is no bug anymore related to this, as #2170235: file_private_path should be in $settings, like file_public_path already fix this, this just provides test coverage for it.
I think the only thing left here is the change record, I'm about to leave, so if someone wants to work on it, assign the issue.
As mentioned above, we need to explain the whole change around stream wrappers, not just this alter hook:
- That the hook is gone and stream wrappers are tagged services instead, include public as exampl
- That the alter hook was removed and that modules can instead use a ServiceProvider to dynamically register a service or not if it is their own (include code from CoreServiceProvider as example)
- If they want to change another service, the can implement alter() on a ServiceProvider and change the service, e.g. to remove it or to replace the class.
- The current alter hook documentation/example still uses the hook to change the label, that no longer work as label/description is a method on the class now, what must be done instead is replace the class for that.
Comment #55
chx CreditAttribution: chx commentedWell that, I guess, is better. But definitely needs change record.
Comment #56
chx CreditAttribution: chx commentedAnother rant but this not to be ignored: again and again and again, this issue went to 50 comments and was close to ready when I entered sad puppy mode and then the issue took a 180 degree turn for the better despite I had no good idea what to do. What about doing the latter without me getting frustrated and sad in all of these?
Comment #57
webchickMy recommendation: Next time, instead of entering 'sad puppy' mode, engage respectfully and collaboratively with the people trying to solve the problem and see if you can put your heads together on a more optimal solution.
Comment #58
chx CreditAttribution: chx commentedThat's the Drupal 7 way. Those days are gone.
Edit: even when I point out on IRC that the approach is flawed and trying to find a solution, people will not engange but rather relentlessly chase the flawed approach ostensibly because Symfony and it drives me absolutely mad (see fileinfo for more). Thanks but I am not interested in these conversations.
Comment #59
znerol CreditAttribution: znerol commentedDo not use this function in tests. Either use
$this->resetAll()
or$this->rebuildContainer()
. Those methods will also update the$container
property ofWebTestBase
.Comment #60
BerdirI don't think this issue is the right place for this discussion.
Created change record: https://www.drupal.org/node/2393323, please review.
Will fix the dfac() call asap.
@chx: Your comment had little to do with how this issue worked out, I first implemented the approach suggested in the issue summary, but when we were looking closer at it for writing tests and the private path change, we noticed there is no use case for dynamically altering the stream wrappers that can not be covered with service providers so we decided to simply remove the hook after I discussed it with @alexpott.
Comment #61
BerdirUsing rebuildContainer() in the test, also switched to assert on the labels. Added a note to the documentation that caches must be cleared and removed the handbook link from the UI, that is already in default.settings.php anyway.
Comment #62
znerol CreditAttribution: znerol commentedComment #63
znerol CreditAttribution: znerol commentedComment #65
alexpottFixing issue title.
Comment #66
catchPrefer the diffstat with this approach :)
@chx: this is a fairly unique situation to not want to have to invoke the hook for.
Additionally even if we'd changed to an event (which I agree has much worse developer experience, particularly for small alters), we may have realised the event was pointless later on, and could still have removed it then too (assuming it was before release). This issue would still be an incremental improvement in terms of improving bootstrap performance.
Committed/pushed to 8.0.x, thanks!
Comment #67
znerol CreditAttribution: znerol commentedCommit message still mentions "event".
Comment #68
catchIt does, I cross-posted with Alex...