Skip to content

Commit 8da495e

Browse files
committed
Stop resetting outside of render
1 parent 97ffdbb commit 8da495e

File tree

9 files changed

+108
-45
lines changed

9 files changed

+108
-45
lines changed

packages/internal-test-utils/internalAct.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,7 @@ export async function serverAct<T>(scope: () => Thenable<T>): Thenable<T> {
249249

250250
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
251251
const j = jest;
252-
// We have always one pending timer running that resets the Owner Stack limit.
253-
if (j.getTimerCount() > 1) {
252+
if (j.getTimerCount() > 0) {
254253
// There's a pending timer. Flush it now. We only do this in order to
255254
// force Suspense fallbacks to display; the fact that it's a timer
256255
// is an implementation detail. If there are other timers scheduled,

packages/react-reconciler/src/ReactFiberWorkLoop.js

+10
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ import {
4949
enableViewTransition,
5050
enableSwipeTransition,
5151
} from 'shared/ReactFeatureFlags';
52+
import {
53+
startResettingOwnerStackLimit,
54+
stopResettingOwnerStackLimit,
55+
} from 'shared/ReactOwnerStackReset';
5256
import ReactSharedInternals from 'shared/ReactSharedInternals';
5357
import is from 'shared/objectIs';
5458

@@ -1320,6 +1324,10 @@ function finishConcurrentRender(
13201324
}
13211325
}
13221326

1327+
if (__DEV__) {
1328+
stopResettingOwnerStackLimit();
1329+
}
1330+
13231331
if (shouldForceFlushFallbacksInDEV()) {
13241332
// We're inside an `act` scope. Commit immediately.
13251333
commitRoot(
@@ -1984,6 +1992,8 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
19841992
finishQueueingConcurrentUpdates();
19851993

19861994
if (__DEV__) {
1995+
startResettingOwnerStackLimit();
1996+
19871997
ReactStrictModeWarnings.discardPendingWarnings();
19881998
}
19891999

packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js

+17-22
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ describe('ReactOwnerStacks', () => {
104104
let i = 0;
105105
i <
106106
siblingsBeforeStackOne -
107-
// One built-in JSX callsite for the unknown Owner Stack
108-
1 -
109-
// <App /> callsite
110-
1 -
107+
// JSX callsites before this are irrelevant since we always reset the limit
108+
// when we start a render. Both <App /> and <UnknownOwner /> were created
109+
// before we actually start rendering.
110+
0 -
111111
// Stop so that OwnerStackOne will be right before cutoff
112112
1;
113113
i++
@@ -149,8 +149,7 @@ describe('ReactOwnerStacks', () => {
149149
stackOne: normalizeCodeLocInfo(stackOne),
150150
stackTwo: normalizeCodeLocInfo(stackTwo),
151151
}).toEqual({
152-
// We continue resetting periodically.
153-
pendingTimers: 1,
152+
pendingTimers: 0,
154153
stackOne: '\n in App (at **)',
155154
stackTwo: __VARIANT__
156155
? // captured right after cutoff
@@ -176,8 +175,7 @@ describe('ReactOwnerStacks', () => {
176175
stackOne: normalizeCodeLocInfo(stackOne),
177176
stackTwo: normalizeCodeLocInfo(stackTwo),
178177
}).toEqual({
179-
// We continue resetting periodically.
180-
pendingTimers: 1,
178+
pendingTimers: 0,
181179
// rendered everything before cutoff
182180
stackOne: '\n in App (at **)',
183181
stackTwo: '\n in App (at **)',
@@ -192,10 +190,10 @@ describe('ReactOwnerStacks', () => {
192190
let i = 0;
193191
i <
194192
siblingsBeforeStackOne -
195-
// One built-in JSX callsite for the unknown Owner Stack
196-
1 -
197-
// <App /> callsite
198-
1 -
193+
// JSX callsites before this are irrelevant since we always reset the limit
194+
// when we start a render. Both <App /> and <UnknownOwner /> were created
195+
// before we actually start rendering.
196+
0 -
199197
// Stop so that OwnerStackOne will be right before cutoff
200198
1;
201199
i++
@@ -273,8 +271,7 @@ describe('ReactOwnerStacks', () => {
273271
stackOne: normalizeCodeLocInfo(stackOne),
274272
stackTwo: normalizeCodeLocInfo(stackTwo),
275273
}).toEqual({
276-
// 1 for periodically resetting the Owner Stack limit
277-
pendingTimers: 1,
274+
pendingTimers: 0,
278275
stackOne: '\n in App (at **)',
279276
stackTwo:
280277
// captured after we reset the limit
@@ -295,10 +292,10 @@ describe('ReactOwnerStacks', () => {
295292
let i = 0;
296293
i <
297294
siblingsBeforeStackOne -
298-
// One built-in JSX callsite for the unknown Owner Stack
299-
1 -
300-
// <App /> callsite
301-
1 -
295+
// JSX callsites before this are irrelevant since we always reset the limit
296+
// when we start a render. Both <App /> and <UnknownOwner /> were created
297+
// before we actually start rendering.
298+
0 -
302299
// Stop so that OwnerStackOne will be right before cutoff
303300
1;
304301
i++
@@ -357,8 +354,7 @@ describe('ReactOwnerStacks', () => {
357354
stackTwo: normalizeCodeLocInfo(stackTwo),
358355
}).toEqual({
359356
// 1 for the timeout
360-
// 1 for periodically resetting the Owner Stack limit
361-
pendingTimers: 2,
357+
pendingTimers: 1,
362358
stackOne: '\n in App (at **)',
363359
stackTwo: undefined,
364360
});
@@ -372,8 +368,7 @@ describe('ReactOwnerStacks', () => {
372368
stackOne: normalizeCodeLocInfo(stackOne),
373369
stackTwo: normalizeCodeLocInfo(stackTwo),
374370
}).toEqual({
375-
// 1 for periodically resetting the Owner Stack limit
376-
pendingTimers: 1,
371+
pendingTimers: 0,
377372
stackOne: '\n in App (at **)',
378373
stackTwo:
379374
// captured after we reset the limit

packages/react-server/src/ReactFizzServer.js

+9
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ import {
133133
callRenderInDEV,
134134
} from './ReactFizzCallUserSpace';
135135

136+
import {
137+
startResettingOwnerStackLimit,
138+
stopResettingOwnerStackLimit,
139+
} from 'shared/ReactOwnerStackReset';
136140
import {
137141
getIteratorFn,
138142
ASYNC_ITERATOR,
@@ -4571,6 +4575,8 @@ export function performWork(request: Request): void {
45714575
if (__DEV__) {
45724576
prevGetCurrentStackImpl = ReactSharedInternals.getCurrentStack;
45734577
ReactSharedInternals.getCurrentStack = getCurrentStackInDEV;
4578+
4579+
startResettingOwnerStackLimit();
45744580
}
45754581
const prevResumableState = currentResumableState;
45764582
setCurrentResumableState(request.resumableState);
@@ -4591,10 +4597,13 @@ export function performWork(request: Request): void {
45914597
fatalError(request, error, errorInfo, null);
45924598
} finally {
45934599
setCurrentResumableState(prevResumableState);
4600+
45944601
ReactSharedInternals.H = prevDispatcher;
45954602
ReactSharedInternals.A = prevAsyncDispatcher;
45964603

45974604
if (__DEV__) {
4605+
stopResettingOwnerStackLimit();
4606+
45984607
ReactSharedInternals.getCurrentStack = prevGetCurrentStackImpl;
45994608
}
46004609
if (prevDispatcher === HooksDispatcher) {

packages/react-server/src/ReactFlightServer.js

+12
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ import {DefaultAsyncDispatcher} from './flight/ReactFlightAsyncDispatcher';
103103
import {resolveOwner, setCurrentOwner} from './flight/ReactFlightCurrentOwner';
104104

105105
import {getOwnerStackByComponentInfoInDev} from 'shared/ReactComponentInfoStack';
106+
import {
107+
startResettingOwnerStackLimit,
108+
stopResettingOwnerStackLimit,
109+
} from 'shared/ReactOwnerStackReset';
106110

107111
import {
108112
callComponentInDEV,
@@ -4058,6 +4062,10 @@ function performWork(request: Request): void {
40584062
currentRequest = request;
40594063
prepareToUseHooksForRequest(request);
40604064

4065+
if (__DEV__) {
4066+
startResettingOwnerStackLimit();
4067+
}
4068+
40614069
const hadAbortableTasks = request.abortableTasks.size > 0;
40624070
try {
40634071
const pingedTasks = request.pingedTasks;
@@ -4080,6 +4088,10 @@ function performWork(request: Request): void {
40804088
logRecoverableError(request, error, null);
40814089
fatalError(request, error);
40824090
} finally {
4091+
if (__DEV__) {
4092+
stopResettingOwnerStackLimit();
4093+
}
4094+
40834095
ReactSharedInternals.H = prevDispatcher;
40844096
resetHooksForRequest();
40854097
currentRequest = prevRequest;

packages/react-server/src/__tests__/ReactFlightServer-test.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ describe('ReactFlight', () => {
7373
let i = 0;
7474
i <
7575
siblingsBeforeStackOne -
76-
// One built-in JSX callsite for the unknown Owner Stack
77-
1 -
78-
// <App /> callsite
79-
1 -
76+
// JSX callsites before this are irrelevant since we always reset the limit
77+
// when we start a render. Both <App /> and <UnknownOwner /> were created
78+
// before we actually start rendering.
79+
0 -
8080
// Stop so that OwnerStackOne will be right before cutoff
8181
1;
8282
i++
@@ -143,8 +143,7 @@ describe('ReactFlight', () => {
143143
stackOne: normalizeCodeLocInfo(stackOne),
144144
stackTwo: normalizeCodeLocInfo(stackTwo),
145145
}).toEqual({
146-
// 1 for periodically resetting the Owner Stack limit
147-
pendingTimers: 1,
146+
pendingTimers: 0,
148147
stackOne: '\n in App (at **)',
149148
stackTwo:
150149
// captured after we reset the limit

packages/react/src/ReactSharedInternalsClient.js

+1-7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type SharedStateClient = {
3939
// ReactDebugCurrentFrame
4040
getCurrentStack: null | (() => string),
4141

42+
// ReactOwnerStackReset
4243
recentlyCreatedOwnerStacks: 0,
4344
};
4445

@@ -60,14 +61,7 @@ if (__DEV__) {
6061
ReactSharedInternals.thrownErrors = [];
6162
// Stack implementation injected by the current renderer.
6263
ReactSharedInternals.getCurrentStack = (null: null | (() => string));
63-
// ReactOwnerStack
6464
ReactSharedInternals.recentlyCreatedOwnerStacks = 0;
65-
if (typeof setInterval === 'function') {
66-
// TODO: Stop outside of rendering.
67-
setInterval(() => {
68-
ReactSharedInternals.recentlyCreatedOwnerStacks = 0;
69-
}, 1000);
70-
}
7165
}
7266

7367
export default ReactSharedInternals;

packages/react/src/ReactSharedInternalsServer.js

+1-8
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export type SharedStateServer = {
4040
// ReactDebugCurrentFrame
4141
getCurrentStack: null | (() => string),
4242

43-
//
43+
// ReactOwnerStackReset
4444
recentlyCreatedOwnerStacks: 0,
4545
};
4646

@@ -62,14 +62,7 @@ if (enableTaint) {
6262
if (__DEV__) {
6363
// Stack implementation injected by the current renderer.
6464
ReactSharedInternals.getCurrentStack = (null: null | (() => string));
65-
// ReactOwnerStack
6665
ReactSharedInternals.recentlyCreatedOwnerStacks = 0;
67-
if (typeof setInterval === 'function') {
68-
// TODO: Stop outside of rendering.
69-
setInterval(() => {
70-
ReactSharedInternals.recentlyCreatedOwnerStacks = 0;
71-
}, 1000);
72-
}
7366
}
7467

7568
export default ReactSharedInternals;
+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import ReactSharedInternals from 'shared/ReactSharedInternals';
11+
12+
let resetOwnerStackIntervalId: mixed = null;
13+
14+
export function startResettingOwnerStackLimit() {
15+
if (__DEV__) {
16+
ReactSharedInternals.recentlyCreatedOwnerStacks = 0;
17+
18+
if (typeof setInterval === 'function') {
19+
// Renderers might start in render but stop in commit.
20+
// So we need to be resilient against start being called multiple times.
21+
if (resetOwnerStackIntervalId === null) {
22+
clearInterval((resetOwnerStackIntervalId: any));
23+
}
24+
resetOwnerStackIntervalId = setInterval(() => {
25+
ReactSharedInternals.recentlyCreatedOwnerStacks = 0;
26+
}, 1000);
27+
}
28+
} else {
29+
// These errors should never make it into a build so we don't need to encode them in codes.json
30+
// eslint-disable-next-line react-internal/prod-error-codes
31+
throw new Error(
32+
'startResettingOwnerStackLimit should never be called in production mode. This is a bug in React.',
33+
);
34+
}
35+
}
36+
37+
export function stopResettingOwnerStackLimit() {
38+
if (__DEV__) {
39+
if (typeof setInterval === 'function') {
40+
if (resetOwnerStackIntervalId !== null) {
41+
clearInterval((resetOwnerStackIntervalId: any));
42+
resetOwnerStackIntervalId = null;
43+
}
44+
}
45+
} else {
46+
// These errors should never make it into a build so we don't need to encode them in codes.json
47+
// eslint-disable-next-line react-internal/prod-error-codes
48+
throw new Error(
49+
'stopResettingOwnerStackLimit should never be called in production mode. This is a bug in React.',
50+
);
51+
}
52+
}

0 commit comments

Comments
 (0)