refactor(core): minor ComponentDef improvements#46093
refactor(core): minor ComponentDef improvements#46093crisbeto wants to merge 1 commit intoangular:mainfrom
Conversation
Makes the following improvements in the runtime: * Uses the unique ID of the component definition to keep track of its injector in the `StandaloneFeature`, instead of the definition itself. This reduces the amount of memory we can leak, if something doesn't get cleaned up. * Changes the naming and description of the `ComponentDef.id` to reflect what it is used for.
|
|
||
| if (!this.cachedInjectors.has(componentDef)) { | ||
| if (!this.cachedInjectors.has(componentDef.id)) { | ||
| const providers = internalImportProvidersFrom(false, componentDef.type); |
There was a problem hiding this comment.
This reduces the amount of memory we can leak, if something doesn't get cleaned up.
@crisbeto I think this line and the underlying logic in the internalImportProvidersFrom (this one specifically) would prevent the componentDef from being released from the memory, i.e. there will always be a reference to the componentDef.type (which points back to componentDef) when the StandaloneService is around. If my understanding is correct, this change has no effect on the memory management.
There was a problem hiding this comment.
Isn't that one going to get destroyed together with the LView or am I misunderstanding?
Either way, my thinking with this change is that it's functionally the same and in the worst case it'll work exactly the same as it does now. In the best case we might get a small memory improvement 🤷
There was a problem hiding this comment.
@crisbeto looking at the code again, I think this change makes sense 👍
The code that I referred to in my previous message (this one) is actually invoked for NgModules only. For the nested standalone components we don't store their defs in an injector, we just iterate over its dependencies, so there should be no extra references. The change that you've made should allow to avoid a reference from the cached injector map. So technically if there are no other references to a particular componentDef (once a component is destroyed) - it should be released from the memory. Currently we would retain the componentDef even if it's unused (when there are no active instances of that component).
Also adding @pkozlowski-opensource as a reviewer, since he is the most familiar with this code.
There was a problem hiding this comment.
So, I don't see harm in making those changes but also not sure if it moves a needle. Observations:
- standalone injectors are used in the dynamic instantiation scenarios;
- we are going through the
ComponentFactoryto dynamically instantiate components; ComponentFactoryalready holds onto component def / type;ApplicationRefis using the above info to have a collection of bootstrapped types - actually I'm not sure why we are doing this...
The net effect of the above is that we already hold references to all the dynamically bootstrapped types.
What I like about this PR is that it clarifies the component's id purpose.
What I dislike is that it is a bit harder now to understand the standalone future.
As I've said, no strong opinion here - don't mind either way.
There was a problem hiding this comment.
My understanding is that lazy-loaded components (via router) would benefit from this change. For example, if we move from route A, which uses ComponentA to route B, which uses ComponentB (both components are standalone) - the ComponentA class (and everything that it refers to and depends on) would be retained in memory, since there is a reference to it (because it's used as a key in a map) in the Standalone injector service. With this change, this reference is no longer present, thus allowing the memory to be released (unless there are some other refs to the class retained somewhere). If my understanding is correct, this PR fixes a memory leak that may happen in certain scenarios (lazy loading).
There was a problem hiding this comment.
I'm not familiar with the router internals to confirm that this is a problem. More specifically I don't know how component reuse works. Anyhow, I see your point: having less references === more safety.
I don't mind either way. Another option we could consider is the usage of a WeekMap
There was a problem hiding this comment.
Andrew raised this over DM: we can't use a WeakMap, because we have to be able to iterate over the keys in ngOnDestroy.
There was a problem hiding this comment.
Was going to suggest a WeakMap as well but any iteration rules it out unfortunately. I think it would otherwise be ideal here though. What if we used a combination of WeakMap for the lookup and a Set (or array) for the injector cleanup. Has a side benefit of making it more clear that there is both caching and cleanup tracking happening in the class.
There was a problem hiding this comment.
Oh right, WeakMap is not an option then.
As I've said, no objections on merging this PR, it does provide an additional safety net.
There was a problem hiding this comment.
We could use a WeakMap and a separate Set to keep track of the injector instances themselves (for iteration). Either way is fine with me.
| export interface ComponentDef<T> extends DirectiveDef<T> { | ||
| /** | ||
| * Runtime unique component ID. | ||
| * Unique ID for the component. Used in view encapsulation and |
There was a problem hiding this comment.
"for the component" => "for the component type / definition" - or anything else that makes it clear that we are talking about component classes and not instances.
alxhub
left a comment
There was a problem hiding this comment.
Note that this PR should not merge within the current feature freeze period and should wait for 14.0.1.
|
|
||
| if (!this.cachedInjectors.has(componentDef)) { | ||
| if (!this.cachedInjectors.has(componentDef.id)) { | ||
| const providers = internalImportProvidersFrom(false, componentDef.type); |
There was a problem hiding this comment.
We could use a WeakMap and a separate Set to keep track of the injector instances themselves (for iteration). Either way is fine with me.
|
Blocked until after the commit freeze. |
|
This PR was merged into the repository by commit 0206c10. |
Makes the following improvements in the runtime: * Uses the unique ID of the component definition to keep track of its injector in the `StandaloneFeature`, instead of the definition itself. This reduces the amount of memory we can leak, if something doesn't get cleaned up. * Changes the naming and description of the `ComponentDef.id` to reflect what it is used for. PR Close #46093
|
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Makes the following improvements in the runtime:
StandaloneFeature, instead of the definition itself. This reduces the amount of memory we can leak, if something doesn't get cleaned up.ComponentDef.idto reflect what it is used for.