Skip to content
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

DevTools: Fix "unknown" updater in profiler when a component unsuspends #28330

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
34 changes: 33 additions & 1 deletion packages/react-reconciler/src/ReactFiberLane.js
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,8 @@ export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
// idle updates until after all the regular updates have finished; there's no
// way it could unblock a transition.
if (updateLane !== IdleLane) {
movePendingUpdatersToLane(root, root.pingedLanes, updateLane);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think we need to transfer updaters from root.suspendedLanes but I haven't really thought about this for more than a sec.


root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
}
Expand Down Expand Up @@ -656,7 +658,8 @@ export function markRootSuspended(
}

export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
root.pingedLanes |= root.suspendedLanes & pingedLanes;
// TODO: When would we ever ping lanes that we aren't suspended on?
root.pingedLanes |= pingedLanes;
Comment on lines +661 to +662
Copy link
Collaborator Author

@eps1lon eps1lon Jun 2, 2024

Choose a reason for hiding this comment

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

Do we need to investigate this further? Just transferring the updaters from root.pingedLanes to updateLane isn't enough. For some reason we're pinging when root.suspendedLanes & pingedLanes === NoLanes. Which seems odd intuitively but maybe the bug is root.suspendedLanes having no overlap with pingedLanes?

Copy link
Collaborator Author

@eps1lon eps1lon Jun 2, 2024

Choose a reason for hiding this comment

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

When would we ever ping lanes that we aren't suspended on?

Maybe if we suspend on two Wakeables A and B, A gets pinged earlier, we unsuspend completely i.e. don't suspend on B again (e.g. conditional use?) and then we wouldn't want to retry when B gets pinged?

Grasping for straws here since all tests still pass with this change.

}

export function markRootFinished(
Expand Down Expand Up @@ -905,6 +908,35 @@ export function addFiberToLanesMap(
}
}

function movePendingUpdatersToLane(
root: FiberRoot,
sourceLanes: Lanes,
targetLane: Lane,
) {
if (!enableUpdaterTracking) {
return;
}
if (!isDevToolsPresent) {
return;
}
const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;
const targetIndex = laneToIndex(targetLane);
const targetUpdaters = pendingUpdatersLaneMap[targetIndex];
let lanes = sourceLanes;
while (lanes > 0) {
const index = laneToIndex(lanes);
const lane = 1 << index;

const sourceUpdaters = pendingUpdatersLaneMap[index];
sourceUpdaters.forEach(sourceUpdater => {
targetUpdaters.add(sourceUpdater);
});
sourceUpdaters.clear();

lanes &= ~lane;
}
}

export function movePendingFibersToMemoized(root: FiberRoot, lanes: Lanes) {
if (!enableUpdaterTracking) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ describe('updaters', () => {
schedulerTags.push(fiber.tag);
schedulerTypes.push(fiber.elementType);
});
fiberRoot.pendingUpdatersLaneMap.forEach((pendingUpdaters, index) => {
if (pendingUpdaters.size > 0) {
// TODO: Is it ever ok to have dangling pending updaters or is this always a bug?
// const lane = 1 << index;
// throw new Error(
// `Lane ${lane} has pending updaters. Either you didn't assert on all updates in your test or React is leaking updaters.`,
// );
Comment on lines +54 to +58
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

}
});
allSchedulerTags.push(schedulerTags);
allSchedulerTypes.push(schedulerTypes);
}),
Expand Down Expand Up @@ -266,9 +275,6 @@ describe('updaters', () => {
await waitForAll([]);
});

// This test should be convertable to createRoot but the allScheduledTypes assertions are no longer the same
// So I'm leaving it in legacy mode for now and just disabling if legacy mode is turned off
// @gate !disableLegacyMode
it('should cover suspense pings', async () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The original issue in the test was that the updater was on the Default lane, the updater got pinged, and then we said we should render on a Retry lane (markRootPinged). This left the updater dangling in pending updaters since we only move the pending updaters to memoized on the lane we actually render (Retry lane).

let data = null;
let resolver = null;
Expand Down Expand Up @@ -303,10 +309,11 @@ describe('updaters', () => {
}
};

const root = ReactDOMClient.createRoot(document.createElement('div'));
await act(() => {
ReactDOM.render(<Parent />, document.createElement('div'));
assertLog(['onCommitRoot']);
root.render(<Parent />);
});
assertLog(['onCommitRoot']);
expect(setShouldSuspend).not.toBeNull();
expect(allSchedulerTypes).toEqual([[null]]);

Expand Down