webpack / webpack

A bundler for javascript and friends. Packs many modules into a few bundled assets. Code Splitting allows for loading parts of the application on demand. Through "loaders", modules can be CommonJs, AMD, ES6 modules, CSS, Images, JSON, Coffeescript, LESS, ... and your custom stuff.
https://webpack.js.org
MIT License
64.55k stars 8.77k forks source link

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

Closed nigelellis closed 2 years ago

nigelellis commented 3 years ago

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 https://github.com/webpack/webpack/issues/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:

sokra commented 3 years ago

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: https://github.com/webpack-contrib/mini-css-extract-plugin/pull/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 commented 3 years ago

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

nigelellis commented 3 years ago

@sokra -- looks like https://github.com/webpack-contrib/mini-css-extract-plugin/pull/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 commented 3 years ago

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 commented 3 years ago

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

alexander-akait commented 3 years ago

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 commented 3 years ago

maybe fixed by https://github.com/webpack/webpack/pull/13184

nigelellis commented 3 years ago

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 https://github.com/webpack/webpack/pull/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 commented 3 years ago

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 commented 3 years ago

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

sokra commented 3 years ago

cache: { type: "filesystem" }

vijaybritto commented 3 years ago

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

sokra commented 3 years ago

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 commented 3 years ago

@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 commented 3 years ago

@vijaybritto which webpack version?

vijaybritto commented 3 years ago

5.35.1

alexander-akait commented 3 years ago

@vijaybritto Can you try latest version?

sibelius commented 3 years ago

@vijaybritto can you share your original webpack config?

alexander-akait commented 3 years ago

@nigel-codaio The problem still exists?

kanoshin commented 3 years ago

@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 commented 3 years ago

@kanoshin What is webpack version?

kanoshin commented 3 years ago

@alexander-akait 5.38.1

alexander-akait commented 3 years ago

@kanoshin we need entire snapshot, please do it not only once, 1-2-5-10 runs, you can put it here, because I don't think it contains something sensitivity

kanoshin commented 3 years ago

I also tried setting maxMemoryGenerations: 0 and experimental option from mini-css-extract-plugin but it didn't help. We have a codebase with about 9k typescript & css files.

alexander-akait commented 3 years ago

Feel free to ping me when you prepare more snapshots, maybe leaking in non official plugins, you can test it locally

kanoshin commented 3 years ago

@alexander-akait attaching the snapshots. It's almost impossible to take the snapshot in our larger app, so I decided to use a smaller one but it still shows a very disproportional memory usage comparing to the codebase size. This app is around 223MB of source code with node_modules but initial memory usage is 3x of that amount and then it keeps growing between rebuilds.

What I'm trying to say is that it's not just a memory leak I'm reporting but most importantly is that simply the initial memory consumption for larger apps makes filesystem cache unusable for larger codebases.

Google drive with zip archive: https://drive.google.com/file/d/1_XR27NpTQnYcOnFZFowqJqcB1FEbe1g0/view?usp=sharing.

Screen Shot 2021-06-11 at 11 26 12 AM
sokra commented 3 years ago

@kanoshin thanks for these profiles. That were very useful. I needed to implement some custom tooling to analyse them, since the devtools show WeakMap references very badly.

Anyway I found two problems:

  1. The less-loader keeps hold on the last webpack compilation the processed less files.

  2. There is a weird reference of some Compilations in v8 feedback vectors:

image

I hope 273bd8f (#13580) will fix that.

I also found some places where memory usage could be improved a bit by getting rid of same unused data, but these where not leaks...

sokra commented 3 years ago

@kanoshin I released the 2. change as https://github.com/webpack/webpack/releases/tag/v5.39.1 Maybe you could test with that version again and capture new profiles (In this case a single one after 20 rebuilds would be enough). It's difficult to see from the profile if this is was the only problem...

sokra commented 3 years ago

~If you want to "workaround" for problem 1, you can remove these lines: https://github.com/webpack-contrib/less-loader/blob/465ffc4052642d799bb29a85056517db31ee1bf5/src/utils.js#L194-L209~

See less-loader@10

alexander-akait commented 3 years ago

I think we can do major release for less-loader and drop it

alexander-akait commented 3 years ago

@kanoshin Please try https://github.com/webpack-contrib/less-loader/releases/tag/v10.0.0

alexander-akait commented 3 years ago

Please update https://github.com/webpack-contrib/mini-css-extract-plugin/releases/tag/v1.6.1, many memory leaks were fixed

kanoshin commented 3 years ago

@sokra @alexander-akait I made the same measurements using updated less-loader and mini-css-extract-plugin. Looks like memory consumption improved in absolute numbers but I still see steady growth from snapshot to snapshot. This time I used heapdump package for taking snapshots.

There are still references to Compilation between the snapshots:

Screen Shot 2021-06-29 at 2 53 42 PM

Here are the snapshots: https://drive.google.com/file/d/1QPk9Qf5dBDt_YDUhZcEn4p9bTOVwbQeW/view?usp=sharing

sokra commented 3 years ago

hmm... interesting... Could you try to run gc() before doing the heapdump? (--expose-gc)

kanoshin commented 3 years ago

@sokra sure, just did that again. Here are two snapshots one after 1 rebuild and second one after ~20 rebuilds across 4-5 different files (TS, LESS, CSS) https://drive.google.com/file/d/1p0v_sdl65CqaaS1PY2WU2iSWzfk-Cn7X/view?usp=sharing.

Screen Shot 2021-06-29 at 4 37 00 PM
sokra commented 3 years ago

Ok it took me a full day to implement tooling for analysing heapsnapshots, but I think I created something that works. Using the devtools it's next to impossible to follow the retainer chain when WeakMaps are involved. The tricky thing with WeakMaps it that you would need to follow the key retainer and the WeakMap retainer while skipping over cycles where the key is held by something the value references.

Anyway here is a dump of retainers of the 12 Compilation objects in your heapsnapshot:

(It's still not easy to read and you need to know a lot about v8 to understand it. A normal leak is a simple chain. When WeakMaps are involved you will see multiple chains.)

synthetic (GC roots) @3
 [13] = synthetic (Global handles) @29
  internal 4492 = processImmediate() in node:internal/timers @6541
   internal context = object system / Context @3758169
    internal previous = object system / Context @3203705
     context timerListMap = object {  } @3203647
      [60000] = object TimersList @5832419
       ._idleNext = object Timeout @7284333
        ._onTimeout = () @11585955
         internal context = object system / Context @1657275
          context compiler = object Compiler @600341
           ._lastCompilation = object Compilation @5653795

This is the last active compilation, which is held by the Compiler. That's fine.


synthetic (GC roots) @3
 [13] = synthetic (Global handles) @29
  internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363
   .require = require() in node:internal/modules/cjs/helpers @4210341
    .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023
     ./Users/kirilla/repos/st/app/Clients/Web/node_modules/less/lib/less/plugin-manager.js = object Module @591753
      internal 2 / part of key (Module @591753) -> value (Object @1942751) pair in WeakMap (table @3687911) = object { __esModule, default } @1942751
synthetic (GC roots) @3
 [13] = synthetic (Global handles) @29
  internal 4516 = () in node:internal/process/esm_loader @6589
   internal context = object system / Context @3004645
    context ESMLoader = object Loader @3004659
     .cjsCache = object WeakMap @4793971
      internal table = array  @3687911
       internal 1235 / part of key (Module @591753) -> value (Object @1942751) pair in WeakMap (table @3687911) = object { __esModule, default } @1942751
        .default = PluginManagerFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/less/lib/less/plugin-manager.js @1940823
         internal context = object system / Context @1754891
          context pm = object PluginManager @8405329
           .webpackLoaderContext = object { getOptions, emitWarning, emitError, getLogger, resolve, getResolve, emitFile, addBuildDependency, utils, rootContext, ... } @8725371
            .getOptions = getOptions() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModule.js @9361177
             internal context = object system / Context @9361179
              context compilation = object Compilation @7747467

This is leak number 1. Again (or still) related to the less-loader. While we removed our own reference from the less object, less itself seem to leak the last plugin manager used, which references our loader context and so the Compilation.

maybe we can remove webpackLoaderContext from pluginManager again after finishing compilation? cc @alexander-akait But it also makes sense to fix the pm leak in less: https://github.com/less/less.js/blob/c5181800980f0a24dfaa5fe71fba813a6a80542a/packages/less/src/less/plugin-manager.js#L158 cc @matthew-dean


synthetic (GC roots) @3
 [13] = synthetic (Global handles) @29
  internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363
   .require = require() in node:internal/modules/cjs/helpers @4210341
    .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023
     ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/ChunkGraph.js = object Module @2512045
      internal 2 / part of key (Module @2512045) -> value (ChunkGraph @2640331) pair in WeakMap (table @3687911) = ChunkGraph() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/ChunkGraph.js @2640331
synthetic (GC roots) @3
 [13] = synthetic (Global handles) @29
  internal 4516 = () in node:internal/process/esm_loader @6589
   internal context = object system / Context @3004645
    context ESMLoader = object Loader @3004659
     .cjsCache = object WeakMap @4793971
      internal table = array  @3687911
       internal 7718 / part of key (Module @2512045) -> value (ChunkGraph @2640331) pair in WeakMap (table @3687911) = ChunkGraph() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/ChunkGraph.js @2640331
        internal context = object system / Context @3437037
         internal previous = object system / Context @3417781
          context 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
synthetic (GC roots) @3
 [13] = synthetic (Global handles) @29
  internal 4492 = processImmediate() in node:internal/timers @6541
   internal context = object system / Context @3758169
    internal previous = object system / Context @3203705
     context timerListMap = object {  } @3203647
      [60000] = object TimersList @5832419
       ._idleNext = object Timeout @7284333
        ._onTimeout = () @11585955
         internal context = object system / Context @1657275
          context compiler = object Compiler @600341
           internal 39 / part of key (Compiler @600341) -> value (PersistentChildCompilerSingletonPlugin @601845) pair in WeakMap (table @601787) = object PersistentChildCompilerSingletonPlugin @601845
synthetic (GC roots) @3
 [13] = synthetic (Global handles) @29
  internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363
   .require = require() in node:internal/modules/cjs/helpers @4210341
    .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023
     ./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

This is leak number 2. Two layers of WeakMaps are involved here. There is probably no way to find that in the normal tooling.

We can clean it up a bit by assuming that the module scoped variable chunkGraphForChunkMap in webpack/lib/ChunkGraph.js, and the Compiler @600341 are globals:

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

So the html-webpack-plugin keeps a reference the (child) Compilation used to compute the html files.

Not sure if this is intendend. It seem to leak it because it references the Chunk @990383 via compiledEntries.*.entry cc @jantimon


object Compilation @1652005
 .compiler = object Compiler @3351893
  .parentCompilation = object Compilation @609611

Compilation @1652005 is the compilation from the html-webpack-plugin. As it's a child compilation is has a reference to the parent Compilation.

This makes the reference to Compilation @1652005 expensive.


Now it gets dirty. The following 8 Compilations are referenced by the Compilation @609611 from above (which seem to be the first Compilation):

synthetic (GC roots) @3
 [13] = synthetic (Global handles) @29
  internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363
   .require = require() in node:internal/modules/cjs/helpers @4210341
    .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023
     ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527
      .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351
       internal context = object system / Context @1661983
        context unsafeCacheDependencies = object WeakMap @3523185
         internal table = array  @3524939
          internal 21778 / part of key (HarmonyImportSideEffectDependency @1741453) -> value (Object @4567883) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @4567883
object Compilation @609611
 .chunkGraph = object ChunkGraph @990597
  .moduleGraph = object ModuleGraph @946751
   ._dependencyMap = object Map @1656927
    internal table = array  @1656953
     internal 49021 = object HarmonyImportSideEffectDependency @1741453
      internal 13 / part of key (HarmonyImportSideEffectDependency @1741453) -> value (Object @4567883) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @4567883
       .module = object NormalModule /Users/kirilla/repos/st/app/Clients/Web/node_modules/query-string/index.js @1361323
        .parser = object JavascriptParser @6141493
         .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @5786111
          .callMemberChain = object HookMap @5786183
           ._map = object Map @10164927
            internal table = array  @7039523
             internal 9 = object Hook @7039527
              .taps = object Array @10244077
               [0] = object { type, fn, name } @6142947
                .fn = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @6142949
                 internal context = object system / Context @8392167
                  context this = object CommonJsExportsParserPlugin @10247099
                   .moduleGraph = object ModuleGraph @5282155
                    ._cacheModuleGraphModuleKey2 = object JsonpChunkLoadingRuntimeModule @5383121
                     .compilation = object Compilation @6140385

This sounds like a problem in webpack. It should not have this line: .parser = object JavascriptParser @6141493.

Usually the parser reference should be removed from the Modules once that Compilation is done, but that doesn't seem to be the case for all modules.

This seem to multiply the leak before, as for each leaked Compilation this problem will cause more Compilation to leak. Compilations should not be connected together via references...

Investigating... (EDIT: fixed by #13680)

More similar stacks ``` synthetic (GC roots) @3 [13] = synthetic (Global handles) @29 internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363 .require = require() in node:internal/modules/cjs/helpers @4210341 .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023 ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527 .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351 internal context = object system / Context @1661983 context unsafeCacheDependencies = object WeakMap @3523185 internal table = array @3524939 internal 14261 / part of key (HarmonyImportSideEffectDependency @3265667) -> value (Object @3076253) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3076253 object Compilation @609611 .chunkGraph = object ChunkGraph @990597 .moduleGraph = object ModuleGraph @946751 ._dependencyMap = object Map @1656927 internal table = array @1656953 internal 23881 = object HarmonyImportSideEffectDependency @3265667 internal 13 / part of key (HarmonyImportSideEffectDependency @3265667) -> value (Object @3076253) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3076253 .module = object NormalModule /Users/kirilla/repos/st/app/Clients/Web/node_modules/css-loader/dist/runtime/getUrl.js @3076255 .parser = object JavascriptParser @8037063 .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @10248425 .assignMemberChain = object HookMap @10248743 ._map = object Map @10248975 internal table = array @8920059 internal 12 = object Hook @8920065 .taps = object Array @8921161 [0] = object { type, fn, name } @8040393 .fn = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @8040539 internal context = object system / Context @8040813 context this = object CommonJsExportsParserPlugin @8921909 .moduleGraph = object ModuleGraph @5231249 ._cacheModuleGraphModuleKey2 = object JsonpChunkLoadingRuntimeModule @5243209 .compilation = object Compilation @6185599 ``` ``` synthetic (GC roots) @3 [13] = synthetic (Global handles) @29 internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363 .require = require() in node:internal/modules/cjs/helpers @4210341 .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023 ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527 .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351 internal context = object system / Context @1661983 context unsafeCacheDependencies = object WeakMap @3523185 internal table = array @3524939 internal 7032 / part of key (HarmonyImportSideEffectDependency @611679) -> value (Object @3450075) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3450075 object Compilation @609611 .chunkGraph = object ChunkGraph @990597 .moduleGraph = object ModuleGraph @946751 ._dependencyMap = object Map @1656927 internal table = array @1656953 internal 54880 = object HarmonyImportSideEffectDependency @611679 internal 13 / part of key (HarmonyImportSideEffectDependency @611679) -> value (Object @3450075) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3450075 .module = object NormalModule /Users/kirilla/repos/st/app/Clients/Web/node_modules/css-loader/dist/runtime/cssWithMappingToString.js @2749631 .parser = object JavascriptParser @6255461 .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @6859293 .assignMemberChain = object HookMap @8451137 ._map = object Map @8451139 internal table = array @6557459 internal 6 = object Hook @6557461 .taps = object Array @8582421 [0] = object { type, fn, name } @6858213 .fn = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @6858217 internal context = object system / Context @6858219 context this = object CommonJsExportsParserPlugin @8451317 .moduleGraph = object ModuleGraph @5594365 ._cacheModuleGraphModuleKey2 = object JsonpChunkLoadingRuntimeModule @5769151 .compilation = object Compilation @6255277 ``` ``` synthetic (GC roots) @3 [13] = synthetic (Global handles) @29 internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363 .require = require() in node:internal/modules/cjs/helpers @4210341 .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023 ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527 .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351 internal context = object system / Context @1661983 context unsafeCacheDependencies = object WeakMap @3523185 internal table = array @3524939 internal 7915 / part of key (HarmonyImportSideEffectDependency @864565) -> value (Object @3315623) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3315623 object Compilation @609611 .chunkGraph = object ChunkGraph @990597 .moduleGraph = object ModuleGraph @946751 ._dependencyMap = object Map @1656927 internal table = array @1656953 internal 55528 = object HarmonyImportSideEffectDependency @864565 internal 13 / part of key (HarmonyImportSideEffectDependency @864565) -> value (Object @3315623) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3315623 .module = object NormalModule /Users/kirilla/repos/st/app/Clients/Web/node_modules/react-slick/lib/index.js @1386033 .parser = object JavascriptParser @5913529 .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @9042675 .assignMemberChain = object HookMap @10364859 ._map = object Map @6695609 internal table = array @6692743 internal 6 = object Hook @6692745 .taps = object Array @6694569 [0] = object { type, fn, name } @9042147 .fn = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @9042151 internal context = object system / Context @9042153 context this = object CommonJsExportsParserPlugin @9044309 .moduleGraph = object ModuleGraph @5805661 ._cacheModuleGraphModuleKey2 = object JsonpChunkLoadingRuntimeModule @6106391 .compilation = object Compilation @6701521 ``` ``` synthetic (GC roots) @3 [13] = synthetic (Global handles) @29 internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363 .require = require() in node:internal/modules/cjs/helpers @4210341 .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023 ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527 .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351 internal context = object system / Context @1661983 context unsafeCacheDependencies = object WeakMap @3523185 internal table = array @3524939 internal 15836 / part of key (HarmonyImportSideEffectDependency @1741695) -> value (Object @3450059) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3450059 object Compilation @609611 .chunkGraph = object ChunkGraph @990597 .moduleGraph = object ModuleGraph @946751 ._dependencyMap = object Map @1656927 internal table = array @1656953 internal 54868 = object HarmonyImportSideEffectDependency @1741695 internal 13 / part of key (HarmonyImportSideEffectDependency @1741695) -> value (Object @3450059) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3450059 .module = object NormalModule /Users/kirilla/repos/st/app/Clients/Web/node_modules/css-loader/dist/runtime/cssWithMappingToString.js @2749619 .parser = object JavascriptParser @5663417 .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @5663419 .expression = object HookMap @9706301 ._map = object Map @9706595 internal table = array @9706969 internal 87 = object Hook @9707187 .taps = object Array @4922405 [0] = object { type, fn, name } @6446041 .fn = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @7630693 internal context = object system / Context @7631285 context this = object CommonJsExportsParserPlugin @7640441 .moduleGraph = object ModuleGraph @5821805 ._cacheModuleGraphModuleKey2 = object JsonpChunkLoadingRuntimeModule @5423517 .compilation = object Compilation @7131009 ``` ``` synthetic (GC roots) @3 [13] = synthetic (Global handles) @29 internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363 .require = require() in node:internal/modules/cjs/helpers @4210341 .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023 ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527 .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351 internal context = object system / Context @1661983 context unsafeCacheDependencies = object WeakMap @3523185 internal table = array @3524939 internal 5968 / part of key (HarmonyImportSpecifierDependency @6535681) -> value (Object @10698419) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @10698419 object Compilation @7131009 ._modules = object Map @10819971 internal table = array @5423179 internal 9981 = object JsonpChunkLoadingRuntimeModule @5423517 .chunkGraph = object ChunkGraph @6546863 .moduleGraph = object ModuleGraph @5821805 ._dependencyMap = object Map @10021659 internal table = array @4210807 internal 82036 = object HarmonyImportSpecifierDependency @6535681 internal 22 / part of key (HarmonyImportSpecifierDependency @6535681) -> value (Object @10698419) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @10698419 .module = object NormalModule /Users/kirilla/repos/st/app/Clients/Web/node_modules/expose-loader/dist/cjs.js??ruleSet[1].rules[10]!/Users/kirilla/repos/st/app/Clients/Web/node_modules/react/index.js @7771271 .parser = object JavascriptParser @7182717 .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @8337939 .call = object HookMap @8337973 ._map = object Map @6212041 internal table = array @6212043 internal 39 = object Hook @9758389 .taps = object Array @9521699 [0] = object { type, fn, name } @9521005 .fn = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @9521007 internal context = object system / Context @8338009 context this = object CommonJsExportsParserPlugin @8196557 .moduleGraph = object ModuleGraph @5789185 ._cacheModuleGraphModuleKey1 = object OnChunksLoadedRuntimeModule @6270799 .compilation = object Compilation @5901063 ``` ``` synthetic (GC roots) @3 [13] = synthetic (Global handles) @29 internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363 .require = require() in node:internal/modules/cjs/helpers @4210341 .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023 ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527 .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351 internal context = object system / Context @1661983 context unsafeCacheDependencies = object WeakMap @3523185 internal table = array @3524939 internal 35361 / part of key (HarmonyImportSpecifierDependency @560921) -> value (Object @2918813) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @2918813 object Compilation @609611 .chunkGraph = object ChunkGraph @990597 .moduleGraph = object ModuleGraph @946751 ._dependencyMap = object Map @1656927 internal table = array @1656953 internal 71575 = object HarmonyImportSpecifierDependency @560921 internal 22 / part of key (HarmonyImportSpecifierDependency @560921) -> value (Object @2918813) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @2918813 .module = object NormalModule /Users/kirilla/repos/st/app/Clients/Web/node_modules/css-loader/dist/runtime/cssWithMappingToString.js @2918815 .parser = object JavascriptParser @7790499 .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @7790501 .expression = object HookMap @10262519 ._map = object Map @10262705 internal table = array @10263249 internal 45 = object Hook @10263585 .taps = object Array @9728763 [1] = object { type, fn, name } @9725253 .fn = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @9725255 internal context = object system / Context @10400689 context this = object CommonJsExportsParserPlugin @10401115 .moduleGraph = object ModuleGraph @5982513 ._cacheModuleGraphModuleKey1 = object OnChunksLoadedRuntimeModule @6821171 .compilation = object Compilation @8131497 ``` ``` synthetic (GC roots) @3 [13] = synthetic (Global handles) @29 internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363 .require = require() in node:internal/modules/cjs/helpers @4210341 .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023 ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527 .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351 internal context = object system / Context @1661983 context unsafeCacheDependencies = object WeakMap @3523185 internal table = array @3524939 internal 1595 / part of key (HarmonyImportSideEffectDependency @527545) -> value (Object @2861547) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @2861547 object Compilation @609611 .chunkGraph = object ChunkGraph @990597 .moduleGraph = object ModuleGraph @946751 ._dependencyMap = object Map @1656927 internal table = array @1656953 internal 37564 = object HarmonyImportSideEffectDependency @527545 internal 13 / part of key (HarmonyImportSideEffectDependency @527545) -> value (Object @2861547) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @2861547 .module = object NormalModule /Users/kirilla/repos/st/app/Clients/Web/node_modules/expose-loader/dist/cjs.js??ruleSet[1].rules[10]!/Users/kirilla/repos/st/app/Clients/Web/node_modules/react/index.js @2861549 .parser = object JavascriptParser @6210879 .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @9452405 .assignMemberChain = object HookMap @11874091 ._map = object Map @10784115 internal table = array @10784117 internal 6 = object Hook @8794631 .taps = object Array @8794635 [0] = object { type, fn, name } @10734067 .fn = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @10734071 internal context = object system / Context @10734073 context this = object CommonJsExportsParserPlugin @11723357 .moduleGraph = object ModuleGraph @7514477 ._cacheModuleGraphModuleKey2 = object JsonpChunkLoadingRuntimeModule @7731475 .compilation = object Compilation @8755487 ``` ``` synthetic (GC roots) @3 [13] = synthetic (Global handles) @29 internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363 .require = require() in node:internal/modules/cjs/helpers @4210341 .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023 ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527 .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351 internal context = object system / Context @1661983 context unsafeCacheDependencies = object WeakMap @3523185 internal table = array @3524939 internal 45975 / part of key (HarmonyImportSideEffectDependency @523979) -> value (Object @1446123) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @1446123 object Compilation @609611 .chunkGraph = object ChunkGraph @990597 .moduleGraph = object ModuleGraph @946751 ._dependencyMap = object Map @1656927 internal table = array @1656953 internal 39865 = object HarmonyImportSideEffectDependency @523979 internal 13 / part of key (HarmonyImportSideEffectDependency @523979) -> value (Object @1446123) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @1446123 .module = object NormalModule @1446125 .parser = object JavascriptParser @7002557 .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @9727591 .call = object HookMap @9727665 ._map = object Map @9727959 internal table = array @9728067 internal 39 = object Hook @9870125 ._x = object Array @9870135 [0] = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @9937759 internal context = object system / Context @11191271 context this = object CommonJsExportsParserPlugin @11996473 .moduleGraph = object ModuleGraph @6571845 ._cacheModuleGraphModuleKey1 = object OnChunksLoadedRuntimeModule @7881417 .compilation = object Compilation @8899997 ``` ``` synthetic (GC roots) @3 [13] = synthetic (Global handles) @29 internal 4355 / DevTools console = object { dir, dirxml, profile, profileEnd, clear, table, keys, values, debug, undebug, ... } @6363 .require = require() in node:internal/modules/cjs/helpers @4210341 .cache = object { /Users/kir...le-call.js, /Users/kir...s/index.js, /Users/kir...hortOut.js, /Users/kir...terator.js, /Users/kir...i/index.js, /Users/kir...oopHook.js, /Users/kir...ailHook.js, /Users/kir...b/pkcs1.js, /Users/kir...ject-dp.js, /Users/kir...p/index.js, ... } @169023 ./Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js = object Module @2512527 .exports = NormalModuleFactory() in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/NormalModuleFactory.js @3350351 internal context = object system / Context @1661983 context unsafeCacheDependencies = object WeakMap @3523185 internal table = array @3524939 internal 11945 / part of key (HarmonyImportSpecifierDependency @527439) -> value (Object @3294237) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3294237 object Compilation @609611 .chunkGraph = object ChunkGraph @990597 .moduleGraph = object ModuleGraph @946751 ._dependencyMap = object Map @1656927 internal table = array @1656953 internal 49048 = object HarmonyImportSpecifierDependency @527439 internal 22 / part of key (HarmonyImportSpecifierDependency @527439) -> value (Object @3294237) pair in WeakMap (table @3524939) = object { module, fileDependencies, missingDependencies, contextDependencies } @3294237 .module = object NormalModule @1361253 .parser = object JavascriptParser @6186181 .hooks = object { evaluateTypeof, evaluate, evaluateIdentifier, evaluateDe...Identifier, evaluateCa...sionMember, isPure, preStatement, blockPreStatement, statement, statementIf, ... } @11664599 .assignMemberChain = object HookMap @11664665 ._map = object Map @9988143 internal table = array @9988145 internal 6 = object Hook @9988489 .taps = object Array @9990183 [0] = object { type, fn, name } @11784255 .fn = () in /Users/kirilla/repos/st/app/Clients/Web/node_modules/webpack/lib/dependencies/CommonJsExportsParserPlugin.js @11784259 internal context = object system / Context @9953659 context this = object CommonJsExportsParserPlugin @9953661 .moduleGraph = object ModuleGraph @8219955 ._cacheModuleGraphModuleKey2 = object JsonpChunkLoadingRuntimeModule @8669323 .compilation = object Compilation @9926431 ```
jantimon commented 3 years ago

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 commented 3 years ago

@jantimon this is memory leak...

sokra commented 3 years ago

@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 commented 3 years ago

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 commented 3 years ago

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

alexander-akait commented 3 years ago

@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 commented 3 years ago

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 :+1: 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 commented 3 years ago

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

jantimon commented 3 years ago

@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 commented 3 years ago

@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 commented 3 years ago

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

alexander-akait commented 3 years ago

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

StreetStrider commented 3 years ago

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

alexander-akait commented 3 years ago

thanks I will look soon