Angular Component Memory Leak - on Chrome and Microsoft Edge (could be all browsers, haven't tested all of them yet)
Sometimes it won't leak memory, sometimes it will. That's what makes this one of the biggest head scratchers I've faced in a while.
Please don't immediately write this off as "I can't replicate, so it must be fake", if when you first run the program below and it doesn't leak memory. Try it a couple times first, please. I swear it will eventually get into a state when started (because this bug has been plaguing me for a while), that causes it to leak memory.
I've narrow it down to this simple app, since this memory leak has been messing with a much larger production app that I've been working on.
Below is the simplified app that can be used to test with.
https://github.com/kevinpbaker/angular-memory-killer
package.json
{
"name": "memory-killer",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve --port 4201",
"build": "ng build --prod --aot --buildOptimizer --commonChunk --vendorChunk --optimization --progress",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~11.2.14",
"@angular/cdk": "^11.2.13",
"@angular/common": "~11.2.14",
"@angular/compiler": "~11.2.14",
"@angular/core": "~11.2.14",
"@angular/forms": "~11.2.14",
"@angular/material": "^11.2.13",
"@angular/platform-browser": "~11.2.14",
"@angular/platform-browser-dynamic": "~11.2.14",
"@angular/router": "~11.2.14",
"rxjs": "~6.5.4",
"rxjs-compat": "^6.5.5",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1102.18",
"@angular/cli": "~11.2.18",
"@angular/compiler-cli": "~11.2.14",
"@angular/language-service": "~11.2.14",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^6.0.0",
"jasmine-core": "~4.0.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.13",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.7.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "4.0.8"
}
}
app.module.ts
import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppComponent, MemoryKiller2Component, MemoryKillerComponent} from './app.component';
@NgModule({
declarations: [
AppComponent,
MemoryKillerComponent,
MemoryKiller2Component
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Memory Killer</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>
app.component.ts
import {Component} from '@angular/core';
import {timer} from 'rxjs';
import {map} from 'rxjs/operators';
@Component({
selector: 'app-root',
template: '<app-memory-killer *ngIf="killCycle$|async"></app-memory-killer>',
})
export class AppComponent {
// change 1000 to 1 if you want to speed up the memory killer
killCycle$ = timer(0, 1000).pipe(map(k => k % 2 !== 0));
}
@Component({
selector: 'app-memory-killer',
template: `
<div class="memory-killer">
<app-memory-killer2></app-memory-killer2>
<app-memory-killer2></app-memory-killer2>
<app-memory-killer2></app-memory-killer2>
</div>
`,
styles: [`.memory-killer { display: flex; }`]
})
export class MemoryKillerComponent { }
@Component({
selector: 'app-memory-killer2',
template: `
<div class="memory-killer2">
<div>Killing your memory</div>
<div>Killing your memory</div>
<div>Killing your memory</div>
</div>
`,
styles: [`.memory-killer2 { display: flex; }`],
})
export class MemoryKiller2Component { }
Microsoft Edge Detached Elements Viewer and Memory Inspector - Version 97.0.1072.55
Chrome Memory Inspector - Version 97.0.4692.99
Any input as to if this is an angular bug, a browser bug, or if you could point me to the right person that could help me figure this out would be greatly appreciated.
This issue was a browser bug. It has been fixed by the chromium dev team. See the last post within "Original Angular issue post"
Original Angular issue post: https://github.com/angular/angular/issues/45080
Chromium bug specifying fix: https://bugs.chromium.org/p/chromium/issues/detail?id=1308845
This bug has been fixed in Chrome Version M102: See Chromium Bug Report.
The leak happens because LayoutInline gets added to
TextAutoresizer::fingerprints_
but is never removed. LayoutBlocks gets removed because it callsTextAutoresizer::Destroy()
fromWillBeDestroyed()
. The Oilpanize CL just made the leak bigger because now we retain LayoutObjects pointed byTextAutoresizer::fingerprints_
.The leak detector could not catch this leak because TextAutoresizer is owned by the document so the leak will go away on navigation.
The following revision refers to this bug: https://chromium.googlesource.com/chromium/src/+/3b3305a8d8566a9ba5ef6ccf9d363e6497f06356
commit 3b3305a8d8566a9ba5ef6ccf9d363e6497f06356 Author: Keishi Hattori keishi@chromium.org Date: Mon Apr 04 23:34:17 2022
LayoutInline should be removed from
TextAutosizer::FingerprintMapper
LayoutInline was not removed from
TextAutosizer::FingerprintMapper::fingerprints_
causing a memory leak.This CL removed LayoutInline from TextAutosizer in
WillBeDestroyed()
.