Skip to content

fix(ext/workers): settle UserWorker.fetch without waiting for the request body pipe - #755

Open
da1suk8 wants to merge 1 commit into
supabase:mainfrom
da1suk8:fix/user-worker-fetch-settle
Open

da1suk8 wants to merge 1 commit into
supabase:mainfrom
da1suk8:fix/user-worker-fetch-settle

Conversation

@da1suk8

@da1suk8 da1suk8 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #742. UserWorker.fetch() waits for the request body pipe and the response together with Promise.allSettled. When the body is a branch of a tee'd stream, which is what req.clone() produces (the issue's reproduction, and the CLI relay until supabase/cli#6580), the pipe never settles:

  • As soon as op_user_worker_fetch_send returns, whether with WorkerAlreadyRetired before dispatch or with an early response from a worker that never reads the body, the Rust side cancels the request body resource.
  • The pipe's next write fails, so pipeTo cancels its source. Cancelling one branch of a tee does not resolve until the other branch is cancelled too (ReadableStreamTee in the Streams spec), and the other branch belongs to the caller's original Request, which nobody reads.
  • allSettled never resolves, so neither does fetch(), and the main service can neither answer nor retry.

A request without a body, or one whose body is passed through as-is, rejects promptly; only the cloned body hangs. The early-response variant is the runtime side of #737.

This makes fetch() await only the response. The body pipe keeps running in the background with its rejection swallowed as before (#374), and requestBodyRid is closed once the response has settled so the resource does not linger in the table. fetch() has no handle to the other tee branch, so the pipe cannot be unblocked from here; not waiting for it matches what the Rust side already assumes.

Validation

  • test_user_worker_fetch_settles_when_worker_already_retired (new fixture main_retired_worker_fetch, which keeps a worker handle across requests) dispatches to a retired worker with no body, with the inbound body passed through, and with a cloned body; all three now reject with WorkerAlreadyRetired within the deadline. On main, the cloned case times out.
  • test_user_worker_fetch_settles_when_response_arrives_before_body_is_sent forwards a cloned two-chunk upload to a worker that returns 413 without reading it; the 413 now comes back as soon as it arrives. On main, the call times out.
  • The existing request-body and worker-lifecycle integration tests (POST, abort, null body, uploads, WebSocket upgrades, wall clock and CPU limits, early drop, idle cleanup) still pass; clippy and the format check pass.

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.

UserWorker.fetch never settles when a request with a body is rejected before dispatch (WorkerAlreadyRetired)

1 participant