mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 00:20:04 +01:00
[Flight] Emit timestamps only in forwards advancing time in debug info (#33482)
Previously you weren't guaranteed to have only advancing time entries,
you could jump back in time, but now it omits unnecessary duplicates and
clamps automatically if you emit a previous time entry to enforce
forwards order only.
The reason I didn't do this originally is because `await` can jump in
the order because we're trying to encode a graph into a flat timeline
for simplicity of the protocol and consumers.
```js
async function a() {
await fetch1();
await fetch2();
}
async function b() {
await fetch3();
}
async function foo() {
const p = a();
await b();
return p;
}
```
This can effectively create two parallel sequences:
```
--1.................----2.......--
------3......---------------------
```
This can now be flattened to either:
```
--1.................3---2.......--
```
Or:
```
------3......1......----2.......--
```
Depending on which one we visit first. Regardless, information is lost.
I'd say that the second one is worse encoding of this scenario because
it pretends that we weren't waiting for part of the timespan that we
were. To solve this I think we should probably make `emitAsyncSequence`
create a temporary flat list and then sort it by start time before
emitting.
Although we weren't actually blocked since there was some CPU time that
was able to proceed to get to 3. So maybe the second one is actually
better. If we wanted that consistently we'd have to figure out what the
intersection was.
---------
Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>
This commit is contained in:
parent
c38e268978
commit
56408a5b12
|
|
@ -37,8 +37,19 @@ async function delay(text, ms) {
|
|||
return new Promise(resolve => setTimeout(() => resolve(text), ms));
|
||||
}
|
||||
|
||||
async function delayTwice() {
|
||||
await delay('', 20);
|
||||
await delay('', 10);
|
||||
}
|
||||
|
||||
async function delayTrice() {
|
||||
const p = delayTwice();
|
||||
await delay('', 40);
|
||||
return p;
|
||||
}
|
||||
|
||||
async function Bar({children}) {
|
||||
await delay('deferred text', 10);
|
||||
await delayTrice();
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
|
|
|
|||
167
packages/react-client/src/ReactFlightClient.js
vendored
167
packages/react-client/src/ReactFlightClient.js
vendored
|
|
@ -2902,6 +2902,46 @@ function resolveTypedArray(
|
|||
resolveBuffer(response, id, view);
|
||||
}
|
||||
|
||||
function logComponentInfo(
|
||||
response: Response,
|
||||
root: SomeChunk<any>,
|
||||
componentInfo: ReactComponentInfo,
|
||||
trackIdx: number,
|
||||
startTime: number,
|
||||
componentEndTime: number,
|
||||
childrenEndTime: number,
|
||||
isLastComponent: boolean,
|
||||
): void {
|
||||
// $FlowFixMe: Refined.
|
||||
if (
|
||||
isLastComponent &&
|
||||
root.status === ERRORED &&
|
||||
root.reason !== response._closedReason
|
||||
) {
|
||||
// If this is the last component to render before this chunk rejected, then conceptually
|
||||
// this component errored. If this was a cancellation then it wasn't this component that
|
||||
// errored.
|
||||
logComponentErrored(
|
||||
componentInfo,
|
||||
trackIdx,
|
||||
startTime,
|
||||
componentEndTime,
|
||||
childrenEndTime,
|
||||
response._rootEnvironmentName,
|
||||
root.reason,
|
||||
);
|
||||
} else {
|
||||
logComponentRender(
|
||||
componentInfo,
|
||||
trackIdx,
|
||||
startTime,
|
||||
componentEndTime,
|
||||
childrenEndTime,
|
||||
response._rootEnvironmentName,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function flushComponentPerformance(
|
||||
response: Response,
|
||||
root: SomeChunk<any>,
|
||||
|
|
@ -2957,21 +2997,20 @@ function flushComponentPerformance(
|
|||
// in parallel with the previous.
|
||||
const debugInfo = __DEV__ && root._debugInfo;
|
||||
if (debugInfo) {
|
||||
for (let i = 1; i < debugInfo.length; i++) {
|
||||
let startTime = 0;
|
||||
for (let i = 0; i < debugInfo.length; i++) {
|
||||
const info = debugInfo[i];
|
||||
if (typeof info.time === 'number') {
|
||||
startTime = info.time;
|
||||
}
|
||||
if (typeof info.name === 'string') {
|
||||
// $FlowFixMe: Refined.
|
||||
const startTimeInfo = debugInfo[i - 1];
|
||||
if (typeof startTimeInfo.time === 'number') {
|
||||
const startTime = startTimeInfo.time;
|
||||
if (startTime < trackTime) {
|
||||
// The start time of this component is before the end time of the previous
|
||||
// component on this track so we need to bump the next one to a parallel track.
|
||||
trackIdx++;
|
||||
}
|
||||
trackTime = startTime;
|
||||
break;
|
||||
if (startTime < trackTime) {
|
||||
// The start time of this component is before the end time of the previous
|
||||
// component on this track so we need to bump the next one to a parallel track.
|
||||
trackIdx++;
|
||||
}
|
||||
trackTime = startTime;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let i = debugInfo.length - 1; i >= 0; i--) {
|
||||
|
|
@ -2979,6 +3018,7 @@ function flushComponentPerformance(
|
|||
if (typeof info.time === 'number') {
|
||||
if (info.time > parentEndTime) {
|
||||
parentEndTime = info.time;
|
||||
break; // We assume the highest number is at the end.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3006,85 +3046,72 @@ function flushComponentPerformance(
|
|||
}
|
||||
childTrackIdx = childResult.track;
|
||||
const childEndTime = childResult.endTime;
|
||||
childTrackTime = childEndTime;
|
||||
if (childEndTime > childTrackTime) {
|
||||
childTrackTime = childEndTime;
|
||||
}
|
||||
if (childEndTime > childrenEndTime) {
|
||||
childrenEndTime = childEndTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (debugInfo) {
|
||||
let endTime = 0;
|
||||
// Write debug info in reverse order (just like stack traces).
|
||||
let componentEndTime = 0;
|
||||
let isLastComponent = true;
|
||||
let endTime = -1;
|
||||
let endTimeIdx = -1;
|
||||
for (let i = debugInfo.length - 1; i >= 0; i--) {
|
||||
const info = debugInfo[i];
|
||||
if (typeof info.time === 'number') {
|
||||
if (info.time > childrenEndTime) {
|
||||
childrenEndTime = info.time;
|
||||
}
|
||||
if (endTime === 0) {
|
||||
// Last timestamp is the end of the last component.
|
||||
endTime = info.time;
|
||||
}
|
||||
if (typeof info.time !== 'number') {
|
||||
continue;
|
||||
}
|
||||
if (typeof info.name === 'string' && i > 0) {
|
||||
// $FlowFixMe: Refined.
|
||||
const componentInfo: ReactComponentInfo = info;
|
||||
const startTimeInfo = debugInfo[i - 1];
|
||||
if (typeof startTimeInfo.time === 'number') {
|
||||
const startTime = startTimeInfo.time;
|
||||
if (
|
||||
isLastComponent &&
|
||||
root.status === ERRORED &&
|
||||
root.reason !== response._closedReason
|
||||
) {
|
||||
// If this is the last component to render before this chunk rejected, then conceptually
|
||||
// this component errored. If this was a cancellation then it wasn't this component that
|
||||
// errored.
|
||||
logComponentErrored(
|
||||
if (componentEndTime === 0) {
|
||||
// Last timestamp is the end of the last component.
|
||||
componentEndTime = info.time;
|
||||
}
|
||||
const time = info.time;
|
||||
if (endTimeIdx > -1) {
|
||||
// Now that we know the start and end time, we can emit the entries between.
|
||||
for (let j = endTimeIdx - 1; j > i; j--) {
|
||||
const candidateInfo = debugInfo[j];
|
||||
if (typeof candidateInfo.name === 'string') {
|
||||
if (componentEndTime > childrenEndTime) {
|
||||
childrenEndTime = componentEndTime;
|
||||
}
|
||||
// $FlowFixMe: Refined.
|
||||
const componentInfo: ReactComponentInfo = candidateInfo;
|
||||
logComponentInfo(
|
||||
response,
|
||||
root,
|
||||
componentInfo,
|
||||
trackIdx,
|
||||
startTime,
|
||||
endTime,
|
||||
time,
|
||||
componentEndTime,
|
||||
childrenEndTime,
|
||||
response._rootEnvironmentName,
|
||||
root.reason,
|
||||
isLastComponent,
|
||||
);
|
||||
} else {
|
||||
logComponentRender(
|
||||
componentInfo,
|
||||
componentEndTime = time; // The end time of previous component is the start time of the next.
|
||||
// Track the root most component of the result for deduping logging.
|
||||
result.component = componentInfo;
|
||||
isLastComponent = false;
|
||||
} else if (candidateInfo.awaited) {
|
||||
if (endTime > childrenEndTime) {
|
||||
childrenEndTime = endTime;
|
||||
}
|
||||
// $FlowFixMe: Refined.
|
||||
const asyncInfo: ReactAsyncInfo = candidateInfo;
|
||||
logComponentAwait(
|
||||
asyncInfo,
|
||||
trackIdx,
|
||||
startTime,
|
||||
time,
|
||||
endTime,
|
||||
childrenEndTime,
|
||||
response._rootEnvironmentName,
|
||||
);
|
||||
}
|
||||
// Track the root most component of the result for deduping logging.
|
||||
result.component = componentInfo;
|
||||
// Set the end time of the previous component to the start of the previous.
|
||||
endTime = startTime;
|
||||
}
|
||||
isLastComponent = false;
|
||||
} else if (info.awaited && i > 0 && i < debugInfo.length - 2) {
|
||||
// $FlowFixMe: Refined.
|
||||
const asyncInfo: ReactAsyncInfo = info;
|
||||
const startTimeInfo = debugInfo[i - 1];
|
||||
const endTimeInfo = debugInfo[i + 1];
|
||||
if (
|
||||
typeof startTimeInfo.time === 'number' &&
|
||||
typeof endTimeInfo.time === 'number'
|
||||
) {
|
||||
const awaitStartTime = startTimeInfo.time;
|
||||
const awaitEndTime = endTimeInfo.time;
|
||||
logComponentAwait(
|
||||
asyncInfo,
|
||||
trackIdx,
|
||||
awaitStartTime,
|
||||
awaitEndTime,
|
||||
response._rootEnvironmentName,
|
||||
);
|
||||
}
|
||||
}
|
||||
endTime = time; // The end time of the next entry is this time.
|
||||
endTimeIdx = i;
|
||||
}
|
||||
}
|
||||
result.endTime = childrenEndTime;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export type PromiseNode = {
|
|||
start: number, // start time when the Promise was created
|
||||
end: number, // end time when the Promise was resolved.
|
||||
awaited: null | AsyncSequence, // the thing that ended up resolving this promise
|
||||
previous: null, // where we created the promise is not interesting since creating it doesn't mean waiting.
|
||||
previous: null | AsyncSequence, // represents what the last return of an async function depended on before returning
|
||||
};
|
||||
|
||||
export type AwaitNode = {
|
||||
|
|
|
|||
153
packages/react-server/src/ReactFlightServer.js
vendored
153
packages/react-server/src/ReactFlightServer.js
vendored
|
|
@ -777,6 +777,10 @@ function serializeThenable(
|
|||
}
|
||||
}
|
||||
if (newTask.status === PENDING) {
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
// If this is async we need to time when this task finishes.
|
||||
newTask.timed = true;
|
||||
}
|
||||
// We expect that the only status it might be otherwise is ABORTED.
|
||||
// When we abort we emit chunks in each pending task slot and don't need
|
||||
// to do so again here.
|
||||
|
|
@ -786,11 +790,6 @@ function serializeThenable(
|
|||
},
|
||||
);
|
||||
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
// If this is async we need to time when this task finishes.
|
||||
newTask.timed = true;
|
||||
}
|
||||
|
||||
return newTask.id;
|
||||
}
|
||||
|
||||
|
|
@ -1341,12 +1340,7 @@ function renderFunctionComponent<Props>(
|
|||
|
||||
// Track when we started rendering this component.
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
task.timed = true;
|
||||
emitTimingChunk(
|
||||
request,
|
||||
componentDebugID,
|
||||
(task.time = performance.now()),
|
||||
);
|
||||
advanceTaskTime(request, task, performance.now());
|
||||
}
|
||||
|
||||
emitDebugChunk(request, componentDebugID, componentDebugInfo);
|
||||
|
|
@ -1890,8 +1884,8 @@ function visitAsyncNode(
|
|||
request: Request,
|
||||
task: Task,
|
||||
node: AsyncSequence,
|
||||
cutOff: number,
|
||||
visited: Set<AsyncSequence>,
|
||||
cutOff: number,
|
||||
): null | PromiseNode | IONode {
|
||||
if (visited.has(node)) {
|
||||
// It's possible to visit them same node twice when it's part of both an "awaited" path
|
||||
|
|
@ -1900,11 +1894,11 @@ function visitAsyncNode(
|
|||
}
|
||||
visited.add(node);
|
||||
// First visit anything that blocked this sequence to start in the first place.
|
||||
if (node.previous !== null) {
|
||||
if (node.previous !== null && node.end > request.timeOrigin) {
|
||||
// We ignore the return value here because if it wasn't awaited in user space, then we don't log it.
|
||||
// It also means that it can just have been part of a previous component's render.
|
||||
// TODO: This means that some I/O can get lost that was still blocking the sequence.
|
||||
visitAsyncNode(request, task, node.previous, cutOff, visited);
|
||||
visitAsyncNode(request, task, node.previous, visited, cutOff);
|
||||
}
|
||||
switch (node.tag) {
|
||||
case IO_NODE: {
|
||||
|
|
@ -1923,24 +1917,23 @@ function visitAsyncNode(
|
|||
const awaited = node.awaited;
|
||||
let match = null;
|
||||
if (awaited !== null) {
|
||||
const ioNode = visitAsyncNode(request, task, awaited, cutOff, visited);
|
||||
const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
|
||||
if (ioNode !== null) {
|
||||
// This Promise was blocked on I/O. That's a signal that this Promise is interesting to log.
|
||||
// We don't log it yet though. We return it to be logged by the point where it's awaited.
|
||||
// The ioNode might be another PromiseNode in the case where none of the AwaitNode had
|
||||
// unfiltered stacks.
|
||||
if (
|
||||
if (ioNode.tag === PROMISE_NODE) {
|
||||
// If the ioNode was a Promise, then that means we found one in user space since otherwise
|
||||
// we would've returned an IO node. We assume this has the best stack.
|
||||
match = ioNode;
|
||||
} else if (
|
||||
filterStackTrace(request, parseStackTrace(node.stack, 1)).length ===
|
||||
0
|
||||
) {
|
||||
// Typically we assume that the outer most Promise that was awaited in user space has the
|
||||
// most actionable stack trace for the start of the operation. However, if this Promise
|
||||
// was created inside only third party code, then try to use the inner node instead.
|
||||
// This could happen if you pass a first party Promise into a third party to be awaited there.
|
||||
if (ioNode.end < 0) {
|
||||
// If we haven't defined an end time, use the resolve of the outer Promise.
|
||||
ioNode.end = node.end;
|
||||
}
|
||||
// If this Promise was created inside only third party code, then try to use
|
||||
// the inner I/O node instead. This could happen if third party calls into first
|
||||
// party to perform some I/O.
|
||||
match = ioNode;
|
||||
} else {
|
||||
match = node;
|
||||
|
|
@ -1955,30 +1948,17 @@ function visitAsyncNode(
|
|||
}
|
||||
return match;
|
||||
}
|
||||
case UNRESOLVED_AWAIT_NODE:
|
||||
// We could be inside the .then() which is about to resolve this node.
|
||||
// TODO: We could call emitAsyncSequence in a microtask to avoid this issue.
|
||||
// Fallthrough to the resolved path.
|
||||
case UNRESOLVED_AWAIT_NODE: {
|
||||
return null;
|
||||
}
|
||||
case AWAIT_NODE: {
|
||||
const awaited = node.awaited;
|
||||
let match = null;
|
||||
if (awaited !== null) {
|
||||
const ioNode = visitAsyncNode(request, task, awaited, cutOff, visited);
|
||||
const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
|
||||
if (ioNode !== null) {
|
||||
const startTime: number = node.start;
|
||||
let endTime: number;
|
||||
if (node.tag === UNRESOLVED_AWAIT_NODE) {
|
||||
// If we haven't defined an end time, use the resolve of the inner Promise.
|
||||
// This can happen because the ping gets invoked before the await gets resolved.
|
||||
if (ioNode.end < node.start) {
|
||||
// If we're awaiting a resolved Promise it could have finished before we started.
|
||||
endTime = node.start;
|
||||
} else {
|
||||
endTime = ioNode.end;
|
||||
}
|
||||
} else {
|
||||
endTime = node.end;
|
||||
}
|
||||
const endTime: number = node.end;
|
||||
if (endTime <= request.timeOrigin) {
|
||||
// This was already resolved when we started this render. It must have been either something
|
||||
// that's part of a start up sequence or externally cached data. We exclude that information.
|
||||
|
|
@ -2002,14 +1982,12 @@ function visitAsyncNode(
|
|||
match = ioNode;
|
||||
} else {
|
||||
// Outline the IO node.
|
||||
if (ioNode.end < 0) {
|
||||
ioNode.end = endTime;
|
||||
}
|
||||
serializeIONode(request, ioNode);
|
||||
|
||||
// We log the environment at the time when the last promise pigned ping which may
|
||||
// be later than what the environment was when we actually started awaiting.
|
||||
const env = (0, request.environmentName)();
|
||||
emitTimingChunk(request, task.id, startTime);
|
||||
advanceTaskTime(request, task, startTime);
|
||||
// Then emit a reference to us awaiting it in the current task.
|
||||
request.pendingChunks++;
|
||||
emitDebugChunk(request, task.id, {
|
||||
|
|
@ -2018,23 +1996,14 @@ function visitAsyncNode(
|
|||
owner: node.owner,
|
||||
stack: stack,
|
||||
});
|
||||
emitTimingChunk(request, task.id, endTime);
|
||||
markOperationEndTime(request, task, endTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// We need to forward after we visit awaited nodes because what ever I/O we requested that's
|
||||
// the thing that generated this node and its virtual children.
|
||||
let debugInfo: null | ReactDebugInfo;
|
||||
if (node.tag === UNRESOLVED_AWAIT_NODE) {
|
||||
const promise = node.debugInfo.deref();
|
||||
debugInfo =
|
||||
promise === undefined || promise._debugInfo === undefined
|
||||
? null
|
||||
: promise._debugInfo;
|
||||
} else {
|
||||
debugInfo = node.debugInfo;
|
||||
}
|
||||
const debugInfo: null | ReactDebugInfo = node.debugInfo;
|
||||
if (debugInfo !== null) {
|
||||
forwardDebugInfo(request, task, debugInfo);
|
||||
}
|
||||
|
|
@ -2051,37 +2020,23 @@ function emitAsyncSequence(
|
|||
request: Request,
|
||||
task: Task,
|
||||
node: AsyncSequence,
|
||||
cutOff: number,
|
||||
): void {
|
||||
const visited: Set<AsyncSequence> = new Set();
|
||||
const awaitedNode = visitAsyncNode(request, task, node, cutOff, visited);
|
||||
const awaitedNode = visitAsyncNode(request, task, node, visited, task.time);
|
||||
if (awaitedNode !== null) {
|
||||
// Nothing in user space (unfiltered stack) awaited this.
|
||||
if (awaitedNode.end < 0) {
|
||||
// If this was I/O directly without a Promise, then it means that some custom Thenable
|
||||
// called our ping directly and not from a native .then(). We use the current ping time
|
||||
// as the end time and treat it as an await with no stack.
|
||||
// TODO: If this I/O is recurring then we really should have different entries for
|
||||
// each occurrence. Right now we'll only track the first time it is invoked.
|
||||
awaitedNode.end = performance.now();
|
||||
}
|
||||
serializeIONode(request, awaitedNode);
|
||||
request.pendingChunks++;
|
||||
// We log the environment at the time when we ping which may be later than what the
|
||||
// environment was when we actually started awaiting.
|
||||
const env = (0, request.environmentName)();
|
||||
// If we don't have any thing awaited, the time we started awaiting was internal
|
||||
// when we yielded after rendering. The cutOff time is basically that.
|
||||
const awaitStartTime = cutOff;
|
||||
// If the end time finished before we started, it could've been a cached thing so
|
||||
// we clamp it to the cutOff time. Effectively leading to a zero-time await.
|
||||
const awaitEndTime = awaitedNode.end < cutOff ? cutOff : awaitedNode.end;
|
||||
emitTimingChunk(request, task.id, awaitStartTime);
|
||||
// when we yielded after rendering. The current task time is basically that.
|
||||
emitDebugChunk(request, task.id, {
|
||||
awaited: ((awaitedNode: any): ReactIOInfo), // This is deduped by this reference.
|
||||
env: env,
|
||||
});
|
||||
emitTimingChunk(request, task.id, awaitEndTime);
|
||||
markOperationEndTime(request, task, awaitedNode.end);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2092,7 +2047,7 @@ function pingTask(request: Request, task: Task): void {
|
|||
if (enableAsyncDebugInfo) {
|
||||
const sequence = getCurrentAsyncSequence();
|
||||
if (sequence !== null) {
|
||||
emitAsyncSequence(request, task, sequence, task.time);
|
||||
emitAsyncSequence(request, task, sequence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4295,19 +4250,13 @@ function forwardDebugInfo(
|
|||
debugInfo: ReactDebugInfo,
|
||||
) {
|
||||
const id = task.id;
|
||||
const minimumTime =
|
||||
enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0;
|
||||
for (let i = 0; i < debugInfo.length; i++) {
|
||||
const info = debugInfo[i];
|
||||
if (typeof info.time === 'number') {
|
||||
// When forwarding time we need to ensure to convert it to the time space of the payload.
|
||||
// We clamp the time to the starting render of the current component. It's as if it took
|
||||
// no time to render and await if we reuse cached content.
|
||||
emitTimingChunk(
|
||||
request,
|
||||
id,
|
||||
info.time < minimumTime ? minimumTime : info.time,
|
||||
);
|
||||
markOperationEndTime(request, task, info.time);
|
||||
} else {
|
||||
if (typeof info.name === 'string') {
|
||||
// We outline this model eagerly so that we can refer to by reference as an owner.
|
||||
|
|
@ -4384,6 +4333,40 @@ function emitTimingChunk(
|
|||
request.completedRegularChunks.push(processedChunk);
|
||||
}
|
||||
|
||||
function advanceTaskTime(
|
||||
request: Request,
|
||||
task: Task,
|
||||
timestamp: number,
|
||||
): void {
|
||||
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
|
||||
return;
|
||||
}
|
||||
// Emits a timing chunk, if the new timestamp is higher than the previous timestamp of this task.
|
||||
if (timestamp > task.time) {
|
||||
emitTimingChunk(request, task.id, timestamp);
|
||||
task.time = timestamp;
|
||||
} else if (!task.timed) {
|
||||
// If it wasn't timed before, e.g. an outlined object, we need to emit the first timestamp and
|
||||
// it is now timed.
|
||||
emitTimingChunk(request, task.id, task.time);
|
||||
}
|
||||
task.timed = true;
|
||||
}
|
||||
|
||||
function markOperationEndTime(request: Request, task: Task, timestamp: number) {
|
||||
if (!enableProfilerTimer || !enableComponentPerformanceTrack) {
|
||||
return;
|
||||
}
|
||||
// This is like advanceTaskTime() but always emits a timing chunk even if it doesn't advance.
|
||||
// This ensures that the end time of the previous entry isn't implied to be the start of the next one.
|
||||
if (timestamp > task.time) {
|
||||
emitTimingChunk(request, task.id, timestamp);
|
||||
task.time = timestamp;
|
||||
} else {
|
||||
emitTimingChunk(request, task.id, task.time);
|
||||
}
|
||||
}
|
||||
|
||||
function emitChunk(
|
||||
request: Request,
|
||||
task: Task,
|
||||
|
|
@ -4475,7 +4458,7 @@ function emitChunk(
|
|||
function erroredTask(request: Request, task: Task, error: mixed): void {
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
if (task.timed) {
|
||||
emitTimingChunk(request, task.id, (task.time = performance.now()));
|
||||
markOperationEndTime(request, task, performance.now());
|
||||
}
|
||||
}
|
||||
task.status = ERRORED;
|
||||
|
|
@ -4558,7 +4541,7 @@ function retryTask(request: Request, task: Task): void {
|
|||
// We've finished rendering. Log the end time.
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
if (task.timed) {
|
||||
emitTimingChunk(request, task.id, (task.time = performance.now()));
|
||||
markOperationEndTime(request, task, performance.now());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4685,7 +4668,7 @@ function abortTask(task: Task, request: Request, errorId: number): void {
|
|||
// Track when we aborted this task as its end time.
|
||||
if (enableProfilerTimer && enableComponentPerformanceTrack) {
|
||||
if (task.timed) {
|
||||
emitTimingChunk(request, task.id, (task.time = performance.now()));
|
||||
markOperationEndTime(request, task, performance.now());
|
||||
}
|
||||
}
|
||||
// Instead of emitting an error per task.id, we emit a model that only
|
||||
|
|
|
|||
|
|
@ -30,6 +30,27 @@ import {enableAsyncDebugInfo} from 'shared/ReactFeatureFlags';
|
|||
const pendingOperations: Map<number, AsyncSequence> =
|
||||
__DEV__ && enableAsyncDebugInfo ? new Map() : (null: any);
|
||||
|
||||
// Keep the last resolved await as a workaround for async functions missing data.
|
||||
let lastRanAwait: null | AwaitNode = null;
|
||||
|
||||
function resolvePromiseOrAwaitNode(
|
||||
unresolvedNode: UnresolvedAwaitNode | UnresolvedPromiseNode,
|
||||
endTime: number,
|
||||
): AwaitNode | PromiseNode {
|
||||
const resolvedNode: AwaitNode | PromiseNode = (unresolvedNode: any);
|
||||
resolvedNode.tag = ((unresolvedNode.tag === UNRESOLVED_PROMISE_NODE
|
||||
? PROMISE_NODE
|
||||
: AWAIT_NODE): any);
|
||||
// The Promise can be garbage collected after this so we should extract debugInfo first.
|
||||
const promise = unresolvedNode.debugInfo.deref();
|
||||
resolvedNode.debugInfo =
|
||||
promise === undefined || promise._debugInfo === undefined
|
||||
? null
|
||||
: promise._debugInfo;
|
||||
resolvedNode.end = endTime;
|
||||
return resolvedNode;
|
||||
}
|
||||
|
||||
// Initialize the tracing of async operations.
|
||||
// We do this globally since the async work can potentially eagerly
|
||||
// start before the first request and once requests start they can interleave.
|
||||
|
|
@ -129,42 +150,76 @@ export function initAsyncDebugInfo(): void {
|
|||
}
|
||||
pendingOperations.set(asyncId, node);
|
||||
},
|
||||
before(asyncId: number): void {
|
||||
const node = pendingOperations.get(asyncId);
|
||||
if (node !== undefined) {
|
||||
switch (node.tag) {
|
||||
case IO_NODE: {
|
||||
lastRanAwait = null;
|
||||
// Log the end time when we resolved the I/O. This can happen
|
||||
// more than once if it's a recurring resource like a connection.
|
||||
const ioNode: IONode = (node: any);
|
||||
ioNode.end = performance.now();
|
||||
break;
|
||||
}
|
||||
case UNRESOLVED_AWAIT_NODE: {
|
||||
// If we begin before we resolve, that means that this is actually already resolved but
|
||||
// the promiseResolve hook is called at the end of the execution. So we track the time
|
||||
// in the before call instead.
|
||||
// $FlowFixMe
|
||||
lastRanAwait = resolvePromiseOrAwaitNode(node, performance.now());
|
||||
break;
|
||||
}
|
||||
case AWAIT_NODE: {
|
||||
lastRanAwait = node;
|
||||
break;
|
||||
}
|
||||
case UNRESOLVED_PROMISE_NODE: {
|
||||
// We typically don't expected Promises to have an execution scope since only the awaits
|
||||
// have a then() callback. However, this can happen for native async functions. The last
|
||||
// piece of code that executes the return after the last await has the execution context
|
||||
// of the Promise.
|
||||
const resolvedNode = resolvePromiseOrAwaitNode(
|
||||
node,
|
||||
performance.now(),
|
||||
);
|
||||
// We are missing information about what this was unblocked by but we can guess that it
|
||||
// was whatever await we ran last since this will continue in a microtask after that.
|
||||
// This is not perfect because there could potentially be other microtasks getting in
|
||||
// between.
|
||||
resolvedNode.previous = lastRanAwait;
|
||||
lastRanAwait = null;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
lastRanAwait = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
promiseResolve(asyncId: number): void {
|
||||
const node = pendingOperations.get(asyncId);
|
||||
if (node !== undefined) {
|
||||
let resolvedNode: AwaitNode | PromiseNode;
|
||||
switch (node.tag) {
|
||||
case UNRESOLVED_AWAIT_NODE: {
|
||||
const awaitNode: AwaitNode = (node: any);
|
||||
awaitNode.tag = AWAIT_NODE;
|
||||
resolvedNode = awaitNode;
|
||||
break;
|
||||
}
|
||||
case UNRESOLVED_AWAIT_NODE:
|
||||
case UNRESOLVED_PROMISE_NODE: {
|
||||
const promiseNode: PromiseNode = (node: any);
|
||||
promiseNode.tag = PROMISE_NODE;
|
||||
resolvedNode = promiseNode;
|
||||
resolvedNode = resolvePromiseOrAwaitNode(node, performance.now());
|
||||
break;
|
||||
}
|
||||
case IO_NODE:
|
||||
case AWAIT_NODE:
|
||||
case PROMISE_NODE: {
|
||||
// We already resolved this in the before hook.
|
||||
resolvedNode = node;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// eslint-disable-next-line react-internal/prod-error-codes
|
||||
throw new Error(
|
||||
'A Promise should never be an IO_NODE. This is a bug in React.',
|
||||
);
|
||||
default:
|
||||
// eslint-disable-next-line react-internal/prod-error-codes
|
||||
throw new Error(
|
||||
'A Promise should never be resolved twice. This is a bug in React or Node.js.',
|
||||
);
|
||||
}
|
||||
// Log the end time when we resolved the promise.
|
||||
resolvedNode.end = performance.now();
|
||||
// The Promise can be garbage collected after this so we should extract debugInfo first.
|
||||
const promise = node.debugInfo.deref();
|
||||
resolvedNode.debugInfo =
|
||||
promise === undefined || promise._debugInfo === undefined
|
||||
? null
|
||||
: promise._debugInfo;
|
||||
const currentAsyncId = executionAsyncId();
|
||||
if (asyncId !== currentAsyncId) {
|
||||
// If the promise was not resolved by itself, then that means that
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
"awaited": {
|
||||
"end": 0,
|
||||
"env": "Server",
|
||||
"name": "getData",
|
||||
"name": "delay",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
|
|
@ -438,19 +438,19 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
},
|
||||
"stack": [
|
||||
[
|
||||
"getData",
|
||||
"delay",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
156,
|
||||
27,
|
||||
156,
|
||||
5,
|
||||
133,
|
||||
12,
|
||||
132,
|
||||
3,
|
||||
],
|
||||
[
|
||||
"Component",
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
165,
|
||||
22,
|
||||
163,
|
||||
158,
|
||||
21,
|
||||
156,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -547,9 +547,6 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
"time": 0,
|
||||
},
|
||||
{
|
||||
"awaited": {
|
||||
"end": 0,
|
||||
|
|
@ -637,9 +634,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
608,
|
||||
605,
|
||||
109,
|
||||
599,
|
||||
596,
|
||||
94,
|
||||
],
|
||||
],
|
||||
|
|
@ -708,9 +705,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
679,
|
||||
676,
|
||||
109,
|
||||
655,
|
||||
652,
|
||||
50,
|
||||
],
|
||||
],
|
||||
|
|
@ -790,9 +787,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
761,
|
||||
758,
|
||||
109,
|
||||
744,
|
||||
741,
|
||||
63,
|
||||
],
|
||||
],
|
||||
|
|
@ -817,9 +814,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
757,
|
||||
754,
|
||||
24,
|
||||
756,
|
||||
753,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -849,9 +846,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
757,
|
||||
754,
|
||||
24,
|
||||
756,
|
||||
753,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -868,17 +865,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
746,
|
||||
743,
|
||||
13,
|
||||
745,
|
||||
742,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"ThirdPartyComponent",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
752,
|
||||
749,
|
||||
24,
|
||||
751,
|
||||
748,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -902,9 +899,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
757,
|
||||
754,
|
||||
24,
|
||||
756,
|
||||
753,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -913,17 +910,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
746,
|
||||
743,
|
||||
13,
|
||||
745,
|
||||
742,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"ThirdPartyComponent",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
752,
|
||||
749,
|
||||
24,
|
||||
751,
|
||||
748,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -956,9 +953,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
757,
|
||||
754,
|
||||
24,
|
||||
756,
|
||||
753,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -975,17 +972,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
747,
|
||||
744,
|
||||
13,
|
||||
745,
|
||||
742,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"ThirdPartyComponent",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
752,
|
||||
749,
|
||||
18,
|
||||
751,
|
||||
748,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -1009,9 +1006,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
757,
|
||||
754,
|
||||
24,
|
||||
756,
|
||||
753,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -1020,17 +1017,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
747,
|
||||
744,
|
||||
13,
|
||||
745,
|
||||
742,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"ThirdPartyComponent",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
752,
|
||||
749,
|
||||
18,
|
||||
751,
|
||||
748,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -1108,9 +1105,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1074,
|
||||
1071,
|
||||
40,
|
||||
1052,
|
||||
1049,
|
||||
62,
|
||||
],
|
||||
],
|
||||
|
|
@ -1132,9 +1129,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1074,
|
||||
1071,
|
||||
40,
|
||||
1052,
|
||||
1049,
|
||||
62,
|
||||
],
|
||||
],
|
||||
|
|
@ -1151,17 +1148,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1059,
|
||||
1056,
|
||||
13,
|
||||
1055,
|
||||
1052,
|
||||
25,
|
||||
],
|
||||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1069,
|
||||
1066,
|
||||
13,
|
||||
1068,
|
||||
1065,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -1177,9 +1174,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1074,
|
||||
1071,
|
||||
40,
|
||||
1052,
|
||||
1049,
|
||||
62,
|
||||
],
|
||||
],
|
||||
|
|
@ -1188,17 +1185,17 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1059,
|
||||
1056,
|
||||
13,
|
||||
1055,
|
||||
1052,
|
||||
25,
|
||||
],
|
||||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1069,
|
||||
1066,
|
||||
13,
|
||||
1068,
|
||||
1065,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -1218,9 +1215,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1070,
|
||||
1067,
|
||||
60,
|
||||
1068,
|
||||
1065,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -1232,7 +1229,7 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
"awaited": {
|
||||
"end": 0,
|
||||
"env": "Server",
|
||||
"name": "getData",
|
||||
"name": "delay",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
|
|
@ -1242,28 +1239,36 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1074,
|
||||
1071,
|
||||
40,
|
||||
1052,
|
||||
1049,
|
||||
62,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"delay",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
133,
|
||||
12,
|
||||
132,
|
||||
3,
|
||||
],
|
||||
[
|
||||
"getData",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1055,
|
||||
47,
|
||||
1055,
|
||||
1056,
|
||||
13,
|
||||
1052,
|
||||
25,
|
||||
],
|
||||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1069,
|
||||
1066,
|
||||
13,
|
||||
1068,
|
||||
1065,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -1279,9 +1284,9 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Component",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1070,
|
||||
1067,
|
||||
60,
|
||||
1068,
|
||||
1065,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
@ -1290,9 +1295,346 @@ describe('ReactFlightAsyncDebugInfo', () => {
|
|||
[
|
||||
"Child",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1064,
|
||||
1061,
|
||||
28,
|
||||
1063,
|
||||
1060,
|
||||
5,
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
"time": 0,
|
||||
},
|
||||
{
|
||||
"time": 0,
|
||||
},
|
||||
]
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
it('can track implicit returned promises that are blocked by previous data', async () => {
|
||||
async function delayTwice() {
|
||||
await delay('', 20);
|
||||
await delay('', 10);
|
||||
}
|
||||
|
||||
async function delayTrice() {
|
||||
const p = delayTwice();
|
||||
await delay('', 40);
|
||||
return p;
|
||||
}
|
||||
|
||||
async function Bar({children}) {
|
||||
await delayTrice();
|
||||
return 'hi';
|
||||
}
|
||||
|
||||
const stream = ReactServerDOMServer.renderToPipeableStream(
|
||||
<Bar />,
|
||||
{},
|
||||
{
|
||||
filterStackFrame,
|
||||
},
|
||||
);
|
||||
|
||||
const readable = new Stream.PassThrough(streamOptions);
|
||||
|
||||
const result = ReactServerDOMClient.createFromNodeStream(readable, {
|
||||
moduleMap: {},
|
||||
moduleLoading: {},
|
||||
});
|
||||
stream.pipe(readable);
|
||||
|
||||
expect(await result).toBe('hi');
|
||||
if (
|
||||
__DEV__ &&
|
||||
gate(
|
||||
flags =>
|
||||
flags.enableComponentPerformanceTrack && flags.enableAsyncDebugInfo,
|
||||
)
|
||||
) {
|
||||
expect(getDebugInfo(result)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"time": 0,
|
||||
},
|
||||
{
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Bar",
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1334,
|
||||
40,
|
||||
1316,
|
||||
80,
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
"time": 0,
|
||||
},
|
||||
{
|
||||
"awaited": {
|
||||
"end": 0,
|
||||
"env": "Server",
|
||||
"name": "delay",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Bar",
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1334,
|
||||
40,
|
||||
1316,
|
||||
80,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"delay",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
133,
|
||||
12,
|
||||
132,
|
||||
3,
|
||||
],
|
||||
[
|
||||
"delayTrice",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1324,
|
||||
13,
|
||||
1322,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"Bar",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1329,
|
||||
13,
|
||||
1328,
|
||||
5,
|
||||
],
|
||||
],
|
||||
"start": 0,
|
||||
},
|
||||
"env": "Server",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Bar",
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1334,
|
||||
40,
|
||||
1316,
|
||||
80,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"delayTrice",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1324,
|
||||
13,
|
||||
1322,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"Bar",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1329,
|
||||
13,
|
||||
1328,
|
||||
5,
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
"time": 0,
|
||||
},
|
||||
{
|
||||
"awaited": {
|
||||
"end": 0,
|
||||
"env": "Server",
|
||||
"name": "delay",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Bar",
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1334,
|
||||
40,
|
||||
1316,
|
||||
80,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"delay",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
133,
|
||||
12,
|
||||
132,
|
||||
3,
|
||||
],
|
||||
[
|
||||
"delayTwice",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1318,
|
||||
13,
|
||||
1317,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"delayTrice",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1323,
|
||||
15,
|
||||
1322,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"Bar",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1329,
|
||||
13,
|
||||
1328,
|
||||
5,
|
||||
],
|
||||
],
|
||||
"start": 0,
|
||||
},
|
||||
"env": "Server",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Bar",
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1334,
|
||||
40,
|
||||
1316,
|
||||
80,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"delayTwice",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1318,
|
||||
13,
|
||||
1317,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"delayTrice",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1323,
|
||||
15,
|
||||
1322,
|
||||
5,
|
||||
],
|
||||
[
|
||||
"Bar",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1329,
|
||||
13,
|
||||
1328,
|
||||
5,
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
"time": 0,
|
||||
},
|
||||
{
|
||||
"awaited": {
|
||||
"end": 0,
|
||||
"env": "Server",
|
||||
"name": "delay",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Bar",
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1334,
|
||||
40,
|
||||
1316,
|
||||
80,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"delay",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
133,
|
||||
12,
|
||||
132,
|
||||
3,
|
||||
],
|
||||
[
|
||||
"delayTwice",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1319,
|
||||
13,
|
||||
1317,
|
||||
5,
|
||||
],
|
||||
],
|
||||
"start": 0,
|
||||
},
|
||||
"env": "Server",
|
||||
"owner": {
|
||||
"env": "Server",
|
||||
"key": null,
|
||||
"name": "Bar",
|
||||
"props": {},
|
||||
"stack": [
|
||||
[
|
||||
"Object.<anonymous>",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1334,
|
||||
40,
|
||||
1316,
|
||||
80,
|
||||
],
|
||||
],
|
||||
},
|
||||
"stack": [
|
||||
[
|
||||
"delayTwice",
|
||||
"/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js",
|
||||
1319,
|
||||
13,
|
||||
1317,
|
||||
5,
|
||||
],
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user