Rename SuspendedWork to Task (#21105)
Sebastian Markbåge committed
Mar 25, 2021 at 21:39 UTC
8b741437b12a3faad1b742274af8fcc456e71958
1 file changed
+138
-142
packages/react-server/src/ReactFizzServer.js
+138
-142
@@ -59,18 +59,18 @@ type SuspenseBoundary = {
59
rootSegmentID: number,
60
forceClientRender: boolean, // if it errors or infinitely suspends
61
parentFlushed: boolean,
62
- pendingWork: number, // when it reaches zero we can show this boundary's content
62
+ pendingTasks: number, // when it reaches zero we can show this boundary's content
63
completedSegments: Array<Segment>, // completed but not yet flushed segments.
64
byteSize: number, // used to determine whether to inline children boundaries.
65
- fallbackAbortableWork: Set<SuspendedWork>, // used to cancel work on the fallback if the boundary completes or gets canceled.
65
+ fallbackAbortableTask: Set<Task>, // used to cancel task on the fallback if the boundary completes or gets canceled.
66
};
67
68
-type SuspendedWork = {
68
+type Task = {
69
node: ReactNodeList,
70
ping: () => void,
71
blockedBoundary: Root | SuspenseBoundary,
72
blockedSegment: Segment, // the segment we'll write to
73
- abortSet: Set<SuspendedWork>, // the abortable set that this work belongs to
73
+ abortSet: Set<Task>, // the abortable set that this task belongs to
74
formatContext: FormatContext,
75
assignID: null | SuspenseBoundaryID, // id to assign to the content
76
};
@@ -104,18 +104,18 @@ type Request = {
104
+progressiveChunkSize: number,
105
status: 0 | 1 | 2,
106
nextSegmentId: number,
107
- allPendingWork: number, // when it reaches zero, we can close the connection.
108
- pendingRootWork: number, // when this reaches zero, we've finished at least the root boundary.
107
+ allPendingTasks: number, // when it reaches zero, we can close the connection.
108
+ pendingRootTasks: number, // when this reaches zero, we've finished at least the root boundary.
109
completedRootSegment: null | Segment, // Completed but not yet flushed root segments.
110
- abortableWork: Set<SuspendedWork>,
111
- pingedWork: Array<SuspendedWork>,
110
+ abortableTask: Set<Task>,
111
+ pingedTask: Array<Task>,
112
// Queues to flush in order of priority
113
clientRenderedBoundaries: Array<SuspenseBoundary>, // Errored or client rendered but not yet flushed.
114
completedBoundaries: Array<SuspenseBoundary>, // Completed but not yet fully flushed boundaries to show.
115
partialBoundaries: Array<SuspenseBoundary>, // Partially completed boundaries that can flush its segments early.
116
// onError is called when an error happens anywhere in the tree. It might recover.
117
onError: (error: mixed) => void,
118
- // onCompleteAll is called when all pending work is done but it may not have flushed yet.
118
+ // onCompleteAll is called when all pending task is done but it may not have flushed yet.
119
// This is a good time to start writing if you want only HTML and no intermediate steps.
120
onCompleteAll: () => void,
121
// onReadyToStream is called when there is at least a root fallback ready to show.
@@ -151,19 +151,19 @@ export function createRequest(
151
onCompleteAll: () => void = noop,
152
onReadyToStream: () => void = noop,
153
): Request {
154
- const pingedWork = [];
155
- const abortSet: Set<SuspendedWork> = new Set();
154
+ const pingedTask = [];
155
+ const abortSet: Set<Task> = new Set();
156
const request = {
157
destination,
158
responseState,
159
progressiveChunkSize,
160
status: BUFFERING,
161
nextSegmentId: 0,
162
- allPendingWork: 0,
163
- pendingRootWork: 0,
162
+ allPendingTasks: 0,
163
+ pendingRootTasks: 0,
164
completedRootSegment: null,
165
- abortableWork: abortSet,
166
- pingedWork: pingedWork,
165
+ abortableTask: abortSet,
166
+ pingedTask: pingedTask,
167
clientRenderedBoundaries: [],
168
completedBoundaries: [],
169
partialBoundaries: [],
@@ -175,7 +175,7 @@ export function createRequest(
175
const rootSegment = createPendingSegment(request, 0, null);
176
// There is no parent so conceptually, we're unblocked to flush this segment.
177
rootSegment.parentFlushed = true;
178
- const rootWork = createSuspendedWork(
178
+ const rootTask = createTask(
179
request,
180
children,
181
null,
@@ -184,60 +184,60 @@ export function createRequest(
184
rootContext,
185
null,
186
);
187
- pingedWork.push(rootWork);
187
+ pingedTask.push(rootTask);
188
return request;
189
}
190
191
-function pingSuspendedWork(request: Request, work: SuspendedWork): void {
192
- const pingedWork = request.pingedWork;
193
- pingedWork.push(work);
194
- if (pingedWork.length === 1) {
191
+function pingTask(request: Request, task: Task): void {
192
+ const pingedTask = request.pingedTask;
193
+ pingedTask.push(task);
194
+ if (pingedTask.length === 1) {
195
scheduleWork(() => performWork(request));
196
}
197
}
198
199
function createSuspenseBoundary(
200
request: Request,
201
- fallbackAbortableWork: Set<SuspendedWork>,
201
+ fallbackAbortableTask: Set<Task>,
202
): SuspenseBoundary {
203
return {
204
id: createSuspenseBoundaryID(request.responseState),
205
rootSegmentID: -1,
206
parentFlushed: false,
207
- pendingWork: 0,
207
+ pendingTasks: 0,
208
forceClientRender: false,
209
completedSegments: [],
210
byteSize: 0,
211
- fallbackAbortableWork,
211
+ fallbackAbortableTask,
212
};
213
}
214
215
-function createSuspendedWork(
215
+function createTask(
216
request: Request,
217
node: ReactNodeList,
218
blockedBoundary: Root | SuspenseBoundary,
219
blockedSegment: Segment,
220
- abortSet: Set<SuspendedWork>,
220
+ abortSet: Set<Task>,
221
formatContext: FormatContext,
222
assignID: null | SuspenseBoundaryID,
223
-): SuspendedWork {
224
- request.allPendingWork++;
223
+): Task {
224
+ request.allPendingTasks++;
225
if (blockedBoundary === null) {
226
- request.pendingRootWork++;
226
+ request.pendingRootTasks++;
227
} else {
228
- blockedBoundary.pendingWork++;
228
+ blockedBoundary.pendingTasks++;
229
}
230
- const work = {
230
+ const task = {
231
node,
232
- ping: () => pingSuspendedWork(request, work),
232
+ ping: () => pingTask(request, task),
233
blockedBoundary,
234
blockedSegment,
235
abortSet,
236
formatContext,
237
assignID,
238
};
239
- abortSet.add(work);
240
- return work;
239
+ abortSet.add(task);
240
+ return task;
241
}
242
243
function createPendingSegment(
@@ -270,34 +270,30 @@ function fatalError(request: Request, error: mixed): void {
270
closeWithError(request.destination, error);
271
}
272
273
-function renderNode(
274
- request: Request,
275
- work: SuspendedWork,
276
- node: ReactNodeList,
277
-): void {
273
+function renderNode(request: Request, task: Task, node: ReactNodeList): void {
274
if (typeof node === 'string') {
275
pushTextInstance(
280
- work.blockedSegment.chunks,
276
+ task.blockedSegment.chunks,
277
node,
278
request.responseState,
283
- work.assignID,
279
+ task.assignID,
280
);
285
- work.assignID = null;
281
+ task.assignID = null;
282
return;
283
}
284
285
if (Array.isArray(node)) {
286
if (node.length > 0) {
287
for (let i = 0; i < node.length; i++) {
292
- renderNode(request, work, node[i]);
288
+ renderNode(request, task, node[i]);
289
}
290
} else {
291
pushEmpty(
296
- work.blockedSegment.chunks,
292
+ task.blockedSegment.chunks,
293
request.responseState,
298
- work.assignID,
294
+ task.assignID,
295
);
300
- work.assignID = null;
296
+ task.assignID = null;
297
}
298
return;
299
}
@@ -315,26 +311,26 @@ function renderNode(
311
if (typeof type === 'function') {
312
try {
313
const result = type(props);
318
- renderNode(request, work, result);
314
+ renderNode(request, task, result);
315
} catch (x) {
316
if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
317
// Something suspended, we'll need to create a new segment and resolve it later.
322
- const segment = work.blockedSegment;
318
+ const segment = task.blockedSegment;
319
const insertionIndex = segment.chunks.length;
320
const newSegment = createPendingSegment(request, insertionIndex, null);
321
segment.children.push(newSegment);
326
- const suspendedWork = createSuspendedWork(
322
+ const newTask = createTask(
323
request,
324
node,
329
- work.blockedBoundary,
325
+ task.blockedBoundary,
326
newSegment,
331
- work.abortSet,
332
- work.formatContext,
333
- work.assignID,
327
+ task.abortSet,
328
+ task.formatContext,
329
+ task.assignID,
330
);
331
// We've delegated the assignment.
336
- work.assignID = null;
337
- const ping = suspendedWork.ping;
332
+ task.assignID = null;
333
+ const ping = newTask.ping;
334
x.then(ping, ping);
335
} else {
336
// We can rethrow to terminate the rest of this tree.
@@ -343,28 +339,28 @@ function renderNode(
339
}
340
} else if (typeof type === 'string') {
341
pushStartInstance(
346
- work.blockedSegment.chunks,
342
+ task.blockedSegment.chunks,
343
type,
344
props,
345
request.responseState,
350
- work.assignID,
346
+ task.assignID,
347
);
348
// We must have assigned it already above so we don't need this anymore.
353
- work.assignID = null;
354
- const prevContext = work.formatContext;
355
- work.formatContext = getChildFormatContext(prevContext, type, props);
356
- renderNode(request, work, props.children);
357
- // We expect that errors will fatal the whole work and that we don't need
349
+ task.assignID = null;
350
+ const prevContext = task.formatContext;
351
+ task.formatContext = getChildFormatContext(prevContext, type, props);
352
+ renderNode(request, task, props.children);
353
+ // We expect that errors will fatal the whole task and that we don't need
354
// the correct context. Therefore this is not in a finally.
359
- work.formatContext = prevContext;
360
- pushEndInstance(work.blockedSegment.chunks, type, props);
355
+ task.formatContext = prevContext;
356
+ pushEndInstance(task.blockedSegment.chunks, type, props);
357
} else if (type === REACT_SUSPENSE_TYPE) {
362
- const parentBoundary = work.blockedBoundary;
363
- const parentSegment = work.blockedSegment;
358
+ const parentBoundary = task.blockedBoundary;
359
+ const parentSegment = task.blockedSegment;
360
361
// We need to push an "empty" thing here to identify the parent suspense boundary.
366
- pushEmpty(parentSegment.chunks, request.responseState, work.assignID);
367
- work.assignID = null;
362
+ pushEmpty(parentSegment.chunks, request.responseState, task.assignID);
363
+ task.assignID = null;
364
// Each time we enter a suspense boundary, we split out into a new segment for
365
// the fallback so that we can later replace that segment with the content.
366
// This also lets us split out the main content even if it doesn't suspend,
@@ -372,7 +368,7 @@ function renderNode(
368
const fallback: ReactNodeList = props.fallback;
369
const content: ReactNodeList = props.children;
370
375
- const fallbackAbortSet: Set<SuspendedWork> = new Set();
371
+ const fallbackAbortSet: Set<Task> = new Set();
372
const newBoundary = createSuspenseBoundary(request, fallbackAbortSet);
373
const insertionIndex = parentSegment.chunks.length;
374
// The children of the boundary segment is actually the fallback.
@@ -389,21 +385,21 @@ function renderNode(
385
// no parent segment so there's nothing to wait on.
386
contentRootSegment.parentFlushed = true;
387
392
- // Currently this is running synchronously. We could instead schedule this to pingedWork.
393
- // I suspect that there might be some efficiency benefits from not creating the suspended work
388
+ // Currently this is running synchronously. We could instead schedule this to pingedTask.
389
+ // I suspect that there might be some efficiency benefits from not creating the suspended task
390
// and instead just using the stack if possible.
391
// TODO: Call this directly instead of messing with saving and restoring contexts.
392
397
- // We can reuse the current context and work to render the content immediately without
393
+ // We can reuse the current context and task to render the content immediately without
394
// context switching. We just need to temporarily switch which boundary and which segment
399
- // we're writing to. If something suspends, it'll spawn new suspended work with that context.
400
- work.blockedBoundary = newBoundary;
401
- work.blockedSegment = contentRootSegment;
395
+ // we're writing to. If something suspends, it'll spawn new suspended task with that context.
396
+ task.blockedBoundary = newBoundary;
397
+ task.blockedSegment = contentRootSegment;
398
try {
403
- renderNode(request, work, content);
399
+ renderNode(request, task, content);
400
contentRootSegment.status = COMPLETED;
401
newBoundary.completedSegments.push(contentRootSegment);
406
- if (newBoundary.pendingWork === 0) {
402
+ if (newBoundary.pendingTasks === 0) {
403
// This must have been the last segment we were waiting on. This boundary is now complete.
404
// Therefore we won't need the fallback. We early return so that we don't have to create
405
// the fallback.
@@ -413,34 +409,34 @@ function renderNode(
409
contentRootSegment.status = ERRORED;
410
reportError(request, error);
411
newBoundary.forceClientRender = true;
416
- // We don't need to decrement any work numbers because we didn't spawn any new work.
417
- // We don't need to schedule any work because we know the parent has written yet.
412
+ // We don't need to decrement any task numbers because we didn't spawn any new task.
413
+ // We don't need to schedule any task because we know the parent has written yet.
414
// We do need to fallthrough to create the fallback though.
415
} finally {
420
- work.blockedBoundary = parentBoundary;
421
- work.blockedSegment = parentSegment;
416
+ task.blockedBoundary = parentBoundary;
417
+ task.blockedSegment = parentSegment;
418
}
419
424
- // We create suspended work for the fallback because we don't want to actually work
420
+ // We create suspended task for the fallback because we don't want to actually task
421
// on it yet in case we finish the main content, so we queue for later.
426
- const suspendedFallbackWork = createSuspendedWork(
422
+ const suspendedFallbackTask = createTask(
423
request,
424
fallback,
425
parentBoundary,
426
boundarySegment,
427
fallbackAbortSet,
432
- work.formatContext,
428
+ task.formatContext,
429
newBoundary.id, // This is the ID we want to give this fallback so we can replace it later.
430
);
435
- // TODO: This should be queued at a separate lower priority queue so that we only work
436
- // on preparing fallbacks if we don't have any more main content to work on.
437
- request.pingedWork.push(suspendedFallbackWork);
431
+ // TODO: This should be queued at a separate lower priority queue so that we only task
432
+ // on preparing fallbacks if we don't have any more main content to task on.
433
+ request.pingedTask.push(suspendedFallbackTask);
434
} else {
435
throw new Error('Not yet implemented element type.');
436
}
437
}
438
443
-function erroredWork(
439
+function erroredTask(
440
request: Request,
441
boundary: Root | SuspenseBoundary,
442
segment: Segment,
@@ -451,7 +447,7 @@ function erroredWork(
447
if (boundary === null) {
448
fatalError(request, error);
449
} else {
454
- boundary.pendingWork--;
450
+ boundary.pendingTasks--;
451
if (!boundary.forceClientRender) {
452
boundary.forceClientRender = true;
453
@@ -467,32 +463,32 @@ function erroredWork(
463
}
464
}
465
470
- request.allPendingWork--;
471
- if (request.allPendingWork === 0) {
466
+ request.allPendingTasks--;
467
+ if (request.allPendingTasks === 0) {
468
request.onCompleteAll();
469
}
470
}
471
476
-function abortWorkSoft(suspendedWork: SuspendedWork): void {
477
- // This aborts work without aborting the parent boundary that it blocks.
478
- // It's used for when we didn't need this work to complete the tree.
479
- // If work was needed, then it should use abortWork instead.
472
+function abortTaskSoft(task: Task): void {
473
+ // This aborts task without aborting the parent boundary that it blocks.
474
+ // It's used for when we didn't need this task to complete the tree.
475
+ // If task was needed, then it should use abortTask instead.
476
const request: Request = this;
481
- const boundary = suspendedWork.blockedBoundary;
482
- const segment = suspendedWork.blockedSegment;
477
+ const boundary = task.blockedBoundary;
478
+ const segment = task.blockedSegment;
479
segment.status = ABORTED;
484
- finishedWork(request, boundary, segment);
480
+ finishedTask(request, boundary, segment);
481
}
482
487
-function abortWork(suspendedWork: SuspendedWork): void {
488
- // This aborts the work and aborts the parent that it blocks, putting it into
483
+function abortTask(task: Task): void {
484
+ // This aborts the task and aborts the parent that it blocks, putting it into
485
// client rendered mode.
486
const request: Request = this;
491
- const boundary = suspendedWork.blockedBoundary;
492
- const segment = suspendedWork.blockedSegment;
487
+ const boundary = task.blockedBoundary;
488
+ const segment = task.blockedSegment;
489
segment.status = ABORTED;
490
495
- request.allPendingWork--;
491
+ request.allPendingTasks--;
492
if (boundary === null) {
493
// We didn't complete the root so we have nothing to show. We can close
494
// the request;
@@ -501,12 +497,12 @@ function abortWork(suspendedWork: SuspendedWork): void {
497
close(request.destination);
498
}
499
} else {
504
- boundary.pendingWork--;
500
+ boundary.pendingTasks--;
501
502
// If this boundary was still pending then we haven't already cancelled its fallbacks.
503
// We'll need to abort the fallbacks, which will also error that parent boundary.
508
- boundary.fallbackAbortableWork.forEach(abortWork, request);
509
- boundary.fallbackAbortableWork.clear();
504
+ boundary.fallbackAbortableTask.forEach(abortTask, request);
505
+ boundary.fallbackAbortableTask.clear();
506
507
if (!boundary.forceClientRender) {
508
boundary.forceClientRender = true;
@@ -515,13 +511,13 @@ function abortWork(suspendedWork: SuspendedWork): void {
511
}
512
}
513
518
- if (request.allPendingWork === 0) {
514
+ if (request.allPendingTasks === 0) {
515
request.onCompleteAll();
516
}
517
}
518
}
519
524
-function finishedWork(
520
+function finishedTask(
521
request: Request,
522
boundary: Root | SuspenseBoundary,
523
segment: Segment,
@@ -534,19 +530,19 @@ function finishedWork(
530
);
531
request.completedRootSegment = segment;
532
}
537
- request.pendingRootWork--;
538
- if (request.pendingRootWork === 0) {
533
+ request.pendingRootTasks--;
534
+ if (request.pendingRootTasks === 0) {
535
request.onReadyToStream();
536
}
537
} else {
542
- boundary.pendingWork--;
538
+ boundary.pendingTasks--;
539
if (boundary.forceClientRender) {
540
// This already errored.
545
- } else if (boundary.pendingWork === 0) {
541
+ } else if (boundary.pendingTasks === 0) {
542
// This must have been the last segment we were waiting on. This boundary is now complete.
547
- // We can now cancel any pending work on the fallback since we won't need to show it anymore.
548
- boundary.fallbackAbortableWork.forEach(abortWorkSoft, request);
549
- boundary.fallbackAbortableWork.clear();
543
+ // We can now cancel any pending task on the fallback since we won't need to show it anymore.
544
+ boundary.fallbackAbortableTask.forEach(abortTaskSoft, request);
545
+ boundary.fallbackAbortableTask.clear();
546
if (segment.parentFlushed) {
547
// Our parent segment already flushed, so we need to schedule this segment to be emitted.
548
boundary.completedSegments.push(segment);
@@ -573,22 +569,22 @@ function finishedWork(
569
}
570
}
571
576
- request.allPendingWork--;
577
- if (request.allPendingWork === 0) {
572
+ request.allPendingTasks--;
573
+ if (request.allPendingTasks === 0) {
574
// This needs to be called at the very end so that we can synchronously write the result
575
// in the callback if needed.
576
request.onCompleteAll();
577
}
578
}
579
584
-function retryWork(request: Request, work: SuspendedWork): void {
585
- const segment = work.blockedSegment;
580
+function retryTask(request: Request, task: Task): void {
581
+ const segment = task.blockedSegment;
582
if (segment.status !== PENDING) {
583
// We completed this by other means before we had a chance to retry it.
584
return;
585
}
586
try {
591
- let node = work.node;
587
+ let node = task.node;
588
while (
589
typeof node === 'object' &&
590
node !== null &&
@@ -598,25 +594,25 @@ function retryWork(request: Request, work: SuspendedWork): void {
594
// Doing this here lets us reuse this same Segment if the next component
595
// also suspends.
596
const element: React$Element<any> = (node: any);
601
- work.node = node;
597
+ task.node = node;
598
// TODO: Classes and legacy context etc.
599
node = element.type(element.props);
600
}
601
606
- renderNode(request, work, node);
602
+ renderNode(request, task, node);
603
608
- work.abortSet.delete(work);
604
+ task.abortSet.delete(task);
605
segment.status = COMPLETED;
610
- finishedWork(request, work.blockedBoundary, segment);
606
+ finishedTask(request, task.blockedBoundary, segment);
607
} catch (x) {
608
if (typeof x === 'object' && x !== null && typeof x.then === 'function') {
609
// Something suspended again, let's pick it back up later.
614
- const ping = work.ping;
610
+ const ping = task.ping;
611
x.then(ping, ping);
612
} else {
617
- work.abortSet.delete(work);
613
+ task.abortSet.delete(task);
614
segment.status = ERRORED;
619
- erroredWork(request, work.blockedBoundary, segment, x);
615
+ erroredTask(request, task.blockedBoundary, segment, x);
616
}
617
}
618
}
@@ -629,13 +625,13 @@ function performWork(request: Request): void {
625
ReactCurrentDispatcher.current = Dispatcher;
626
627
try {
632
- const pingedWork = request.pingedWork;
628
+ const pingedTask = request.pingedTask;
629
let i;
634
- for (i = 0; i < pingedWork.length; i++) {
635
- const work = pingedWork[i];
636
- retryWork(request, work);
630
+ for (i = 0; i < pingedTask.length; i++) {
631
+ const task = pingedTask[i];
632
+ retryTask(request, task);
633
}
638
- pingedWork.splice(0, i);
634
+ pingedTask.splice(0, i);
635
if (request.status === FLOWING) {
636
flushCompletedQueues(request);
637
}
@@ -712,7 +708,7 @@ function flushSegment(
708
flushSubtree(request, destination, segment);
709
710
return writeEndSuspenseBoundary(destination);
715
- } else if (boundary.pendingWork > 0) {
711
+ } else if (boundary.pendingTasks > 0) {
712
// This boundary is still loading. Emit a pending suspense boundary wrapper.
713
714
// Assign an ID to refer to the future content by.
@@ -880,7 +876,7 @@ function flushCompletedQueues(request: Request): void {
876
// TODO: It's kind of unfortunate to keep checking this array after we've already
877
// emitted the root.
878
const completedRootSegment = request.completedRootSegment;
883
- if (completedRootSegment !== null && request.pendingRootWork === 0) {
879
+ if (completedRootSegment !== null && request.pendingRootTasks === 0) {
880
flushSegment(request, destination, completedRootSegment);
881
request.completedRootSegment = null;
882
}
@@ -956,17 +952,17 @@ function flushCompletedQueues(request: Request): void {
952
completeWriting(destination);
953
flushBuffered(destination);
954
if (
959
- request.allPendingWork === 0 &&
960
- request.pingedWork.length === 0 &&
955
+ request.allPendingTasks === 0 &&
956
+ request.pingedTask.length === 0 &&
957
request.clientRenderedBoundaries.length === 0 &&
958
request.completedBoundaries.length === 0
959
// We don't need to check any partially completed segments because
964
- // either they have pending work or they're complete.
960
+ // either they have pending task or they're complete.
961
) {
962
if (__DEV__) {
967
- if (request.abortableWork.size !== 0) {
963
+ if (request.abortableTask.size !== 0) {
964
console.error(
969
- 'There was still abortable work at the root when we closed. This is a bug in React.',
965
+ 'There was still abortable task at the root when we closed. This is a bug in React.',
966
);
967
}
968
}
@@ -996,9 +992,9 @@ export function startFlowing(request: Request): void {
992
// This is called to early terminate a request. It puts all pending boundaries in client rendered state.
993
export function abort(request: Request): void {
994
try {
999
- const abortableWork = request.abortableWork;
1000
- abortableWork.forEach(abortWork, request);
1001
- abortableWork.clear();
995
+ const abortableTask = request.abortableTask;
996
+ abortableTask.forEach(abortTask, request);
997
+ abortableTask.clear();
998
if (request.status === FLOWING) {
999
flushCompletedQueues(request);
1000
}