Skip to content

Request stream contents are kept in memory when using fetch #4058

Open
@tobinus

Description

Bug Description

When streaming a file upload, the Node process' memory usage increases until it consumes an amount equal to the size of the file we are trying to upload.

Reproducible By

Save the following code to a file, such as streamUpload.mjs:

import fs from "node:fs";

if (process.argv.length !== 3) {
    console.error(`Usage: node ${process.argv[1]} FILE`);
    process.exit(2);
}

console.log('My PID:', process.pid);

const fileStream = fs.createReadStream(process.argv[2]);

const request = new Request("http://localhost:8000", {
    method: "PUT",
    headers: {
        "Content-Type": "application/json",
    },
    body: fileStream,
    duplex: 'half',
});

setInterval(() => {
    const { heapUsed, heapTotal, external, arrayBuffers} = process.memoryUsage();
    console.log(`Heap used/total: ${heapUsed} / ${heapTotal}; Array buffers/external: ${arrayBuffers} / ${external}`);
}, 1000);

fetch(request)
.then(async response => {
    console.log(response);
    console.log(await response.text());

    await new Promise(resolve => process.nextTick(resolve));
    console.log('Waiting 15 seconds until exiting')
    await new Promise(resolve => setTimeout(resolve, 15000));

    process.exit(0);
}).catch(err => {
    console.error('Encountered error: ', err);
    process.exit(1);
});

Set up netcat as a mock HTTP server in a separate terminal session:

nc -Clq 0 8000

Then try running the Node script using a large file (should be > 100MB):

node streamUpload.mjs path/to/largeFile

You should see that the heap usage stays about the same, while the array buffer usage increases steadily as the upload progresses (as does the external memory use, of course).

If you wish to let the fetch call resolve, you need to write the following into the nc program when the upload is complete:

HTTP/1.1 200 OK

Hello from netcat!

Then hit Enter and CTRL-D.

Logs from running

This is a transcript of what the above script prints out when run as described.
The file I'm referring to is 329738114 bytes large.

Note that I've tried adding some flags for constraining memory consumption.

Result of running the above script
$ node --optimize_for_size --max_old_space_size=10 --gc_interval=100 streamUpload.mjs path/to/file
My PID: 4143
Heap used/total: 7539152 / 10129408; Array buffers/external: 17957015 / 21277630
Heap used/total: 7555888 / 10129408; Array buffers/external: 24051863 / 27372518
Heap used/total: 7137680 / 10129408; Array buffers/external: 26935447 / 30256102
Heap used/total: 7127184 / 10129408; Array buffers/external: 32899223 / 39365606
Heap used/total: 7182544 / 10129408; Array buffers/external: 39452823 / 42773478
Heap used/total: 7562576 / 10129408; Array buffers/external: 45744279 / 49064934
Heap used/total: 7447840 / 10129408; Array buffers/external: 51445911 / 54766566
Heap used/total: 7278840 / 10129408; Array buffers/external: 56754327 / 60074982
Heap used/total: 7637736 / 9080832; Array buffers/external: 62849175 / 66169830
Heap used/total: 7388424 / 10129408; Array buffers/external: 67698839 / 71019494
Heap used/total: 7647112 / 10129408; Array buffers/external: 73531543 / 76852198
Heap used/total: 7425872 / 10391552; Array buffers/external: 78708887 / 82029542
Heap used/total: 8010584 / 10391552; Array buffers/external: 87752855 / 91073510
Heap used/total: 7469160 / 10391552; Array buffers/external: 89915543 / 93236198
Heap used/total: 8038920 / 10391552; Array buffers/external: 98959511 / 102280166
Heap used/total: 8305880 / 10391552; Array buffers/external: 104726679 / 108047334
Heap used/total: 8054872 / 10653696; Array buffers/external: 109838487 / 113159142
Heap used/total: 7805592 / 10653696; Array buffers/external: 114950295 / 118270950
Heap used/total: 8037552 / 10653696; Array buffers/external: 120782999 / 124103654
Heap used/total: 7910616 / 9342976; Array buffers/external: 126550167 / 129870822
Heap used/total: 8152160 / 10391552; Array buffers/external: 132317335 / 135637990
Heap used/total: 7975840 / 10653696; Array buffers/external: 137756823 / 141077478
Heap used/total: 7750144 / 10653696; Array buffers/external: 142934167 / 146254822
Heap used/total: 7971624 / 10653696; Array buffers/external: 148635799 / 151956454
Heap used/total: 8531728 / 10653696; Array buffers/external: 157679767 / 161000422
Heap used/total: 8276056 / 10653696; Array buffers/external: 162791575 / 166112230
Heap used/total: 8479336 / 10653696; Array buffers/external: 168493207 / 171813862
Heap used/total: 8257296 / 10915840; Array buffers/external: 173605015 / 176925670
Heap used/total: 8471160 / 10915840; Array buffers/external: 179503255 / 182823910
Heap used/total: 8190648 / 10915840; Array buffers/external: 184352919 / 187673574
Heap used/total: 8398160 / 10915840; Array buffers/external: 190054551 / 193375206
Heap used/total: 7420120 / 10391552; Array buffers/external: 196280471 / 199601126
Heap used/total: 7281152 / 10653696; Array buffers/external: 201654423 / 204975078
Heap used/total: 7501824 / 10653696; Array buffers/external: 207421591 / 210742246
Heap used/total: 7248248 / 10653696; Array buffers/external: 212402327 / 215722982
Heap used/total: 7450432 / 10653696; Array buffers/external: 217907351 / 221228006
Heap used/total: 8007776 / 10653696; Array buffers/external: 226820247 / 230140902
Heap used/total: 7390304 / 10915840; Array buffers/external: 228458647 / 231779302
Heap used/total: 7903000 / 10915840; Array buffers/external: 237240471 / 240561126
Heap used/total: 8080640 / 10915840; Array buffers/external: 242811031 / 246131686
Heap used/total: 7790800 / 10915840; Array buffers/external: 247660695 / 250981350
Heap used/total: 8025560 / 10915840; Array buffers/external: 253296791 / 256617446
Heap used/total: 7234104 / 10129408; Array buffers/external: 258080919 / 261399687
Heap used/total: 7421152 / 10129408; Array buffers/external: 263651479 / 266970247
Heap used/total: 7125952 / 10129408; Array buffers/external: 268501143 / 271819911
Heap used/total: 7321008 / 10129408; Array buffers/external: 274137239 / 277456007
Heap used/total: 7798696 / 10129408; Array buffers/external: 282919063 / 286237831
Heap used/total: 7155728 / 10129408; Array buffers/external: 284229783 / 287548551
Heap used/total: 6978424 / 10129408; Array buffers/external: 290193559 / 296262828
Heap used/total: 7266184 / 10129408; Array buffers/external: 296091799 / 299408556
Heap used/total: 7819944 / 10129408; Array buffers/external: 305201303 / 308518060
Heap used/total: 7729584 / 10391552; Array buffers/external: 309461143 / 312777900
Heap used/total: 7442880 / 10653696; Array buffers/external: 314179735 / 317496492
Heap used/total: 7627160 / 10653696; Array buffers/external: 319815831 / 323132588
Heap used/total: 7320616 / 10653696; Array buffers/external: 324599959 / 327916716
Heap used/total: 7427328 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7431488 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7434560 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7438016 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7441088 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7444160 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7447232 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7450304 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7453376 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7456448 / 10915840; Array buffers/external: 329803792 / 335912538
Heap used/total: 7470616 / 10915840; Array buffers/external: 329803809 / 333133545
Response {
  status: 200,
  statusText: 'OK',
  headers: Headers {},
  body: ReadableStream { locked: false, state: 'readable', supportsBYOB: true },
  bodyUsed: false,
  ok: true,
  redirected: false,
  type: 'basic',
  url: 'http://localhost:8000/'
}
Heap used/total: 7628296 / 10915840; Array buffers/external: 329803811 / 333133547
Heap used/total: 7631432 / 10915840; Array buffers/external: 329803811 / 333133547
Heap used/total: 7634504 / 10915840; Array buffers/external: 329803811 / 333133547
Heap used/total: 7637576 / 10915840; Array buffers/external: 329803811 / 333133547
Hello from Netcat!

Waiting 15 seconds until exiting
Heap used/total: 7845016 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7847576 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7850072 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7852568 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7855064 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7857560 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7860056 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7862552 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7865048 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7867544 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7870040 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7872536 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7875032 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7877528 / 10915840; Array buffers/external: 329803851 / 333133587
Heap used/total: 7880024 / 10915840; Array buffers/external: 329803851 / 333133587

Expected Behavior

The memory usage should increase by the size of the buffer used between the file stream and the TCP writable stream, and not be affected by the size of the file. The memory usage should stay the same throughout, no matter how much has been streamed.

Environment

I'm using Windows Subsystem for Linux, with Ubuntu 22.04.5 LTS, though I've seen this behaviour on regular Linux installs as well.
Node is installed with nvm, version 22.13.1.

Additional context

I first observed this when following the instructions for how to stream a file upload (from #2202) using FormData. But I was able to reproduce it using plain streams when trying some workarounds.

As a consequence, we cannot use fetch to upload large files in environments where the files are too large to keep in memory.

EDIT: Changed to .mjs file suffix, and added logging of memory usage within the script itself. Added logs from running.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions