Node: http: Memory Leak

Created on 28 Mar 2019  路  12Comments  路  Source: nodejs/node

Running this will cause the memory usage of the node process to infinitly grow...

  • Version: 10.13.0
  • Platform: OSX
  • Subsystem:
const http = require('http')
const { spawn } = require('child_process')
const crypto = require('crypto')
const fs = require('fs')

const dst = fs.createWriteStream('./tmp')

http.createServer((req, res) => {
  req
    .on('data', buf => {
      if (!dst.write(buf)) {
        req.pause()
      }
    })
  dst.on('drain', () => {
    if (req.readable) {
      req.resume()
    }
  })
}).listen(9988, () => {
  spawn('cat', ['/dev/zero'])
    .stdout
    .pipe(http
      .request({
        hostname: 'localhost',
        port: 9988,
        path: `/file`,
        method: 'POST'
      })
    )
})
http memory

Most helpful comment

@ronag Fwiw, I鈥檝e opened a PR to address this in https://github.com/nodejs/node/pull/26965. It would be great if you could verify that that fully works for you.

All 12 comments

Backpressure seems to work, however the buf doesn't seem to be released. They seem to be allocated using some sort of external memory since they don't seem to be part of the node heap.

Interestingly using pipe instead of data & drain does not seem to have the same problem?

Seems like IncomingMessage does some strange stuff that pipe handles which is outside of the normal "stream protocol"?

Does this happen on the client side or on the server side?

@mcollina: server side

Seems related to chunked encoding

@ronag are you working on a PR?

@mcollina no, I鈥檓 trying to find the problem but so far I have no idea. This might be over my head.

Our findings so far seem to indicate a difference between how Readable.resume and Readable.pipe resumes/pauses streams in a way where IncomingMessage and HttpServer depends on. In particular pipeOnDrain and resume seem to differ.

e.g. if instead of calling IncomingMessage.resume() I use the following code:

req._readableState.flowing = true
while (req.read() != null) {
}

I seem to no longer have any leaks. Though I don鈥檛 dare using such a workaround in production without understanding why that helps. My suspicion is the sync vs async resume behavior which is different between resume and pipeOnDrain.

@ronag Fwiw, I鈥檝e opened a PR to address this in https://github.com/nodejs/node/pull/26965. It would be great if you could verify that that fully works for you.

@mcollina I'm a little surprised this wasn't fixed/merged in/into LTS? Is that on purpose?

@mcollina I'm a little surprised this wasn't fixed/merged in/into LTS? Is that on purpose?

I'm a little surprised of your question. There is no evidence of anybody blocking https://github.com/nodejs/node/pull/26965 getting into Node 10. Likely, it seem they just need some help.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vsemozhetbyt picture vsemozhetbyt  路  3Comments

akdor1154 picture akdor1154  路  3Comments

stevenvachon picture stevenvachon  路  3Comments

seishun picture seishun  路  3Comments

Icemic picture Icemic  路  3Comments