Skip to content

Commit

Permalink
flushSync: Exhaust queue even if something throws (#26366)
Browse files Browse the repository at this point in the history
If something throws as a result of `flushSync`, and there's remaining
work left in the queue, React should keep working until all the work is
complete.

If multiple errors are thrown, React will combine them into an
AggregateError object and throw that. In environments where
AggregateError is not available, React will rethrow in an async task.
(All the evergreen runtimes support AggregateError.)

The scenario where this happens is relatively rare, because `flushSync`
will only throw if there's no error boundary to capture the error.

DiffTrain build for [93c10df](93c10df)
  • Loading branch information
acdlite committed Mar 10, 2023
1 parent a91d8c7 commit 1dd9e4e
Show file tree
Hide file tree
Showing 18 changed files with 1,871 additions and 1,567 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ef8bdbecb6dbb9743b895c2e867e5a5264dd6651
93c10dfa6b0848c12189b773b59c77d74cad2a1a
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ if (
}
"use strict";

var ReactVersion = "18.3.0-www-modern-fd5743d3";
var ReactVersion = "18.3.0-www-modern-f87ae691";

// ATTENTION
// When adding new symbols to this file,
Expand Down
77 changes: 51 additions & 26 deletions compiled/facebook-www/ReactART-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
return self;
}

var ReactVersion = "18.3.0-www-classic-9d9aef36";
var ReactVersion = "18.3.0-www-classic-600fa423";

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -3652,46 +3652,71 @@ function flushSyncCallbacksOnlyInLegacyMode() {
function flushSyncCallbacks() {
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrance.
isFlushingSyncQueue = true;
var i = 0;
var previousUpdatePriority = getCurrentUpdatePriority();

try {
var isSync = true;
var queue = syncQueue; // TODO: Is this necessary anymore? The only user code that runs in this
// queue is in the render or commit phases.
isFlushingSyncQueue = true; // Set the event priority to discrete
// TODO: Is this necessary anymore? The only user code that runs in this
// queue is in the render or commit phases, which already set the
// event priority. Should be able to remove.

setCurrentUpdatePriority(DiscreteEventPriority); // $FlowFixMe[incompatible-use] found when upgrading Flow
var previousUpdatePriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
var errors = null;
var queue = syncQueue; // $FlowFixMe[incompatible-use] found when upgrading Flow

for (; i < queue.length; i++) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
var callback = queue[i];
for (var i = 0; i < queue.length; i++) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
var callback = queue[i];

try {
do {
// $FlowFixMe[incompatible-type] we bail out when we get a null
var isSync = true; // $FlowFixMe[incompatible-type] we bail out when we get a null

callback = callback(isSync);
} while (callback !== null);
} catch (error) {
// Collect errors so we can rethrow them at the end
if (errors === null) {
errors = [error];
} else {
errors.push(error);
}
}
}

syncQueue = null;
includesLegacySyncCallbacks = false;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
} // Resume flushing in the next tick
syncQueue = null;
includesLegacySyncCallbacks = false;
setCurrentUpdatePriority(previousUpdatePriority);
isFlushingSyncQueue = false;

scheduleCallback$2(ImmediatePriority, flushSyncCallbacks);
throw error;
} finally {
setCurrentUpdatePriority(previousUpdatePriority);
isFlushingSyncQueue = false;
if (errors !== null) {
if (errors.length > 1) {
if (typeof AggregateError === "function") {
// eslint-disable-next-line no-undef
throw new AggregateError(errors);
} else {
for (var _i = 1; _i < errors.length; _i++) {
scheduleCallback$2(
ImmediatePriority,
throwError.bind(null, errors[_i])
);
}

var firstError = errors[0];
throw firstError;
}
} else {
var error = errors[0];
throw error;
}
}
}

return null;
}

function throwError(error) {
throw error;
}

var nativeConsole = console;
var nativeConsoleLog = null;
var pendingGroupArgs = [];
Expand Down
77 changes: 51 additions & 26 deletions compiled/facebook-www/ReactART-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
return self;
}

var ReactVersion = "18.3.0-www-modern-9e979f3e";
var ReactVersion = "18.3.0-www-modern-ecb0bb1c";

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -3408,46 +3408,71 @@ function flushSyncCallbacksOnlyInLegacyMode() {
function flushSyncCallbacks() {
if (!isFlushingSyncQueue && syncQueue !== null) {
// Prevent re-entrance.
isFlushingSyncQueue = true;
var i = 0;
var previousUpdatePriority = getCurrentUpdatePriority();

try {
var isSync = true;
var queue = syncQueue; // TODO: Is this necessary anymore? The only user code that runs in this
// queue is in the render or commit phases.
isFlushingSyncQueue = true; // Set the event priority to discrete
// TODO: Is this necessary anymore? The only user code that runs in this
// queue is in the render or commit phases, which already set the
// event priority. Should be able to remove.

setCurrentUpdatePriority(DiscreteEventPriority); // $FlowFixMe[incompatible-use] found when upgrading Flow
var previousUpdatePriority = getCurrentUpdatePriority();
setCurrentUpdatePriority(DiscreteEventPriority);
var errors = null;
var queue = syncQueue; // $FlowFixMe[incompatible-use] found when upgrading Flow

for (; i < queue.length; i++) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
var callback = queue[i];
for (var i = 0; i < queue.length; i++) {
// $FlowFixMe[incompatible-use] found when upgrading Flow
var callback = queue[i];

try {
do {
// $FlowFixMe[incompatible-type] we bail out when we get a null
var isSync = true; // $FlowFixMe[incompatible-type] we bail out when we get a null

callback = callback(isSync);
} while (callback !== null);
} catch (error) {
// Collect errors so we can rethrow them at the end
if (errors === null) {
errors = [error];
} else {
errors.push(error);
}
}
}

syncQueue = null;
includesLegacySyncCallbacks = false;
} catch (error) {
// If something throws, leave the remaining callbacks on the queue.
if (syncQueue !== null) {
syncQueue = syncQueue.slice(i + 1);
} // Resume flushing in the next tick
syncQueue = null;
includesLegacySyncCallbacks = false;
setCurrentUpdatePriority(previousUpdatePriority);
isFlushingSyncQueue = false;

scheduleCallback$2(ImmediatePriority, flushSyncCallbacks);
throw error;
} finally {
setCurrentUpdatePriority(previousUpdatePriority);
isFlushingSyncQueue = false;
if (errors !== null) {
if (errors.length > 1) {
if (typeof AggregateError === "function") {
// eslint-disable-next-line no-undef
throw new AggregateError(errors);
} else {
for (var _i = 1; _i < errors.length; _i++) {
scheduleCallback$2(
ImmediatePriority,
throwError.bind(null, errors[_i])
);
}

var firstError = errors[0];
throw firstError;
}
} else {
var error = errors[0];
throw error;
}
}
}

return null;
}

function throwError(error) {
throw error;
}

var nativeConsole = console;
var nativeConsoleLog = null;
var pendingGroupArgs = [];
Expand Down
Loading

0 comments on commit 1dd9e4e

Please sign in to comment.