Problem/Motivation
This is not really a competitive patch but some of the slow down that's been central to this problem has to do with (I think) when the typed data manager stuff gets called. Looking at this further, I was troubled by the implications of the public setters on the classes, so I attempted to remove them and rework Context classes as dumb data objects. The tagged context services that are ALSO event subscribers are just weird to me. I see what we're attempting to do with the @service_id, but it still seems odd to me. Just want to make sure we don't have other options. I don't expect this will pass, I have no clue about the performance.
Proposed resolution
Context objects become immutable.
Remove ContextInterface::setContextValue() and ContextInterface::setContextDefinition()
Add $context_value = NULL as the second parameter to Context::__construct()
Add \Drupal\Core\Plugin\Context\ContextInterface::createFromContext(ContextInterface $old_context, $value) for modifying an existing context
Old code
function whatever(ContextInterface $some_context) {
$context->setContextValue('some new value');
return $context;
}
New code
function whatever(ContextInterface $some_context) {
$context = Context::createFromContext($context, 'some new value');
return $context;
}
Remaining tasks
N/A
User interface changes
N/A
API changes
See "Proposed changes"
Data model changes
N/A
Beta phase evaluation
| Issue category | Bug because: Usage of the plugin context API has not been widespread in core, but as implementation in contrib has begun to occur, it's become clear that the mutability of context objects could cause all sorts of problems since any plugin could radically alter context injected into it for the entire system. |
|---|---|
| Issue priority | Major because: This does not break core without contrib doing something bad, however the setter methods that enable this should not be supported by core. We must remove them. |
| Prioritized changes | The main goal of this issue is performance. This issue was initially filed to prevent unnecessary calls to Typed Data during context creation which can be quite costly. |
| Comment | File | Size | Author |
|---|---|---|---|
| #92 | 2508884-immutable-context-92.patch | 29 KB | tim.plunkett |
| #92 | interdiff.txt | 2 KB | tim.plunkett |
| #87 | 2508884-immutable-context-87.patch | 29.46 KB | tim.plunkett |
| #87 | interdiff.txt | 2.93 KB | tim.plunkett |
| #80 | 2508884-immutable-context-80.patch | 29.6 KB | tim.plunkett |
Comments
Comment #1
wim leersComment #2
wim leersComment #3
eclipsegc commentedTo a certain degree I feel like we're using MORE TypedData here, we're just not asking Drupal to do the work, but instead expecting the developer to do it. The main objective here was to prevent calls to the TypedDataManager during set*() methods and during __construct(). This resulted in the removal of all set*() methods on the ContextInterface. TypedData objects themselves have a setValue() method, so to a certain degree this improvement is moot because of that, but at least we've removed another layer of the problem space and placed the greater problem squarely with TypedData. TypedDataInterface itself might benefit from a similar refactor, but that's a separate issue.
This patch needs a bit of love yet since I added TypedDataInterface dependencies into Plugin Components, but I want to see how this fairs with tests.
Eclipse
Comment #5
eclipsegc commentedCleaning up the tests, some of these have very strange errors in testbot. The component/core issue I mentioned earlier apparently has a test (cool!) I've not tried to fix that yet, will do that once the rest of it appears to be passing.
Eclipse
Comment #6
wim leersInterdiff?
Comment #8
eclipsegc commentedSorry Wim, my bad. I've added interdiffs from 3-5 and 5-8 now. I EXPECT this patch to pass. If it does, I'd love to see if this makes a performance difference.
Eclipse
Comment #9
wim leersNice, green! :)
Comment #10
dsnopekComment #11
wim leersProbably major given the performance impact. But we don't have numbers yet, so definitely not critical.
Comment #12
fabianx commentedCould we get an IS update, please?
Comment #13
dawehner@ecliseGC
In case you really want a profiling keep the patch up to date, please.
Comment #14
fabianx commentedComment #15
eclipsegc commentedReroll
Some of the conflicts break the immutability of context, but we can fix that after we see how test bot feels.
Eclipse
Comment #17
eclipsegc commentedFixing an error in the reroll and attempting to make sure that this takes advantage of cacheable metadata.
Eclipse
Comment #19
eclipsegc commentedThank you berdir for pointing out that I deleted NodeRouteContext.
Eclipse
Comment #21
eclipsegc commentedAttempting to fix the core ContextTest and maintain the immutability of the Context object.
Eclipse
Comment #22
wim leersNo more need for a reroll, but now that we have a green patch: very much needs profiling.
Comment #23
fabianx commentedAnd an IS update of _what_ this does and why and what the impact on core / contrib is.
Comment #24
dawehnerHere it is
Comment #25
wim leers#2354889: Make block context faster by removing onBlock event and replace it with loading from a ContextManager landed (yay, progress!), this needs to be rerolled.
Comment #26
jibranAfter profiling is it a critical or not?
Comment #27
fabianx commentedNot critical at all.
Comment #28
yched commentedGot pointed here by @dsnopek after my comment in #2375695-141: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed
Copy pasting from over there :
So it seems that is another good reason to push through with the approach here of making contexts actually immutable :-)
(Sorry for the noise if that was a concerted plan from the get go. The two issues didn't seem to explicitly relate to each other, nor did #2375695: Condition plugins should provide cache contexts AND cacheability metadata needs to be exposed mentioned the question of immutability, so at least this is an attempt to make it explicit somewhere)
Comment #29
wim leersyched++
Comment #30
fabianx commentedYes, +1 to immutable context objects.
Re-classifying as a bug.
What I am unclear is:
- What API changes does this make?
- What is the impact on already existing contrib modules?
Comment #31
swathin commentedWorked on this issue and here is the new patch.
Comment #33
sravya_12 commentedRerolled from comment #19
Comment #35
eclipsegc commented@swathin, @sravya
You need to provide interdiffs with your patches so that people can keep up with your work without having to reread the whole patch.
@Fabianx
The thing it primarily affects is the context providers. Core (as you well know) does a little of this, and Page_manager and Rules. Neither Page_manager nor Rules are far enough along that this would be a huge issue for this. I tend to think (as someone helping develop on of the modules most heavily impacted by this) that it's unlikely to cause huge issue.
Eclipse
Comment #36
fabianx commented#35: It would be great if you could update the issue summary and add a beta evaluation.
I have already pasted the template in.
---
I know it is a lot of effort to take care of issue summaries and beta evaluations, but it really helps others understand the issues and such they can be both more effective in review and helping. Also usually those issues get to be committed much faster.
Comment #37
eclipsegc commentedfilling in beta evaluation template.
Comment #38
eclipsegc commentedComment #39
dsnopekRestoring some of the issue tags that were lost in #33. Also, I attempted to make some interdiffs (attached) for the patches in #31 and #33, but no promises that they're correct. :-)
Comment #40
dsnopekAnd needs a reroll!
Comment #41
dsnopekEr, 2nd attempt at adding the tags back. :-/
Comment #42
dsnopekEr, nevermind, doesn't need a reroll! I was on the wrong branch left over from generating the interdiffs. Sorry for all the comments so quickly!
Comment #43
wim leersBeta evaluation was added, IS still needs an update AFAICT.
Comment #44
japerryThere is a left-over TODO -- but its very close!
Comment #45
japerryOkay first shot re-roll of 21.
Comment #48
japerryWaiting for @timplunkett to give his snarky review. ;)
Comment #49
tim.plunkettMy snarky review from yesterday got eaten because I took too long to post. Here are the important parts, sans snark.
This kinda sucks as an API change. Why force us to think about TypedData more than we have to?
new new create create a b start
That's a LOT of things to know. From an Entity to TypedData to ContextDefinition to Context? Why should I have to know that much?
This sort of change isn't as bad, but it's still extra work
Comment #50
tim.plunkettIf NodeRouteContext had its code rearranged slightly, the change I called out in #49.2 would be this:
That's pretty awful.
Also this should probably be
$context = NULL;and then after the other calls:if (!$context) {this line.Comment #51
dsnopekDoes that code give you 30 lives in Super IssueQueuetra (tm)?
Comment #52
dsnopekIn a functional programming language (or using functional programming style in any language) that would be totally normal! But point taken in Drupal-land.
Would a helper function be enough? For example, something like:
I dunno if anyone is still working on this, but I may take a stab at fixing the tests after I finish catching up on issues...
Comment #53
dsnopekSo... I like the immutability of the Context object in this patch.
But assuming I'm understanding this, the performance improvement is from not calling
TypedDataManager::create()and requiring the developer to convert the context values to typed data themselves. I'm not terribly crazy about that at all. :-/I totally misunderstood what @tim.plunkett was complaining about above in #49 and #50. I thought it was about have to initialize so many objects, but it's about the extra knowledge that the developer has to have.
The performance improvement would be nice, but not at this high of a DX cost. Maybe we could split this into two issues: (1) for the immutability of the Context objects, and (2) for removing calls to
TypedDataManager::create()?Anyway, here's a new patch that may or may not pass more tests...
Comment #54
dsnopekComment #56
dsnopekThis is what I really meant. :-)
Comment #60
tim.plunkettTalked with @japerry and @EclipseGc and agreed we'll just work on the immutability, and remove the TypedData changes.
Comment #61
dsnopekCool! Then I have a half finished patch which adds a
$context->cloneWithValue($value)and even a few conversions (or revert-sions?). It still needs lots of work (most of the typed data changes are still there), but it's a start.Comment #62
dsnopekJust curious what textbook will say
Comment #65
dsnopekWorking on this now...
Comment #66
dsnopekOk! This should just be changes to make the context immutable. Let's see what the testbot thinks!
Comment #67
tim.plunkettI need to think about this naming, because it doesn't feel right, but I don't yet have a better name.
Otherwise, this is looking great! Thanks @dsnopek
Comment #68
dsnopekCool, thanks! Unassigning for now...
Comment #69
tim.plunkettOkay, a long talk with @japerry, @EclipseGc, and @dsnopek, this is what we came up with.
\Drupal\Core\Plugin\Context\ContextInterface will have this:
\Drupal\Component\Plugin\Context\ContextInterface does not need a dedicated method, since it is not concerned with preserving cacheability. It can just use the constructor directly.
Comment #70
dsnopekI think this patch looks great! Assuming tests pass, this is RTBC as far as I'm concerned. :-) Unfortunately, since I wrote a bunch of this code, I can't RTBC it.
Comment #73
tim.plunkettAs pointed out by @dsnopek, \Drupal\Component\Plugin\ContextAwarePluginBase::__construct() hardcoded the Component Context class. This allows us to override it.
Comment #74
tim.plunkettComment #75
dsnopekInterdiff looks good! Hopefully this one'll pass tests. :-)
Comment #78
eclipsegc commentedThis should happen before the parent::__construct() call. The removal of context from configuration is to keep config at the parent level from having to deal with this at all ever.
Otherwise this seems fine.
Eclipse
Comment #79
tim.plunkettSkipping #78 for the moment, just want to see the failure numbers.
Comment #80
tim.plunkettHere is my closest approximation of what I think @EclipseGc wants.
We cannot simply move the __construct later, since we call getContextDefinition, which needs $this->getPluginDefinition().
Comment #82
eclipsegc commented+10000000
Looks great from my end, who can rtbc this?
Eclipse
Comment #83
cosmicdreams commentedI can. In reading this I think I get what your shooting for.
Comment #84
andypostIS still needs update!
Comment #85
tim.plunkettDone
Comment #86
wim leersNit:
mixed|null $context_valueNit: "Converts" in the doc, vs "create" in the method name. Let's be consistent.
Nit: "[…], keyed by …." — but this also doesn't happen elsewhere, so probably we don't want this either?
Nit: In principle this could all be chained.
What is a "defining characteristic representation"?
Nit:
NodeType::create([…])->save()Comment #87
tim.plunkett1) Yep
2) Sure
3) Leaving as is.
4) Yes, but leaving as is
5) That's c/p from the parent, using inheritdoc instead (we're only defining this to override the @return docblock)
6) Sure.
Comment #88
wim leersRTBC++
Yay for not spreading a crazy doc :P
But why keep the
@return?Comment #89
tim.plunkettBecause of the crazy split between
\Drupal\Component\Plugin\Context\ContextInterface
and
\Drupal\Core\Plugin\Context\ContextInterface
It helps immensely to typehint correctly between Component and Core.
Comment #90
wim leersOkay, thanks for the explanation!
Comment #91
alexpottThe
getTypedDataManagermethod is not on the interface. Is that ok?Can remove the use TypedDataInterface...
Not used.
Not used.
Comment #92
tim.plunkett2) Added #2575711: Context objects are now immutable
3) It's not on any interface, so we have no way to check that. But that is a pre-existing condition in HEAD. As such, I'm including a method_exists call here, but not adding any interfaces.
4) No, it's used elsewhere in the file
5) Yep
6) Yep
Comment #95
japerryThe patch in #92 appears to fix the concerns in #91, thus marking RTBC to get back into Alex's visibility.
Comment #96
alexpottI'm committing this API change under the core committer discretion clause of the beta policy since I think it will eliminate a whole class of likely problem in contrib. Committed 91b4ae5 and pushed to 8.0.x. Thanks!
Removing the unused use from #91.4 on commit.
Comment #98
Torenware commentedIt would appear this commit broke Rules 8.x-3.x pretty hard. I'm not up enough on this issue to know the details of this issue, but I suspect fago et al may need to be involved here.
Comment #99
eclipsegc commentedThat's not surprising, it broke page_manager too, but they just need to stop using the setters and start using the constructor. Should be a pretty straight forward fix.
Eclipse
Comment #100
dsnopek@Torenware: The change record describes how code needs to be updated https://www.drupal.org/node/2575711
Comment #101
Torenware commented@dsnopek,
Thanks. I've looked at that now and spent a day applying that to the Rules code base.
One thing I've having trouble with: Rules has a concept of a set context that has a value of NULL. Post this patch, this is busted, and I don't see a valid way to have a set context with a NULL value. Setting it NULL directly won't work, and I don't see a way to create a TypedData object that gets around the problem either.
Is there some way of doing this I'm not seeing, or does the fix for this issue disallow a context with a NULL value?
Comment #102
klausiFollow-up: #2583135: Context::getContextData() sometimes returns NULL which violates ContextInterface