Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions fixtures/flight/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,69 @@ async function ServerComponent({noCache}) {
return await fetchThirdParty(noCache);
}

let veryDeepObject = [
{
bar: {
baz: {
a: {},
},
},
},
{
bar: {
baz: {
a: {},
},
},
},
{
bar: {
baz: {
a: {},
},
},
},
{
bar: {
baz: {
a: {
b: {
c: {
d: {
e: {
f: {
g: {
h: {
i: {
j: {
k: {
l: {
m: {
yay: 'You reached the end',
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
];

export default async function App({prerender, noCache}) {
const res = await fetch('http://localhost:3001/todos');
const todos = await res.json();

console.log('Expand me:', veryDeepObject);

const dedupedChild = <ServerComponent noCache={noCache} />;
const message = getServerState();
return (
Expand Down
49 changes: 47 additions & 2 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -1774,6 +1774,40 @@ function applyConstructor(
return undefined;
}

function defineLazyGetter<T>(
response: Response,
chunk: SomeChunk<T>,
parentObject: Object,
key: string,
): any {
// We don't immediately initialize it even if it's resolved.
// Instead, we wait for the getter to get accessed.
Object.defineProperty(parentObject, key, {
get: function () {
if (chunk.status === RESOLVED_MODEL) {
// If it was now resolved, then we initialize it. This may then discover
// a new set of lazy references that are then asked for eagerly in case
// we get that deep.
initializeModelChunk(chunk);
}
switch (chunk.status) {
case INITIALIZED: {
return chunk.value;
}
case ERRORED:
throw chunk.reason;
}
// Otherwise, we didn't have enough time to load the object before it was
// accessed or the connection closed. So we just log that it was omitted.
// TODO: We should ideally throw here to indicate a difference.
return OMITTED_PROP_ERROR;
},
enumerable: true,
configurable: false,
});
return null;
}

function extractIterator(response: Response, model: Array<any>): Iterator<any> {
// $FlowFixMe[incompatible-use]: This uses raw Symbols because we're extracting from a native array.
return model[Symbol.iterator]();
Expand Down Expand Up @@ -2014,8 +2048,19 @@ function parseModelString(
if (value.length > 2) {
const debugChannel = response._debugChannel;
if (debugChannel) {
const ref = value.slice(2);
debugChannel('R:' + ref); // Release this reference immediately
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have a good way to release unreachable references since we don't know they exist inside a payload until we parse it and when we parse it, we immediately ask for it.

References held by the server aren't released until all lazy references of the whole response are GC:ed.

const ref = value.slice(2); // We assume this doesn't have a path just id.
const id = parseInt(ref, 16);
if (!response._chunks.has(id)) {
// We haven't seen this id before. Query the server to start sending it.
debugChannel('Q:' + ref);
}
// Start waiting. This now creates a pending chunk if it doesn't already exist.
const chunk = getChunk(response, id);
if (chunk.status === INITIALIZED) {
// We already loaded this before. We can just use the real value.
return chunk.value;
}
return defineLazyGetter(response, chunk, parentObject, key);
}
}

Expand Down
11 changes: 9 additions & 2 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4796,10 +4796,15 @@ function emitConsoleChunk(
const payload = [methodName, stackTrace, owner, env];
// $FlowFixMe[method-unbinding]
payload.push.apply(payload, args);
let json = serializeDebugModel(request, 500, payload);
const objectLimit = request.deferredDebugObjects === null ? 500 : 10;
let json = serializeDebugModel(
request,
objectLimit + stackTrace.length,
payload,
);
if (json[0] !== '[') {
// This looks like an error. Try a simpler object.
json = serializeDebugModel(request, 500, [
json = serializeDebugModel(request, 10 + stackTrace.length, [
methodName,
stackTrace,
owner,
Expand Down Expand Up @@ -5736,6 +5741,8 @@ export function resolveDebugMessage(request: Request, message: string): void {
if (retainedValue !== undefined) {
// If we still have this object, and haven't emitted it before, emit it on the stream.
const counter = {objectLimit: 10};
deferredDebugObjects.retained.delete(id);
deferredDebugObjects.existing.delete(retainedValue);
emitOutlinedDebugModelChunk(request, id, counter, retainedValue);
enqueueFlush(request);
}
Expand Down
Loading