Skip to content

Memory overflow in Readable.toWeb due to the default strategy #47128

Open
@lilsweetcaligula

Description

@lilsweetcaligula

Version

v20.0.0-pre

Platform

Linux Mars 5.19.0-35-generic #36~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Fri Feb 17 15:17:25 UTC 2 x86_64 x86_64 x86_64 GNU/Linux

Subsystem

stream

What steps will reproduce the bug?

To reproduce, run the following script with the debugger enabled, i.e. node inspect mytest.mjs

// mytest.mjs

import fs from 'node:fs'
import process from 'node:process'
import { Readable } from 'node:stream'

const reportMemoryUsage = (maxMb, randomWebStream) => {
	const { arrayBuffers } = process.memoryUsage()

	if (arrayBuffers > maxMb * 1024 * 1024) {
		debugger
		process.exit(1)
	}
}

async function main() {
	const randomNodeStream = fs.createReadStream('/dev/urandom', { highWaterMark: 5556 })
	const randomWebStream = Readable.toWeb(randomNodeStream)

	setInterval(() => reportMemoryUsage(16, randomWebStream), 1e3)
}

main()
  • When the debugger stops at the breakpoint, open up the repl, and print out the contents of randomWebStream, i.e. console.dir(randomWebStream, { depth: 32 }).
  • Inspect the output of the queue in randomWebStream[Symbol(kState)].controller[Symbol(kState)].queue.

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

Always. However, the underlying stream that's being turned into the web stream must not be in the object mode.

What is the expected behavior? Why is that the expected behavior?

Because the underlying stream was not in the object mode and had its highWaterMark set at 5556 bytes, the expected behavior is for the web stream's underlying queue to also have the total size circa 5556 bytes.

What do you see instead?

In my case I see the queue has the length of 5556 elements, with each element being an instance of Uint8Array of size 5556 bytes by itself - thus producing a grand total of 30869136 bytes, - as opposed to the 5556 bytes which the highWaterMark of the underlying stream randomNodeStream had.

Additional information

This may be related to #46347.

The source of this behavior is likely this line:

// When not running in objectMode explicitly, we just fall
// back to a minimal strategy that just specifies the highWaterMark
// and no size algorithm. Using a ByteLengthQueuingStrategy here
// is unnecessary.
return { highWaterMark };

This should probably use the ByteLengthQueuingStrategy instead, as previously suggested by @debadree25 #46347 (comment)

Metadata

Metadata

Assignees

No one assigned

    Labels

    memoryIssues and PRs related to the memory management or memory footprint.streamIssues and PRs related to the stream subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions