Skip to content

runtime-dynamic-worker: reuse warm isolates via stable loader.get id #11

Description

@amondnet

Summary

runtime-dynamic-worker provisions a brand-new Worker isolate on every
execution by passing a unique id to loader.get(...), which defeats
Cloudflare's warm-worker caching. Using a stable id would let Cloudflare return
a warm, cached isolate and remove the per-call boot cost.

Evidence

// packages/kernel/runtime-dynamic-worker/src/executor.ts:449
const worker = options.loader.get(`executor-${crypto.randomUUID()}`, () => ({
  compatibilityDate: "2025-06-01",
  compatibilityFlags: ["nodejs_compat"],
  mainModule: ENTRY_MODULE,
  modules: { ...safeModules, [ENTRY_MODULE]: executorModule },
  globalOutbound: options.globalOutbound ?? null,
}));

A fresh crypto.randomUUID() per call means no two invocations ever share an
id, so the get(id, callback) warm-cache path can never hit. Each execution
pays a full isolate boot. The startup latency is already significant enough that
the code traces it as a dedicated executor.runtime.startup span
(executor.ts:427-430).

Why now

Cloudflare's Worker Loader docs describe two loading modes:

  • load(code) for one-time execution
  • get(id, callback) for cached, warm workers reused by id

(Per the Dynamic Workers docs
and the open-beta changelog, 2026-03-24.)

The current code uses get(...) but with an always-unique id, so it gets the
API shape of warm caching with none of the benefit.

Proposed change

  • Derive a stable id for loader.get(...) from something that legitimately
    identifies a reusable sandbox (e.g. a hash of the assembled executor module
    source / user code, optionally scoped per tenant/connection), instead of a
    random UUID per call.
  • Confirm the isolation model still holds: a warm isolate must not leak state
    across executions that should be isolated. Decide the correct cache key
    granularity (per code-hash is likely safe since the module source is
    identical; per-tenant scoping may be needed if globals can carry state).
  • Keep globalOutbound and module wiring identical so behavior is unchanged
    apart from reuse.

Impact

  • Affects apps/cloud (the only current consumer of runtime-dynamic-worker),
    and any future host-cloudflare adoption.
  • Pure latency optimization; no behavior change intended.
  • Largest open question is the correct cache-key granularity vs isolation
    guarantees, which must be validated before merge.

Tasks

  • Determine a safe, stable cache-key strategy for loader.get(...)
    (code-hash, optionally tenant/connection-scoped).
  • Replace the executor-${crypto.randomUUID()} id in
    packages/kernel/runtime-dynamic-worker/src/executor.ts:449.
  • Verify isolation is preserved across reused warm isolates (no state
    bleed) with a targeted test.
  • Measure executor.runtime.startup before/after to confirm the win.
  • Effect Vitest coverage; gate with format:check / lint / typecheck /
    test.

Notes

No QuickJS-vs-dynamic-worker or warm-vs-cold benchmark exists in the repo today;
the executor.runtime.startup span is the natural measurement point.


Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:refactorCode refactoring without behavior change

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions