Skip to content

stream/web: TransformStream race can throw internal "transformAlgorithm is not a function" #62036

@y-hsgw

Description

@y-hsgw

Version

  • v22.9.0
  • v24.10.0

Platform

Darwin MacBook-Air.local 25.3.0 Darwin Kernel Version 25.3.0: Wed Jan 28 20:49:24 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T8132 arm64

Subsystem

stream/web (TransformStream)

What steps will reproduce the bug?

Run:

import { TransformStream } from 'node:stream/web';
import { setTimeout } from 'node:timers/promises';

const stream = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk);
  },
});

// Important for reproducing this race consistently in my environment.
await setTimeout(500);

const reader = stream.readable.getReader();
const writer = stream.writable.getWriter();

// Release backpressure.
const pendingRead = reader.read();

// Simulate client disconnect / shutdown.
const pendingCancel = reader.cancel(new Error('client disconnected'));

// Late write racing with cancel.
const pendingLateWrite = writer.write('late-write');

const results = await Promise.allSettled([
  pendingRead,
  pendingCancel,
  pendingLateWrite,
]);

console.log(results);

How often does it reproduce? Is there a required condition?

Intermittent race. In my environment, adding await setTimeout(...) before acquiring reader/writer is required for reliable reproduction.

What is the expected behavior? Why is that the expected behavior?

No internal implementation TypeError should leak.

My understanding is that, when shutdown starts concurrently (cancel/abort/close paths), pending or in-flight write() operations are expected to settle coherently with stream shutdown semantics (resolve/reject according to stream state), rather than failing because an internal algorithm slot became non-callable.

What do you see instead?

pendingLateWrite can reject with an internal error:

TypeError: controller[kState].transformAlgorithm is not a function
    at transformStreamDefaultControllerPerformTransform (node:internal/webstreams/transformstream:527:37)
    at transformStreamDefaultSinkWriteAlgorithm (node:internal/webstreams/transformstream:573:10)
    ...

Additional information

This looks like a race where:

  1. transformStreamDefaultSinkWriteAlgorithm has accepted/scheduled a write,
  2. shutdown starts (readable.cancel() / writable.abort() / close path),
  3. transformStreamDefaultControllerClearAlgorithms clears transformAlgorithm,
  4. pending write continues and reaches transformStreamDefaultControllerPerformTransform.

Related discussions:

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions