-
-
Notifications
You must be signed in to change notification settings - Fork 666
fix: fix wrong stream canceled up after cloning (v7) #4415
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
seems wrong.
This is all out of date - ignore this comment
|
|
||
await setImmediate() | ||
assert.doesNotThrow(() => res.clone(), 'Body has already been consumed') | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fails on main
✖ clone body garbage collection should not affect original res (6.698336ms)
AssertionError [ERR_ASSERTION]: Got unwanted exception: Body has already been consumed
return res | ||
} | ||
|
||
const res = await doFetch() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
res
has never had its body read, but ends up being canceled in the finalization registry b/c we register the wrong stream in lib/web/fetch/response.js
// wait for garbage collection | ||
while (true) { | ||
await setImmediate() | ||
global.gc() | ||
if (!ref.deref()) break | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the clone is garbage collected, the original res body stream should not be canceled
Why did you revert #4320? |
Sorry, I ended up not needing to revert it. I'll update the PR description, but I've squashed that change out. The only change now is updating which tee'd stream is put into the finalization registry |
https://github.com/snyamathi/nextjs-mem-leak/blob/main/index.mjs Here's a minimal reproduction of the issue. I ended up finding this while tracing down a memory leak in NextJS /**
* This is a minimal reproduction of an issue in
* https://github.com/nodejs/undici/pull/3458
*
* When cloning a response, the wrong stream is
* registered with undici's finalization registry
* causing the original stream to be reclaimed, not
* the cloned stream.
*/
import { setImmediate } from "node:timers/promises";
const { promise, resolve } = Promise.withResolvers();
const registry = new FinalizationRegistry(resolve);
const doFetch = async () => {
const res = await fetch(
"https://microsoftedge.github.io/Demos/json-dummy-data/512KB-min.json"
);
// Clone res and consume its body
const clone = res.clone();
await clone.json();
// Use finalization registry to know when the *clone* has been reclaimed
registry.register(clone, {});
return res;
};
const res = await doFetch();
global.gc();
await promise;
await setImmediate();
// TypeError: Response.clone: Body has already been consumed.
res.clone(); |
You've caused a regression of the memory leak issue in the cloned response.
|
I actually wonder what actually happens when you tee. Maybe the issue is, that we expect that both streams get consumed? And our expectations are wrong? |
No, each stream could be consumed independently. |
@mcollina |
The finalizationregistry/weakref business should make sure of it. |
I wrote a better solution in #4419 's PR. |
I merged the other PR. Please verify and if you like please backport for v6 |
Pretty much the same as #4414 but for v7
This relates to...
Rationale
The request/stream registered with the finalization registry is mixed up between the clone and the original request.
Changes
This PR just changes which of the two tee'd streams is associated
Features
N/A
Bug Fixes
N/A
Breaking Changes and Deprecations
N/A
Status