-
Notifications
You must be signed in to change notification settings - Fork 161
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
Make read requests abortable #1103
Comments
The desired behavior seems analogous to the IIRC calling If we added an |
Yes, that seems most appropriate. We do the same for While we're on the topic: I think
Yup, that makes sense. I think it's also important to guarantee that if we do reject the read request with an
I think just dequeuing the read request is fine. A while back, I suggested we could pass an Could we also make this work for BYOB readers? We'd probably want something like
|
For BYOB readers I think we'll need to pass an As I understand the As for resolving reads with partial data, it might not be an issue in practice. For the underlying source implementations I maintain (in the Web Serial API and its polyfill based on WebUSB) supporting BYOB readers would never create a situation in which the buffer is partially filled as it will be returned immediately to the caller whenever any data is available. |
Generally sounds good. This is safe in terms of the locking semantics because we cannot release a lock while there is a pending read request. Maybe that is worth noting. Is there a practical use-case for aborting a read request which has subsequent read requests? |
I don't have one. The use case for this coming from Web Serial API developers is being able to unlock the stream without closing it. |
Then how about adding a method |
I haven't heard a sufficiently compelling use case for cancelling a single Strictly speaking, if a call to The fact that this makes byte streams into a special case worries me greatly. |
WICG/serial#122 is another case where this seems to be useful, although again it's possible there are alternate strategies... I'm inclined to think this is a good idea though. I guess the remaining work is:
|
Coming from the Node.js perspective, it would be better for the const controller = new AbortController();
// Assume "rs" contains chunks A and B.
const reader = rs.getReader({ signal: controller.signal });
const promise1 = reader.read();
const promise2 = reader.read();
const promise3 = reader.read();
controller.abort(); There are a couple reasons for this...
When the abort() is called, I would expect any already fulfilled reads to just ignore it but all subsequent still pending read promises to be rejected. So, for the sake of the example, let's assume that promise1 ends up resolving synchronously (for whatever reason) but promise2 and promise3 are still pending. Both promise2 and promise3 would reject with I really cannot think of a case where I would want to abort a single read without aborting the entire subsequent sequence of reads that also may be pending. |
Thanks @jasnell, that's very helpful. It sounds similar to the discussion a few comments up in #1103 (comment). The case in WICG/serial#122 seems like it might desire canceling the read without canceling subsequent reads, but upon reflection, I think you could write it in a different way which avoids that, hmm... |
It seems this might be the low-level primitive whatwg/fetch#180 wants. |
From #1168, it turns out
For the use case of
All The BYOB request should remain valid, so the underlying byte source can continue writing into it. However, it's no longer associated with a pending read-into request, which makes things trickier. Right now, the specification assumes that the first pull-into descriptor corresponds to the first read-into request, but this change would break this assumption. Instead, we want committing this "detached" BYOB request to behave more like calling ...Sounds like a lot of spec refactoring to be done. 😛 |
FYI, there's some user-level security implications for #1143 that this can help address. While it wouldn't be as important on the client side, Deno servers would greatly benefit from it for securely reading bodies. |
Currently, reader.releaseLock() throws an error if there are still pending read requests. However, in #1103 we realized that it would be more useful if we allowed releasing a reader while there are still pending reads, which would reject those reads instead. The user can then acquire a new reader to receive those chunks. For readable byte streams, we also discard pull-into descriptors corresponding to (now rejected) read-into requests. However, we must retain the first pull-into descriptor, since the underlying byte source may still be using it through controller.byobRequest. Instead, we mark this descriptor as "detached", such that when we invalidate this BYOB request (as a result of controller.enqueue() or byobRequest.respond()), the bytes from this descriptor are pushed into the stream's queue and used to fulfill read requests from a future reader. This also allows expressing pipeTo()'s behavior more accurately. Currently, it "cheats" by calling ReadableStreamReaderGenericRelease directly without checking if [[readRequests]] is empty. With the proposed changes, we can safely release the reader when the pipe finishes (even if there's a pending read), and be sure that any unread chunks can be read by a future reader or pipe.
Am I correct that with the change above we can consider this issue closed because |
Correct, #1168 fixes this issue. Closing. Instead of adding a new API, we changed the behavior of the existing const reader = rs.getReader();
const promise1 = reader.read();
const promise2 = reader.read();
const promise3 = reader.read();
reader.releaseLock();
// This is now always allowed, even if one or more read() promises are still pending.
// For example, if rs only contains 2 chunks, then promise1 and promise2 are resolved,
// but promise3 will reject with a TypeError. If you want to use an const controller = new AbortController();
const reader = rs.getReader();
controller.signal.addEventListener("abort", () => {
reader.releaseLock();
});
const promise1 = reader.read();
controller.abort();
This spec change is hot off the press, so no browser has implemented it yet. We've filed issues with all major browsers, you can have a look at the "implementation bugs" listed in #1168 to track their progress. |
Noted by @reillyeon in WICG/serial#112 (comment) .
For some cases it would be nice to dequeue (and possibly abort?) a given read request, without canceling the stream entirely. That is,
The intent here is that
promise1
gets fulfilled with chunk A, andpromise3
gets fulfilled with chunk B.Some questions:
promise2
? Probably reject it with an"AbortError"
DOMException
(like in otherAbortSignal
scenarios).AbortSignal
scenarios).pull()
? I don't think this helps much, but it's worth checking...This is looking relatively straightforward to me to spec, and the use case @reillyeon presents is a good one. Should we just go for it?
The text was updated successfully, but these errors were encountered: