Skip to content

Commit 6a51a9f

Browse files
sebmarkbageeps1lon
andauthored
[DevTools] Track Server Environment Names of Each SuspenseNode (#34605)
Tracks the environment names of the I/O in each SuspenseNode and sent it to the front end when the suspenders change. In the front end, every child boundary should really be treated as it has all environment names of the parents too since they're blocked by the parent too. We could do this tracking on backend but if there's ever one added on the root would need to be send for every child. This lets us highlight which subtrees are blocked by content on the server. --------- Co-authored-by: Sebastian "Sebbie" Silbermann <[email protected]>
1 parent 1fd291d commit 6a51a9f

File tree

4 files changed

+92
-20
lines changed

4 files changed

+92
-20
lines changed

packages/react-devtools-shared/src/backend/fiber/renderer.js

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ type SuspenseNode = {
299299
nextSibling: null | SuspenseNode,
300300
rects: null | Array<Rect>, // The bounding rects of content children.
301301
suspendedBy: Map<ReactIOInfo, Set<DevToolsInstance>>, // Tracks which data we're suspended by and the children that suspend it.
302+
environments: Map<string, number>, // Tracks the Flight environment names that suspended this. I.e. if the server blocked this.
302303
// Track whether any of the items in suspendedBy are unique this this Suspense boundaries or if they're all
303304
// also in the parent sets. This determine whether this could contribute in the loading sequence.
304305
hasUniqueSuspenders: boolean,
@@ -327,6 +328,7 @@ function createSuspenseNode(
327328
nextSibling: null,
328329
rects: null,
329330
suspendedBy: new Map(),
331+
environments: new Map(),
330332
hasUniqueSuspenders: false,
331333
hasUnknownSuspenders: false,
332334
});
@@ -2220,6 +2222,10 @@ export function attach(
22202222
}
22212223
operations[i++] = fiberIdWithChanges;
22222224
operations[i++] = suspense.hasUniqueSuspenders ? 1 : 0;
2225+
operations[i++] = suspense.environments.size;
2226+
suspense.environments.forEach((count, env) => {
2227+
operations[i++] = getStringID(env);
2228+
});
22232229
});
22242230
}
22252231
@@ -2725,6 +2731,13 @@ export function attach(
27252731
return;
27262732
}
27272733
2734+
// TODO: Just enqueue the operations here instead of stashing by id.
2735+
2736+
// Ensure each environment gets recorded in the string table since it is emitted
2737+
// before we loop it over again later during flush.
2738+
suspenseNode.environments.forEach((count, env) => {
2739+
getStringID(env);
2740+
});
27282741
pendingSuspenderChanges.add(fiberInstance.id);
27292742
}
27302743
@@ -2807,7 +2820,20 @@ export function attach(
28072820
let suspendedBySet = suspenseNodeSuspendedBy.get(ioInfo);
28082821
if (suspendedBySet === undefined) {
28092822
suspendedBySet = new Set();
2810-
suspenseNodeSuspendedBy.set(asyncInfo.awaited, suspendedBySet);
2823+
suspenseNodeSuspendedBy.set(ioInfo, suspendedBySet);
2824+
// We've added a dependency. We must increment the ref count of the environment.
2825+
const env = ioInfo.env;
2826+
if (env != null) {
2827+
const environmentCounts = parentSuspenseNode.environments;
2828+
const count = environmentCounts.get(env);
2829+
if (count === undefined || count === 0) {
2830+
environmentCounts.set(env, 1);
2831+
// We've discovered a new environment for this SuspenseNode. We'll to update the node.
2832+
recordSuspenseSuspenders(parentSuspenseNode);
2833+
} else {
2834+
environmentCounts.set(env, count + 1);
2835+
}
2836+
}
28112837
}
28122838
// The child of the Suspense boundary that was suspended on this, or null if suspended at the root.
28132839
// This is used to keep track of how many dependents are still alive and also to get information
@@ -2897,6 +2923,7 @@ export function attach(
28972923
: instance.suspenseNode;
28982924
if (previousSuspendedBy !== null && suspenseNode !== null) {
28992925
const nextSuspendedBy = instance.suspendedBy;
2926+
let changedEnvironment = false;
29002927
for (let i = 0; i < previousSuspendedBy.length; i++) {
29012928
const asyncInfo = previousSuspendedBy[i];
29022929
if (
@@ -2935,7 +2962,26 @@ export function attach(
29352962
}
29362963
}
29372964
if (suspendedBySet !== undefined && suspendedBySet.size === 0) {
2938-
suspenseNode.suspendedBy.delete(asyncInfo.awaited);
2965+
suspenseNode.suspendedBy.delete(ioInfo);
2966+
// Successfully removed all dependencies. We can decrement the ref count of the environment.
2967+
const env = ioInfo.env;
2968+
if (env != null) {
2969+
const environmentCounts = suspenseNode.environments;
2970+
const count = environmentCounts.get(env);
2971+
if (count === undefined || count === 0) {
2972+
throw new Error(
2973+
'We are removing an environment but it was not in the set. ' +
2974+
'This is a bug in React.',
2975+
);
2976+
}
2977+
if (count === 1) {
2978+
environmentCounts.delete(env);
2979+
// Last one. We've now change the set of environments. We'll need to update the node.
2980+
changedEnvironment = true;
2981+
} else {
2982+
environmentCounts.set(env, count - 1);
2983+
}
2984+
}
29392985
}
29402986
if (
29412987
suspenseNode.hasUniqueSuspenders &&
@@ -2948,6 +2994,9 @@ export function attach(
29482994
}
29492995
}
29502996
}
2997+
if (changedEnvironment) {
2998+
recordSuspenseSuspenders(suspenseNode);
2999+
}
29513000
}
29523001
}
29533002

packages/react-devtools-shared/src/devtools/store.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,12 +1759,22 @@ export default class Store extends EventEmitter<{
17591759
break;
17601760
}
17611761
case SUSPENSE_TREE_OPERATION_SUSPENDERS: {
1762-
const changeLength = operations[i + 1];
1763-
i += 2;
1762+
i++;
1763+
const changeLength = operations[i++];
17641764

17651765
for (let changeIndex = 0; changeIndex < changeLength; changeIndex++) {
1766-
const id = operations[i];
1767-
const hasUniqueSuspenders = operations[i + 1] === 1;
1766+
const id = operations[i++];
1767+
const hasUniqueSuspenders = operations[i++] === 1;
1768+
const environmentNamesLength = operations[i++];
1769+
const environmentNames = [];
1770+
for (
1771+
let envIndex = 0;
1772+
envIndex < environmentNamesLength;
1773+
envIndex++
1774+
) {
1775+
const environmentNameStringID = operations[i++];
1776+
environmentNames.push(stringTable[environmentNameStringID]);
1777+
}
17681778
const suspense = this._idToSuspense.get(id);
17691779

17701780
if (suspense === undefined) {
@@ -1777,8 +1787,6 @@ export default class Store extends EventEmitter<{
17771787
break;
17781788
}
17791789

1780-
i += 2;
1781-
17821790
if (__DEBUG__) {
17831791
const previousHasUniqueSuspenders = suspense.hasUniqueSuspenders;
17841792
debug(
@@ -1788,6 +1796,7 @@ export default class Store extends EventEmitter<{
17881796
}
17891797

17901798
suspense.hasUniqueSuspenders = hasUniqueSuspenders;
1799+
// TODO: Recompute the environment names.
17911800
}
17921801

17931802
hasSuspenseTreeChanged = true;

packages/react-devtools-shared/src/devtools/views/Profiler/CommitTreeBuilder.js

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -454,14 +454,22 @@ function updateTree(
454454
}
455455

456456
case SUSPENSE_TREE_OPERATION_SUSPENDERS: {
457-
const changesLength = ((operations[i + 1]: any): number);
458-
459-
if (__DEBUG__) {
460-
const changes = operations.slice(i + 2, i + 2 + changesLength * 2);
461-
debug('Suspender changes', `[${changes.join(',')}]`);
457+
i++;
458+
const changeLength = ((operations[i++]: any): number);
459+
460+
for (let changeIndex = 0; changeIndex < changeLength; changeIndex++) {
461+
const suspenseNodeId = operations[i++];
462+
const hasUniqueSuspenders = operations[i++] === 1;
463+
const environmentNamesLength = operations[i++];
464+
i += environmentNamesLength;
465+
if (__DEBUG__) {
466+
debug(
467+
'Suspender changes',
468+
`Suspense node ${suspenseNodeId} unique suspenders set to ${String(hasUniqueSuspenders)} with ${String(environmentNamesLength)} environments`,
469+
);
470+
}
462471
}
463472

464-
i += 2 + changesLength * 2;
465473
break;
466474
}
467475

packages/react-devtools-shared/src/utils.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -426,12 +426,18 @@ export function printOperationsArray(operations: Array<number>) {
426426
break;
427427
}
428428
case SUSPENSE_TREE_OPERATION_SUSPENDERS: {
429-
const changeLength = operations[i + 1];
430-
i += 2;
431-
const changes = operations.slice(i, i + changeLength * 2);
432-
i += changeLength;
433-
434-
logs.push(`Suspense node suspender changes ${changes.join(',')}`);
429+
i++;
430+
const changeLength = ((operations[i++]: any): number);
431+
432+
for (let changeIndex = 0; changeIndex < changeLength; changeIndex++) {
433+
const id = operations[i++];
434+
const hasUniqueSuspenders = operations[i++] === 1;
435+
const environmentNamesLength = operations[i++];
436+
i += environmentNamesLength;
437+
logs.push(
438+
`Suspense node ${id} unique suspenders set to ${String(hasUniqueSuspenders)} with ${String(environmentNamesLength)} environments`,
439+
);
440+
}
435441

436442
break;
437443
}

0 commit comments

Comments
 (0)