PDA

View Full Version : Grid memory leak



dfrederick
23 Oct 2013, 8:34 AM
I found that the configs stored in the event manager are persisted even after the dom elements are deleted when working with infinite scrolling. I haven't determined if this effects regular grids when refreshing/reloading, but I wouldn't be surprised.

Adding a override to Ext.view.NodeCache and calling Ext.EventMangager.purgeElement before elements are deleted in these methods scroll(), removeElement(), removeRange() fixed the issue for us.






(function () {
'use strict';


/**
* @override
* this override changes the behavior of the scroll method to ensure it purges listeners from dom elements to prevent
* a memory leak of the configuration items passed to the event manager for dom elements
* see below for comments with text 'cleanup eventmanager' for added lines
*/
Ext.define('Ringtail.Common.override.view.NodeCache', {
requires: ['Ringtail.Common.override.Common'],
override: 'Ext.view.NodeCache',


/**
* @override
* added Ext.EventManager.purgeElement(el) to remove listeners for memory leak
*/
scroll: function (newRecords, direction, removeCount) {
var me = this,
elements = me.elements,
recCount = newRecords.length,
i,
el,
removeEnd,
newNodes,
nodeContainer = me.view.getNodeContainer(),
frag = document.createDocumentFragment();


// Scrolling up (content moved down - new content needed at top, remove from bottom)
if (direction === -1) {
for (i = (me.endIndex - removeCount) + 1; i <= me.endIndex; i += 1) {
el = elements[i];
Ext.EventManager.purgeElement(el); // cleanup eventmanager
delete elements[i];
el.parentNode.removeChild(el);
}
me.endIndex -= removeCount;


// grab all nodes rendered, not just the data rows
newNodes = me.view.bufferRender(newRecords, me.startIndex -= recCount);
for (i = 0; i < recCount; i += 1) {
elements[me.startIndex + i] = newNodes[i];
frag.appendChild(newNodes[i]);
}
nodeContainer.insertBefore(frag, nodeContainer.firstChild);
} else {
// Scrolling down (content moved up - new content needed at bottom, remove from top)
removeEnd = me.startIndex + removeCount;
for (i = me.startIndex; i < removeEnd; i += 1) {
el = elements[i];
Ext.EventManager.purgeElement(el); // cleanup eventmanager
delete elements[i];
el.parentNode.removeChild(el);
}
me.startIndex = i;


// grab all nodes rendered, not just the data rows
newNodes = me.view.bufferRender(newRecords, me.endIndex + 1);
for (i = 0; i < recCount; i += 1) {
elements[me.endIndex += 1] = newNodes[i];
frag.appendChild(newNodes[i]);
}
nodeContainer.appendChild(frag);
}
// Keep count consistent.
me.count = me.endIndex - me.startIndex + 1;
},


/**
* @override
* added Ext.EventManager.purgeElement(el) to remove listeners for memory leak
*/
removeElement: function (keys, removeDom) {
var me = this,
elements = me.elements,
el,
deleteCount,
keyIndex = 0,
index,
fromIndex;


// Sort the keys into ascending order so that we can iterate through the elements
// collection, and delete items encountered in the keys array as we encounter them.
if (Ext.isArray(keys)) {
deleteCount = keys.length;
for (keyIndex = 0; keyIndex < deleteCount; keyIndex += 1) {
if (typeof keys[keyIndex] !== 'number') {
keys[keyIndex] = me.indexOf(keys[keyIndex]);
}
}
Ext.Array.sort(keys);
} else {
deleteCount = 1;
keys = [keys];
}


// Iterate through elements starting at the element referenced by the first deletion key.
// We also start off and index zero in the keys to delete array.
for (index = fromIndex = keys[0], keyIndex = 0; index <= me.endIndex; index += 1, fromIndex += 1) {


// If the current index matches the next key in the delete keys array, this
// entry is being deleted, so increment the fromIndex to skip it.
// Advance to next entry in keys array.
if (keyIndex < deleteCount && index === keys[keyIndex]) {
fromIndex += 1;
keyIndex += 1;
if (removeDom) {
Ext.removeNode(elements[index]);
}
}


// Shuffle entries forward of the delete range back into contiguity.
if (fromIndex <= me.endIndex && fromIndex >= me.startIndex) {
el = elements[index] = elements[fromIndex];
el.setAttribute('data-recordIndex', index);
} else {
Ext.EventManager.purgeElement(elements[index]); // cleanup eventmanager
delete elements[index];
}
}
me.endIndex -= deleteCount;
me.count -= deleteCount;
},
/**
* @override
* added Ext.EventManager.purgeElement(el) to remove listeners for memory leak
*/
removeRange: function (start, end, removeDom) {
var me = this,
elements = me.elements,
el,
i,
removeCount,
fromPos;


if (end === undefined) {
end = me.count;
} else {
end = Math.min(me.endIndex + 1, end + 1);
}
if (!start) {
start = 0;
}
removeCount = end - start;
for (i = start, fromPos = end; i < me.endIndex; i += 1, fromPos += 1) {
// Within removal range and we are removing from DOM
if (removeDom && i < end) {
Ext.removeNode(elements[i]);
}
// If the from position is occupied, shuffle that entry back into reference "i"
if (fromPos <= me.endIndex) {
el = elements[i] = elements[fromPos];
el.setAttribute('data-recordIndex', i);
} else {
// The from position has walked off the end, so delete reference "i"
Ext.EventManager.purgeElement(elements[i]); // cleanup eventmanager
delete elements[i];
}
}
me.count -= removeCount;
me.endIndex -= removeCount;
}
});
}());

evant
23 Oct 2013, 12:39 PM
When you say:



configs stored in the event manager


What are you referring to? The element cache? It's an interesting result, because the grids never actually bind any specific events to the nodes, they all use event delegation from the body elements.