facebook / memlab

A framework for finding JavaScript memory leaks and analyzing heap snapshots
https://facebook.github.io/memlab/
MIT License
4.36k stars 119 forks source link

Memory Leak Issue in Grid Sample - No Leaks Found by MemLab #123

Closed vigneshsf3992 closed 6 days ago

vigneshsf3992 commented 1 month ago

In the below grid sample, a memory leak issue occurs, but MemLab shows no leaks found. Below is the HTML and JavaScript code used in the sample for reproducing the issue:

<!DOCTYPE html>
<html>
<head>
    <title>Syncfusion Js grid control</title>
    <link rel="stylesheet" type="text/css" href="https://cdn.syncfusion.com/ej2/26.2.7/material.css" />
    <script src="https://cdn.syncfusion.com/ej2/26.2.7/dist/ej2.min.js"></script>
</head>
<body>
    My First Syncfusion Js grid control
    <button id="render">Render Grid</button>
    <button id="destroy">Destroy Grid</button>
    <div id="Grid"></div>
    <script>
        document.getElementById('render').onclick = function () {
            var grid = new ej.grids.Grid({
                dataSource: [
                    { OrderID: 10248, CustomerID: 'VINET', EmployeeID: 5, ShipName: 'Vins et alcools Chevalier', ShipCity: 'Reims', ShipCountry: 'France' },
                    { OrderID: 10249, CustomerID: 'TOMSP', EmployeeID: 6, ShipName: 'Toms Spezialitäten', ShipCity: 'Münster', ShipCountry: 'Germany' },
                    { OrderID: 10250, CustomerID: 'HANAR', EmployeeID: 4, ShipName: 'Hanari Carnes', ShipCity: 'Rio de Janeiro', ShipCountry: 'Brazil' },
                    { OrderID: 10251, CustomerID: 'VICTE', EmployeeID: 3, ShipName: 'Victuailles en stock', ShipCity: 'Lyon', ShipCountry: 'France' },
                    { OrderID: 10252, CustomerID: 'SUPRD', EmployeeID: 4, ShipName: 'Suprêmes délices', ShipCity: 'Charleroi', ShipCountry: 'Belgium' }
                ],
                columns: [
                    { field: 'OrderID', headerText: 'Order ID', textAlign: 'Right', width: 120 },
                    { field: 'CustomerID', headerText: 'Customer ID', width: 150 },
                    { field: 'EmployeeID', headerText: 'Employee ID', textAlign: 'Right', width: 120 },
                    { field: 'ShipName', headerText: 'Ship Name', width: 200 },
                    { field: 'ShipCity', headerText: 'Ship City', width: 150 },
                    { field: 'ShipCountry', headerText: 'Ship Country', width: 150 }
                ],
                allowPaging: true,
                pageSettings: { pageSize: 5 },
                allowSorting: true,
                allowFiltering: true,
                filterSettings: { type: 'Excel' },
                allowGrouping: true,
                allowExcelExport: true,
                allowPdfExport: true,
                toolbar: ['ExcelExport', 'PdfExport', 'Search']
            });
            grid.appendTo('#Grid');

            grid.toolbarClick = (args) => {
                if (args.item.id === 'Grid_excelexport') {
                    grid.excelExport();
                }
                if (args.item.id === 'Grid_pdfexport') {
                    grid.pdfExport();
                }
            };        
         };
        document.getElementById('destroy').onclick = function () {
            var control = document.getElementById('Grid');
            control.ej2_instances[0].destroy();
        };
    </script>
</body>
</html>

Report:

image

Expected Behavior: MemLab should detect and report any memory leaks if they occur.

Actual Behavior: No leaks are reported by MemLab despite the presence of a memory leak issue.

JacksonGL commented 1 month ago

Here is the test scenario from my attempt to reproduce this issue:

// test.js
function url() {
  return 'file:///path/to/test.html';
}
async function action(page) {
  await page.click('button[id="render"]');
}
async function back(page) {
  await page.click('button[id="destroy"]');
}

module.exports = { action, back, url };

Running command memlab run --scenario ./test.js got this output:

page-load[12.1MB](baseline)[s1] > action-on-page[13.3MB](target)[s2] > revert[13.3MB](final)[s3]  

total time: 50s
Memory usage across all steps:
15.3 _________
13.5 _________
11.7 ___  _  _
 9.9   _  _  _
 8.1   _  _  _
 6.4   _  _  _
 4.6   _  _  _
 2.8   _  _  _
 1.0   _  _  _
     1  2  3  

MemLab found 2 leak(s)
Number of clusters loaded: 0

--Similar leaks in this run: 30--
--Retained size of leaked objects: 6KB--
[Window] (native) @113239 [3.3KB]
  --3 (element)--->  [HTMLDocument] (native) @113237 [6.3KB]
  --__eventList (property)--->  [Object] (object) @828343 [144 bytes]
  --events (property)--->  [Array] (object) @848663 [116 bytes]
  --0 (element)--->  [Object] (object) @848665 [24 bytes]
  --debounce (property)--->  [native_bind] (closure) @819147 [24 bytes]
  --bound_this (internal)--->  [MXe] (object) @848077 [2.9KB]
  --serviceLocator (property)--->  [s9e] (object) @828955 [12.2KB]
  --services (property)--->  [Object] (object) @848267 [12KB]
  --cellRendererFactory (property)--->  [i9e] (object) @848269 [11.5KB]
  --cellRenderMap (property)--->  [Object] (object) @848275 [11.4KB]
  --RowDragIcon (property)--->  [Y7e] (object) @847085 [772 bytes]
  --element (property)--->  [Detached HTMLTableCellElement] (native) @819269 [120 bytes]

--Similar leaks in this run: 1--
--Retained size of leaked objects: 120 bytes--
[<synthetic>] (synthetic) @1 [13.7MB]
  --2 (shortcut)--->  [Window / file://] (object) @6395 [1MB]
  --__eventList (property)--->  [Object] (object) @828319 [192 bytes]
  --events (property)--->  [Array] (object) @847535 [164 bytes]
  --0 (element)--->  [Object] (object) @847537 [24 bytes]
  --debounce (property)--->  [native_bind] (closure) @818691 [24 bytes]
  --bound_this (internal)--->  [g] (object) @829009 [32.2KB]
  --groupModule (property)--->  [_Xe] (object) @847747 [1.9KB]
  --visualElement (property)--->  [Detached HTMLDivElement] (native) @819155 [120 bytes]

However, when I manually take heap snapshots in Chrome, the final snapshot shows no detached DOM elements, which is puzzling. If the browser, for some reason, still considers the DOM elements created during the action step as attached to the DOM tree, MemLab will not report this as a memory leak by default.

MemLab should detect and report any memory leaks if they occur.

By default, this is not the case; any detection tool can be evaluated by precision and recall when viewed as classifiers. To achieve high precision, MemLab employs heuristics, such as detecting DOM elements detached from the DOM tree, to identify objects allocated during the action step but not released during the back step as memory leaks. These heuristics effectively filter out high-confidence memory leaks but may miss leaks not included in the allowlist, therefore sacrificing recall.

To get 100% recall and bypass the heuristic filtering, you can use the --trace-all-objects CLI flag to capture and dump the clustered traces of all objects that were allocated in action but not released after back. However, this will result in noisier data that can be more challenging to read (i.e., low precision).

To get both high precision and high recall for your particular use case, you can utilize the --leak-filter flag and API provided by MemLab to create custom rules for filtering out memory leaks for that Syncfusion library.