-
Notifications
You must be signed in to change notification settings - Fork 48.3k
[Flight] Don't hang forever when prerendering a rejected promise #32953
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Flight] Don't hang forever when prerendering a rejected promise #32953
Conversation
Comparing: 7213509...66ff9e7 Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: Expand to show
|
4c4e48e
to
7700b42
Compare
7700b42
to
c852c2f
Compare
c852c2f
to
aeeba89
Compare
Rendering a rejected promise as the root model is already supported in `renderToReadableStream`. In this case, a stream is returned early, and reading the stream will start flowing, so that enqueued chunks are flushed. When prerendering, flowing is only started when `onAllReady` is called. This is usually done in `performWork`. However, if the root model is a rejected promise, the `onReject` handler also needs to check if there are any abortable tasks left, and if there are none, it must call `onAllReady`. This ensures that the prerender doesn't hang, and a prelude stream is returned.
aeeba89
to
9e15a8c
Compare
I don't love the approach of just checking this everywhere deep inside various places. Because it adds checks in hot paths that get called over and over again. It should ideally only be checked inside a path where we've already checked something else to indicate that we're at the end. Or at least only if we're already decrementing or deleting something at the same time instead of passively all the time. However, there's also a timing issue that I've tried to avoid calling callbacks in things like flush (but ofc things like fatal errors still need that) since that can happen deep inside like a node streaming stack. Where as performWork is more top level of the stack and safer e.g. if it throws or reasons about state. |
I think the conclusion is just that I'd like to be able to see the mutation to the set that we read somewhat close to where we call the callback (but not moving the callback too early because then that can lead to inconsistencies when it observes a partial state or it throws). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we move the allready call back into performWork we need to be wary of a bug that was present in the original.
It's possible to be in a state where there are no abortableTasks but there is an abortListener. If the async streamable thing with the listener emits a value it can schedule a new task.
The thing the hasAbortableTasks
thing was trying to prevent was a ping-after-abort causing a second call to onAllReady
. But we can probably just read the request status after retrying tasks. If it's not OPENING or OPEN then we can I think just omit the onAllReady call
request.abortableTasks.delete(task); | ||
callOnAllReadyIfReady(request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could handle tasks in performWork and listeners in the callbacks. The only time we'd clear a task is if we're in a performWork loop so we can always be certain we'll handle those there.
…ebook#32953) DiffTrain build for [914319a](facebook@914319a)
Rendering a rejected promise as the root model is already supported in
renderToReadableStream
. In this case, a stream is returned early, and reading the stream will start flowing, so that enqueued chunks are flushed.When prerendering, flowing is only started when
onAllReady
is called. This was previously only done inperformWork
. However, if the root model is a rejected promise,erroredTask
also needs to check if there are any abortable tasks left, and if there are none, it must callonAllReady
. This ensures that the prerender doesn't hang, and a prelude stream is returned.In addition, this fixes a bug where we returned the prelude early when a readable stream or async iterable were still in progress. The fix for this issue is to ensure that
abortListeners
is also empty before callingonAllReady
.