Repository navigation
Conversation
kdelay
left a comment
There was a problem hiding this comment.
I ran a variant of the new test where the RSC stream is still open when flush() starts: timer fires, one RSC chunk written, then writer.close(), reader.cancel(), one more RSC chunk.
No unhandled rejection, but the RSC stream is never cancelled. After the enqueue throws, the finally in writeRSCStream releases the reader lock, so rscReader.cancel() in stop() rejects and the .catch swallows it.
In stop(), using rscReader.cancel() only when rscStream.locked, else rscStream.cancel(), cancelled it in my run. The rsc tests still pass.
|
Thank you very much for testing that variant. Applied your suggestion in 82fb88e: I also looked at the other paths that release the lock:
The RSC tests pass (13/13), and Prettier, ESLint and |
The comment on `tryEnqueue` said that a closed stream "can only be detected here". That claimed too much: the close is detected only at the next enqueue, and the RSC stream stays open until then. The comment now states that limit. The other comments keep the same reasons in shorter sentences.
|
Two more changes after a closer look at the cancel paths:
One limit remains, and the comment on RSC tests: 14/14. Prettier, ESLint and |
kdelay
left a comment
There was a problem hiding this comment.
Re-checked at c801cd3; the locked branch covers my case.
- With
server.tsfrommain, the 3 new tests fail; with the PR all pass. pipeThroughvariant, RSC stream open, cancel duringflush(), then one RSC chunk (text, and invalid UTF-8): onmainreader.cancel()rejects withUnable to enqueueand RSC is never cancelled; with the PR it settles and RSC is cancelled.
Note: removing any one of the four new if (cancelled) return checks changes no test result.
|
Thank you for re-checking, and for the You are right about the four checks: none of them changes an outcome, because the code around them already covers each case.
So 905bf97 removes them, and the
|
Fixes #15611
Follow-up to #15286. When the readable side is cancelled while
flush()is in progress, the Streams spec does not call the transformer'scancel(), socancelledstaysfalse. If the flush timer is still pending, it then enqueues into a closed stream, and the unhandled rejection stops the Node process. #15611 has the details and a reproduction app.Changes in
injectRSCPayload:cancel()moves to astop()helper.cancel()calls it, as before.tryEnqueue(). If the enqueue throws, it callsstop(). A failed enqueue is the one signal available in this window.flush()return early after a stop.stop()resolvesflightDataPromise, soflush()cannot wait forever.writeRSCStreamfails after a stop, thecatchreturns instead of callingcontroller.error().Testing:
html-stream-test.ts: it reads, writes and closes, lets the microtasks run, then cancels the reader. Onmainit fails withUnable to enqueue; with this change it passes.pnpm test packages/react-router/__tests__/rsc: 12/12 pass. Prettier, ESLint andtscare clean.react-router, the server survived 500 aborted requests in 5/5 trials (Node 24.15 and 26.5). With 8.4.0 it stopped in 10/10 trials, after 1 to 184 aborted requests.I added a
patchchange file. Thanks for reviewing!