Skip to content

Instantly share code, notes, and snippets.

@domenic
Created February 25, 2015 20:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save domenic/de5e63ae8b1b8b8c3f6c to your computer and use it in GitHub Desktop.
Save domenic/de5e63ae8b1b8b8c3f6c to your computer and use it in GitHub Desktop.
ReadableByteStream array buffers and minimal-copy

Async ReadInto Is a Problem

The overriding design goal of ReadableByteStream---likely above all others---is to enable zero-copy reading from underlying sources. (Well, OK, maybe we'll need one copy from kernel space to user space.)

As we've discovered, there are essentially two forms in which underlying source APIs are provided:

  • The read(2) form: based on read(2). Can be modeled as (buffer, offsetIntoBuffer, desiredBytes) -> bytesRead. This call is synchronous and so will take place in a threadpool (important!). Another thread can observe the buffer filling up with data until the fread call returns.

  • The epoll form: based on epoll(7)/.... TODO explain more and talk more about epoll later insteadof just read(2).

The initial vision for async readInto was:

const bytesRead = await rbs.readInto(arrayBuffer, offsetIntoArrayBuffer, desiredBytes);

This API could be fairly-straightforwardly implemented in a minimal-copy way with a threadpool and read(2): basically pass those exact arguments to the underlying source, and post a message back to the main thread to fulfill the promise when done.

However, this approach involves sharing arrayBuffer's memory between JS and the thread in the threadpool. That is, JS would be able to observe the contents of arrayBuffer changing. This is a no-go for browsers.

A proposed solution is to use ArrayBuffer detaching (formerly "neutering"), probably via the proposed-for-ES2016 ArrayBuffer.transfer method. Using ArrayBuffer.transfer, the most naive fix for the API is:

const { result, bytesRead } = await rbs.readBytes(suppliedArrayBuffer, offset, desiredBytes);

In this example, suppliedArrayBuffer will be immediately detached and transferred to a new buffer, so that changes to suppliedArrayBuffer are not observable. Then, the new buffer will be modified by fread. Finally, the promise will fulfill with that new buffer plus the number of bytes read into it, and here is stored in newArrayBuffer. In this way author code never sees the new buffer in its transitional state.

This is pretty gross though...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment