You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Yes, this behavior used to work in the previous version
The previous version in which this bug was not present was
No response
Description
When running ng serve with i18n configured, every file save (.html files) triggers a rebuild that creats a new piscina ThreadPool for i18n-inline-worker.js. Old pools and their worker threads are not properly cleaned up, causing unbounded memory growth. Each pool spawns worker threads with their own V8 isolates (~120 MB each). After normal deployment activity (~100 saves), the Node.js process consumes over 6GB of memory.
Disabling i18n configuration in angular.json completely eliminates the leak as no new threads are spawned and memory remains stable across development and html file rebuilds.
In the video attached below, you can see the issue in action, each save of the .html file, creates a new thread and increases the memory used by the ng serve nodejs process
Untitled.mov
Minimal Reproduction
Create an Angular project with i18n configured, and use the i18n directive or $localize in the template of an HTML file.
Run ng serve
Open the component's html file and save it repeatedly
Monitor memory and thread count by using Activity Monitor or vmmap / footprint
Observe memory and thread count going up with each save of the html file
The if (executionResult.templateUpdates?.size) runs after the finally block which closes the newly created inliner; but inside the if block we use the closed inliner await inliner.inlineForLocale, which then uses the WorkPool instance:
When doing a test and overriding the method run from the Workpool class, the current worker threads is 0. This causes Piscina to create a new thread (i think, i'm not really sure how Piscina works under the hood), but because this new thread is never called .destroy upon (like we do in the finally block), it becomes an orphan thread, which can't close after the idleTimeout: 4_000, because of the minThreads: 1.
Finally, moving the if block inside the try block (before closing the inliner in the finally block), fixed the issue.
I'm happy to open a PR with the change if it turns out the findings are correct (since I'm not too sure of how Piscina works under the hood fully)
Command
serve
Is this a regression?
The previous version in which this bug was not present was
No response
Description
When running
ng servewith i18n configured, every file save (.html files) triggers a rebuild that creats a new piscinaThreadPoolfori18n-inline-worker.js. Old pools and their worker threads are not properly cleaned up, causing unbounded memory growth. Each pool spawns worker threads with their own V8 isolates (~120 MB each). After normal deployment activity (~100 saves), the Node.js process consumes over 6GB of memory.Disabling i18n configuration in
angular.jsoncompletely eliminates the leak as no new threads are spawned and memory remains stable across development and html file rebuilds.In the video attached below, you can see the issue in action, each save of the .html file, creates a new thread and increases the memory used by the
ng servenodejs processUntitled.mov
Minimal Reproduction
ng serveException or Error
Your Environment
Anything else relevant?
From what I've gathered, the following function creates a new ThreadPool on every rebuild
angular-cli/packages/angular/build/src/builders/application/i18n.ts
Line 32 in 70a81fa
Each call to inlineI18n, which is triggered on every rebuild via
angular-cli/packages/angular/build/src/builders/application/execute-build.ts
Lines 273 to 278 in 70a81fa
creates a new i18nInliner, which creates a new WorkerPool, so each pool immediately spawns at least one OS thread with its own V8 isolate
Now I THINK (not entirely sure, but I did a little fix and it fixed the thread spawning problem) the issue is this piece of code:
angular-cli/packages/angular/build/src/builders/application/i18n.ts
Lines 73 to 165 in 70a81fa
The
if (executionResult.templateUpdates?.size)runs after the finally block which closes the newly created inliner; but inside the if block we use the closed inlinerawait inliner.inlineForLocale, which then uses the WorkPool instance:angular-cli/packages/angular/build/src/tools/esbuild/i18n-inliner.ts
Lines 228 to 231 in 70a81fa
When doing a test and overriding the method run from the Workpool class, the current worker threads is 0. This causes Piscina to create a new thread (i think, i'm not really sure how Piscina works under the hood), but because this new thread is never called .destroy upon (like we do in the finally block), it becomes an orphan thread, which can't close after the
idleTimeout: 4_000, because of theminThreads: 1.Finally, moving the
ifblock inside thetryblock (before closing the inliner in thefinallyblock), fixed the issue.I'm happy to open a PR with the change if it turns out the findings are correct (since I'm not too sure of how Piscina works under the hood fully)