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
Fix tee() incorrectly closing before enqueuing to the second branch #1172
Fix tee() incorrectly closing before enqueuing to the second branch #1172
Conversation
Given the complexity of the problem, the solution is quite readable! IMHO this is ready for spec text. |
I'm getting a ton of link errors when building the spec text locally:
The PR build ( |
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.
lgtm
Are there any other issues with the reentrancy here? Would it better to avoid the reentrancy by adding even more microtasks?
No, I don't think there are. Only
Nah, I prefer it this way. Adding more microtasks could make these sorts of bugs less obvious and even more difficult to debug. I also feel like the stream specification should try to do as much as possible within the same microtask, even if that means dealing with reentrancy. |
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.
LGTM, just don't forget to roll WPTs now that they're merged, and also file bugs on all three engines.
Interestingly, all engines already pass the new test because they haven't yet implemented #1045. They still use a promise for So does it still make sense to file bugs? 🤔 |
Fascinating! In that case no need to file bugs, yeah. The test will ensure that they don't make the same mistake in their implementations that we did with the spec. |
Tests are currently failing because the latest WPT already contains the new tests for #1171. This PR should go green once the other PR has landed. |
aee52eb
to
4f0f800
Compare
I found a case where
tee()
inadvertently drops a chunk for the second branch, even though neither of the branches have been canceled. This is obviously very bad, because both branches should receive all enqueued chunks.See web-platform-tests/wpt#31159 for the problematic test case. Here's how it breaks down for
ReadableStreamDefaultTee
:controller.enqueue('a')
. Since there is a pending read request fromtee()
on the original stream, we synchronously run itschunkSteps
. However, this first callsqueueMicrotask()
, so the remainder will happen later.controller.close()
. Since we've already processed all pending read requests, the stream immediately moves tostate = 'closed'
.chunkSteps
.reading = false
.ReadableStreamDefaultControllerEnqueue
for branch1. Since there is a pending read request for branch1, it synchronously fulfills the request. And since we created both branches with the default HWM = 1,ReadableStreamDefaultControllerShouldCallPull
is also true. So we call branch1'spullAlgorithm
.pullAlgorithm
. However, we've already setreading = false
, we do start a new read request on the original stream.closeSteps
.chunkSteps
. So we callReadableStreamDefaultControllerEnqueue
for branch2... except, it's already closed, so the chunk is ignored! 😱💥This PR fixes it by keeping
reading = true
until after we've enqueued/responded to both branches. Afterwards, we check if one of the two branches tried to callpull()
(through an extrareadAgain
flag), and then callpull()
ourselves. When teeing a readable byte stream, we also have to keep track which of the two branches calledpull()
, so we can use the appropriate BYOB request if available.To do:
(See WHATWG Working Mode: Changes for more details.)
Preview | Diff