googleapis / googleapis/google-cloud-node

`File#createReadStream()` can throw `ERR_STREAM_UNABLE_TO_PIPE` if the consumer closes before the response stream is piped

Open
#7,603 1 comment 3 reactions 0 assignees View on GitHub
api: storage
Dominant language
TypeScript
Stars
3.2k
Forks
712
Avg merge
2d 3h
Merged PRs (30d)
99

Description

# `File#createReadStream()` can throw `ERR_STREAM_UNABLE_TO_PIPE` if the consumer closes before the response stream is piped

## Bug shape

If a caller uses `file.createReadStream()` and the downstream consumer closes early, the library can throw:

```text
Error [ERR_STREAM_UNABLE_TO_PIPE]: Cannot pipe to a closed or destroyed stream
```

The stack points to the `pipeline(...)` call inside the `createReadStream()` response path.

## Stack trace

```text
node:internal/streams/pipeline:264
throw new ERR_STREAM_UNABLE_TO_PIPE();
^

Error [ERR_STREAM_UNABLE_TO_PIPE]: Cannot pipe to a closed or destroyed stream
at pipelineImpl (node:internal/streams/pipeline:264:15)
at pipeline (node:internal/streams/pipeline:183:10)
at onResponse (file:///prod/backend/node_modules/@google-cloud/storage/build/esm/src/file.js:1067:13)
at Util.handleResp (file:///prod/backend/node_modules/@google-cloud/storage/build/esm/src/nodejs-common/util.js:173:9)
at Duplexify. (file:///prod/backend/node_modules/@google-cloud/storage/build/esm/src/file.js:1102:22)
at Duplexify.emit (node:events:508:28)
at PassThrough.emit (node:events:508:28)
at onResponse (/prod/backend/node_modules/retry-request/index.js:253:19)
at PassThrough. (/prod/backend/node_modules/retry-request/index.js:183:11)
at PassThrough.emit (node:events:520:35) {
code: 'ERR_STREAM_UNABLE_TO_PIPE'
}
```

## Repro shape

This seems to require:

1. create a GCS read stream with `file.createReadStream()`
2. attach it to a downstream consumer that may close early
3. let the downstream consumer close before the GCS response path finishes wiring up the internal pipeline

Examples of downstream consumers that can close early:

- an HTTP response stream when the client disconnects
- a proxy/load balancer closing the request
- any writable/transform stream that is destroyed before the source finishes

## Why this seems library-side

The failing stack points into the `createReadStream()` implementation, specifically the code path that does:

```ts
pipeline(rawResponseStream, ...transformStreams, throughStream, onComplete);
```

The issue appears to be that `throughStream` can already be closed or destroyed by the time this `pipeline(...)` call happens.

## Actual behavior

The process throws `ERR_STREAM_UNABLE_TO_PIPE` from the library read path.

## Expected behavior

If the downstream consumer closes early, the library should surface a normal stream error or safely stop/drain the source stream, but it should not synchronously throw from the internal `pipeline(...)` setup path.

## Version

- `@google-cloud/storage@7.17.0`
- Node.js `22.x`

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.