Skip to content

TransformStream: unawaited writer.write() chunks dropped when writer.close() runs before the queued transform jobs #6607

Description

@proggeramlug

Summary

Unawaited writer.write() chunks on a TransformStream are silently dropped when writer.close() is called in the same synchronous run: the close path closes the readable side before the queued transform jobs deliver their chunks, so the chunks hit a Closed readable and are discarded. Node delivers them.

Reproduces identically on a pristine main build (found while validating #6602 — unrelated to id recycling).

Repro

async function main() {
  const ts = new TransformStream();
  const writer = ts.writable.getWriter();
  writer.write("hello");
  writer.write(" world");
  writer.close();
  let out = "";
  for await (const chunk of ts.readable) out += chunk;
  console.log("transform:", out);
}
main();
  • Node: transform: hello world
  • Perry: transform: (empty)

Mechanism

transform_write (streams/transform.rs) queues the transformer invocation as a two-hop microtask job (transform_write_jobtransform_write_job2) but bypasses the writable's write_queue. writer.close()js_writer_closetransform_close runs synchronously, and with no user flush() promise to defer on, finish_transform_close calls js_readable_stream_controller_close(readable) immediately — before the queued write jobs run. When transform_write_job2 finally executes, controller_enqueue sees state == Closed and drops the chunk (the write promise still resolves, so the loss is silent).

Per WHATWG, TransformStreamDefaultSinkCloseAlgorithm only runs after queued writes complete — the sink close should chain behind the pending write jobs (or the transform write path should flow through the writable queue that finish_writable_close already waits on).

Awaiting each writer.write() avoids the drop, which is why pipeTo-driven paths (Next.js SSR) don't hit this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions