stream: fix addAbortSignal() for web streams#62450
stream: fix addAbortSignal() for web streams#62450ikeyan wants to merge 2 commits intonodejs:mainfrom
Conversation
|
Review requested:
|
| stream[kControllerAbortFunction] = (reason) => { | ||
| setPromiseHandled(writableStreamAbort(stream, reason)); | ||
| }; |
There was a problem hiding this comment.
Slightly off-topic: should we make this a class method instead? For WritableStream, we could do:
class WritableStream {
[kControllerAbortFunction](reason) {
setPromiseHandled(writableStreamAbort(this, reason));
}
}For ReadableStream, the method could check the type of this[kState].controller and then call either readableStreamDefaultControllerError or readableByteStreamControllerError.
| stream[kState].controller = controller; | ||
| stream[kControllerErrorFunction] = FunctionPrototypeBind(controller.error, controller); | ||
| stream[kControllerAbortFunction] = | ||
| (error) => readableStreamDefaultControllerError(controller, error); |
There was a problem hiding this comment.
Out of curiosity: why are we erroring the stream instead of canceling it?
- A consumer can cancel the stream with
readableStreamCancel, and the stream will call thesource.cancelcallback so it can clean up any resources. - With
controller.error, there is no so such callback, since the Streams standard assumes that the error was signaled by the source. If we allow a consumer to inject an error into the stream, this may leak resources in the source.
There was a problem hiding this comment.
@MattiasBuelens
Should I include that fix here as well?
There was a problem hiding this comment.
Hmm, that's probably not a good idea. The addAbortSignal documentation currently states that it calls controller.error, so changing that will be a breaking change... 🤔
Let's stick with the current behavior for now. I'll put up a separate draft PR to discuss if we want to change it or not.
Actually, this PR is already introducing a breaking change for WritableStream... so maybe we should just fix both. 😅
MattiasBuelens
left a comment
There was a problem hiding this comment.
I believe this is a breaking change for WritableStreams.
I think this PR is a good change: using controller.error from a consumer API is fundamentally wrong, and we got this wrong from the start in #46273. But that does mean this PR is a semver-major change.
While we're at it, I think we should fix the behavior for ReadableStreams too to call readableStreamCancel instead.
@nodejs/streams Thoughts?
| stream[kState].controller = controller; | ||
| stream[kControllerErrorFunction] = FunctionPrototypeBind(controller.error, controller); | ||
| stream[kControllerAbortFunction] = (reason) => { | ||
| setPromiseHandled(writableStreamAbort(stream, reason)); |
There was a problem hiding this comment.
This is a breaking change. From doc/api/stream.md:
Calling
aborton theAbortControllercorresponding to the passed
AbortSignalwill behave the same way as calling.destroy(new AbortError())
on the stream, andcontroller.error(new AbortError())for webstreams.
We'll need to at least update the docs to document the new behavior.
This fixes
stream.addAbortSignal()for web streams.addAbortSignal()usedkControllerErrorFunction, which mixed stream erroringwith abort handling.
For readable byte streams,
ReadableByteStreamControllerleft the default no-ophook in place, so
addAbortSignal()could not abort the stream.For writable streams,
WritableStreamDefaultControllerwired the hook tocontroller.error(), so the stream became errored butcontroller.signal.abortedstayedfalse.This replaces
kControllerErrorFunctionwithkControllerAbortFunctionandwires each controller to the internal operation that matches its abort path:
ReadableStreamDefaultControllerusesreadableStreamDefaultControllerError()ReadableByteStreamControllerusesreadableByteStreamControllerError()WritableStreamDefaultControlleruseswritableStreamAbort()The updated test covers
addAbortSignal()on readable byte streams, abortsthat race with pending pulls, writable abort signal state, and readable and
writable controllers with overridden public methods.