Re: [whatwg/streams] Returning buffers to the underlying source (Issue #1217)

> An alternative or perhaps a companion to BYOB readers is the concept of a "two-phase" read where the `UnderlyingSource` enqueues a chunk but can get a later signal that the buffer holding that chunk is no longer in use and so can be reused for another chunk.

It sounds like you want an API where the *underlying source* is in control of how its buffer is re-used for repeated `enqueue()` calls, rather than relying on the reader to re-use the same buffer for repeated `read(view)` calls?

Then you'd need a quite different reader API too. Right now, `read(view)` returns a view, and the consumer can do whatever it wants with that view for however long it wants. Instead, we'd need an API to *restrict* how long the consumer can use the view. Some ideas:
* Add a `reader.reclaim(buffer)` method. This might be quite error-prone though, or even confusing if you're doing multiple parallel reads across multiple buffers.
* Add an async callback argment to `reader.read()`, which is called with the `{ done, value }` tuple. That value will only remain valid for the duration of the async callback, so the stream can automatically reclaim its buffer when the callback completes. (This is inspired by [Web Locks `request()`](https://developer.mozilla.org/en-US/docs/Web/API/LockManager/request).)

Another pain point is that the consumer might want to *re-transfer* the buffer a couple of times before returning ownership of the buffer to the stream. So it's not good enough to look at the *original* value of `value.buffer`, we'd need a way to reclaim ownership of the *final* (possibly re-transferred) buffer.

> This could be useful, for example, when the data source being wrapped by the `UnderlyingSource` provides a BYOB-style API and the buffers passed as chunks to `enqueue()` could eventually be reused once they have been returned to the source.

Not entirely sure why you'd need this, since this already works today:
```javascript
let stream = new ReadableStream({ type: 'bytes', /* ... */ });
let reader = stream.getReader({ mode: 'byob' });

let wrappedStream = new ReadableStream({
  type: 'bytes',
  autoAllocateChunkSize: 1024, // ensure byobRequest exists, even when using a default reader
  async pull(controller) {
    const { done, value } = await reader.read(controller.byobRequest.view);
    if (done) {
      controller.close();
    }
    controller.byobRequest.respondWithNewView(value);
  },
  async cancel(reason) {
    await reader.cancel(reason);
  }
});
```
But yes, that requires the *consumer* to re-use the buffer. The underlying source has no say in this.

> It could also be useful for sources implemented by the platform which read data from shared memory

Correct me if I'm wrong, but I *think* this requires `SharedArrayBuffer`s? I don't think it's safe to construct a regular `ArrayBuffer` on top of a shared memory region.

That makes things quite difficult. We rely on transferring `ArrayBuffer`s to pass ownership around, but `SharedArrayBuffer`s are not transferable. We'd have to revisit a lot of our assumptions if we cannot transfer buffers within the stream. Also, I'm not sure how a reader API would *safely* use chunks backed by shared array buffers. 😕

-- 
Reply to this email directly or view it on GitHub:
https://github.com/whatwg/streams/issues/1217#issuecomment-1034859874

You are receiving this because you are subscribed to this thread.

Message ID: <whatwg/streams/issues/1217/1034859874@github.com>

Received on Thursday, 10 February 2022 12:23:12 UTC