[Flight] Include I/O not awaited in user space (#33715)

If I/O is not awaited in user space in a "previous" path we used to just
drop it on the floor. There's a few strategies we could apply here. My
first commit just emits it without an await but that would mean we don't
have an await stack when there's no I/O in a follow up.

I went with a strategy where the "previous" I/O is used only if the
"next" didn't have I/O. This may still drop I/O on the floor if there's
two back to back within internals for example. It would only log the
first one even though the outer await may have started earlier.

It may also log deeper in the "next" path if that had user space stacks
and then the outer await will appear as if it awaited after.

So it's not perfect.
This commit is contained in:
Sebastian Markbåge 2025-07-07 10:33:27 -04:00 committed by GitHub
parent bb402876f7
commit 0378b46e7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 568 additions and 426 deletions

View File

@ -2061,20 +2061,17 @@ function visitAsyncNode(
return null;
}
visited.add(node);
let previousIONode = null;
// First visit anything that blocked this sequence to start in the first place.
if (node.previous !== null && node.end > request.timeOrigin) {
// We ignore the returned io nodes 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.
const ioNode = visitAsyncNode(
previousIONode = visitAsyncNode(
request,
task,
node.previous,
visited,
cutOff,
);
if (ioNode === undefined) {
if (previousIONode === undefined) {
// Undefined is used as a signal that we found a suitable aborted node and we don't have to find
// further aborted nodes.
return undefined;
@ -2085,17 +2082,17 @@ function visitAsyncNode(
return node;
}
case UNRESOLVED_PROMISE_NODE: {
return null;
return previousIONode;
}
case PROMISE_NODE: {
if (node.end <= 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.
// The technique for debugging the effects of uncached data on the render is to simply uncache it.
return null;
return previousIONode;
}
const awaited = node.awaited;
let match = null;
let match: void | null | PromiseNode | IONode = previousIONode;
if (awaited !== null) {
const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
if (ioNode === undefined) {
@ -2143,11 +2140,11 @@ function visitAsyncNode(
return match;
}
case UNRESOLVED_AWAIT_NODE: {
return null;
return previousIONode;
}
case AWAIT_NODE: {
const awaited = node.awaited;
let match = null;
let match: void | null | PromiseNode | IONode = previousIONode;
if (awaited !== null) {
const ioNode = visitAsyncNode(request, task, awaited, visited, cutOff);
if (ioNode === undefined) {

File diff suppressed because it is too large Load Diff