Skip to content

alchemy dev: Effect-hosted DO workers never register in the dev registry — cross-script DO bindings 503 #859

Description

@asterikx

Symptom

Under alchemy dev (2.0.0-beta.63), a worker binding a Durable Object from another worker via scriptName gets persistent 503s:

Durable Object "EvalHost" defined in worker "eval-host-dev" not found. Make sure the worker is running locally and exports the class.

Setup: the documented container pattern — an async consumer worker + a small Effect worker hosting Cloudflare.DurableObject + Cloudflare.Container (Containers.layer), bound cross-script:

env: {
  EVAL_CONTAINER: Cloudflare.DurableObject<CfDurableObject>("EvalHost", { scriptName: `eval-host-${stage}` }),
}

Deployed stages work. Local dev worked on 2.0.0-beta.61 and is broken on 2.0.0-beta.63 (beta.62 untested here; it already contains the relevant rewrite, see below).

Live forensics (three parallel alchemy dev stacks: app, eval, opt)

  1. Registry (~/.local/state/alchemy/registry/) contains entries only for the three subscriber workers (app-website-dev, eval-worker-dev, optrun-worker-dev). The two Effect host workers (eval-host-dev, opt-host-dev) have no entry at all — hence RegistryProxy.worker's resolution finds nothing and returns the 503.
  2. Process table: 8 workerd processes. Only three carry --debug-port=127.0.0.1:0 — exactly the three registered workers (Runtime.start passes {"debug-port": "127.0.0.1:0"} unconditionally and its context.start(ports) performs the registry.write). The host workers exist only as their stable-port proxy instances; no real (debug-port) instance is running for them.
  3. Dev output: the host workers log [host-worker] Changes detected, interrupting existing instance and the resource applies report updated — but a [host-worker] Started in …ms line never appears (the sibling API workers log [worker] Started in 3023ms normally). The hosts' runtime.start never completes.

The publish machinery itself is complete and correct in @distilled.cloud/cloudflare-runtime 0.13.5: RegistryProxyLive derives kind: "durable-object" services from worker.durableObjectNamespaces, and DurableObjectNamespace.local handles the subscribe side. The failure is upstream of it: the host worker's real instance never starts, so nothing ever publishes.

Regression window / what changed

Between beta.61 and beta.63, LocalWorkerProvider's serve path was rewritten (beta.61 src/Cloudflare/Workers/LocalWorkerProvider.ts ~L207-227 → beta.63 serveWith ~L232-280 + restartWorker + dropServeState):

  • beta.61: start the new workerd first, then Effect.forkDetach(Scope.close(previous)).
  • beta.63: Scope.close(previous) first (commented: "Both runtimes use the same registry key. Close the old scope first so its unregister finalizer cannot delete the replacement registration."), then Scope.fork + runtime.start, serialized per worker id via a new semaphore, with latestServes/localRuntimeState.workerRestarts powering sibling-triggered restarts (restartWorker explicitly no-ops when the worker hasn't served yet, assuming "the pending first serve will already observe the updated state").

Hypothesis (labeled as such)

The interrupt/re-serve flow around first startup loses host workers: their first serve gets interrupted (Changes detected, interrupting existing instance — e.g. the sibling Container resource updating), and nothing re-serves them afterwards — restartWorker no-ops (no latestServes entry yet, since the first serve never completed), and unlike consumer workers nothing else ever pokes them again. Net result: proxy up, real instance absent, no registry entry, every cross-script DO binding 503s. Workers that subscribe to something (queues, cross-script bindings) happen to survive, which matches the observed 3-registered/2-missing split exactly.

I couldn't pin the exact interleaving from outside — happy to run instrumented builds or provide more traces. Related: #856 (another beta.63 local-dev issue from the same setup — the capnweb RPC layer dropping Redacted in provider results; both currently block the container pattern locally, #856 patchable, this one not).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions