Skip to content

Guarantee upstream completion beyond Workers waitUntil #411

Description

@Menci

Problem

PR #409 detaches dispatched upstream requests from client cancellation and drains their responses through executionCtx.waitUntil(). This preserves terminal usage, dumps, and performance telemetry when the remaining work completes within Cloudflare Workers' post-disconnect grace period.

Cloudflare limits all waitUntil() work for an HTTP invocation to 30 seconds after the response completes or the client disconnects. Promises still pending at that point are canceled. A sufficiently long upstream response can therefore still be truncated, so a stateless Worker cannot provide the absolute invariant that every dispatched request is consumed completely.

An already-dispatched fetch() cannot transfer its connection or ReadableStream ownership to another execution context. Durable ownership must be selected before dispatch.

Required outcome

  • Select a client-independent owner before the upstream request is dispatched.
  • Keep streaming visible output to the connected client without making that connection the owner of upstream work.
  • Consume the complete upstream response after any client disconnect, with no 30-second grace-period dependency.
  • Extract and persist terminal usage, dumps, performance data, and any response state exactly once.
  • Prevent retries, reconnects, and workflow replay from double-dispatching or double-billing an upstream request.
  • Preserve the dispatch-time disconnect gate: if the client is already gone, do not start a new request.
  • Cover HTTP streaming and Responses WebSocket transports.

Architecture direction

Use a Workflow as the durable request owner before dispatch. A Workflow step has no wall-time limit and can read the upstream response and persist terminal accounting independently of the client invocation. Deliver live chunks to the frontend through a durable broker/buffer (for example a Durable Object plus persisted replay data). The frontend Worker only subscribes and forwards; it never owns the upstream connection.

A plain Service Binding does not detach lifetime from its caller. A Durable Object alone also needs careful persistence: Cloudflare does not guarantee that a plain outbound fetch() response body keeps a Durable Object alive, and deployments/runtime updates can terminate in-flight work.

References

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions