Skip to content
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

[Webpack 5]: cache leak with incremental compilation #13127

Closed
nigelellis opened this issue Apr 13, 2021 · 59 comments · Fixed by webpack-contrib/less-loader#426
Closed

[Webpack 5]: cache leak with incremental compilation #13127

nigelellis opened this issue Apr 13, 2021 · 59 comments · Fixed by webpack-contrib/less-loader#426

Comments

@nigelellis
Copy link

Bug report

What is the current behavior?
We recently upgraded from webpack4 to webpack5 and are experiencing memory leaks when using webpack5 caching.

We have the following caching policy defined:

  "cache": {
    "cacheDirectory": "/home/admin/src/coda/.webpack-cache",
    "type": "filesystem",
    "store": "pack",
    "name": "baseApp",
    "idleTimeoutForInitialStore": 0
  }

When we launch webpack devserver the VM usage climbs to about 2.5GB and then stabilizes. The bundle outputs in about 70s. When we mutate a source file, this correctly triggers a recompile, and memory doubles before hitting our max cap of 6GB. The process shortly OOM's after that.

We've tried various options for the filesystem cache including maxAge, maxGeneration, etc. all with no difference. I also tried removing contentHashs and setting output: {clean: true} to no avail. The only thing that does help is disabling caching completely (cache=false) but that causes a horrible regression in incremental compilation times.

Here's a snapshot of our running webpack5 config:

{
  "context": "/home/admin/src/coda",
  "devServer": {
    "stats": "errors-only"
  },
  "watchOptions": {
    "ignored": [
      "/home/admin/src/coda/home"
    ]
  },
  "devtool": "cheap-module-source-map",
  "output": {
    "crossOriginLoading": "anonymous",
    "devtoolModuleFilenameTemplate": "[resource-path]",
    "devtoolFallbackModuleFilenameTemplate": "[resource-path]?[contenthash]",
    "path": "/home/admin/src/coda/artifacts/browser-app",
    "filename": "[name].[chunkhash].entry.js",
    "chunkFilename": "[name].[contenthash].chunk.js",
    "publicPath": "/dev/cdn/assets/"
  },
  "resolve": {
    "symlinks": false,
    "fallback": {
      "path": "path-browserify",
      "child_process": false,
      "tls": false,
      "fs": false,
      "net": false,
      "http": "stream-http",
      "https": "https-browserify",
      "stream": "stream-browserify",
      "crypto": "crypto-browserify",
      "os": "os-browserify/browser",
      "zlib": "browserify-zlib"
    },
    "alias": {
      "webpack-intermediate-assets": "/home/admin/src/coda/build/webpack-intermediate-assets",
      "@kr-modules": "/home/admin/src/coda/modules"
    },
    "extensions": [
      ".ts",
      ".tsx",
      ".js",
      ".json"
    ],
    "modules": [
      "/home/admin/src/coda/node_modules"
    ],
    "unsafeCache": true
  },
  "stats": {
    "modules": false,
    "children": false
  },
  "cache": {
    "cacheDirectory": "/home/admin/src/coda/.webpack-cache",
    "type": "filesystem",
    "store": "pack",
    "name": "baseApp",
    "idleTimeoutForInitialStore": 0
  },
  "entry": {
    "browser": "@kr-modules/browser/app/entrypoint",
    "external-form": "@kr-modules/browser/external-form/entrypoint"
  },
  "mode": "development",
  "module": {
    "rules": [
      {
        "exclude": [
          {}
        ],
        "test": {},
        "use": [
          {
            "loader": "cache-loader",
            "options": {
              "cacheDirectory": "/home/admin/src/coda/.cache-loader/baseApp"
            }
          },
          {
            "loader": "thread-loader",
            "options": {
              "poolTimeout": null,
              "workers": 3,
              "name": "ts-pool"
            }
          },
          {
            "loader": "ts-loader",
            "options": {
              "configFile": "tsconfig.dev.json",
              "transpileOnly": true,
              "happyPackMode": true,
              "onlyCompileBundledFiles": true,
              "experimentalFileCaching": true,
              "compilerOptions": {
                "module": "esnext"
              }
            }
          }
        ]
      },
      {
        "test": {},
        "exclude": [
          {}
        ],
        "use": [
          "/home/admin/src/coda/node_modules/mini-css-extract-plugin/dist/loader.js",
          {
            "loader": "css-loader",
            "options": {
              "modules": {
                "localIdentName": "[name]--[local]--[hash:base64:8]",
                "mode": "global"
              },
              "sourceMap": true,
              "url": false,
              "esModule": true
            }
          },
          {
            "loader": "postcss-loader",
            "options": {
              "sourceMap": true
            }
          },
          {
            "loader": "less-loader",
            "options": {
              "sourceMap": true,
              "lessOptions": {
                "modifyVars": {
                  "asset-url-prefix": "'/dev/cdn/assets/795f0f96cc72'",
                  "root-asset-url-prefix": "'/dev/cdn'"
                }
              }
            }
          }
        ]
      },
      {
        "test": {},
        "use": {
          "loader": "file-loader",
          "options": {
            "name": "img/[name].[contenthash].[ext]"
          }
        }
      },
      {
        "test": {},
        "loader": "null-loader"
      }
    ]
  },
  "plugins": [
    {
      "resourceRegExp": {},
      "newContentRegExp": {}
    },
    {
      "definitions": {
        "process.env.NODE_ENV": "\"development\"",
        "process.env.LOAD_DOCUMENTATION": "false"
      }
    },
    {},
    {
      "paths": [
        {},
        {}
      ]
    },
    {
      "options": {
        "basePath": "",
        "fileName": "../server/baseApp.manifest.json",
        "filter": null,
        "map": null,
        "publicPath": null,
        "removeKeyHash": {},
        "sort": null,
        "transformExtensions": {},
        "useEntryKeys": false,
        "writeToFileEmit": false
      }
    },
    {
      "options": {
        "filename": "[name].[contenthash].css",
        "ignoreOrder": false,
        "chunkFilename": "[name].[contenthash].chunk.css"
      }
    },
    {
      "options": {
        "filter": {},
        "allow": "(Apache-2.0 OR BSD-2-Clause OR BSD-3-Clause OR ISC OR MIT OR OFL-1.1 OR Unlicense OR WTFPL OR W3C OR Zlib)",
        "ignore": [
          "highcharts@7.2.2",
          "coda-packs-sdk@0.0.1",
          "coda-icons@0.0.1"
        ],
        "override": {
          "@improbable-eng/grpc-web@0.14.0": {
            "licenseName": "Unlicense"
          }
          // shortened...
        },
        "emitError": true,
        "outputFilename": "OpenSourceSoftwareLicenses-baseApp.json"
      }
    },
    {
      "options": {},
      "timeEventData": {},
      "smpPluginAdded": false
    },
    {
      "definitions": {}
    },
    {
      "definitions": {
        "Buffer": [
          "buffer",
          "Buffer"
        ],
        "process": "process/browser"
      }
    },
    {
      "options": {
        "resourceRegExp": {}
      }
    }
  ],
  "optimization": {
    "minimize": false,
    "concatenateModules": false,
    "minimizer": [
      // TerserPlugin
      {
        "options": {
          "test": {},
          "extractComments": true,
          "cache": true,
          "parallel": 8,
          "terserOptions": {
            "ecma": 2018,
            "compress": {
              "ecma": 2018,
              "keep_classnames": true,
              "unsafe": true,
              "unsafe_arrows": false,
              "unsafe_math": false
            }
          }
        }
      },
      // OptimizeCSSAssetsPlugin
      {
        "pluginDescriptor": {
          "name": "OptimizeCssAssetsWebpackPlugin"
        },
        "options": {
          "assetProcessors": [
            {
              "phase": "compilation.optimize-chunk-assets",
              "regExp": {}
            }
          ],
          "assetNameRegExp": {},
          "cssProcessorOptions": {
            "map": {
              "annotationPrefix": "",
              "inline": false
            }
          },
          "cssProcessorPluginOptions": {}
        },
        "phaseAssetProcessors": {
          "compilation.optimize-chunk-assets": [
            {
              "phase": "compilation.optimize-chunk-assets",
              "regExp": {}
            }
          ],
          "compilation.optimize-assets": [],
          "emit": []
        },
        "deleteAssetsMap": {}
      }
    ]
  }
}

Here's sample output during the compile:

2021-04-13-17:59:20 0|app-webpack     | [baseApp] Change in modules/global-style/colors.less; rebuilding…
2021-04-13-18:00:33 0|app-webpack     |  SMP  ⏱
2021-04-13-18:00:33 0|app-webpack     | General output time took 1 min, 12.86 secs
2021-04-13-18:00:33 0|app-webpack     |  SMP  ⏱  Loaders
2021-04-13-18:00:33 0|app-webpack     | mini-css-extract-plugin, and
2021-04-13-18:00:33 0|app-webpack     | css-loader, and
2021-04-13-18:00:33 0|app-webpack     | postcss-loader, and
2021-04-13-18:00:33 0|app-webpack     | less-loader took 1 min, 5.029 secs
2021-04-13-18:00:33 0|app-webpack     |   module count = 750
2021-04-13-18:00:33 0|app-webpack     | css-loader, and
2021-04-13-18:00:33 0|app-webpack     | postcss-loader, and
2021-04-13-18:00:33 0|app-webpack     | less-loader took 1 min, 4.92 secs
2021-04-13-18:00:33 0|app-webpack     |   module count = 750
2021-04-13-18:00:33 0|app-webpack     | assets by status 10.8 MiB [cached] 6 assets
2021-04-13-18:00:33 0|app-webpack     | assets by status 43.7 MiB [emitted]
2021-04-13-18:00:33 0|app-webpack     |   assets by info 43.5 MiB [immutable]
2021-04-13-18:00:33 0|app-webpack     |     assets by path *.js 41.8 MiB
2021-04-13-18:00:33 0|app-webpack     |       asset browser.6762f8d648ff98edcf15.entry.js 23.1 MiB [emitted] [immutable] (name: browser) 1 related asset
2021-04-13-18:00:33 0|app-webpack     |       asset external-form.61557166b57a93b7fb7f.entry.js 18.7 MiB [emitted] [immutable] (name: external-form) 1 related asset
2021-04-13-18:00:33 0|app-webpack     |     assets by path *.css 1.65 MiB
2021-04-13-18:00:33 0|app-webpack     |       asset browser.e5ff57d15328ceb4a662.css 1000 KiB [emitted] [immutable] (name: browser) 1 related asset
2021-04-13-18:00:33 0|app-webpack     |       asset external-form.31948c4ab1b96ab265b5.css 689 KiB [emitted] [immutable] (name: external-form) 1 related asset
2021-04-13-18:00:33 0|app-webpack     |   asset OpenSourceSoftwareLicenses-baseApp.json 215 KiB [emitted]
2021-04-13-18:00:33 0|app-webpack     |   asset ../server/baseApp.manifest.json 2.79 KiB [emitted]
2021-04-13-18:00:33 0|app-webpack     | Entrypoint browser 24.1 MiB (20.5 MiB) = browser.e5ff57d15328ceb4a662.css 1000 KiB browser.6762f8d648ff98edcf15.entry.js 23.1 MiB 2 auxiliary assets
2021-04-13-18:00:33 0|app-webpack     | Entrypoint external-form 19.4 MiB (17.1 MiB) = external-form.31948c4ab1b96ab265b5.css 689 KiB external-form.61557166b57a93b7fb7f.entry.js 18.7 MiB 2 auxiliary assets
2021-04-13-18:00:33 0|app-webpack     | webpack 5.32.0 compiled successfully in 72866 ms

Versioning information:
webpack: 5.32.0
webpack-cli: 4.5.0
webpack-dev-server: ^.11.2

I noticed similar issues reported in #12947. The proposal there was to use a memory cache and set output.clean. I've tried that with {type: "memory", maxGenerations: 1} and output.clean, as well as stripping contentHashes. I still see the issue.

Anything else I can do to help narrow down the underlying issue? Any workaround that would
maintain caching without the OOM?

Thanks,
Nigel.

If the current behavior is a bug, please provide the steps to reproduce.
I'm just running:

webpack-cli -w --color --env dev--config=...

and then mutate a source file.

What is the expected behavior?
The memory working-set should be stable and not grow without bounds.

Other relevant information:
webpack version: 5.32.0
Node.js version: 14.16.1
Operating System: MacOS (or Linux Buster)
Additional tools:

@nigelellis nigelellis changed the title Webpack5: cache leak with incremental compilation [Webpack 5]: cache leak with incremental compilation Apr 13, 2021
@sokra
Copy link
Member

sokra commented Apr 14, 2021

Sounds like a memory leak somewhere. Difficult to find without access to the repo or a memory profile.

But I think the huge memory usage originates from mini-css-extract-plugin and we have an PR open to add an experimental largely more memory efficient mode.

I'm looking for testers and your case seems like a good fit.

Here is the PR: webpack-contrib/mini-css-extract-plugin#737
see instructions here. You can use the version of the pr with "mini-css-extract-plugin": "webpack-contrib/mini-css-extract-plugin#feature/import-module" in package.json

@alexander-akait
Copy link
Member

@nigel-codaio Maybe you can provide example of repo, so we can investigate memory leak

@nigelellis
Copy link
Author

@sokra -- looks like webpack-contrib/mini-css-extract-plugin#737 was released with v1.5.0. I'll try pulling this in to see if it helps.

@alexander-akait -- I haven't had luck trimming this down but should be able to set aside time to investigate this week.

@nigelellis
Copy link
Author

I tried the latest mini-css-extract-plugin (v1.5.0) with today's webpack5 release (5.34.0) and still hit the issue. @sokra it definitely appears related to the mini-css-extract plugin as editing non-less files doesn't exhibit the memory leak.

Sharing the full project is challenging as this is a commercial codebase. Are there any guides or instructions on how I might perform memory profile? Any best practices to follow? I'm game to dig in but don't know how to start. Thanks!

@alexander-akait
Copy link
Member

alexander-akait commented Apr 20, 2021

@nigel-codaio Maybe you can provide full configuration when memory leak? I think you have non official plugin(s) with leaking

@alexander-akait
Copy link
Member

alexander-akait commented Apr 20, 2021

Small notes - OptimizeCssAssetsWebpackPlugin is deprecated and potential leaking, please use https://github.com/webpack-contrib/css-minimizer-webpack-plugin/. What is terser-webpack-plugin version? You don't need cache-loader, there big bugs with webpack v5 and possible leaks, please use cache.type: 'filesystem' (now we have built-in cache)

@sokra
Copy link
Member

sokra commented Apr 20, 2021

maybe fixed by #13184

@nigelellis
Copy link
Author

Hi @sokra and @alexander-akait -- thanks for the suggestions.

I made the following changes today:

  1. Migrated to css-minimizer-webpack-plugin
  2. Removed cache-loader -- we are already using the webpack5 filecache. I'd previously noticed some benefits of the cache-loader with typescript but these were relatively small.
  3. Bumped our main app bundle memory from 4GB to 6GB

With these changes in place, things appear to be working better. @sokra I also pulled in #13184 and tried bundling with that. This also gives some improvement and appears to have a lower memory cap than without it.

What's the recommendation of filecache vs. memory cache? Our production builds don't use any form of caching currently so I'm mostly focused on webpack devserver behavior with an eye on incremental compilation cost. Is there a discussion anywhere on the tradeoffs between the caching approaches, and a best-practice example on the optimal config for dev?

Thanks.

@sokra
Copy link
Member

sokra commented Apr 21, 2021

You can use the filesystem cache for production and development. The filesystem cache includes a memory cache to avoid reading/writing to disk too often.
You usually don't need to change any of the advanced settings for the cache. The defaults should work fine.

@vijaybritto
Copy link

@sokra The filesystem cache includes a memory cache or you mean cache: { type: 'memory' } ?

@sokra
Copy link
Member

sokra commented Apr 21, 2021

cache: { type: "filesystem" }

@vijaybritto
Copy link

@sokra so you mean even though we write cache type as filesystem there will be things stored in memory as well?

@sokra
Copy link
Member

sokra commented Apr 21, 2021

Yes, memory cache is faster. So for incremental builds that's faster. But cache items only stay for a few generations in the memory cache. See maxMemoryGenerations.
You could disable that with maxMemoryGenerations: 0, where cache items are only stored in memory until they are serialized to disk. After that they need to be read from disk on access.

@vijaybritto
Copy link

vijaybritto commented Apr 25, 2021

@sokra The memory consumption stays low if I give cache type as 'memory' but when I give it as filesystem the memory usage is very high and the process crashes in just a few minutes.

I'm sticking with cache type memory for now.

@sokra
Copy link
Member

sokra commented Apr 25, 2021

@vijaybritto which webpack version?

@vijaybritto
Copy link

5.35.1

@alexander-akait
Copy link
Member

@vijaybritto Can you try latest version?

@sibelius
Copy link

@vijaybritto can you share your original webpack config?

alan-agius4 added a commit to angular/angular-cli that referenced this issue May 18, 2021
…ng builds

This reduce memory consumption during re-builds.

```
runtime.ba93f81591909b93394f.hot-update.js.map will be removed
styles.ba93f81591909b93394f.hot-update.js.map will be removed
runtime.ba93f81591909b93394f.hot-update.json will be removed
runtime.ba93f81591909b93394f.hot-update.js will be removed
styles.ba93f81591909b93394f.hot-update.js will be removed
```

See webpack/webpack#12947 (comment) and webpack/webpack#13127

(cherry picked from commit f5f41ea)
alan-agius4 added a commit to angular/angular-cli that referenced this issue May 18, 2021
…ng builds

This reduce memory consumption during re-builds.

```
runtime.ba93f81591909b93394f.hot-update.js.map will be removed
styles.ba93f81591909b93394f.hot-update.js.map will be removed
runtime.ba93f81591909b93394f.hot-update.json will be removed
runtime.ba93f81591909b93394f.hot-update.js will be removed
styles.ba93f81591909b93394f.hot-update.js will be removed
```

See webpack/webpack#12947 (comment) and webpack/webpack#13127
@alexander-akait
Copy link
Member

@nigel-codaio The problem still exists?

@kanoshin
Copy link

@alexander-akait @sokra we observe the similar issue with our application. Memory consumption with cache: { type: "memory" } is around 2GB but with filesystem cache it grows dramatically (7-8GB). This basically makes filesystem cache unusable in our setup, my personal laptop starts memory swapping aggressively which degrades performance.

I was able to make a memory snapshot with Chrome, here is a screenshot. Lmk what other info you need, I can probably share the entire snapshot if you give me your email.
Screen Shot 2021-06-09 at 6 01 02 PM

@alexander-akait
Copy link
Member

@kanoshin What is webpack version?

@kanoshin
Copy link

@alexander-akait 5.38.1

@jantimon
Copy link
Contributor

jantimon commented Jul 1, 2021

The html-webpack-plugin stores a singleton of PersistentChildCompilerSingletonPlugin to the compiler using a WeakMap:
https://github.com/jantimon/html-webpack-plugin/blob/8f8f7c53c4e4f822020d6da9de0304f8c23de08f/lib/cached-child-compiler.js#L60-L67

This singleton stores a the result of a child compiler:

https://github.com/jantimon/html-webpack-plugin/blob/8f8f7c53c4e4f822020d6da9de0304f8c23de08f/lib/cached-child-compiler.js#L165-L209

@alexander-akait
Copy link
Member

@jantimon this is memory leak...

@sokra
Copy link
Member

sokra commented Jul 2, 2021

@jantimon this is memory leak...

Let's call it a "cache". It doesn't leak a infinite amount of memory, only the latest compilation used for html generation.

Maybe we can reduce the memory usage a bit by not referencing Chunk objects from the compilation (Chunks will hold on the whole Compilation and parent Compilation, because you can reach other chunks from there and modules, and for backward-compat reasons, etc.)

@jantimon
Copy link
Contributor

jantimon commented Jul 2, 2021

I also can't see any leaks here. But we can definitely try to improve the cache size :)

Maybe I should explain why this cache was introduced at all:

Previously the html-webpack-plugin child compiler would compile on every watch run even if no relevant file was changed.
With the new filesnapshot api it was finally possible to run a child compiler only once the child compilers file dependencies change

@alexander-akait
Copy link
Member

@jantimon Why we need to keep whole compilation, maybe we can cache only related stuff?

@alexander-akait
Copy link
Member

@kanoshin Can you test again, please update webpack https://github.com/webpack/webpack/releases/tag/v5.42.0 and less-loader https://github.com/webpack-contrib/less-loader/releases/tag/v10.0.1

@StreetStrider
Copy link

I've tried it. I see no memory growth when modifying both TS and LESS modules. We had +100Mb per modification of all files in package once. Right now it is gone 👍 However, when I modify LESS files there's an occasinal error in the debugger. It crashes the process. This is it:

VM3690 AssetGenerator.js:268 Uncaught TypeError: Cannot read property 'dataUrl' of undefined
    at AssetGenerator.getTypes (/home/strider/Projects/st/app/Clients/Web/node_modules/webpack/lib/asset/AssetGenerator.js:268:24)
    at NormalModule.getSourceTypes (/home/strider/Projects/st/app/Clients/Web/node_modules/webpack/lib/NormalModule.js:1091:39)
    at NormalModule.cleanupForCache (/home/strider/Projects/st/app/Clients/Web/node_modules/webpack/lib/NormalModule.js:362:45)
    at NormalModuleFactory.cleanupForCache (/home/strider/Projects/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js:670:11)
    at Compiler._cleanupLastNormalModuleFactory (/home/strider/Projects/st/app/Clients/Web/node_modules/webpack/lib/Compiler.js:383:34)
    at Compiler.createNormalModuleFactory (/home/strider/Projects/st/app/Clients/Web/node_modules/webpack/lib/Compiler.js:1049:8)
    at Compiler.newCompilationParams (/home/strider/Projects/st/app/Clients/Web/node_modules/webpack/lib/Compiler.js:1071:30)
    at Compiler.compile (/home/strider/Projects/st/app/Clients/Web/node_modules/webpack/lib/Compiler.js:1082:23)
    at /home/strider/Projects/st/app/Clients/Web/node_modules/webpack/lib/Watching.js:188:19
    at Hook.eval [as callAsync] (eval at create (/home/strider/Projects/st/app/Clients/Web/node_modules/webpack/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:24:1)

I remember no such error in the previous tests (before today's fixes). May it be caused by the fixes?

@alexander-akait
Copy link
Member

alexander-akait commented Jul 2, 2021

Yes, regression on our side, please wait, we will fix it in near future

@jantimon
Copy link
Contributor

jantimon commented Jul 2, 2021

@jantimon Why we need to keep whole compilation, maybe we can cache only related stuff?
yes that's what I meant by we should try to decrease the cache size 😄

Right now the following is cached:

https://github.com/jantimon/html-webpack-plugin/blob/8f8f7c53c4e4f822020d6da9de0304f8c23de08f/lib/cached-child-compiler.js#L31-L37

{
  dependencies: FileDependencies,
  compiledEntries: {[entryName: string]: ChildCompilationResultEntry}
}

ChildCompilationResultEntry is coming from: https://github.com/jantimon/html-webpack-plugin/blob/8f8f7c53c4e4f822020d6da9de0304f8c23de08f/lib/child-compiler.js#L189-L192

{
            content: templateSource,
            hash: childCompilation.hash || 'XXXX',
            entry: entries[entryIndex]
}

I assume that entries might keep the entire compilation in memory or I missed something

@alexander-akait
Copy link
Member

@jackfranklin yes

chunkGraphForChunkMap = object WeakMap @4947027
 internal table = array  @990375
  internal 9 / part of key (Chunk @990383) -> value (ChunkGraph @990385) pair in WeakMap (table @990375) = object ChunkGraph @990385
object Compiler @600341
 internal 39 / part of key (Compiler @600341) -> value (PersistentChildCompilerSingletonPlugin @601845) pair in WeakMap (table @601787) = object PersistentChildCompilerSingletonPlugin @601845
/Users/kirilla/repos/st/app/Clients/Web/node_modules/html-webpack-plugin/lib/cached-child-compiler.js = object Module @2512555
 .exports = object { CachedChil...ompilation } @3688233
  .CachedChildCompilation = CachedChildCompilation() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/html-webpack-plugin/lib/cached-child-compiler.js @601335
   internal context = object system / Context @601299
    context compilerMap = object WeakMap @601305
     internal table = array  @601787
      internal 3 / part of key (Compiler @600341) -> value (PersistentChildCompilerSingletonPlugin @601845) pair in WeakMap (table @601787) = object PersistentChildCompilerSingletonPlugin @601845
       .compilationState = object { isCompiling, isVerifyingCache, entries, compiledEntries, compilationResult, mainCompilationHash } @7146865
        .compilationResult = object { compiledEntries, dependencies, mainCompilationHash } @3496709
         .compiledEntries = object { /Users/kir.../index.ejs } @3554355
          ./Users/kirilla/repos/st/app/Clients/Web/node_modules/html-webpack-plugin/lib/loader.js!/Users/kirilla/repos/st/app/Clients/Web/packages/auth/app/index.ejs = object { content, hash, entry } @2857067
           .entry = object Chunk @990383
            internal 20 / part of key (Chunk @990383) -> value (ChunkGraph @990385) pair in WeakMap (table @990375) = object ChunkGraph @990385
             ._cacheChunkGraphModuleKey2 = object PublicPathRuntimeModule @2341369
              .compilation = object Compilation @1652005

above trace where they stored

@StreetStrider
Copy link

I see some ModuleGraphConnection objects in the delta. I've also detected them in the previous tests.
изображение

@alexander-akait
Copy link
Member

@StreetStrider It can be any non official plugin, please provide profiling

@StreetStrider
Copy link

@alexander-akait
https://drive.google.com/file/d/14C4JDlYMssApdpXl5OLPvWYCQCcbKXQC/view

@alexander-akait
Copy link
Member

thanks I will look soon

@sokra
Copy link
Member

sokra commented Jul 8, 2021

I open-sourced a tool to easily analyse these heapsnapshots: https://github.com/sokra/heapdump-analyser

NODE_OPTIONS=--max_old_space_size=16000 npx heapdump-analyser /path/to/file.heapsnapshot Compilation

This shows the retainer graphs for all Compilation objects within the heapsnapshot.

@sokra
Copy link
Member

sokra commented Jul 8, 2021

@StreetStrider I can't see a leak in your profiles...

PS: It would be better to have 3 profiles when analysing differences between profiles

@StreetStrider
Copy link

@sokra thanks, I was not exactly sure. Increasing in delta does not mean leak directly, but the numbers seemed suspicious to me.

How to prepare the third profile? Usually I take 1st profile, then make some actions, then take 2nd. Do I need to just add another round to that process?

@sokra
Copy link
Member

sokra commented Jul 8, 2021

Another round. Then view the 3rd profile with Objects allocated between 1 and 2 selected.

@sokra
Copy link
Member

sokra commented Jul 8, 2021

So you see objects that were created by an rebuild that are still alive after another rebuild

@webpack-bot
Copy link
Contributor

This issue had no activity for at least three months.

It's subject to automatic issue closing if there is no activity in the next 15 days.

@webpack-bot
Copy link
Contributor

Issue was closed because of inactivity.

If you think this is still a valid issue, please file a new issue with additional information.

@helloitsjoe
Copy link
Contributor

helloitsjoe commented Nov 29, 2023

Hi folks, we've discovered this same memory leak and I can reproduce it with a vanilla Webpack/HtmlWebpackPlugin setup, repo here: https://github.com/helloitsjoe/webpack-memory-leak

I'm happy to open a new issue here or in html-webpack-plugin if that's preferred.

I wrote details in the README, and some of this may be restating points above, but here's a summary:

In HtmlWebpackPlugin, htmlWebpackPluginHooksMap (WeakMap) adds a compilation every time the template HTML file index.template.html is modified. These WeakMap entries are never garbage collected, but I haven't been able to determine why. My one lead is that moduleGraphForModuleMap (WeakMap in Webpack) keys are holding onto references to the Compilation objects.

Here are some relevant screenshots, also in the readme, let me know if I can help more or provide more info:

Heap snapshot

Heap snapshot

We can see that each key in that WeakMap has a reference to the compilation, which has a reference to the HtmlWebpackPlugin compiler:

WeakMap

WeakMap

@helloitsjoe
Copy link
Contributor

helloitsjoe commented Dec 7, 2023

I've opened #17851 to track this and believe I have a fix, I will have a PR ready shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants