Skip to content

Commit

Permalink
[Flight] Serialize rate limited objects in console logs as marker an …
Browse files Browse the repository at this point in the history
…increase limit (#30294)

This marker can then be emitted as a getter. When this object gets
accessed we use a special error to let the user know what is going on.

<img width="1350" alt="Screenshot 2024-07-08 at 10 13 46 PM"
src="https://github.com/facebook/react/assets/63648/e3eb698f-e02d-4394-a051-ba9ac3236480">

When you click the `...`:
<img width="1357" alt="Screenshot 2024-07-08 at 10 13 56 PM"
src="https://github.com/facebook/react/assets/63648/4b8ce1cf-d762-49a4-97b9-aeeb1aa8722c">

I also increased the object limit in console logs. It was arbitrarily
set very low before.

These limits are per message. So if you have a loop of many logs it can
quickly add up a lot of strain on the server memory and the client. This
is trying to find some tradeoff. Unfortunately we don't really do much
deduping in these logs so with cyclic objects it ends up maximizing the
limit and then siblings aren't logged.

Ideally we should be able to lazy load them but that requires a lot of
plumbing to wire up so if we can avoid it we should try to. If we want
to that though one idea is to use the getter to do a sync XHR to load
more data but the server needs to retain the objects in memory for an
unknown amount of time. The client could maybe send a signal to retain
them until a weakref clean up but even then it kind of needs a heartbeat
to let the server know the client is still alive. That's a lot of
complexity. There's probably more we can do to optimize deduping and
other parts of the protocol to make it possible to have even higher
limits.
  • Loading branch information
sebmarkbage authored Jul 10, 2024
1 parent 3b2e5f2 commit 14fdd0e
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 3 deletions.
23 changes: 23 additions & 0 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,29 @@ function parseModelString(
}
// Fallthrough
}
case 'Y': {
if (__DEV__) {
// In DEV mode we encode omitted objects in logs as a getter that throws
// so that when you try to access it on the client, you know why that
// happened.
Object.defineProperty(parentObject, key, {
get: function () {
// We intentionally don't throw an error object here because it looks better
// without the stack in the console which isn't useful anyway.
// eslint-disable-next-line no-throw-literal
throw (
'This object has been omitted by React in the console log ' +
'to avoid sending too much data from the server. Try logging smaller ' +
'or more specific objects.'
);
},
enumerable: true,
configurable: false,
});
return null;
}
// Fallthrough
}
default: {
// We assume that anything else is a reference ID.
const ref = value.slice(1);
Expand Down
10 changes: 7 additions & 3 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1851,6 +1851,10 @@ function serializeSymbolReference(name: string): string {
return '$S' + name;
}

function serializeLimitedObject(): string {
return '$Y';
}

function serializeNumber(number: number): string | number {
if (Number.isFinite(number)) {
if (number === 0 && 1 / number === -Infinity) {
Expand Down Expand Up @@ -3181,10 +3185,10 @@ function renderConsoleValue(
}
}

if (counter.objectCount > 20) {
if (counter.objectCount > 500) {
// We've reached our max number of objects to serialize across the wire so we serialize this
// object but no properties inside of it, as a place holder.
return Array.isArray(value) ? [] : {};
// as a marker so that the client can error when this is accessed by the console.
return serializeLimitedObject();
}

counter.objectCount++;
Expand Down

0 comments on commit 14fdd0e

Please sign in to comment.