Skip to content

Commit 99a8fc7

Browse files
author
Brian Vaughn
committed
Split mutable source pending expiration times into first and last
1 parent 4481095 commit 99a8fc7

File tree

4 files changed

+36
-21
lines changed

4 files changed

+36
-21
lines changed

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ import {
7575
getCurrentPriorityLevel,
7676
} from './SchedulerWithReactIntegration';
7777
import {
78-
getPendingExpirationTime,
78+
getFirstPendingExpirationTime,
79+
getLastPendingExpirationTime,
7980
getWorkInProgressVersion,
8081
markSourceAsDirty,
8182
setPendingExpirationTime,
@@ -916,16 +917,14 @@ function readFromUnsubcribedMutableSource<Source, Snapshot>(
916917
isSafeToReadFromSource = currentRenderVersion === version;
917918
} else {
918919
// If there's no version, then we should fallback to checking the update time.
919-
const pendingExpirationTime = getPendingExpirationTime(root);
920+
const pendingExpirationTime = getFirstPendingExpirationTime(root);
920921

921922
if (pendingExpirationTime === NoWork) {
922923
isSafeToReadFromSource = true;
923924
} else {
924925
// If the source has pending updates, we can use the current render's expiration
925926
// time to determine if it's safe to read again from the source.
926-
isSafeToReadFromSource =
927-
pendingExpirationTime === NoWork ||
928-
pendingExpirationTime >= renderExpirationTime;
927+
isSafeToReadFromSource = pendingExpirationTime >= renderExpirationTime;
929928
}
930929

931930
if (isSafeToReadFromSource) {
@@ -974,7 +973,8 @@ function useMutableSource<Source, Snapshot>(
974973

975974
const dispatcher = ReactCurrentDispatcher.current;
976975

977-
const [currentSnapshot, setSnapshot] = dispatcher.useState(() =>
976+
// eslint-disable-next-line prefer-const
977+
let [currentSnapshot, setSnapshot] = dispatcher.useState(() =>
978978
readFromUnsubcribedMutableSource(root, source, getSnapshot),
979979
);
980980
let snapshot = currentSnapshot;
@@ -1032,7 +1032,7 @@ function useMutableSource<Source, Snapshot>(
10321032
// There is no mechanism currently to associate these updates though,
10331033
// so for now we fall back to synchronously flushing all pending updates.
10341034
// TODO: Improve this later.
1035-
markRootExpiredAtTime(root, getPendingExpirationTime(root));
1035+
markRootExpiredAtTime(root, getLastPendingExpirationTime(root));
10361036
}
10371037
}
10381038
}
@@ -1091,7 +1091,7 @@ function useMutableSource<Source, Snapshot>(
10911091
// We missed a mutation before committing.
10921092
// It's possible that other components using this source also have pending updates scheduled.
10931093
// In that case, we should ensure they all commit together.
1094-
markRootExpiredAtTime(root, getPendingExpirationTime(root));
1094+
markRootExpiredAtTime(root, getLastPendingExpirationTime(root));
10951095
}
10961096
}
10971097

packages/react-reconciler/src/ReactFiberRoot.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ type BaseFiberRootProperties = {|
7979
lastExpiredTime: ExpirationTime,
8080
// Used by useMutableSource hook to avoid tearing within this root
8181
// when external, mutable sources are read from during render.
82-
mutableSourcePendingUpdateTime: ExpirationTime,
82+
mutableSourceFirstPendingUpdateTime: ExpirationTime,
83+
mutableSourceLastPendingUpdateTime: ExpirationTime,
8384
|};
8485

8586
// The following attributes are only used by interaction tracing builds.
@@ -130,7 +131,8 @@ function FiberRootNode(containerInfo, tag, hydrate) {
130131
this.nextKnownPendingLevel = NoWork;
131132
this.lastPingedTime = NoWork;
132133
this.lastExpiredTime = NoWork;
133-
this.mutableSourcePendingUpdateTime = NoWork;
134+
this.mutableSourceFirstPendingUpdateTime = NoWork;
135+
this.mutableSourceLastPendingUpdateTime = NoWork;
134136

135137
if (enableSchedulerTracing) {
136138
this.interactionThreadID = unstable_getThreadID();

packages/react-reconciler/src/ReactMutableSource.js

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,20 @@ export function clearPendingUpdates(
3030
root: FiberRoot,
3131
expirationTime: ExpirationTime,
3232
): void {
33-
if (root.mutableSourcePendingUpdateTime <= expirationTime) {
34-
root.mutableSourcePendingUpdateTime = NoWork;
33+
if (root.mutableSourceFirstPendingUpdateTime <= expirationTime) {
34+
root.mutableSourceFirstPendingUpdateTime = NoWork;
3535
}
36+
if (root.mutableSourceLastPendingUpdateTime <= expirationTime) {
37+
root.mutableSourceLastPendingUpdateTime = NoWork;
38+
}
39+
}
40+
41+
export function getFirstPendingExpirationTime(root: FiberRoot): ExpirationTime {
42+
return root.mutableSourceFirstPendingUpdateTime;
3643
}
3744

38-
export function getPendingExpirationTime(root: FiberRoot): ExpirationTime {
39-
return root.mutableSourcePendingUpdateTime;
45+
export function getLastPendingExpirationTime(root: FiberRoot): ExpirationTime {
46+
return root.mutableSourceLastPendingUpdateTime;
4047
}
4148

4249
export function setPendingExpirationTime(
@@ -46,13 +53,19 @@ export function setPendingExpirationTime(
4653
// Because we only track one pending update time per root,
4754
// track the lowest priority update.
4855
// It's inclusive of all other pending updates.
49-
//
50-
// TODO This currently gives us a false positive in some cases
51-
// when it comes to determining if it's safe to read during an update.
52-
root.mutableSourcePendingUpdateTime = Math.max(
53-
root.mutableSourcePendingUpdateTime,
56+
root.mutableSourceLastPendingUpdateTime = Math.max(
57+
root.mutableSourceLastPendingUpdateTime,
5458
expirationTime,
5559
);
60+
61+
if (root.mutableSourceFirstPendingUpdateTime === NoWork) {
62+
root.mutableSourceFirstPendingUpdateTime = expirationTime;
63+
} else {
64+
root.mutableSourceFirstPendingUpdateTime = Math.min(
65+
root.mutableSourceFirstPendingUpdateTime,
66+
expirationTime,
67+
);
68+
}
5669
}
5770

5871
export function markSourceAsDirty(mutableSource: MutableSource<any>): void {

packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,7 @@ describe('useMutableSource', () => {
12371237
ReactNoop.discreteUpdates(() => {
12381238
// At high priority, toggle y so that it reads from A instead of B.
12391239
// Simultaneously, mutate A.
1240-
mutateA('high-pri baz');
1240+
mutateA('baz');
12411241
root.render(
12421242
<App
12431243
getSnapshotFirst={getSnapshotA}
@@ -1246,7 +1246,7 @@ describe('useMutableSource', () => {
12461246
);
12471247

12481248
// If this update were processed before the next mutation,
1249-
// it would be expected to yield "high-pri baz" and "high-pri baz".
1249+
// it would be expected to yield "baz" and "baz".
12501250
});
12511251

12521252
// At lower priority, mutate A again.

0 commit comments

Comments
 (0)