Splitting this out from #2368275: EntityRouteEnhancer and ContentFormControllerSubscriber implicitly depend on too many services which will reduce the dependency chain of the entity route enhancer. Opening this issue to address the issue more systemically, see the discussion there for more background.
Issue summary
Route enhancers are implemented as event listeners and all of them are run on every request. This is unnecessary since each route enhancer will only apply to a subset of routes. This is problematic as all dependencies together with their chain will have to be initialized, even they aren't used
Proposed solution
Proposed by znerol:
Wrap the existing route enhancers / route filters with a lazy version of them. The lazy version has stored for each route which enhancers/filters apply
to them. Based upon that, you just have to initialize the route filters/enhancers which are actually needed.
Api change
A new interface got introduced:
\Drupal\Core\Routing\Enhancer\RouteEnhancerInterface
and
\Drupal\Core\Routing\RouteFilterInterface
Existing route filters / enhancers will automatically take over all lazy ones, but you can opt out explictly using the 'non_lazy_route_filters' tag.
Beta phase evaluation
| Issue priority | Critical because of performance |
|---|---|
| Prioritized changes | The main goal of this issue is performance. |
| Disruption | Not disruptive, existing code with continue to work as exactly the same as before |
| Comment | File | Size | Author |
|---|---|---|---|
| #90 | interdiff.txt | 3.25 KB | dawehner |
| #90 | 2368769-90.patch | 22.5 KB | dawehner |
Comments
Comment #1
catchComment #2
dawehnerI kinda talked with lsmith about that but he seems to be not perfectly comfortable to add this lazyness to CMF.
I'll try to subclass the required classes to implement it. Note: We should do the same for route filters as well.
This speeds up things like not requiring
AcceptHeaderMatcherfor quite a lot of requests. (yes, HTML requests are still kinda special)Comment #3
znerol commentedIt might be possible to add one single proxy route enhancer to the dynamic router which then forwards
applyRouteEnhancers()to lazy loaded drupal route enhancers. Like this we could avoid subclassing dynamic router.Comment #4
larowlanPretty sure on admin pages they run more than once because of breadcrumb building, on a deep admin page eg admin/structure/types/manage/page/fields they might even run six times. Hope I'm wrong on that.
Comment #5
dawehnerJust posting work so far, not really helpful at all yet.
Comment #6
dawehnerThere is no reason that I block progress on that issue.
Comment #7
dawehner@kgoel wanted to work on this issue, just in case someone wants to directly work on this issue.
Comment #8
larowlan@kgoel - still keen to work on this?
I'm willing to take a look too.
Comment #9
kgoel commentedStill need to take care route filters and add applies method in ParamConversionEnhancer.php, EntityRouteEnhancer.php, and AuthenticationEnhancer.php
Comment #10
jibranThis can be replaced by {inheritdoc}.
TRUE
space missing before {
Incomplete docs.
Comment #11
catchThis looks encouraging so far.
Comment #12
kgoel commentedComment #13
kgoel commentedComment #14
dawehnercan we cast this to a bool or even use $route->hasOption('parameters') maybe?
Comment #15
kgoel commentedSure, hasOption is better since it returns bool.
Comment #16
kgoel commentedComment #17
kgoel commentedComment #18
dawehner@kgoel
If possible, try to post interdiffs, even the progress might be small, but it makes it possible to follow your work a bit.
Comment #19
kgoel commentedlong interdiff....since the old branch but will keep in mind to post interdiff from now on.
Comment #20
heddnThis will cause a runtime issue.
Comment #21
kgoel commentedThanks Lucas! To be on the safe side, I blame late night patch :p
Comment #22
kgoel commentedComment #23
Crell commentedThis doesn't remove an API, just technically provides an alternate, preferred approach. Non-lazy enhancers would still work, I think. That means there's no API change here, and doesn't solve a *current* significant performance issue (as the big one was already addressed). So... why is this critical? Seems like a major to me.
Comment #24
kgoel commentedComment #25
dawehnerWorked a bit on adding @todos about the next steps we should do and fixed some small parts on the way.
Comment #27
catchBeing able to fix this without an API change is good and the patch looks encouraging.
Question then comes down to whether the API addition could go into a patch-level release or only a minor release, and whether high-usage contrib modules are going to have route enhancers with long dependency chains or not. Don't know the exact answer to those questions.
See issue summary of #1744302: [meta] Resolve known performance regressions in Drupal 8 for the 'critical performance issue' criteria.
Comment #28
cilefen commentedHas this been profiled to show improved performance? I don't understand why it is critical otherwise. I reviewed some of the comments.
This should be on one line. What about "Determines route enhance rules."?
This is a bit wordy for me. What about "Declares if the route enhancer applies to the given route."?
"False otherwise" is probably unnecessary.
I don't understand what this means.
Should be "enhancers to the route".
This is unclear to me.
"enhancers"
Same as above.
Comment #29
kgoel commentedStill need to address -
Do I need to create a new event listener?
Comment #31
dawehnerA new one is probably a good idea here :)
Comment #32
kgoel commentedComment #33
kgoel commentedComment #34
kgoel commentedComment #44
almaudoh commentedThis could be why the tests are failing:
s/routeEnhaner/routeEnhancer/
Comment #45
kgoel commented@almaudoh - Thanks for catching that! However, test is failing because of
There is something wrong with registering listener as a service and I am trying to figure out what have I missed here :)
Comment #46
dawehner@kgoel
Are you sure its fine to use 4 spaces as intentation instead of 2, as the other part of the file?
Did you tried to put a print statement/debugger into
RouteFilterSubscriber::onRequest()and see whether its triggered when you calldrush cr?Comment #47
kgoel commentedFixed space and spell.
Comment #48
dawehner.
Comment #49
kgoel commentedI don't think its called when I tried to run drush cr and this is what I am getting on my local -
Comment #50
kgoel commentedComment #52
kgoel commentedThanks @dawehner for your help!
Comment #54
kgoel commentedhmmm....I am not sure what's wrong at or around line 649 with arguments in core.services.yml .
Comment #55
kgoel commentedComment #57
kgoel commentedComment #59
cilefen commentedIn Url::createFromRequest(), line 263, \Drupal::service('router.no_access_checks')->matchRequest($request); returns an array without the '_raw_variables' key set.
Comment #60
dawehnerThere are various things which have been wrong in my initial patch, see interdiff, ...
Comment #61
kgoel commentedI have added route enhancer back for ParamConversionEnhancer.
Comment #62
dawehnerNow things install at least
Comment #64
dawehnerA small fix for the patch, but a great step for the bot.
Comment #66
dawehnerPriority matters.
Comment #68
dawehnerLet's fix it + minor nitpicks.
Comment #69
larowlanDoes this belong as an enhancer (unrelated) - should it be hard-coded like the access subscriber for security reasons?
The grammar here is a bit patchy
can we get a follow up for this or do you intend to do it here?
is there a chance this could lead to stuff getting serialized inadvertently?
extra space here
Comment #70
dawehnerThank you for your review!
There are various issues around that point. Hard coding is probably not a solution (much like hard coding access made things impossible / much harder)
Improved a bit.
It works, let's just get rid of the todos
MH, its the same as every other service, ... we rely on the
DependencySerializationTraitComment #71
dawehnerWorked on the issue summary
Comment #72
wim leersLooks good; only one central question:
Why add a new service instead of making the existing one lazy?
I think the answer is what catch said in #27:
Which is indeed great, but it also makes me question the value like Crell did in #23:
It's great that the current patch keeps disruption at zero. But that is not without consequences:
If we were to do an API change instead, the entire change would boil down to a single additional method being required, which could even choose to simply return
TRUE. IMHO that disruption cost is very low, because the changes are trivial, and doesn't cause a ripple effect in other parts of core. And there'd be no ambiguity in the DX.s/all route/all lazy route/
s/all route filters/all lazy route filters/
Can't this check for the presence of
{slugs}?Comment #73
dawehnerThis route was chosen, because we don't want to hack the dynamic router of symfony CMF, which is fine.
Right, but do you really think that there aren't ton of examples where people can do crap? Just imagine if someone uses a service in a event subscriber, boom!
So what about renaming the tag from lazy_route_enhancer and route_enhancer to non_lazy_route_enhancer?
We can't because a lot of code relies on the existance of '_raw_variables' being out there.
Comment #74
wim leersI think that'd help a bit.
The DX would still be worse than it is today though, but I guess route enhancers are not very commonly written anyway, and the clarification you suggested would help a bit. What about marking the non-lazy one as deprecated in 9.x? That sends a soft signal, subtly helping people to decide.
:(
I couldn't find any code that'd cause problems with this though; either I'm overlooking something, or that big amount of code relying on the existence of
_raw_variablesdoesn't affect us checking for the presence of slugs? And controllers relying on raw variables automatically implies they're not using the upcasted parameter? (Perhaps I'm saying something stupid now, but without diving into routing system internals, I think what I wrote makes sense?)Comment #75
dawehnerI don't remember which code broke, but there was certainly quite a lot of it, see earlier fails. It was more generic code than simple controllers,
Comment #76
wim leersAny chance that's simply no longer true due to changes in that code?
Other reviewers: do you share the concerns I raised in #72.1? Do you agree that what dawehner proposed in #73 (his lazy vs non-lazy naming proposal) would help?
Comment #77
dawehnerI highly doubt that, but let's try it out.
Comment #79
dawehnerLet's use the other one, but also rename the tag.
Comment #80
almaudoh commentedI agree with #76that using non-lazy would be better. @dawehner in #73 you said "non_lazy_route_enhancer" but in the code #79 you used not_lazy_route_enhancer. If you ask me, I think "non_lazy_xx" sounds better than "not_lazy_xx".
Comment #81
wim leersI have to agree with almaudoh.
Comment #82
dawehnerMeh
Comment #83
dawehnerMeh
Comment #84
dawehnerComment #85
wim leersI think this needs a CR for the API change+addition? After that, this is RTBC.
Comment #86
dawehnerDone
Comment #87
wim leersI clarified the CR further.
No further remarks.
Comment #88
webchickAwesome. :) Passing this to catch for review, since he's our performance guru.
Comment #89
alexpottThe
callhere is wrong.let's call this route_filter.lazy_collector
Let's call this route_enhancer.lazy_collector
Comment #90
dawehnerThere we go
Comment #91
wim leersComment #92
alexpottCommitted 2b1f9f4 and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation to the issue summary.
Doc fixes made on commit.