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

pipeline causes memory leak when used with async generator #37084

Closed
matthieusieben opened this issue Jan 26, 2021 · 2 comments
Closed

pipeline causes memory leak when used with async generator #37084

matthieusieben opened this issue Jan 26, 2021 · 2 comments
Labels
memory Issues and PRs related to the memory management or memory footprint. stream Issues and PRs related to the stream subsystem.

Comments

@matthieusieben
Copy link
Contributor

matthieusieben commented Jan 26, 2021

  • Version: v15.6.0
  • Platform: Darwin MBPM.local 20.2.0 Darwin Kernel Version 20.2.0: Wed Dec 2 20:39:59 PST 2020; root:xnu-7195.60.75~1/RELEASE_X86_64 x86_64
  • Subsystem: stream.pipeline

What steps will reproduce the bug?

  • Launch the following script with node --inspect-brk --expose-gc test.js
  • Open a chrome inspector and play until the first debugger statement is reached (will auto pause).
  • Start an Allocation timeline profiling
  • Start running the script again (F8)
  • When reaching the second debugger, stop the profiling
  • Notice 10 references to the Passthrough are still in memory
const { PassThrough, pipeline } = require('stream')
const { promisify } = require('util')
const nextTick = promisify(process.nextTick)

const consume = async (it) => {
  for await (const _item of it) continue
}

async function* genItems(count) {
  const source = async function* () {
    // Generate way too many items
    while (true) {
      await nextTick() // simulate async
      yield { foo: 'bar' }
    }
  }

  const duplex = new PassThrough({ objectMode: true })

  const stream = pipeline(source, duplex, (err) => {
    console.log('done', err) // err.code === ERR_STREAM_PREMATURE_CLOSE
  })

  for await (const item of stream) {
    yield item
    if (--count <= 0) break
  }
}

const test = async () => {
  const stream = genItems(2)
  await consume(stream)
}

const main = async () => {
  debugger // <== Start memory allocation timeline profiling now
  for (let i = 0; i < 10; i++) await test()
  global.gc()
  debugger // <== Stop memory profiling now
}

main()

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

Only occurs when breaking a loop on the async generator.

What is the expected behavior?

It should be possible to use a stream pipeline as an async generator without having memory leaks.

What do you see instead?

When profiling, references to the Passthrough still appear in memory:

Additional information

Might be linked to #35452

@Lxxyx Lxxyx added memory Issues and PRs related to the memory management or memory footprint. stream Issues and PRs related to the stream subsystem. labels Jan 27, 2021
@matthieusieben
Copy link
Contributor Author

so the "leak" is probably linked to the console keeping a ref (through the error).

@meixg
Copy link
Member

meixg commented Mar 10, 2022

With this #35452 solved, I tried on the master branch and this problem has gone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
memory Issues and PRs related to the memory management or memory footprint. stream Issues and PRs related to the stream subsystem.
Projects
None yet
Development

No branches or pull requests

3 participants