fix(ivy): `LFrame` needs to release memory on `leaveView()` (#35156)
Root cause is that for perf reasons we cache `LFrame` so that we don't have to allocate it all the time. To be extra fast we clear the `LFrame` on `enterView()` rather that on `leaveView()`. The implication of this strategy is that the deepest `LFrame` will retain objects until the `LFrame` allocation depth matches the deepest object. The fix is to simply clear the `LFrame` on `leaveView()` rather then on `enterView()` Fix #35148 PR Close #35156
This commit is contained in:
parent
27cd01f606
commit
b9b512f729
|
@ -3,7 +3,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 1485,
|
||||
"main-es2015": 141000,
|
||||
"main-es2015": 141569,
|
||||
"polyfills-es2015": 36657
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 1485,
|
||||
"main-es2015": 16312,
|
||||
"main-es2015": 16514,
|
||||
"polyfills-es2015": 36657
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 1485,
|
||||
"main-es2015": 147082,
|
||||
"main-es2015": 147647,
|
||||
"polyfills-es2015": 36657
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 1485,
|
||||
"main-es2015": 136250,
|
||||
"main-es2015": 136777,
|
||||
"polyfills-es2015": 37334
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2289,
|
||||
"main-es2015": 246583,
|
||||
"main-es2015": 247198,
|
||||
"polyfills-es2015": 36657,
|
||||
"5-es2015": 751
|
||||
}
|
||||
|
@ -49,7 +49,7 @@
|
|||
"master": {
|
||||
"uncompressed": {
|
||||
"runtime-es2015": 2289,
|
||||
"main-es2015": 225584,
|
||||
"main-es2015": 226144,
|
||||
"polyfills-es2015": 36657,
|
||||
"5-es2015": 779
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||
import {assertDefined} from '../util/assert';
|
||||
import {assertDefined, assertEqual} from '../util/assert';
|
||||
import {assertLViewOrUndefined} from './assert';
|
||||
import {TNode} from './interfaces/node';
|
||||
import {CONTEXT, DECLARATION_VIEW, LView, OpaqueViewState, TVIEW, TView} from './interfaces/view';
|
||||
|
@ -374,29 +374,8 @@ export function enterDI(newView: LView, tNode: TNode) {
|
|||
instructionState.lFrame = newLFrame;
|
||||
newLFrame.previousOrParentTNode = tNode !;
|
||||
newLFrame.lView = newView;
|
||||
if (ngDevMode) {
|
||||
// resetting for safety in dev mode only.
|
||||
newLFrame.isParent = DEV_MODE_VALUE;
|
||||
newLFrame.selectedIndex = DEV_MODE_VALUE;
|
||||
newLFrame.contextLView = DEV_MODE_VALUE;
|
||||
newLFrame.elementDepthCount = DEV_MODE_VALUE;
|
||||
newLFrame.currentNamespace = DEV_MODE_VALUE;
|
||||
newLFrame.currentSanitizer = DEV_MODE_VALUE;
|
||||
newLFrame.bindingRootIndex = DEV_MODE_VALUE;
|
||||
newLFrame.currentQueryIndex = DEV_MODE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
const DEV_MODE_VALUE: any =
|
||||
'Value indicating that DI is trying to read value which it should not need to know about.';
|
||||
|
||||
/**
|
||||
* This is a light weight version of the `leaveView` which is needed by the DI system.
|
||||
*
|
||||
* Because the implementation is same it is only an alias
|
||||
*/
|
||||
export const leaveDI = leaveView;
|
||||
|
||||
/**
|
||||
* Swap the current lView with a new lView.
|
||||
*
|
||||
|
@ -412,21 +391,25 @@ export const leaveDI = leaveView;
|
|||
export function enterView(newView: LView, tNode: TNode | null): void {
|
||||
ngDevMode && assertLViewOrUndefined(newView);
|
||||
const newLFrame = allocLFrame();
|
||||
if (ngDevMode) {
|
||||
assertEqual(newLFrame.isParent, true, 'Expected clean LFrame');
|
||||
assertEqual(newLFrame.lView, null, 'Expected clean LFrame');
|
||||
assertEqual(newLFrame.tView, null, 'Expected clean LFrame');
|
||||
assertEqual(newLFrame.selectedIndex, 0, 'Expected clean LFrame');
|
||||
assertEqual(newLFrame.elementDepthCount, 0, 'Expected clean LFrame');
|
||||
assertEqual(newLFrame.currentDirectiveIndex, -1, 'Expected clean LFrame');
|
||||
assertEqual(newLFrame.currentNamespace, null, 'Expected clean LFrame');
|
||||
assertEqual(newLFrame.currentSanitizer, null, 'Expected clean LFrame');
|
||||
assertEqual(newLFrame.bindingRootIndex, -1, 'Expected clean LFrame');
|
||||
assertEqual(newLFrame.currentQueryIndex, 0, 'Expected clean LFrame');
|
||||
}
|
||||
const tView = newView[TVIEW];
|
||||
instructionState.lFrame = newLFrame;
|
||||
newLFrame.previousOrParentTNode = tNode !;
|
||||
newLFrame.isParent = true;
|
||||
newLFrame.lView = newView;
|
||||
newLFrame.tView = tView;
|
||||
newLFrame.selectedIndex = 0;
|
||||
newLFrame.contextLView = newView !;
|
||||
newLFrame.elementDepthCount = 0;
|
||||
newLFrame.currentDirectiveIndex = -1;
|
||||
newLFrame.currentNamespace = null;
|
||||
newLFrame.currentSanitizer = null;
|
||||
newLFrame.bindingRootIndex = -1;
|
||||
newLFrame.bindingIndex = tView.bindingStartIndex;
|
||||
newLFrame.currentQueryIndex = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -461,8 +444,52 @@ function createLFrame(parent: LFrame | null): LFrame {
|
|||
return lFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* A lightweight version of leave which is used with DI.
|
||||
*
|
||||
* This function only resets `previousOrParentTNode` and `LView` as those are the only properties
|
||||
* used with DI (`enterDI()`).
|
||||
*
|
||||
* NOTE: This function is reexported as `leaveDI`. However `leaveDI` has return type of `void` where
|
||||
* as `leaveViewLight` has `LFrame`. This is so that `leaveViewLight` can be used in `leaveView`.
|
||||
*/
|
||||
function leaveViewLight(): LFrame {
|
||||
const oldLFrame = instructionState.lFrame;
|
||||
instructionState.lFrame = oldLFrame.parent;
|
||||
oldLFrame.previousOrParentTNode = null !;
|
||||
oldLFrame.lView = null !;
|
||||
return oldLFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a lightweight version of the `leaveView` which is needed by the DI system.
|
||||
*
|
||||
* NOTE: this function is an alias so that we can change the type of the function to have `void`
|
||||
* return type.
|
||||
*/
|
||||
export const leaveDI: () => void = leaveViewLight;
|
||||
|
||||
/**
|
||||
* Leave the current `LView`
|
||||
*
|
||||
* This pops the `LFrame` with the associated `LView` from the stack.
|
||||
*
|
||||
* IMPORTANT: We must zero out the `LFrame` values here otherwise they will be retained. This is
|
||||
* because for performance reasons we don't release `LFrame` but rather keep it for next use.
|
||||
*/
|
||||
export function leaveView() {
|
||||
instructionState.lFrame = instructionState.lFrame.parent;
|
||||
const oldLFrame = leaveViewLight();
|
||||
oldLFrame.isParent = true;
|
||||
oldLFrame.tView = null !;
|
||||
oldLFrame.selectedIndex = 0;
|
||||
oldLFrame.contextLView = null !;
|
||||
oldLFrame.elementDepthCount = 0;
|
||||
oldLFrame.currentDirectiveIndex = -1;
|
||||
oldLFrame.currentNamespace = null;
|
||||
oldLFrame.currentSanitizer = null;
|
||||
oldLFrame.bindingRootIndex = -1;
|
||||
oldLFrame.bindingIndex = -1;
|
||||
oldLFrame.currentQueryIndex = 0;
|
||||
}
|
||||
|
||||
export function nextContextImpl<T = any>(level: number): T {
|
||||
|
|
|
@ -485,6 +485,9 @@
|
|||
{
|
||||
"name": "leaveView"
|
||||
},
|
||||
{
|
||||
"name": "leaveViewLight"
|
||||
},
|
||||
{
|
||||
"name": "locateHostElement"
|
||||
},
|
||||
|
|
|
@ -380,6 +380,9 @@
|
|||
{
|
||||
"name": "leaveView"
|
||||
},
|
||||
{
|
||||
"name": "leaveViewLight"
|
||||
},
|
||||
{
|
||||
"name": "locateHostElement"
|
||||
},
|
||||
|
|
|
@ -911,6 +911,9 @@
|
|||
{
|
||||
"name": "leaveView"
|
||||
},
|
||||
{
|
||||
"name": "leaveViewLight"
|
||||
},
|
||||
{
|
||||
"name": "listenerInternal"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue