@samitouri / QOS-React-2 / commits / 8b741437b1

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 }