@samitouri / QOS-React-2 / commits / 3addf205bf

Remove Task priority (#11307)

* Remove Task priority The concept of Task priority was originally added as a way to avoid reentrancy. Sync priority is for work that flushes synchronously, and Task is for work that flushes at the end of the event loop. But it turns out that in most cases, it's simpler to model Task and Sync as the same priority level. For example, it's never correct to flush Sync work from the update queue without flushing Task. Doing so can lead to infinite update loops. And using a separate priority level is not necessary to avoid reentrancy. We already track when work is being rendered, and exit before entering the render cycle again. That alone is sufficient. This commit removes Task priority from the codebase. Now we use the same level for both truly synchronous work and work that is deferred until the end of the event loop. This also enables us to remove DOM-specific legacy cases from the reconciler and lift them to the renderer. * Remove isUnbatched from FiberRoot Simpler to render unbatched roots immediately. * Use a cyclic linked list for root schedule Avoids the need for a separate `isScheduled` field.

Andrew Clark committed Oct 25, 2017 at 16:51 UTC 3addf205bf6ad1333861d662ec955fcb11db9d24
2 files changed +137 -109
packages/react-reconciler/src/ReactFiberExpirationTime.js
+1 -3
@@ -14,14 +14,12 @@ export type ExpirationTime = number;
14
15 const NoWork = 0;
16 const Sync = 1;
17 -const Task = 2;
17 const Never = 2147483647; // Max int32: Math.pow(2, 31) - 1
18
19 const UNIT_SIZE = 10;
21 -const MAGIC_NUMBER_OFFSET = 3;
20 +const MAGIC_NUMBER_OFFSET = 2;
21
22 exports.Sync = Sync;
24 -exports.Task = Task;
23 exports.NoWork = NoWork;
24 exports.Never = Never;
25
packages/react-reconciler/src/ReactFiberScheduler.js
+136 -106
@@ -56,7 +56,6 @@ var {createWorkInProgress} = require('./ReactFiber');
56 var {onCommitRoot} = require('./ReactFiberDevToolsHook');
57 var {
58 NoWork,
59 - Task,
59 Sync,
60 Never,
61 msToExpirationTime,
@@ -1172,15 +1171,6 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1171 expirationTime = computeAsyncExpiration();
1172 }
1173 }
1175 -
1176 - if (
1177 - expirationTime === Sync &&
1178 - isBatchingUpdates &&
1179 - (!isUnbatchingUpdates || isCommitting)
1180 - ) {
1181 - // If we're in a batch, downgrade sync to task.
1182 - expirationTime = Task;
1183 - }
1174 return expirationTime;
1175 }
1176
@@ -1250,7 +1240,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1240 }
1241
1242 function scheduleErrorRecovery(fiber: Fiber) {
1253 - scheduleWorkImpl(fiber, Task, true);
1243 + scheduleWorkImpl(fiber, Sync, true);
1244 }
1245
1246 function recalculateCurrentTime(): ExpirationTime {
@@ -1318,15 +1308,18 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1308 );
1309 }
1310
1311 + // Add the root to the schedule.
1312 // Check if this root is already part of the schedule.
1322 - if (root.remainingExpirationTime === NoWork) {
1313 + if (root.nextScheduledRoot === null) {
1314 // This root is not already scheduled. Add it.
1315 root.remainingExpirationTime = expirationTime;
1316 if (lastScheduledRoot === null) {
1317 firstScheduledRoot = lastScheduledRoot = root;
1318 + root.nextScheduledRoot = root;
1319 } else {
1320 lastScheduledRoot.nextScheduledRoot = root;
1321 lastScheduledRoot = root;
1322 + lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1323 }
1324 } else {
1325 // This root is already scheduled, but its priority may have increased.
@@ -1340,21 +1333,28 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1333 }
1334 }
1335
1343 - // If we're not already rendering, schedule work to flush now (if it's
1344 - // sync) or later (if it's async).
1345 - if (!isRendering) {
1346 - // TODO: Remove distinction between sync and task. Maybe we can remove
1347 - // these magic numbers entirely by always comparing to the current time?
1348 - if (expirationTime === Sync) {
1349 - if (isUnbatchingUpdates) {
1350 - performWork(Sync, null);
1351 - } else {
1352 - performWork(Task, null);
1353 - }
1354 - } else if (!isCallbackScheduled) {
1355 - isCallbackScheduled = true;
1356 - scheduleDeferredCallback(flushAsyncWork);
1336 + if (isRendering) {
1337 + // Prevent reentrancy. Remaining work will be scheduled at the end of
1338 + // the currently rendering batch.
1339 + return;
1340 + }
1341 +
1342 + if (isBatchingUpdates) {
1343 + // Flush work at the end of the batch.
1344 + if (isUnbatchingUpdates) {
1345 + // ...unless we're inside unbatchedUpdates, in which case we should
1346 + // flush it now.
1347 + performWorkOnRoot(root, Sync);
1348 }
1349 + return;
1350 + }
1351 +
1352 + // TODO: Get rid of Sync and use current time?
1353 + if (expirationTime === Sync) {
1354 + performWork(Sync, null);
1355 + } else if (!isCallbackScheduled) {
1356 + isCallbackScheduled = true;
1357 + scheduleDeferredCallback(performAsyncWork);
1358 }
1359 }
1360
@@ -1362,34 +1362,60 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1362 let highestPriorityWork = NoWork;
1363 let highestPriorityRoot = null;
1364
1365 - let previousScheduledRoot = null;
1366 - let root = firstScheduledRoot;
1367 - while (root !== null) {
1368 - const remainingExpirationTime = root.remainingExpirationTime;
1369 - if (remainingExpirationTime === NoWork) {
1370 - // If this root no longer has work, remove it from the scheduler.
1371 - let next = root.nextScheduledRoot;
1372 - root.nextScheduledRoot = null;
1373 - if (previousScheduledRoot === null) {
1374 - firstScheduledRoot = next;
1365 + if (lastScheduledRoot !== null) {
1366 + let previousScheduledRoot = lastScheduledRoot;
1367 + let root = firstScheduledRoot;
1368 + while (root !== null) {
1369 + const remainingExpirationTime = root.remainingExpirationTime;
1370 + if (remainingExpirationTime === NoWork) {
1371 + // This root no longer has work. Remove it from the scheduler.
1372 +
1373 + // TODO: This check is redudant, but Flow is confused by the branch
1374 + // below where we set lastScheduledRoot to null, even though we break
1375 + // from the loop right after.
1376 + invariant(
1377 + previousScheduledRoot !== null && lastScheduledRoot !== null,
1378 + 'Should have a previous and last root. This error is likely ' +
1379 + 'caused by a bug in React. Please file an issue.',
1380 + );
1381 + if (root === root.nextScheduledRoot) {
1382 + // This is the only root in the list.
1383 + root.nextScheduledRoot = null;
1384 + firstScheduledRoot = lastScheduledRoot = null;
1385 + break;
1386 + } else if (root === firstScheduledRoot) {
1387 + // This is the first root in the list.
1388 + const next = root.nextScheduledRoot;
1389 + firstScheduledRoot = next;
1390 + lastScheduledRoot.nextScheduledRoot = next;
1391 + root.nextScheduledRoot = null;
1392 + } else if (root === lastScheduledRoot) {
1393 + // This is the last root in the list.
1394 + lastScheduledRoot = previousScheduledRoot;
1395 + lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
1396 + root.nextScheduledRoot = null;
1397 + break;
1398 + } else {
1399 + previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
1400 + root.nextScheduledRoot = null;
1401 + }
1402 + root = previousScheduledRoot.nextScheduledRoot;
1403 } else {
1376 - previousScheduledRoot.nextScheduledRoot = next;
1377 - }
1378 - if (next === null) {
1379 - lastScheduledRoot = null;
1404 + if (
1405 + highestPriorityWork === NoWork ||
1406 + remainingExpirationTime < highestPriorityWork
1407 + ) {
1408 + // Update the priority, if it's higher
1409 + highestPriorityWork = remainingExpirationTime;
1410 + highestPriorityRoot = root;
1411 + }
1412 + if (root === lastScheduledRoot) {
1413 + break;
1414 + }
1415 + previousScheduledRoot = root;
1416 + root = root.nextScheduledRoot;
1417 }
1381 - root = next;
1382 - continue;
1383 - } else if (
1384 - highestPriorityWork === NoWork ||
1385 - remainingExpirationTime < highestPriorityWork
1386 - ) {
1387 - // Update the priority, if it's higher
1388 - highestPriorityWork = remainingExpirationTime;
1389 - highestPriorityRoot = root;
1418 }
1391 - previousScheduledRoot = root;
1392 - root = root.nextScheduledRoot;
1419 }
1420
1421 // If the next root is the same as the previous root, this is a nested
@@ -1408,18 +1434,11 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1434 nextFlushedExpirationTime = highestPriorityWork;
1435 }
1436
1411 - function flushAsyncWork(dl) {
1437 + function performAsyncWork(dl) {
1438 performWork(NoWork, dl);
1439 }
1440
1441 function performWork(minExpirationTime: ExpirationTime, dl: Deadline | null) {
1416 - invariant(
1417 - !isRendering,
1418 - 'performWork was called recursively. This error is likely caused ' +
1419 - 'by a bug in React. Please file an issue.',
1420 - );
1421 -
1422 - isRendering = true;
1442 deadline = dl;
1443
1444 // Keep working on roots until there's no more work, or until the we reach
@@ -1432,49 +1451,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1451 nextFlushedExpirationTime <= minExpirationTime) &&
1452 !deadlineDidExpire
1453 ) {
1435 - // Check if this is async work or sync/expired work.
1436 - // TODO: Pass current time as argument to renderRoot, commitRoot
1437 - if (nextFlushedExpirationTime <= recalculateCurrentTime()) {
1438 - // Flush sync work.
1439 - let finishedWork = nextFlushedRoot.finishedWork;
1440 - if (finishedWork !== null) {
1441 - // This root is already complete. We can commit it.
1442 - nextFlushedRoot.finishedWork = null;
1443 - nextFlushedRoot.remainingExpirationTime = commitRoot(finishedWork);
1444 - } else {
1445 - nextFlushedRoot.finishedWork = null;
1446 - finishedWork = renderRoot(nextFlushedRoot, nextFlushedExpirationTime);
1447 - if (finishedWork !== null) {
1448 - // We've completed the root. Commit it.
1449 - nextFlushedRoot.remainingExpirationTime = commitRoot(finishedWork);
1450 - }
1451 - }
1452 - } else {
1453 - // Flush async work.
1454 - let finishedWork = nextFlushedRoot.finishedWork;
1455 - if (finishedWork !== null) {
1456 - // This root is already complete. We can commit it.
1457 - nextFlushedRoot.finishedWork = null;
1458 - nextFlushedRoot.remainingExpirationTime = commitRoot(finishedWork);
1459 - } else {
1460 - nextFlushedRoot.finishedWork = null;
1461 - finishedWork = renderRoot(nextFlushedRoot, nextFlushedExpirationTime);
1462 - if (finishedWork !== null) {
1463 - // We've completed the root. Check the deadline one more time
1464 - // before committing.
1465 - if (!shouldYield()) {
1466 - // Still time left. Commit the root.
1467 - nextFlushedRoot.remainingExpirationTime = commitRoot(
1468 - finishedWork,
1469 - );
1470 - } else {
1471 - // There's no time left. Mark this root as complete. We'll come
1472 - // back and commit it later.
1473 - nextFlushedRoot.finishedWork = finishedWork;
1474 - }
1475 - }
1476 - }
1477 - }
1454 + performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime);
1455 // Find the next highest priority work.
1456 findHighestPriorityRoot();
1457 }
@@ -1489,13 +1466,12 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1466 // If there's work left over, schedule a new callback.
1467 if (nextFlushedRoot !== null && !isCallbackScheduled) {
1468 isCallbackScheduled = true;
1492 - scheduleDeferredCallback(flushAsyncWork);
1469 + scheduleDeferredCallback(performAsyncWork);
1470 }
1471
1472 // Clean-up.
1473 deadline = null;
1474 deadlineDidExpire = false;
1498 - isRendering = false;
1475 nestedUpdateCount = 0;
1476
1477 if (hasUnhandledError) {
@@ -1506,6 +1482,60 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1482 }
1483 }
1484
1485 + function performWorkOnRoot(root, expirationTime) {
1486 + invariant(
1487 + !isRendering,
1488 + 'performWorkOnRoot was called recursively. This error is likely caused ' +
1489 + 'by a bug in React. Please file an issue.',
1490 + );
1491 +
1492 + isRendering = true;
1493 +
1494 + // Check if this is async work or sync/expired work.
1495 + // TODO: Pass current time as argument to renderRoot, commitRoot
1496 + if (expirationTime <= recalculateCurrentTime()) {
1497 + // Flush sync work.
1498 + let finishedWork = root.finishedWork;
1499 + if (finishedWork !== null) {
1500 + // This root is already complete. We can commit it.
1501 + root.finishedWork = null;
1502 + root.remainingExpirationTime = commitRoot(finishedWork);
1503 + } else {
1504 + root.finishedWork = null;
1505 + finishedWork = renderRoot(root, expirationTime);
1506 + if (finishedWork !== null) {
1507 + // We've completed the root. Commit it.
1508 + root.remainingExpirationTime = commitRoot(finishedWork);
1509 + }
1510 + }
1511 + } else {
1512 + // Flush async work.
1513 + let finishedWork = root.finishedWork;
1514 + if (finishedWork !== null) {
1515 + // This root is already complete. We can commit it.
1516 + root.finishedWork = null;
1517 + root.remainingExpirationTime = commitRoot(finishedWork);
1518 + } else {
1519 + root.finishedWork = null;
1520 + finishedWork = renderRoot(root, expirationTime);
1521 + if (finishedWork !== null) {
1522 + // We've completed the root. Check the deadline one more time
1523 + // before committing.
1524 + if (!shouldYield()) {
1525 + // Still time left. Commit the root.
1526 + root.remainingExpirationTime = commitRoot(finishedWork);
1527 + } else {
1528 + // There's no time left. Mark this root as complete. We'll come
1529 + // back and commit it later.
1530 + root.finishedWork = finishedWork;
1531 + }
1532 + }
1533 + }
1534 + }
1535 +
1536 + isRendering = false;
1537 + }
1538 +
1539 // When working on async work, the reconciler asks the renderer if it should
1540 // yield execution. For DOM, we implement this with requestIdleCallback.
1541 function shouldYield() {
@@ -1546,7 +1576,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1576 } finally {
1577 isBatchingUpdates = previousIsBatchingUpdates;
1578 if (!isBatchingUpdates && !isRendering) {
1549 - performWork(Task, null);
1579 + performWork(Sync, null);
1580 }
1581 }
1582 }
@@ -1579,7 +1609,7 @@ module.exports = function<T, P, I, TI, PI, C, CC, CX, PL>(
1609 'flushSync was called from inside a lifecycle method. It cannot be ' +
1610 'called when React is already rendering.',
1611 );
1582 - performWork(Task, null);
1612 + performWork(Sync, null);
1613 }
1614 }
1615