Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory leak on Promise.all with async/await #34328

Closed
marian2js opened this issue Jul 12, 2020 · 11 comments
Closed

Memory leak on Promise.all with async/await #34328

marian2js opened this issue Jul 12, 2020 · 11 comments
Labels
async_hooks Issues and PRs related to the async hooks subsystem. promises Issues and PRs related to ECMAScript promises.

Comments

@marian2js
Copy link

  • Version: 14.5 / 12.18
  • Platform: macOS Catalina / MacBook Pro 2019 (Darwin 19.5.0 Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64 x86_64)

What steps will reproduce the bug?

(async function () {
  const a = new Array(10).fill(0)
  for (let i = 0; i < 1e8; i++) {
    await Promise.all(a.map(() => Promise.resolve()))
    if (i % 1e5 === 0) {
      console.log(Math.round(process.memoryUsage().heapUsed / 1024 / 1024), 'MB')
    }
  }
})()

Output:

454 MB
78 MB
140 MB
230 MB
152 MB
216 MB
250 MB
303 MB
367 MB
431 MB
496 MB
710 MB
774 MB
838 MB
891 MB
956 MB
1020 MB
1073 MB
1137 MB
1201 MB
1266 MB
RangeError: Value undefined out of range for undefined options property undefined
    at Map.set (<anonymous>)
    at AsyncHook.init (domain.js:68:15)
    at PromiseWrap.emitInitNative (internal/async_hooks.js:154:43)
    at Promise.then (<anonymous>)
    at Function.all (<anonymous>)
    at repl:4:19
    at processTicksAndRejections (internal/process/task_queues.js:97:5)

How often does it reproduce? Is there a required condition?

The issue is always reproducible, I tried it in node 14.5 and 12.18.

What is the expected behavior?

Memory usage shouldn't increase since we are waiting for all promises to be resolved before moving to the next iteration.

@bnoordhuis bnoordhuis added async_hooks Issues and PRs related to the async hooks subsystem. promises Issues and PRs related to ECMAScript promises. labels Jul 13, 2020
@bnoordhuis
Copy link
Member

I can't reproduce with master - memory remains stable - so this is likely a bug that's been fixed but not released yet.

cc @nodejs/async_hooks - does any of you know what commit / PR contains the fix?

@Flarna
Copy link
Member

Flarna commented Jul 13, 2020

I can't reproduce this with 12.18.2 either. But I tested on windows and linux only - not mac.

@Flarna
Copy link
Member

Flarna commented Jul 13, 2020

I can reproduce something like this (even on master) if I prepend following code:

const { createHook } = require("async_hooks");

const m = new Map();
createHook({
  init(id) {
    m.set(id, "val");
  },
  destroy(id) {
    m.delete(id);
  }
}).enable();

Reason is most likely that destroy hook gets never called as it is scheduled via SetImmediate but the more or less endless running promise look is not allowing the process to get into the next tick.

see

env->SetImmediate(&DestroyAsyncIdsCallback, CallbackFlags::kUnrefed);

@bnoordhuis
Copy link
Member

I thought that error message looked familiar! This is a duplicate of #33896, see the example in #33896 (comment).

cc @addaleax

@Flarna
Copy link
Member

Flarna commented Jul 13, 2020

I think using isolate->EnqueueMicrotask() could help here as it has the same priority then promises so it should trigger the destroy hook inbetween cleanup.

I did a quick check and it seems to work in this usecase (some tests are failing which needs to be checked).
@addaleax Do you expect any negative impact by moving to EnqueueMicrotask? Maybe performance as it get less bulking?

@puzpuzpuz
Copy link
Member

The original script probably has to be run in REPL. REPL creates a domain which in its turn installs a hook. I'm AFK and can't verify this, but that might explain the above messages.

@Crusader4Christ
Copy link

node
Welcome to Node.js v12.16.1.
Type ".help" for more information.

(async function () {
... const a = new Array(10).fill(0)
... for (let i = 0; i < 1e8; i++) {
..... await Promise.all(a.map(() => Promise.resolve()))
..... if (i % 1e5 === 0) {
....... console.log(Math.round(process.memoryUsage().heapUsed / 1024 / 1024), 'MB')
....... }
..... }
... })()
Promise { }
3 MB
208 MB
457 MB
380 MB
709 MB
875 MB
1051 MB
1217 MB
RangeError: Value undefined out of range for undefined options property undefined

@mayrbenjamin92
Copy link
Contributor

Hmm for me this looks like your sample code is heavily blocking the event-loop. Hmm.

@ronkorving
Copy link
Contributor

I'm with @mayrbenjamin92 here. The code seems to inflate memory by design. It synchronously creates a ton of promises. How could it not inflate memory?

@marian2js
Copy link
Author

@ronkorving The code is not synchronous because there is the await in the 4th line. We are waiting for the promises to be resolved before going to the next iteration in the loop and creating new promises.
This issue can be closed because it's a duplicated of #33896.

@ronkorving
Copy link
Contributor

@marian2js My bad, I don't know what I was thinking (or seeing).

Flarna added a commit to dynatrace-oss-contrib/node that referenced this issue Jul 30, 2020
Use a microtask to call destroy hooks in case there are a lot queued
as immediate may be scheduled late in case of long running
promise chains.

Queuing a mircrotasks in GC context is not allowed therefore an
interrupt is triggered to do this in JS context as fast as possible.

fixes: nodejs#34328
refs: nodejs#33896
Flarna added a commit that referenced this issue Aug 2, 2020
Use a microtask to call destroy hooks in case there are a lot queued
as immediate may be scheduled late in case of long running
promise chains.

Queuing a mircrotasks in GC context is not allowed therefore an
interrupt is triggered to do this in JS context as fast as possible.

fixes: #34328
refs: #33896

PR-URL: #34342
Fixes: #34328
Refs: #33896
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
codebytere pushed a commit that referenced this issue Aug 5, 2020
Use a microtask to call destroy hooks in case there are a lot queued
as immediate may be scheduled late in case of long running
promise chains.

Queuing a mircrotasks in GC context is not allowed therefore an
interrupt is triggered to do this in JS context as fast as possible.

fixes: #34328
refs: #33896

PR-URL: #34342
Fixes: #34328
Refs: #33896
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
addaleax pushed a commit that referenced this issue Sep 22, 2020
Use a microtask to call destroy hooks in case there are a lot queued
as immediate may be scheduled late in case of long running
promise chains.

Queuing a mircrotasks in GC context is not allowed therefore an
interrupt is triggered to do this in JS context as fast as possible.

fixes: #34328
refs: #33896

PR-URL: #34342
Fixes: #34328
Refs: #33896
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
addaleax pushed a commit that referenced this issue Sep 22, 2020
Use a microtask to call destroy hooks in case there are a lot queued
as immediate may be scheduled late in case of long running
promise chains.

Queuing a mircrotasks in GC context is not allowed therefore an
interrupt is triggered to do this in JS context as fast as possible.

fixes: #34328
refs: #33896

PR-URL: #34342
Fixes: #34328
Refs: #33896
Reviewed-By: Gus Caplan <me@gus.host>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
async_hooks Issues and PRs related to the async hooks subsystem. promises Issues and PRs related to ECMAScript promises.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants