Skip to content

Commit 7cafeff

Browse files
authored
[Flight] Close Debug Channel when All Lazy References Have Been GC:ed (#33718)
When we have a debug channel open that can ask for more objects. That doesn't close until all lazy objects have been explicitly asked for. If you GC an object before the lazy references inside of it before asking for or releasing the objects, then it'll never close. This ensures that if there are no more PendingChunk and no more ResolvedModelChunk then we can close the connection. There's two sources of retaining the Response object. On one side we have a handle to it from the stream coming from the server. On the other side we have a handle to it from ResolvedModelChunk to ask for more data when we lazily parse a model. This PR makes a weak handle from the stream to the Response. However, it keeps a strong reference alive whenever we're waiting on a pending chunk because then the stream might be the root if the only listeners are the callbacks passed to the promise and no references to the promise itself. The pending chunks count can end up being zero even if we might get more data because the references might be inside lazy chunks. In this case the lazy chunks keeps the Response alive. When the lazy chunk gets parsed it can find more chunks that then end up pending to keep the response strongly alive until they resolve.
1 parent 0378b46 commit 7cafeff

File tree

3 files changed

+209
-52
lines changed

3 files changed

+209
-52
lines changed

fixtures/flight/src/index.js

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,49 @@ function findSourceMapURL(fileName) {
1616

1717
let updateRoot;
1818
async function callServer(id, args) {
19-
const response = fetch('/', {
20-
method: 'POST',
21-
headers: {
22-
Accept: 'text/x-component',
23-
'rsc-action': id,
24-
},
25-
body: await encodeReply(args),
26-
});
27-
const {returnValue, root} = await createFromFetch(response, {
28-
callServer,
29-
findSourceMapURL,
30-
});
19+
let response;
20+
if (
21+
process.env.NODE_ENV === 'development' &&
22+
typeof WebSocketStream === 'function'
23+
) {
24+
const requestId = crypto.randomUUID();
25+
const wss = new WebSocketStream(
26+
'ws://localhost:3001/debug-channel?' + requestId
27+
);
28+
const debugChannel = await wss.opened;
29+
response = createFromFetch(
30+
fetch('/', {
31+
method: 'POST',
32+
headers: {
33+
Accept: 'text/x-component',
34+
'rsc-action': id,
35+
'rsc-request-id': requestId,
36+
},
37+
body: await encodeReply(args),
38+
}),
39+
{
40+
callServer,
41+
debugChannel,
42+
findSourceMapURL,
43+
}
44+
);
45+
} else {
46+
response = createFromFetch(
47+
fetch('/', {
48+
method: 'POST',
49+
headers: {
50+
Accept: 'text/x-component',
51+
'rsc-action': id,
52+
},
53+
body: await encodeReply(args),
54+
}),
55+
{
56+
callServer,
57+
findSourceMapURL,
58+
}
59+
);
60+
}
61+
const {returnValue, root} = await response;
3162
// Refresh the tree with the new RSC payload.
3263
startTransition(() => {
3364
updateRoot(root);

0 commit comments

Comments
 (0)