Skip to content

Streams, Buffers, and Worker Threads #33240

@jasnell

Description

@jasnell

There is a bit of undefined grey area when passing a Buffer instance into a stream.Writable: who has ownership responsibility for the Buffer?

See this example as reference: https://github.com/jasnell/piscina/pull/34/files#diff-123d6328fe4f2579686ee49111e38427

In the example, I create a stream.Duplex instance that wraps a MessagePort. On _write, I post the given Buffer to the MessagePort and include it in the transfer list to avoid copying the data:

  _write (chunk, encoding, callback) {
    if (typeof chunk === 'string') {
      chunk = Buffer.from(chunk, encoding);
    }
    this.#port.postMessage(chunk, [chunk.buffer]);
    callback();
  }

This is where it gets fun. In my example, from within a worker thread, I open a pipeline that reads from a file, passes that into a gzip compressor, then out to my custom duplex. What happens is a fun little Abort...

james@ubuntu:~/nearform/piscina/examples/stream$ node index
node[30456]: ../src/node_zlib.cc:323:static void node::{anonymous}::CompressionStream<CompressionContext>::Write(const v8::FunctionCallbackInfo<v8::Value>&) [with bool async = true; CompressionContext = node::{anonymous}::ZlibContext]: Assertion `Buffer::IsWithinBounds(out_off, out_len, Buffer::Length(out_buf))' failed.
 1: 0xa295b0 node::Abort() [node]
 2: 0xa2962e  [node]
 3: 0xae6b1a  [node]
 4: 0xc0251b  [node]
 5: 0xc03ac6  [node]
 6: 0xc04146 v8::internal::Builtin_HandleApiCall(int, unsigned long*, v8::internal::Isolate*) [node]
2
 7: 0x13a5919  [node]
Aborted (core dumped)

The reason for the Abort is that my custom Duplex just transferred ownership of the Buffer away to the main thread while the gzip compressor was still using it.

Replace the gzip compressor with the brotli compressor, and things work! Change the postMessage() call to remove the transferList and things work!

Obviously the Abort itself is problematic and we need to figure out how to avoid that in the zlib, so that's issue #1.... issue #2 is that we really should try to specify some ownership expectations on Buffer instances passed into streams instances.

/cc @nodejs/streams @ronag @addaleax @nodejs/zlib

Metadata

Metadata

Assignees

No one assigned

    Labels

    streamIssues and PRs related to the stream subsystem.workerIssues and PRs related to Worker support.zlibIssues and PRs related to the zlib subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions