Summary#
When piping firstTs.readable.pipeTo(secondTs.writable) between two
TransformStream instances, the first chunk flows through correctly but
subsequent read() calls on secondTs.readable never resolve. The pipe's
returned promise also never settles.
Discovered while working on Phase 20's built-in transform streams (PR for "Implement built-in transform streams").
Minimal repro#
var first = new TransformStream({}); // identity passthrough
var second = new TransformStream({}); // identity passthrough
var w = first.writable.getWriter();
w.write(\"x\");
w.close();
first.readable.pipeTo(second.writable).then(
function() { console.log(\"piped\"); },
function(e) { console.log(\"err:\" + e); }
);
var r = second.readable.getReader();
r.read().then(function(res1) {
console.log(\"r1:\" + (res1.done ? \"done\" : res1.value));
r.read().then(function(res2) {
console.log(\"r2:\" + (res2.done ? \"done\" : res2.value));
});
});
Expected output: `r1:x`, then `r2:done`, then `piped`. Actual output: only `r1:x` is logged; the second `r.read()` never resolves.
Workarounds in use#
The Phase 20 transform-streams tests sidestep the bug by:
- Feeding pre-compressed bytes directly to `DecompressionStream.writable` rather than piping `CompressionStream.readable` → `DecompressionStream.writable`.
- Storing `Uint8Array(...)` results in a local variable before passing to `writer.write()` (related: nested method-call arg quirk).
Possible causes (untested)#
- A microtask scheduled when the second `writer.ready` resolves never runs, even though the .then was registered on the same promise that is later resolved by `refreshReady`. Suggests the promise's reaction list (or the promise itself) is being collected or replaced between the .then registration and the resolve call.
- The single-chunk variant happens to work for `deflate-raw` because the source emits exactly one chunk (so only one pipe iteration is needed).
Acceptance#
A regression test in `crates/js/src/streams.rs` that runs the repro above and asserts both `r2:done` and `piped` are logged.