Skip to content

fix(storage-gcs): destroy the GCS read stream when the response body is cancelled#16959

Open
via-marketing-co-il wants to merge 1 commit into
payloadcms:mainfrom
via-marketing-co-il:fix/storage-gcs-stream-cancel
Open

fix(storage-gcs): destroy the GCS read stream when the response body is cancelled#16959
via-marketing-co-il wants to merge 1 commit into
payloadcms:mainfrom
via-marketing-co-il:fix/storage-gcs-stream-cancel

Conversation

@via-marketing-co-il

Copy link
Copy Markdown

What?

getFile in @payloadcms/storage-gcs wraps the GCS Node read stream in a web ReadableStream with no cancel() handler and unguarded controller.enqueue/close/error calls.

Why?

When the consumer cancels the response body — a browser aborting an image/video download mid-stream, or server code that serves HEAD by reading GET and cancelling the body — the controller closes but the GCS stream keeps emitting. The next 'data' event then throws an uncaught exception at process level (it originates inside an EventEmitter listener, so no promise chain catches it), and the GCS download keeps running (wasted egress):

⨯ uncaughtException:  TypeError: Invalid state: Controller is already closed
    at PassThroughShim.<anonymous> (…)
  code: 'ERR_INVALID_STATE'

Reproduced in production on Cloud Run (Payload 3.84.1, also verified present in 3.85.1 and current main): a single curl -I https://<host>/api/media/file/<file>.webp-style cancelled read triggers it deterministically ~50–100 ms after the response headers are returned.

Notably, @payloadcms/storage-s3 already handles this case (abort listener + stream destroy, #13430); storage-gcs never received the equivalent.

How?

Mirror the S3 adapter's intent with the minimal change inside the existing ReadableStream wrapper:

  • add a cancel() handler that destroys the GCS read stream (stops the download, prevents further events)
  • guard enqueue/close/error behind a settled flag
  • if enqueue ever throws anyway, destroy the stream instead of crashing the process

No behavior change for normal, fully-consumed reads (ranged or full). Fixes the uncaught ERR_INVALID_STATE crash-noise and the leaked GCS download on every cancelled media response.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant