Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 49 additions & 38 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ import {
hoistHoistables,
createHoistableState,
createPreambleState,
isWorkLoopExternallyDriven,
supportsRequestStorage,
requestStorage,
pushFormStateMarkerIsMatching,
Expand Down Expand Up @@ -527,7 +528,7 @@ function RequestInstance(
progressiveChunkSize === undefined
? DEFAULT_PROGRESSIVE_CHUNK_SIZE
: progressiveChunkSize;
this.status = OPENING;
this.status = isWorkLoopExternallyDriven ? OPEN : OPENING;
this.fatalError = null;
this.nextSegmentId = 0;
this.allPendingTasks = 0;
Expand Down Expand Up @@ -790,12 +791,16 @@ export function resolveRequest(): null | Request {
function pingTask(request: Request, task: Task): void {
const pingedTasks = request.pingedTasks;
pingedTasks.push(task);
if (request.pingedTasks.length === 1) {
request.flushScheduled = request.destination !== null;
if (request.trackedPostpones !== null || request.status === OPENING) {
scheduleMicrotask(() => performWork(request));
} else {
scheduleWork(() => performWork(request));
if (isWorkLoopExternallyDriven) {
return;
} else {
if (request.pingedTasks.length === 1) {
request.flushScheduled = request.destination !== null;
if (request.trackedPostpones !== null || request.status === OPENING) {
scheduleMicrotask(() => performWork(request));
} else {
scheduleWork(() => performWork(request));
}
}
}
}
Expand Down Expand Up @@ -6029,39 +6034,45 @@ function flushCompletedQueues(
}

export function startWork(request: Request): void {
request.flushScheduled = request.destination !== null;
// When prerendering we use microtasks for pinging work
if (supportsRequestStorage) {
scheduleMicrotask(() => requestStorage.run(request, performWork, request));
if (isWorkLoopExternallyDriven) {
return;
} else {
scheduleMicrotask(() => performWork(request));
}
scheduleWork(() => {
if (request.status === OPENING) {
request.status = OPEN;
}

if (request.trackedPostpones === null) {
// this is either a regular render or a resume. For regular render we want
// to call emitEarlyPreloads after the first performWork because we want
// are responding to a live request and need to balance sending something early
// (i.e. don't want for the shell to finish) but we need something to send.
// The only implementation of this is for DOM at the moment and during resumes nothing
// actually emits but the code paths here are the same.
// During a prerender we don't want to be too aggressive in emitting early preloads
// because we aren't responding to a live request and we can wait for the prerender to
// postpone before we emit anything.
if (supportsRequestStorage) {
requestStorage.run(
request,
enqueueEarlyPreloadsAfterInitialWork,
request,
);
} else {
enqueueEarlyPreloadsAfterInitialWork(request);
}
request.flushScheduled = request.destination !== null;
// When prerendering we use microtasks for pinging work
if (supportsRequestStorage) {
scheduleMicrotask(() =>
requestStorage.run(request, performWork, request),
);
} else {
scheduleMicrotask(() => performWork(request));
}
});
scheduleWork(() => {
if (request.status === OPENING) {
request.status = OPEN;
}

if (request.trackedPostpones === null) {
// this is either a regular render or a resume. For regular render we want
// to call emitEarlyPreloads after the first performWork because we want
// are responding to a live request and need to balance sending something early
// (i.e. don't want for the shell to finish) but we need something to send.
// The only implementation of this is for DOM at the moment and during resumes nothing
// actually emits but the code paths here are the same.
// During a prerender we don't want to be too aggressive in emitting early preloads
// because we aren't responding to a live request and we can wait for the prerender to
// postpone before we emit anything.
if (supportsRequestStorage) {
requestStorage.run(
request,
enqueueEarlyPreloadsAfterInitialWork,
request,
);
} else {
enqueueEarlyPreloadsAfterInitialWork(request);
}
}
});
}
}

function enqueueEarlyPreloadsAfterInitialWork(request: Request) {
Expand Down
2 changes: 2 additions & 0 deletions packages/react-server/src/forks/ReactFizzConfig.custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const isPrimaryRenderer = false;

export const supportsClientAPIs = true;

export const isWorkLoopExternallyDriven =
$$$config.isWorkLoopExternallyDriven === true;
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);

Expand Down
2 changes: 2 additions & 0 deletions packages/react-server/src/forks/ReactFizzConfig.dom-edge.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM';

export * from 'react-client/src/ReactClientConsoleConfigServer';

export const isWorkLoopExternallyDriven = false;

// For now, we get this from the global scope, but this will likely move to a module.
export const supportsRequestStorage = typeof AsyncLocalStorage === 'function';
export const requestStorage: AsyncLocalStorage<Request | void> =
Expand Down
20 changes: 20 additions & 0 deletions packages/react-server/src/forks/ReactFizzConfig.dom-fb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {Request} from 'react-server/src/ReactFizzServer';

export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM';

export * from 'react-client/src/ReactClientConsoleConfigBrowser';

// This renderer is pulled from the outside through renderNextChunk. Promises
// can ping tasks, but the outer caller decides when to process them.
export const isWorkLoopExternallyDriven = true;

export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy';

export * from 'react-client/src/ReactClientConsoleConfigPlain';

export const isWorkLoopExternallyDriven = false;
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM';

export * from 'react-client/src/ReactClientConsoleConfigServer';

export const isWorkLoopExternallyDriven = false;
export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request | void> =
new AsyncLocalStorage();
1 change: 1 addition & 0 deletions packages/react-server/src/forks/ReactFizzConfig.dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export * from 'react-dom-bindings/src/server/ReactFizzConfigDOM';

export * from 'react-client/src/ReactClientConsoleConfigBrowser';

export const isWorkLoopExternallyDriven = false;
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
1 change: 1 addition & 0 deletions packages/react-server/src/forks/ReactFizzConfig.markup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export * from 'react-markup/src/ReactFizzConfigMarkup.js';

export * from 'react-client/src/ReactClientConsoleConfigPlain';

export const isWorkLoopExternallyDriven = false;
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
2 changes: 2 additions & 0 deletions packages/react-server/src/forks/ReactFizzConfig.noop.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const isPrimaryRenderer = false;

export const supportsClientAPIs = true;

export const isWorkLoopExternallyDriven =
$$$config.isWorkLoopExternallyDriven === true;
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
export * from '../ReactServerStreamConfigFB';

export function scheduleMicrotask(callback: () => void) {
// We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
callback();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we leave a TODO here, as you mentioned the following in the PR description?

For now I've implemented these two for the fb build as synchronous however it is likely that queueMicrotask and setTimeout or similar are preferred.

}

export function scheduleWork(callback: () => void) {
// We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
callback();
}
Loading