@samitouri / QOS-React / commits / 7bccdbd765

Fix "Consecutive" Event Logs in Performance Track (#34659)

Reset EventTime when clearing timers. We need to track repeat updates separately. Typically we always reset all timers when we've logged an update. The same update shouldn't be logged again. I was trying to be clever and not reset the XEventTime because we also need the timestamp to know if it's a repeat event. However, because of this it looked like we had an event schedule an update even after we had reset them. This always resets the XEventTime to -1.1 and then stashes the old time on EventRepeatTime which is our indication whether the next update was a repeat of the old event. --------- Co-authored-by: Ruslan Lesiutin <28902667+hoxyq@users.noreply.github.com> Co-authored-by: Ricky <rickhanlonii@gmail.com>

Sebastian Markbåge committed Oct 1, 2025 at 10:53 UTC 7bccdbd765f03254658da9086e9c5763842aa3ed
2 files changed +29 -25
packages/react-reconciler/src/ReactFiberWorkLoop.js
+6 -6
@@ -281,7 +281,7 @@ import {
281 blockingUpdateComponentName,
282 blockingEventTime,
283 blockingEventType,
284 - blockingEventIsRepeat,
284 + blockingEventRepeatTime,
285 blockingSuspendedTime,
286 gestureClampTime,
287 gestureUpdateTime,
@@ -291,7 +291,7 @@ import {
291 gestureUpdateComponentName,
292 gestureEventTime,
293 gestureEventType,
294 - gestureEventIsRepeat,
294 + gestureEventRepeatTime,
295 gestureSuspendedTime,
296 transitionClampTime,
297 transitionStartTime,
@@ -302,7 +302,7 @@ import {
302 transitionUpdateComponentName,
303 transitionEventTime,
304 transitionEventType,
305 - transitionEventIsRepeat,
305 + transitionEventRepeatTime,
306 transitionSuspendedTime,
307 clearBlockingTimers,
308 clearGestureTimers,
@@ -2017,7 +2017,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
2017 clampedUpdateTime,
2018 clampedEventTime,
2019 gestureEventType,
2020 - gestureEventIsRepeat,
2020 + gestureEventRepeatTime > 0,
2021 gestureUpdateType === PINGED_UPDATE,
2022 renderStartTime,
2023 gestureUpdateTask,
@@ -2065,7 +2065,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
2065 clampedUpdateTime,
2066 clampedEventTime,
2067 blockingEventType,
2068 - blockingEventIsRepeat,
2068 + blockingEventRepeatTime > 0,
2069 blockingUpdateType === SPAWNED_UPDATE,
2070 blockingUpdateType === PINGED_UPDATE,
2071 renderStartTime,
@@ -2119,7 +2119,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
2119 clampedUpdateTime,
2120 clampedEventTime,
2121 transitionEventType,
2122 - transitionEventIsRepeat,
2122 + transitionEventRepeatTime > 0,
2123 transitionUpdateType === PINGED_UPDATE,
2124 renderStartTime,
2125 transitionUpdateTask,
packages/react-reconciler/src/ReactProfilerTimer.js
+23 -19
@@ -74,7 +74,7 @@ export let blockingUpdateMethodName: null | string = null; // The name of the me
74 export let blockingUpdateComponentName: null | string = null; // The name of the component where first sync update happened.
75 export let blockingEventTime: number = -1.1; // Event timeStamp of the first setState.
76 export let blockingEventType: null | string = null; // Event type of the first setState.
77 -export let blockingEventIsRepeat: boolean = false;
77 +export let blockingEventRepeatTime: number = -1.1;
78 export let blockingSuspendedTime: number = -1.1;
79
80 export let gestureClampTime: number = -0;
@@ -85,7 +85,7 @@ export let gestureUpdateMethodName: null | string = null; // The name of the met
85 export let gestureUpdateComponentName: null | string = null; // The name of the component where first gesture update happened.
86 export let gestureEventTime: number = -1.1; // Event timeStamp of the first setState.
87 export let gestureEventType: null | string = null; // Event type of the first setState.
88 -export let gestureEventIsRepeat: boolean = false;
88 +export let gestureEventRepeatTime: number = -1.1;
89 export let gestureSuspendedTime: number = -1.1;
90
91 // TODO: This should really be one per Transition lane.
@@ -98,7 +98,7 @@ export let transitionUpdateMethodName: null | string = null; // The name of the
98 export let transitionUpdateComponentName: null | string = null; // The name of the component where first transition update happened.
99 export let transitionEventTime: number = -1.1; // Event timeStamp of the first transition.
100 export let transitionEventType: null | string = null; // Event type of the first transition.
101 -export let transitionEventIsRepeat: boolean = false;
101 +export let transitionEventRepeatTime: number = -1.1;
102 export let transitionSuspendedTime: number = -1.1;
103
104 export let retryClampTime: number = -0;
@@ -137,10 +137,10 @@ export function startUpdateTimerByLane(
137 const newEventTime = resolveEventTimeStamp();
138 const newEventType = resolveEventType();
139 if (
140 - newEventTime !== gestureEventTime ||
140 + newEventTime !== gestureEventRepeatTime ||
141 newEventType !== gestureEventType
142 ) {
143 - gestureEventIsRepeat = false;
143 + gestureEventRepeatTime = -1.1;
144 }
145 gestureEventTime = newEventTime;
146 gestureEventType = newEventType;
@@ -160,10 +160,10 @@ export function startUpdateTimerByLane(
160 const newEventTime = resolveEventTimeStamp();
161 const newEventType = resolveEventType();
162 if (
163 - newEventTime !== blockingEventTime ||
163 + newEventTime !== blockingEventRepeatTime ||
164 newEventType !== blockingEventType
165 ) {
166 - blockingEventIsRepeat = false;
166 + blockingEventRepeatTime = -1.1;
167 } else if (newEventType !== null) {
168 // If this is a second update in the same event, we treat it as a spawned update.
169 // This might be a microtask spawned from useEffect, multiple flushSync or
@@ -185,10 +185,10 @@ export function startUpdateTimerByLane(
185 const newEventTime = resolveEventTimeStamp();
186 const newEventType = resolveEventType();
187 if (
188 - newEventTime !== transitionEventTime ||
188 + newEventTime !== transitionEventRepeatTime ||
189 newEventType !== transitionEventType
190 ) {
191 - transitionEventIsRepeat = false;
191 + transitionEventRepeatTime = -1.1;
192 }
193 transitionEventTime = newEventTime;
194 transitionEventType = newEventType;
@@ -213,10 +213,10 @@ export function startHostActionTimer(fiber: Fiber): void {
213 const newEventTime = resolveEventTimeStamp();
214 const newEventType = resolveEventType();
215 if (
216 - newEventTime !== blockingEventTime ||
216 + newEventTime !== blockingEventRepeatTime ||
217 newEventType !== blockingEventType
218 ) {
219 - blockingEventIsRepeat = false;
219 + blockingEventRepeatTime = -1.1;
220 } else if (newEventType !== null) {
221 // If this is a second update in the same event, we treat it as a spawned update.
222 // This might be a microtask spawned from useEffect, multiple flushSync or
@@ -234,10 +234,10 @@ export function startHostActionTimer(fiber: Fiber): void {
234 const newEventTime = resolveEventTimeStamp();
235 const newEventType = resolveEventType();
236 if (
237 - newEventTime !== transitionEventTime ||
237 + newEventTime !== transitionEventRepeatTime ||
238 newEventType !== transitionEventType
239 ) {
240 - transitionEventIsRepeat = false;
240 + transitionEventRepeatTime = -1.1;
241 }
242 transitionEventTime = newEventTime;
243 transitionEventType = newEventType;
@@ -292,7 +292,8 @@ export function clearBlockingTimers(): void {
292 blockingUpdateMethodName = null;
293 blockingUpdateComponentName = null;
294 blockingSuspendedTime = -1.1;
295 - blockingEventIsRepeat = true;
295 + blockingEventRepeatTime = blockingEventTime;
296 + blockingEventTime = -1.1;
297 blockingClampTime = now();
298 }
299
@@ -305,10 +306,10 @@ export function startAsyncTransitionTimer(): void {
306 const newEventTime = resolveEventTimeStamp();
307 const newEventType = resolveEventType();
308 if (
308 - newEventTime !== transitionEventTime ||
309 + newEventTime !== transitionEventRepeatTime ||
310 newEventType !== transitionEventType
311 ) {
311 - transitionEventIsRepeat = false;
312 + transitionEventRepeatTime = -1.1;
313 }
314 transitionEventTime = newEventTime;
315 transitionEventType = newEventType;
@@ -329,7 +330,8 @@ export function clearTransitionTimers(): void {
330 transitionUpdateTime = -1.1;
331 transitionUpdateType = 0;
332 transitionSuspendedTime = -1.1;
332 - transitionEventIsRepeat = true;
333 + transitionEventRepeatTime = transitionEventTime;
334 + transitionEventTime = -1.1;
335 transitionClampTime = now();
336 }
337
@@ -342,7 +344,8 @@ export function clearGestureTimers(): void {
344 gestureUpdateTime = -1.1;
345 gestureUpdateType = 0;
346 gestureSuspendedTime = -1.1;
345 - gestureEventIsRepeat = true;
347 + gestureEventRepeatTime = gestureEventTime;
348 + gestureEventTime = -1.1;
349 gestureClampTime = now();
350 }
351
@@ -352,7 +355,8 @@ export function clearGestureUpdates(): void {
355 gestureUpdateTime = -1.1;
356 gestureUpdateType = 0;
357 gestureSuspendedTime = -1.1;
355 - gestureEventIsRepeat = true;
358 + gestureEventRepeatTime = gestureEventTime;
359 + gestureEventTime = -1.1;
360 }
361
362 export function clampBlockingTimers(finalTime: number): void {