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.


Metadata

Metadata

Assignees

No one assigned

    Labels

    type:refactorCode refactoring without behavior change

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions