GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — vm: fix leak in vm.compileFunction when importModuleDynamically is used by joyeecheung · Pull Request #46785 · nodejs/node

Conversation

@joyeecheung

@joyeecheung joyeecheung commented Feb 22, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Previously in the implementation there was a cycle that V8 could not detect:

Strong global reference to CompiledFnEntry (JS wrapper)
    -> strong reference to callback setting (through the
       callbackMap key-value pair)
    -> importModuleDynamically (wrapper in internalCompileFunction())
    -> Strong reference to the compiled function (through closure in
       internalCompileFunction())

The CompiledFnEntry only gets GC'ed when the compiled function is GC'ed. Since the compiled function is always reachable as described above, there is a leak.

We only needed the first strong global reference because we didn't want the function to outlive the CompiledFnEntry. In this case it can be solved by using a private symbol instead of going with the global reference + destruction in the weak callback, which V8's GC is not going to understand.

Fixes: #42080

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

tonivj5, bricss, sirDonovan, and marvinhagemeister reacted with thumbs up emoji tonivj5, bricss, sirDonovan, odeadglaz, and isker reacted with heart emoji
All reactions
  • 👍 4 reactions
  • ❤️ 5 reactions
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run. labels Feb 22, 2023
@joyeecheung
joyeecheung marked this pull request as draft February 22, 2023 19:49
@joyeecheung

joyeecheung commented Feb 22, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

Draft for now because I need to do some tests to see if the lifetime management is still safe..but it does fix #42080 locally for me.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

addaleax
addaleax approved these changes Feb 23, 2023
View reviewed changes
@joyeecheung joyeecheung mentioned this pull request Feb 23, 2023
vm: memory leak on vm.compileFunction with importModuleDynamically #44211
Closed
@joyeecheung
joyeecheung marked this pull request as ready for review February 23, 2023 15:48
@joyeecheung
joyeecheung force-pushed the fix-compile-function-leak branch from 20a2568 to f77f70e Compare February 23, 2023 16:06
@joyeecheung

joyeecheung commented Feb 23, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

I did some analysis in #44211 (comment) - so I think this should fix the leak in vm.compileFunction(). Although vm.SyntheticModule/vm.SourceTextModule are still leaking, so if the user is doing something like:

const preBakedModule = new vm.SyntheticModule();

vm.compileFunction(code, [], {
  importModuleDynamically: () => {
    return preBakedModule;
  },
});

Then the small leakage of that single SyntheticTextModule is probably fine. But if they are doing something like:

vm.compileFunction(code, [], {
  importModuleDynamically: (specifier) => {
    return new vm.SyntheticModule(...);
  },
});

Then they are going to leak much more. To fix the modules properly we'd need something like https://chromium-review.googlesource.com/c/v8/v8/+/3172764 so that's probably going to take much longer. But anyway this PR at least fixes the leak of vm.compileFunction() itself (then we are not leaking even when importModuleDynamically() is just a noop), so I think we can do this PR first.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@nodejs-github-bot

nodejs-github-bot commented Feb 23, 2023

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/49931/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joyeecheung joyeecheung mentioned this pull request Feb 23, 2023
Memory leak in vm.compileFunction when using importModuleDynamically #42080
Closed
@joyeecheung
vm: fix leak in vm.compileFunction when importModuleDynamically is used
219c771
Previously in the implementation there was a cycle that V8 could not
detect:

Strong global reference to CompiledFnEntry (JS wrapper)
    -> strong reference to callback setting (through the
       callbackMap key-value pair)
    -> importModuleDynamically (wrapper in internalCompileFunction())
    -> Strong reference to the compiled function (through closure in
       internalCompileFunction())

The CompiledFnEntry only gets GC'ed when the compiled function is GC'ed.
Since the compiled function is always reachable as described above,
there is a leak.

We only needed the first strong global reference because we didn't want
the function to outlive the CompiledFnEntry. In this case it can be
solved by using a private symbol instead of going with the global
reference + destruction in the weak callback, which V8's GC is not
going to understand.
@nodejs-github-bot

nodejs-github-bot commented Feb 23, 2023

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/49953/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joyeecheung
joyeecheung force-pushed the fix-compile-function-leak branch from f77f70e to 219c771 Compare February 23, 2023 21:43
@nodejs-github-bot

nodejs-github-bot commented Feb 23, 2023

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/49954/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@github-actions github-actions Bot mentioned this pull request Feb 24, 2023
CI Reliability 2023-02-24 nodejs/reliability#505
Open
25 tasks
jasnell
jasnell approved these changes Feb 24, 2023
View reviewed changes
legendecas
legendecas approved these changes Feb 24, 2023
View reviewed changes

@legendecas legendecas left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great analysis, thanks!

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@nodejs-github-bot

nodejs-github-bot commented Feb 24, 2023

Copy link
Copy Markdown
Collaborator

CI: https://ci.nodejs.org/job/node-test-pull-request/49973/

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joyeecheung joyeecheung added the commit-queue PRs queued for automated landing through the Commit Queue. label Feb 27, 2023
@nodejs-github-bot nodejs-github-bot added commit-queue-failed PRs whose Commit Queue landing failed and need manual intervention before retrying. and removed commit-queue PRs queued for automated landing through the Commit Queue. labels Feb 27, 2023
@nodejs-github-bot

nodejs-github-bot commented Feb 27, 2023

Copy link
Copy Markdown
Collaborator
Commit Queue failed
- Loading data for nodejs/node/pull/46785
✔  Done loading data for nodejs/node/pull/46785
----------------------------------- PR info ------------------------------------
Title      vm: fix leak in vm.compileFunction when importModuleDynamically is used (#46785)
Author     Joyee Cheung  (@joyeecheung)
Branch     joyeecheung:fix-compile-function-leak -> nodejs:main
Labels     c++, lib / src, needs-ci
Commits    1
 - vm: fix leak in vm.compileFunction when importModuleDynamically is used
Committers 1
 - Joyee Cheung 
PR-URL: https://github.com/nodejs/node/pull/46785
Fixes: https://github.com/nodejs/node/issues/42080
Reviewed-By: Anna Henningsen 
Reviewed-By: James M Snell 
Reviewed-By: Chengzhong Wu 
------------------------------ Generated metadata ------------------------------
PR-URL: https://github.com/nodejs/node/pull/46785
Fixes: https://github.com/nodejs/node/issues/42080
Reviewed-By: Anna Henningsen 
Reviewed-By: James M Snell 
Reviewed-By: Chengzhong Wu 
--------------------------------------------------------------------------------
   ℹ  This PR was created on Wed, 22 Feb 2023 19:47:29 GMT
   ✔  Approvals: 3
   ✔  - Anna Henningsen (@addaleax): https://github.com/nodejs/node/pull/46785#pullrequestreview-1310473405
   ✔  - James M Snell (@jasnell) (TSC): https://github.com/nodejs/node/pull/46785#pullrequestreview-1312336429
   ✔  - Chengzhong Wu (@legendecas) (TSC): https://github.com/nodejs/node/pull/46785#pullrequestreview-1312758385
   ✖  Last GitHub CI failed
   ℹ  Last Full PR CI on 2023-02-24T08:36:57Z: https://ci.nodejs.org/job/node-test-pull-request/49973/
- Querying data for job/node-test-pull-request/49973/
   ✔  Last Jenkins CI successful
--------------------------------------------------------------------------------
   ✔  Aborted `git node land` session in /home/runner/work/node/node/.ncu
https://github.com/nodejs/node/actions/runs/4283087915
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

benjamingr
benjamingr approved these changes Feb 28, 2023
View reviewed changes
@legendecas

legendecas commented Mar 1, 2023

Copy link
Copy Markdown
Member

Landed in 986498b

SimenB, odeadglaz, falkenhawk, peterjdrb, and alii reacted with hooray emoji
All reactions
  • 🎉 5 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@legendecas legendecas closed this Mar 1, 2023
legendecas pushed a commit that referenced this pull request Mar 1, 2023
@joyeecheung @legendecas
vm: fix leak in vm.compileFunction when importModuleDynamically is used
986498b
Previously in the implementation there was a cycle that V8 could not
detect:

Strong global reference to CompiledFnEntry (JS wrapper)
    -> strong reference to callback setting (through the
       callbackMap key-value pair)
    -> importModuleDynamically (wrapper in internalCompileFunction())
    -> Strong reference to the compiled function (through closure in
       internalCompileFunction())

The CompiledFnEntry only gets GC'ed when the compiled function is GC'ed.
Since the compiled function is always reachable as described above,
there is a leak.

We only needed the first strong global reference because we didn't want
the function to outlive the CompiledFnEntry. In this case it can be
solved by using a private symbol instead of going with the global
reference + destruction in the weak callback, which V8's GC is not
going to understand.

PR-URL: #46785
Fixes: #42080
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
targos pushed a commit that referenced this pull request Mar 13, 2023
@joyeecheung @targos
vm: fix leak in vm.compileFunction when importModuleDynamically is used
16f6d7e
Previously in the implementation there was a cycle that V8 could not
detect:

Strong global reference to CompiledFnEntry (JS wrapper)
    -> strong reference to callback setting (through the
       callbackMap key-value pair)
    -> importModuleDynamically (wrapper in internalCompileFunction())
    -> Strong reference to the compiled function (through closure in
       internalCompileFunction())

The CompiledFnEntry only gets GC'ed when the compiled function is GC'ed.
Since the compiled function is always reachable as described above,
there is a leak.

We only needed the first strong global reference because we didn't want
the function to outlive the CompiledFnEntry. In this case it can be
solved by using a private symbol instead of going with the global
reference + destruction in the weak callback, which V8's GC is not
going to understand.

PR-URL: #46785
Fixes: #42080
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
targos pushed a commit that referenced this pull request Mar 14, 2023
@joyeecheung @targos
vm: fix leak in vm.compileFunction when importModuleDynamically is used
77b4aca
Previously in the implementation there was a cycle that V8 could not
detect:

Strong global reference to CompiledFnEntry (JS wrapper)
    -> strong reference to callback setting (through the
       callbackMap key-value pair)
    -> importModuleDynamically (wrapper in internalCompileFunction())
    -> Strong reference to the compiled function (through closure in
       internalCompileFunction())

The CompiledFnEntry only gets GC'ed when the compiled function is GC'ed.
Since the compiled function is always reachable as described above,
there is a leak.

We only needed the first strong global reference because we didn't want
the function to outlive the CompiledFnEntry. In this case it can be
solved by using a private symbol instead of going with the global
reference + destruction in the weak callback, which V8's GC is not
going to understand.

PR-URL: #46785
Fixes: #42080
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Chengzhong Wu <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
@targos targos added the dont-land-on-v18.x label Mar 15, 2023
@targos

targos commented Mar 15, 2023

Copy link
Copy Markdown
Member

This change broke many people: #47096
Revert PR: #47101

falkenhawk reacted with eyes emoji
All reactions
  • 👀 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@rene-leanix rene-leanix mentioned this pull request Mar 20, 2023
chore: replace vm.Script with vm.compileFunction jestjs/jest#12205
Closed
@turtlesoupy

turtlesoupy commented Mar 28, 2023

Copy link
Copy Markdown

Since this is reverted on 19.8.1 is there a follow up issue I can track for a follow up diff that isn't crashy?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@legendecas

legendecas commented Mar 29, 2023

Copy link
Copy Markdown
Member

You can track the original issue: #42080.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joyeecheung

joyeecheung commented Jun 21, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

I have another fix that passes the test here locally and also locally no longer crashes the reproduction in #47096: #48510

adanski reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@joyeecheung joyeecheung mentioned this pull request Jun 22, 2023
module: rework of memory management in vm APIs with the importModuleDynamically option #48510
Closed
joyeecheung added a commit to joyeecheung/node that referenced this pull request Jun 22, 2023
@joyeecheung
module: use symbol in WeakMap to manage host defined options
f27339a
Previously when managing the importModuleDynamically callback of
vm.compileFunction(), we use an ID number as the host defined option
and maintain a per-Environment ID -> CompiledFnEntry map to retain
the top-level referrer function returned by vm.compileFunction() in
order to pass it back to the callback, but it would leak because with
how we used v8::Persistent to maintain this reference, V8 would not
be able to understand the cycle and would just think that the
CompiledFnEntry was supposed to live forever. We made an attempt
to make that reference known to V8 by making the CompiledFnEntry weak
and using a private symbol to make CompiledFnEntry strongly
references the top-level referrer function in
nodejs#46785, but that turned out to be
unsound, because the there's no guarantee that the top-level function
must be alive while import() can still be initiated from that
function, since V8 could discard the top-level function and only keep
inner functions alive, so relying on the top-level function to keep
the CompiledFnEntry alive could result in use-after-free which caused
a revert of that fix.

With this patch we use a symbol in the host defined options instead of
a number, because with the stage-3 symbol-as-weakmap-keys proposal
we could directly use that symbol to keep the referrer alive using a
WeakMap. As a bonus this also keeps the other kinds of referrers
alive as long as import() can still be initiated from that
Script/Module, so this also fixes the long-standing crash caused by
vm.Script being GC'ed too early when its importModuleDynamically
callback still needs it.
joyeecheung added a commit to joyeecheung/node that referenced this pull request Jun 27, 2023
@joyeecheung
module: use symbol in WeakMap to manage host defined options
9ab0b4b
Previously when managing the importModuleDynamically callback of
vm.compileFunction(), we use an ID number as the host defined option
and maintain a per-Environment ID -> CompiledFnEntry map to retain
the top-level referrer function returned by vm.compileFunction() in
order to pass it back to the callback, but it would leak because with
how we used v8::Persistent to maintain this reference, V8 would not
be able to understand the cycle and would just think that the
CompiledFnEntry was supposed to live forever. We made an attempt
to make that reference known to V8 by making the CompiledFnEntry weak
and using a private symbol to make CompiledFnEntry strongly
references the top-level referrer function in
nodejs#46785, but that turned out to be
unsound, because the there's no guarantee that the top-level function
must be alive while import() can still be initiated from that
function, since V8 could discard the top-level function and only keep
inner functions alive, so relying on the top-level function to keep
the CompiledFnEntry alive could result in use-after-free which caused
a revert of that fix.

With this patch we use a symbol in the host defined options instead of
a number, because with the stage-3 symbol-as-weakmap-keys proposal
we could directly use that symbol to keep the referrer alive using a
WeakMap. As a bonus this also keeps the other kinds of referrers
alive as long as import() can still be initiated from that
Script/Module, so this also fixes the long-standing crash caused by
vm.Script being GC'ed too early when its importModuleDynamically
callback still needs it.
joyeecheung added a commit to joyeecheung/node that referenced this pull request Sep 12, 2023
@joyeecheung
module: use symbol in WeakMap to manage host defined options
684efdc
Previously when managing the importModuleDynamically callback of
vm.compileFunction(), we use an ID number as the host defined option
and maintain a per-Environment ID -> CompiledFnEntry map to retain
the top-level referrer function returned by vm.compileFunction() in
order to pass it back to the callback, but it would leak because with
how we used v8::Persistent to maintain this reference, V8 would not
be able to understand the cycle and would just think that the
CompiledFnEntry was supposed to live forever. We made an attempt
to make that reference known to V8 by making the CompiledFnEntry weak
and using a private symbol to make CompiledFnEntry strongly
references the top-level referrer function in
nodejs#46785, but that turned out to be
unsound, because the there's no guarantee that the top-level function
must be alive while import() can still be initiated from that
function, since V8 could discard the top-level function and only keep
inner functions alive, so relying on the top-level function to keep
the CompiledFnEntry alive could result in use-after-free which caused
a revert of that fix.

With this patch we use a symbol in the host defined options instead of
a number, because with the stage-3 symbol-as-weakmap-keys proposal
we could directly use that symbol to keep the referrer alive using a
WeakMap. As a bonus this also keeps the other kinds of referrers
alive as long as import() can still be initiated from that
Script/Module, so this also fixes the long-standing crash caused by
vm.Script being GC'ed too early when its importModuleDynamically
callback still needs it.
nodejs-github-bot pushed a commit that referenced this pull request Sep 14, 2023
@joyeecheung @nodejs-github-bot
module: use symbol in WeakMap to manage host defined options
bc9a875
Previously when managing the importModuleDynamically callback of
vm.compileFunction(), we use an ID number as the host defined option
and maintain a per-Environment ID -> CompiledFnEntry map to retain
the top-level referrer function returned by vm.compileFunction() in
order to pass it back to the callback, but it would leak because with
how we used v8::Persistent to maintain this reference, V8 would not
be able to understand the cycle and would just think that the
CompiledFnEntry was supposed to live forever. We made an attempt
to make that reference known to V8 by making the CompiledFnEntry weak
and using a private symbol to make CompiledFnEntry strongly
references the top-level referrer function in
#46785, but that turned out to be
unsound, because the there's no guarantee that the top-level function
must be alive while import() can still be initiated from that
function, since V8 could discard the top-level function and only keep
inner functions alive, so relying on the top-level function to keep
the CompiledFnEntry alive could result in use-after-free which caused
a revert of that fix.

With this patch we use a symbol in the host defined options instead of
a number, because with the stage-3 symbol-as-weakmap-keys proposal
we could directly use that symbol to keep the referrer alive using a
WeakMap. As a bonus this also keeps the other kinds of referrers
alive as long as import() can still be initiated from that
Script/Module, so this also fixes the long-standing crash caused by
vm.Script being GC'ed too early when its importModuleDynamically
callback still needs it.

PR-URL: #48510
Refs: #44211
Refs: #42080
Refs: #47096
Refs: #43205
Refs: #38695
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Stephen Belanger <[email protected]>
@joyeecheung joyeecheung mentioned this pull request Sep 26, 2023
[v20.x] backport vm-related memory fixes #49874
Closed
joyeecheung added a commit to joyeecheung/node that referenced this pull request Sep 28, 2023
@joyeecheung
module: use symbol in WeakMap to manage host defined options
35735ba
Previously when managing the importModuleDynamically callback of
vm.compileFunction(), we use an ID number as the host defined option
and maintain a per-Environment ID -> CompiledFnEntry map to retain
the top-level referrer function returned by vm.compileFunction() in
order to pass it back to the callback, but it would leak because with
how we used v8::Persistent to maintain this reference, V8 would not
be able to understand the cycle and would just think that the
CompiledFnEntry was supposed to live forever. We made an attempt
to make that reference known to V8 by making the CompiledFnEntry weak
and using a private symbol to make CompiledFnEntry strongly
references the top-level referrer function in
nodejs#46785, but that turned out to be
unsound, because the there's no guarantee that the top-level function
must be alive while import() can still be initiated from that
function, since V8 could discard the top-level function and only keep
inner functions alive, so relying on the top-level function to keep
the CompiledFnEntry alive could result in use-after-free which caused
a revert of that fix.

With this patch we use a symbol in the host defined options instead of
a number, because with the stage-3 symbol-as-weakmap-keys proposal
we could directly use that symbol to keep the referrer alive using a
WeakMap. As a bonus this also keeps the other kinds of referrers
alive as long as import() can still be initiated from that
Script/Module, so this also fixes the long-standing crash caused by
vm.Script being GC'ed too early when its importModuleDynamically
callback still needs it.

PR-URL: nodejs#48510
Refs: nodejs#44211
Refs: nodejs#42080
Refs: nodejs#47096
Refs: nodejs#43205
Refs: nodejs#38695
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Stephen Belanger <[email protected]>
ruyadorno pushed a commit that referenced this pull request Sep 28, 2023
@joyeecheung @ruyadorno
module: use symbol in WeakMap to manage host defined options
69e4218
Previously when managing the importModuleDynamically callback of
vm.compileFunction(), we use an ID number as the host defined option
and maintain a per-Environment ID -> CompiledFnEntry map to retain
the top-level referrer function returned by vm.compileFunction() in
order to pass it back to the callback, but it would leak because with
how we used v8::Persistent to maintain this reference, V8 would not
be able to understand the cycle and would just think that the
CompiledFnEntry was supposed to live forever. We made an attempt
to make that reference known to V8 by making the CompiledFnEntry weak
and using a private symbol to make CompiledFnEntry strongly
references the top-level referrer function in
#46785, but that turned out to be
unsound, because the there's no guarantee that the top-level function
must be alive while import() can still be initiated from that
function, since V8 could discard the top-level function and only keep
inner functions alive, so relying on the top-level function to keep
the CompiledFnEntry alive could result in use-after-free which caused
a revert of that fix.

With this patch we use a symbol in the host defined options instead of
a number, because with the stage-3 symbol-as-weakmap-keys proposal
we could directly use that symbol to keep the referrer alive using a
WeakMap. As a bonus this also keeps the other kinds of referrers
alive as long as import() can still be initiated from that
Script/Module, so this also fixes the long-standing crash caused by
vm.Script being GC'ed too early when its importModuleDynamically
callback still needs it.

Backport-PR-URL: #49874
PR-URL: #48510
Refs: #44211
Refs: #42080
Refs: #47096
Refs: #43205
Refs: #38695
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Stephen Belanger <[email protected]>
joyeecheung added a commit to joyeecheung/node that referenced this pull request Dec 1, 2023
@joyeecheung
module: use symbol in WeakMap to manage host defined options
0fb483f
Previously when managing the importModuleDynamically callback of
vm.compileFunction(), we use an ID number as the host defined option
and maintain a per-Environment ID -> CompiledFnEntry map to retain
the top-level referrer function returned by vm.compileFunction() in
order to pass it back to the callback, but it would leak because with
how we used v8::Persistent to maintain this reference, V8 would not
be able to understand the cycle and would just think that the
CompiledFnEntry was supposed to live forever. We made an attempt
to make that reference known to V8 by making the CompiledFnEntry weak
and using a private symbol to make CompiledFnEntry strongly
references the top-level referrer function in
nodejs#46785, but that turned out to be
unsound, because the there's no guarantee that the top-level function
must be alive while import() can still be initiated from that
function, since V8 could discard the top-level function and only keep
inner functions alive, so relying on the top-level function to keep
the CompiledFnEntry alive could result in use-after-free which caused
a revert of that fix.

With this patch we use a symbol in the host defined options instead of
a number, because with the stage-3 symbol-as-weakmap-keys proposal
we could directly use that symbol to keep the referrer alive using a
WeakMap. As a bonus this also keeps the other kinds of referrers
alive as long as import() can still be initiated from that
Script/Module, so this also fixes the long-standing crash caused by
vm.Script being GC'ed too early when its importModuleDynamically
callback still needs it.

PR-URL: nodejs#48510
Refs: nodejs#44211
Refs: nodejs#42080
Refs: nodejs#47096
Refs: nodejs#43205
Refs: nodejs#38695
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Stephen Belanger <[email protected]>
joyeecheung added a commit to joyeecheung/node that referenced this pull request Dec 1, 2023
@joyeecheung
module: use symbol in WeakMap to manage host defined options
bb3462a
Previously when managing the importModuleDynamically callback of
vm.compileFunction(), we use an ID number as the host defined option
and maintain a per-Environment ID -> CompiledFnEntry map to retain
the top-level referrer function returned by vm.compileFunction() in
order to pass it back to the callback, but it would leak because with
how we used v8::Persistent to maintain this reference, V8 would not
be able to understand the cycle and would just think that the
CompiledFnEntry was supposed to live forever. We made an attempt
to make that reference known to V8 by making the CompiledFnEntry weak
and using a private symbol to make CompiledFnEntry strongly
references the top-level referrer function in
nodejs#46785, but that turned out to be
unsound, because the there's no guarantee that the top-level function
must be alive while import() can still be initiated from that
function, since V8 could discard the top-level function and only keep
inner functions alive, so relying on the top-level function to keep
the CompiledFnEntry alive could result in use-after-free which caused
a revert of that fix.

With this patch we use a symbol in the host defined options instead of
a number, because with the stage-3 symbol-as-weakmap-keys proposal
we could directly use that symbol to keep the referrer alive using a
WeakMap. As a bonus this also keeps the other kinds of referrers
alive as long as import() can still be initiated from that
Script/Module, so this also fixes the long-standing crash caused by
vm.Script being GC'ed too early when its importModuleDynamically
callback still needs it.

PR-URL: nodejs#48510
Refs: nodejs#44211
Refs: nodejs#42080
Refs: nodejs#47096
Refs: nodejs#43205
Refs: nodejs#38695
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Stephen Belanger <[email protected]>
richardlau pushed a commit that referenced this pull request Mar 15, 2024
@joyeecheung @richardlau
module: use symbol in WeakMap to manage host defined options
a86a2e1
Previously when managing the importModuleDynamically callback of
vm.compileFunction(), we use an ID number as the host defined option
and maintain a per-Environment ID -> CompiledFnEntry map to retain
the top-level referrer function returned by vm.compileFunction() in
order to pass it back to the callback, but it would leak because with
how we used v8::Persistent to maintain this reference, V8 would not
be able to understand the cycle and would just think that the
CompiledFnEntry was supposed to live forever. We made an attempt
to make that reference known to V8 by making the CompiledFnEntry weak
and using a private symbol to make CompiledFnEntry strongly
references the top-level referrer function in
#46785, but that turned out to be
unsound, because the there's no guarantee that the top-level function
must be alive while import() can still be initiated from that
function, since V8 could discard the top-level function and only keep
inner functions alive, so relying on the top-level function to keep
the CompiledFnEntry alive could result in use-after-free which caused
a revert of that fix.

With this patch we use a symbol in the host defined options instead of
a number, because with the stage-3 symbol-as-weakmap-keys proposal
we could directly use that symbol to keep the referrer alive using a
WeakMap. As a bonus this also keeps the other kinds of referrers
alive as long as import() can still be initiated from that
Script/Module, so this also fixes the long-standing crash caused by
vm.Script being GC'ed too early when its importModuleDynamically
callback still needs it.

PR-URL: #48510
Backport-PR-URL: #51004
Refs: #44211
Refs: #42080
Refs: #47096
Refs: #43205
Refs: #38695
Reviewed-By: Antoine du Hamel <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Reviewed-By: Stephen Belanger <[email protected]>
@SimenB SimenB mentioned this pull request May 8, 2026
vm: SourceTextModule memory leak - instances retained via async context frames after evaluate() #63186
Open
@vishalranaut vishalranaut mentioned this pull request Jun 14, 2026
vm: fix SourceTextModule memory leak #63913
Closed
@robobun robobun mentioned this pull request Sep 13, 2026
node:vm: break Strong<> cycle through NodeVMScriptFetcher importModuleDynamically callback oven-sh/bun#29866
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@jasnell jasnell jasnell approved these changes
@addaleax addaleax addaleax approved these changes
@benjamingr benjamingr benjamingr approved these changes
@legendecas legendecas legendecas approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. commit-queue-failed PRs whose Commit Queue landing failed and need manual intervention before retrying. lib / src Issues and PRs involving general changes in the lib/ or src/ directories. needs-ci PRs that need a full CI run.

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Memory leak in vm.compileFunction when using importModuleDynamically

Uh oh!

There was an error while loading. Please reload this page.

8 participants

@joyeecheung @nodejs-github-bot @legendecas @targos @turtlesoupy @jasnell @addaleax @benjamingr
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.