Skip to content

Commit 183d1f4

Browse files
acdlitesebmarkbage
authored andcommitted
Fix: Measure expiration times relative to module initialization (#15357)
We use bitwise operations to compute expiration times, which means they need to be smaller than 31 bits. So we measure times relative to module initialization, similar to `performance.now`. This was already working in the old fiber scheduler, but we didn't have a test for it.
1 parent b4bc33a commit 183d1f4

File tree

2 files changed

+30
-2
lines changed

2 files changed

+30
-2
lines changed

packages/react-reconciler/src/ReactFiberScheduler.new.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,20 +228,21 @@ let interruptedBy: Fiber | null = null;
228228
// In other words, because expiration times determine how updates are batched,
229229
// we want all updates of like priority that occur within the same event to
230230
// receive the same expiration time. Otherwise we get tearing.
231+
let initialTimeMs: number = now();
231232
let currentEventTime: ExpirationTime = NoWork;
232233

233234
export function requestCurrentTime() {
234235
if (workPhase === RenderPhase || workPhase === CommitPhase) {
235236
// We're inside React, so it's fine to read the actual time.
236-
return msToExpirationTime(now());
237+
return msToExpirationTime(now() - initialTimeMs);
237238
}
238239
// We're not inside React, so we may be in the middle of a browser event.
239240
if (currentEventTime !== NoWork) {
240241
// Use the same start time for all updates until we enter React again.
241242
return currentEventTime;
242243
}
243244
// This is the first update since React yielded. Compute a new start time.
244-
currentEventTime = msToExpirationTime(now());
245+
currentEventTime = msToExpirationTime(now() - initialTimeMs);
245246
return currentEventTime;
246247
}
247248

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,31 @@ describe('ReactExpiration', () => {
245245
'1 [D] [render]',
246246
]);
247247
});
248+
249+
it('should measure expiration times relative to module initialization', () => {
250+
// Tests an implementation detail where expiration times are computed using
251+
// bitwise operations.
252+
253+
jest.resetModules();
254+
Scheduler = require('scheduler');
255+
// Before importing the renderer, advance the current time by a number
256+
// larger than the maximum allowed for bitwise operations.
257+
const maxSigned31BitInt = 1073741823;
258+
Scheduler.advanceTime(maxSigned31BitInt * 100);
259+
260+
// Now import the renderer. On module initialization, it will read the
261+
// current time.
262+
ReactNoop = require('react-noop-renderer');
263+
264+
ReactNoop.render('Hi');
265+
266+
// The update should not have expired yet.
267+
expect(Scheduler).toFlushExpired([]);
268+
expect(ReactNoop).toMatchRenderedOutput(null);
269+
270+
// Advance the time some more to expire the update.
271+
Scheduler.advanceTime(10000);
272+
expect(Scheduler).toFlushExpired([]);
273+
expect(ReactNoop).toMatchRenderedOutput('Hi');
274+
});
248275
});

0 commit comments

Comments
 (0)