Skip to content

fix(base): make user worker reuse code-aware - #736

Open
CesarManzoCode wants to merge 3 commits into
supabase:mainfrom
CesarManzoCode:fix/code-aware-worker-reuse
Open

fix(base): make user worker reuse code-aware#736
CesarManzoCode wants to merge 3 commits into
supabase:mainfrom
CesarManzoCode:fix/code-aware-worker-reuse

Conversation

@CesarManzoCode

Copy link
Copy Markdown

What kind of change does this PR introduce?

Bug fix.

Description

User workers are currently reused based on servicePath alone.

That means a subsequent EdgeRuntime.userWorkers.create() call can provide a
different executable artifact while still receiving an already-active worker
running the previous artifact.

This PR makes warm-worker reuse code-aware.

A worker is now reusable only when both its service path and executable identity
match the incoming request.

Executable identity is derived deterministically with SHA-256 from the
reuse-relevant inputs, including:

  • ESZip bundle contents
  • inline module code
  • explicit entrypoint
  • importMapPath

Pre-parsed ESZip values that cannot be deterministically identified are treated
as non-reusable.

When a request arrives with a different executable identity, incompatible active
workers for that service path are retired through the existing lifecycle path
and a fresh worker is created.

Same artifact → warm-worker reuse remains unchanged.

Different artifact → the previous worker cannot be silently reused.

Existing semaphore, retirement, idle cleanup, shutdown, per_request,
per_worker, and forceCreate behavior is preserved.

Regression

Added an end-to-end regression that uses one servicePath with two different
ESZip bundles:

  1. bundle A serves MARKER_A
  2. bundle A again reuses the existing worker
  3. bundle B must serve MARKER_B
  4. bundle A again must serve MARKER_A

Without the code-identity check, step 3 fails because the runtime reuses the
worker still serving MARKER_A.

With this change, the test passes.

Additional unit coverage verifies:

  • identical ESZip → compatible
  • different ESZip → incompatible
  • identical module code → compatible
  • different module code → incompatible
  • different entrypoint → incompatible
  • different importMapPath → incompatible
  • full 256-bit SHA-256 identity
  • opaque pre-parsed ESZip → never reusable

Validation

  • cargo fmt -p base -p ext_workers --check
  • cargo clippy -p ext_workers -p base --all-targets -- -D warnings
  • cargo test -p ext_workers
  • cargo test -p base --test integration_tests test_user_worker_reuse_is_code_aware -- --test-threads=1

All targeted checks pass.

Related

Related to #721.

This fixes a reproducible stale-worker reuse path in the open-source runtime.
The hosted multi-region propagation behavior reported in #721 involves
infrastructure outside this repository, so this PR intentionally does not claim
to explain the entire hosted incident.

CesarManzoCode and others added 3 commits September 5, 2026 16:43
Adds a regression test that drives EdgeRuntime.userWorkers.create() through
the main_eszip main worker with a fixed servicePath but two different eszip
bundles (marker "A" then marker "B", then "A" again).

Before the fix the pool keys warm-worker reuse solely by servicePath, so the
second request is served by the worker still running bundle "A" and the
assertion for "MARKER_B" fails - the runtime serves code that was never
deployed for that request (supabase#721).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsXvAfDZbQvCVBYhq7L8Xx
WorkerPool keyed warm-worker reuse for a service path on the service path
alone. Because the reuse lookup happens before a worker is built from the
incoming WorkerContextInitOpts, an already-active worker was returned even
when the caller supplied a different code artifact (a redeployed eszip
bundle, different inline module code, or a different entrypoint). The new
code was then silently ignored - see supabase#721.

Introduce WorkerCodeIdentity: a deterministic, content-derived digest over
the executable artifact (inline eszip bytes, inline module code) plus the
service path and any explicit entrypoint. A pre-parsed eszip cannot be
digested cheaply, so it maps to `Opaque` and is never eligible for reuse.

The pool now stores the identity on each UserWorkerProfile and on the
per-service ActiveWorkerRegistry, and:

  * maybe_active_worker() rejects the registry when its identity is
    incompatible with the request, retiring the stale workers so the
    caller proceeds through normal creation;
  * add_user_worker() reconciles the same way, so a concurrent create for
    a different artifact does not pool mismatched code under one key.

Same artifact -> reuse still allowed. Different artifact -> the existing
worker is retired via the normal retirement path and a fresh worker is
built. Semaphore/concurrency, retirement, idle, shutdown, per_request and
force_create behaviour are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DsXvAfDZbQvCVBYhq7L8Xx
Three pre-upstream hardening changes to WorkerCodeIdentity (introduced in
the previous commit for supabase#721):

* Replace the 64-bit xxh3 digest with a full SHA-256 (`[u8; 32]`, never
  truncated). Worker identity is a correctness boundary: two different
  executable artifacts colliding under a small non-cryptographic hash
  would let the pool hand back a worker running code that was never
  deployed for the request. `sha2` is already a workspace dependency, so
  this drops the `xxhash-rust` dependency from `ext_workers` rather than
  adding one.

* Fold the effective import map path (`context.importMapPath`) into the
  digest. `crates/base/src/runtime` passes that path to the module loader
  both when it builds an eszip from filesystem/inline source and when it
  loads a pre-built eszip (via `MigrateOptions`), so the same source can
  resolve bare specifiers differently under a different import map. Two
  requests that agree on servicePath, entrypoint and source but disagree
  on importMapPath must not silently share a warm worker. Read through the
  existing `WorkerRuntimeOpts` context, no new configuration channel.

* Document that filesystem source under `service_path` is intentionally
  outside the reuse identity: a plain file-backed worker keeps the
  pre-existing semantics (an on-disk edit needs `force_create`, exactly as
  before). The digest still improves that case - a changed entrypoint or
  import map path now forces a fresh worker - and never falls to `Opaque`,
  so no new stale-code path is created for a normal non-forceCreate caller.

Tests: add import-map identity coverage and a full-SHA-256 known-answer
test; the existing content-addressing unit test and the
`test_user_worker_reuse_is_code_aware` end-to-end regression are unchanged
in intent and still pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rFnYFfUJAPgKAX4ajrQ2t
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