Change forEach to forEachLimit and purge cache to fix memory consumption issues#8609
Change forEach to forEachLimit and purge cache to fix memory consumption issues#8609dav-is wants to merge 8 commits intowebpack:masterfrom dav-is:change-foreach-to-foreachlimit
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
| source.existsAt = targetPath; | ||
| source.emitted = true; | ||
| this.outputFileSystem.writeFile(targetPath, content, callback); | ||
| source._cachedSource = undefined; |
There was a problem hiding this comment.
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.
If writeFile fails, wouldn't the whole process fail?
There was a problem hiding this comment.
That's an assumption this piece of code should not make.
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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.
@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!
…timizations (#6114) We want our new memory optimizations (webpack/webpack#8609)
…timizations (#6114) We want our new memory optimizations (webpack/webpack#8609)
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
_cachedSourceWhen setting
source._cachedSource = undefinedwithforEachLimitThe heap memory usage went down drastically and stopped running out of memory. The
_cachedSourceshould be removed after a file is emitted.Performance Concerns
I ran
timeon two processes.Before PR using
max_old_space_sizeso node didn't run out of memory:2m9swith1.4GBpeak memory usageAfter PR using standard memory limts:
2m15swithout 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
sourcebeing aCachedSource. This doesn't matter because I'm not accessing the variable, I'm just setting it toundefined. If thesourceisn't aCachedSourcethen it will have no effect and will not cause any errors.The alternative of this is adding a method to
CachedSourceto purge the cache and checking thatsourceis an instance ofCachedSourceand 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
forEachLimitlimitI 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.