-
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
ReadableStreamBYOBReader.prototype.readFully(view) #1143
Comments
Makes sense to me; thanks for thinking through the design.
IMO we should only do that if we do #1072. Even then, I'm not 100% sure, because it only makes sense on byte streams, and so quarantining it into the reader seems a bit nicer from a theoretical purity perspective. (This sort of type mismatch affects a lot of the suggestions in #1019...)
Seems like it should; any reason to deviate? |
Sure! 🙂
It might be a bit weird to have a Anyway, I'm gonna try some experiments on the reference implementation, see if I can get this thing to work. 👨🔬 |
#1175 might be an API that's more easily optimized by implementations while offering the same semantics if needed. The reason is that you may want to allocate 1 large buffer & read as much from the kernel in one shot as you can with subsequent reads to fill up to the minimum amount. In other words, allocate 256kb since that's the typical socket buffer size, read at least 64kb & gracefully handle any extra data between 0 & 192kb without issuing another read. Any "unaligned" overflow can then be handled by moving the overflow back to the beginning of the large buffer & resuming reading into the subview (& accounting for that in your next If you do the |
@MattiasBuelens How hard would it be to just add an optional second argument to |
It shouldn't be that difficult. Currently #1145 works by adding a fill boolean flag to each pull-into descriptor to decide if it should fill up to at least element size (for I do like that we can express everything in terms of
Not 100% sure about the API though, but we can do some bikeshedding. 😁
|
const { done, value } = reader.read(new Uint16Array(8), { atLeast: 2 });
// value.length will be between 2 and 8
// value.byteLength will be between 4 and 16 bytes Anyway, it turns out that we can fairly easily generalize the So, if we're happy with the approach and decide on an API, I can merge that branch into #1145 and we continue from there (update spec text, add tests,...). 🙂 |
Yeah, just realized this today. Needs to be defined in terms of number of elements, not number of bytes (otherwise passing in an odd number would be nonsensical for a multi-byte array). |
I'm happy to add support for this in the Node.js implementation also. So +1 on it. |
Is this going to be accompanied with a timeout parameter or abort signal? Otherwise, I'm concerned about the possibility of a malicious actor spamming an endpoint with large but extremely low data rate requests (think: a single minimum-size TCP packet once per second) and forcing it to run out of memory. Of course, intermediate proxies can largely mitigate this, but not every client or server can trust the source of their data, and this would be valuable for knocking out that vulnerability a bit easier. |
There's a separate discussion ongoing to make reads abortable in #1103. Combined with whatwg/dom#951, you could implement timeouts on reads like this: const reader = readable.getReader({ mode: 'byob', signal: AbortSignal.timeout(10_000) });
const { done, value } = await reader.read(new Uint8Array(1024), { atLeast: 32 });
(Note that these APIs are not yet finalized, I'm basing this example on the current proposals.) |
We ended up with a slightly different approach for #1103. Instead of adding a new API that accepts an const reader = readable.getReader({ mode: 'byob' });
const signal = AbortSignal.timeout(10_000);
signal.addEventListener('abort', () => reader.releaseLock());
// Or alternatively:
setTimeout(() => reader.releaseLock(), 10_000);
// Use reader as normal
const { done, value } = await reader.read(new Uint8Array(1024), { min: 32 }); |
As suggested in #1019 (comment), developers may want to read into a BYOB buffer until it is completely filled (instead of only partially filling it with however many bytes the underlying source provides). We already have an example for this with a
readInto()
helper function, but it may be worth integrating this into the standard itself for better ergonomics and/or optimization opportunities.Concretely, I suggest we extend
ReadableStreamBYOBReader
:This method performs a BYOB read into
view
:n
bytes wheren < view.byteLength
, then the stream will immediately start pulling again with the "remainder" ofview
(i.e.view.subarray(n)
, assumingview
is aUint8Array
).n
bytes wheren === view.byteLength
, then the method fulfills with{ done: false, value }
wherevalue
is the completely filled view.{ done: true, value }
wherevalue
is the (partially) filled view.{ done: true, value: undefined }
, like with regular BYOB reads.Some remarks:
Promise<ArrayBufferView>
, because then you wouldn't know if the stream is closed. Also, this wouldn't work if the stream is cancelled... unless we're okay with returningPromise<undefined>
in that case (which I personally don't find very pretty).readFully()
pull-into descriptors. Or another (simpler?) solution might be to always put the pull-into descriptor for the remainder at the front of the[[pendingPullIntos]]
queue (rather than at the back).ReadableStream.readFully(view)
? Would it have the same signature, i.e. returningPromise<ReadableStreamBYOBReadResult>
?The text was updated successfully, but these errors were encountered: