-
-
Notifications
You must be signed in to change notification settings - Fork 8.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Change forEach to forEachLimit and purge cache to fix memory consumption issues #8609
Conversation
|
For maintainers only:
|
You need to add the types in the declaration.d.ts file as we have no typings for neo-async |
Thank you for your pull request! The most important CI builds succeeded, we’ll review the pull request soon. |
From testing so far it seems like this doesn't solve our issue. |
@sokra I added more details to the initial post |
lib/Compiler.js
Outdated
@@ -340,6 +341,7 @@ class Compiler extends Tapable { | |||
source.existsAt = targetPath; | |||
source.emitted = true; | |||
this.outputFileSystem.writeFile(targetPath, content, callback); | |||
source._cachedSource = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
writeFile
is async and potentially may fail; does it change how _cachedSource
should be cleared? (for ex., only on successful write, or only after the async operation is complete)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If writeFile
fails, wouldn't the whole process fail?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's an assumption this piece of code should not make.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also this code currently seems to assume that writeFile
uses _cachedSource
synchronously which may not be the case depending on writeFile
implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Say a single file's cache wasn't cleared, it would have the memory footprint of a single file (not a big deal).
Say a single file's cache was prematurely pruged, webpack knows how to repopulate the cache so it wouldn't lose the source (it's only a cache)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What should be clear is that the cache is never purged and piles up to a GB+. Removing it right after it's written is the obvious end of life for a cache element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What makes you think that writeFile could be asynchronous? The forEachLimit is the async part
The fact that a callback
is passed to it. Is the writeFile
call guaranteed to finish all its work synchronously despite receiving a callback?
What should be clear is that the cache is never purged and piles up to a GB+. Removing it right after it's written is the obvious end of life for a cache element.
Yes, I get that. That's why I'm trying to clarify when this "after it's written" event happens. Is it guaranteed to happen synchronously within the writeFile
call? If so, the code is correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's getting pretty late here (2am) I'll take another look at what you're saying in the morning 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's getting pretty late here (2am) I'll take another look at what you're saying in the morning 👍
Yes, it's 12am on my end. Thanks for addressing the memory issue and paying attention to my comments!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sompylasar I looked into it and it makes sense to purge the cache before writeFile
and I tested it and the fix still works. Thanks for your feedback 👍
I can't easily take this change, as it would be a breaking change. While it probably improves memory/performance for you, it could affect other plugin negatively. Plugins could access the We need to put this behind an opt-in flag and enable it by default in the next major. Instead of removing the |
Closing in favor of #8642 |
This version (https://github.com/webpack/webpack/releases/tag/v4.29.0) includes a memory leak fix for assets (webpack/webpack#8609, webpack/webpack#8642). Thanks to @Meligy for pinging about this!
This version (https://github.com/webpack/webpack/releases/tag/v4.29.0) includes a memory leak fix for assets (webpack/webpack#8609, webpack/webpack#8642). Thanks to @Meligy for pinging about this!
This version (https://github.com/webpack/webpack/releases/tag/v4.29.0) includes a memory leak fix for assets (webpack/webpack#8609, webpack/webpack#8642). Thanks to @Meligy for pinging about this!
…timizations (#6114) We want our new memory optimizations (webpack/webpack#8609)
This version (https://github.com/webpack/webpack/releases/tag/v4.29.0) includes a memory leak fix for assets (webpack/webpack#8609, webpack/webpack#8642). Thanks to @Meligy for pinging about this!
I did some memory profiling on a large app to find the issue. The app was crashing with memory usage of around 1.4GB.
The timelines are only from the "additional chunk assets processing (91%)" stage plus or minus some time. As a note, the profiling slows down the process considerably and I started the profiling manually close to the crash, so don't pay much attention to the lengths of time shown in the timelines.
Before applying this PR
Note that it allocates 550MB all at once (this is not the heap usage, only inside the profiling scope)
When applying the
forEachLimit
(15) changeI noticed the same amount of memory being allocated, except now it was adding 34MB of memory usage every frame. Note that each time it is storing
_cachedSource
When setting
source._cachedSource = undefined
withforEachLimit
The heap memory usage went down drastically and stopped running out of memory. The
_cachedSource
should be removed after a file is emitted.Performance Concerns
I ran
time
on two processes.Before PR using
max_old_space_size
so node didn't run out of memory:2m9s
with1.4GB
peak memory usageAfter PR using standard memory limts:
2m15s
without running out of memory (other processes in the build use a lot of memory so the peak usage wouldn't be a useful metric here)Accessing Private Variables
When I spoke to @sokra about this PR he had issue with the fact that I was using private variables and I can't depend on
source
being aCachedSource
. This doesn't matter because I'm not accessing the variable, I'm just setting it toundefined
. If thesource
isn't aCachedSource
then it will have no effect and will not cause any errors.The alternative of this is adding a method to
CachedSource
to purge the cache and checking thatsource
is an instance ofCachedSource
and calling the method. There's no such thing as private variables in JavaScript and webpack doesn't use TypeScript, so I don't really see a reason to do this.If anyone has another solution, I'd be open to suggestions.
The
forEachLimit
limitI set the limit to 15 and it seems reasonable, but might need to be tweaked.
Thanks to @timneutkens for his help in diagnosing this issue with me.