Skip to content

Cloudflare Workflow teardown can deadlock on instrumented waitUntil #23005

Description

@matthew-gizmo

Environment

  • @sentry/cloudflare 10.69.0
  • Cloudflare Workflows

Problem

instrumentWorkflowWithSentry captures context.waitUntil.bind(context) inside each run() call. init() instruments that same context. When Cloudflare reuses a Workflow instance, a later run can therefore capture Sentry’s flush-lock-aware waitUntil wrapper instead of the original function.

At the end of that run, the wrapper schedules flushAndDispose(client) through the captured function. This can register the teardown promise in the same flush lock that client.flush() is waiting to drain, so teardown never settles. In production this surfaced as Worker code had hung immediately after otherwise successful workflow completion.

Application work registered through the instrumented waitUntil should remain tracked; only Sentry’s own teardown must bypass its instrumentation.

Suggested fix

Capture the original function before client initialization, matching the existing RPC wrapper:

const waitUntil = getOriginalWaitUntil(context).bind(context);
const client = init({ ...options, ctx: context, enableDedupe: false });

The CommonJS build can use the corresponding flush.getOriginalWaitUntil(context) export.

Regression shape

A regression test can reuse one instrumented Workflow instance for two runs. During the second run, register controlled pending application work through the instrumented context, return successfully, then release that work and await both the application and teardown promises. With the current implementation the teardown promise remains pending; using getOriginalWaitUntil lets both settle while genuine workflow errors still propagate.

Metadata

Metadata

Assignees

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions