Skip to content

Commit 701d128

Browse files
committed
Error recovery should have task priority
This uncovered a separate issue where some errors were being unscheduled and rescheduled multiple times before flushing. Turns out we need to check both a fiber and its alternate when determining if it represents a failed unit of work. I didn't notice this before because we were scheduling a new update on *every* boundary at the end of the commit phase, not just the ones that captured an error during that commit. I updated the unit tests to catch this in the future.
1 parent d4dd340 commit 701d128

File tree

7 files changed

+130
-117
lines changed

7 files changed

+130
-117
lines changed

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ var {
5454
var {
5555
Placement,
5656
ContentReset,
57+
Err,
5758
} = require('ReactTypeOfSideEffect');
5859
var ReactCurrentOwner = require('ReactCurrentOwner');
5960
var ReactFiberClassComponent = require('ReactFiberClassComponent');
@@ -502,6 +503,9 @@ module.exports = function<T, P, I, TI, C>(
502503
throw new Error('Invalid type of work');
503504
}
504505

506+
// Add an error effect so we can handle the error during the commit phase
507+
workInProgress.effectTag |= Err;
508+
505509
if (workInProgress.pendingWorkPriority === NoWork ||
506510
workInProgress.pendingWorkPriority > priorityLevel) {
507511
return bailoutOnLowPriority(current, workInProgress);

src/renderers/shared/fiber/ReactFiberCommitWork.js

Lines changed: 17 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -204,50 +204,42 @@ module.exports = function<T, P, I, TI, C>(
204204
}
205205
}
206206

207-
function commitNestedUnmounts(root : Fiber): boolean {
207+
function commitNestedUnmounts(root : Fiber): void {
208208
// While we're inside a removed host node we don't want to call
209209
// removeChild on the inner nodes because they're removed by the top
210210
// call anyway. We also want to call componentWillUnmount on all
211211
// composites before this host node is removed from the tree. Therefore
212212
// we do an inner loop while we're still inside the host node.
213-
let noErrors = true;
214213
let node : Fiber = root;
215214
while (true) {
216-
const noError = commitUnmount(node);
217-
noErrors = noErrors && noError;
215+
commitUnmount(node);
218216
if (node.child) {
219217
// TODO: Coroutines need to visit the stateNode.
220218
node.child.return = node;
221219
node = node.child;
222220
continue;
223221
}
224222
if (node === root) {
225-
return noErrors;
223+
return;
226224
}
227225
while (!node.sibling) {
228226
if (!node.return || node.return === root) {
229-
return noErrors;
227+
return;
230228
}
231229
node = node.return;
232230
}
233231
node.sibling.return = node.return;
234232
node = node.sibling;
235233
}
236-
// This is unreachable but without it Flow complains about implicitly-
237-
// returned undefined.
238-
return noErrors; // eslint-disable-line no-unreachable
239234
}
240235

241-
// Returns true if it completed without any errors
242-
function unmountHostComponents(parent, current): boolean {
236+
function unmountHostComponents(parent, current): void {
243237
// We only have the top Fiber that was inserted but we need recurse down its
244238
// children to find all the terminal nodes.
245-
let noErrors = true;
246239
let node : Fiber = current;
247240
while (true) {
248241
if (node.tag === HostComponent || node.tag === HostText) {
249-
const noError = commitNestedUnmounts(node);
250-
noErrors = noErrors && noError;
242+
commitNestedUnmounts(node);
251243
// After all the children have unmounted, it is now safe to remove the
252244
// node from the tree.
253245
removeChild(parent, node.stateNode);
@@ -260,8 +252,7 @@ module.exports = function<T, P, I, TI, C>(
260252
continue;
261253
}
262254
} else {
263-
const noError = commitUnmount(node);
264-
noErrors = noErrors && noError;
255+
commitUnmount(node);
265256
if (node.child) {
266257
// TODO: Coroutines need to visit the stateNode.
267258
node.child.return = node;
@@ -270,11 +261,11 @@ module.exports = function<T, P, I, TI, C>(
270261
}
271262
}
272263
if (node === current) {
273-
return noErrors;
264+
return;
274265
}
275266
while (!node.sibling) {
276267
if (!node.return || node.return === current) {
277-
return noErrors;
268+
return;
278269
}
279270
node = node.return;
280271
if (node.tag === HostPortal) {
@@ -286,16 +277,13 @@ module.exports = function<T, P, I, TI, C>(
286277
node.sibling.return = node.return;
287278
node = node.sibling;
288279
}
289-
// This is unreachable but without it Flow complains about implicitly-
290-
// returned undefined.
291-
return noErrors; // eslint-disable-line no-unreachable
292280
}
293281

294-
function commitDeletion(current : Fiber) : boolean {
282+
function commitDeletion(current : Fiber) : void {
295283
// Recursively delete all host nodes from the parent.
296284
const parent = getHostParent(current);
297285
// Detach refs and call componentWillUnmount() on the whole subtree.
298-
const noErrors = unmountHostComponents(parent, current);
286+
unmountHostComponents(parent, current);
299287

300288
// Cut off the return pointers to disconnect it from the tree. Ideally, we
301289
// should clear the child pointer of the parent alternate to let this
@@ -308,12 +296,9 @@ module.exports = function<T, P, I, TI, C>(
308296
current.alternate.child = null;
309297
current.alternate.return = null;
310298
}
311-
return noErrors;
312299
}
313300

314-
// Returns true if it completed without any errors
315-
function commitUnmount(current : Fiber) : boolean {
316-
let noErrors = true;
301+
function commitUnmount(current : Fiber) : void {
317302
switch (current.tag) {
318303
case ClassComponent: {
319304
detachRef(current);
@@ -322,27 +307,24 @@ module.exports = function<T, P, I, TI, C>(
322307
const error = tryCallComponentWillUnmount(instance);
323308
if (error) {
324309
captureError(current, error);
325-
noErrors = false;
326310
}
327311
}
328-
break;
312+
return;
329313
}
330314
case HostComponent: {
331315
detachRef(current);
332-
break;
316+
return;
333317
}
334318
case CoroutineComponent: {
335-
const noError = commitNestedUnmounts(current.stateNode);
336-
noErrors = noErrors && noError;
337-
break;
319+
commitNestedUnmounts(current.stateNode);
320+
return;
338321
}
339322
case HostPortal: {
340323
// TODO: this is recursive.
341324
commitDeletion(current);
342-
break;
325+
return;
343326
}
344327
}
345-
return noErrors;
346328
}
347329

348330
function commitWork(current : ?Fiber, finishedWork : Fiber) : void {

0 commit comments

Comments
 (0)