@nimbus-sh/fabric 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +26 -11
  2. package/dist/bindings.d.ts +1 -0
  3. package/dist/bindings.d.ts.map +1 -1
  4. package/dist/bindings.js +2 -0
  5. package/dist/budgets.d.ts +50 -29
  6. package/dist/budgets.d.ts.map +1 -1
  7. package/dist/budgets.js +88 -40
  8. package/dist/connections.d.ts +1 -1
  9. package/dist/connections.js +1 -1
  10. package/dist/do-calls.d.ts +91 -9
  11. package/dist/do-calls.d.ts.map +1 -1
  12. package/dist/do-calls.js +161 -22
  13. package/dist/facet-pool.d.ts +3 -0
  14. package/dist/facet-pool.d.ts.map +1 -1
  15. package/dist/facet-pool.js +3 -0
  16. package/dist/fanout.d.ts +40 -32
  17. package/dist/fanout.d.ts.map +1 -1
  18. package/dist/fanout.js +50 -53
  19. package/dist/fenced-work.d.ts +3 -3
  20. package/dist/host-wasm.d.ts +29 -0
  21. package/dist/host-wasm.d.ts.map +1 -0
  22. package/dist/host-wasm.js +31 -0
  23. package/dist/image-store.d.ts +1 -1
  24. package/dist/image-store.d.ts.map +1 -1
  25. package/dist/image-store.js +34 -2
  26. package/dist/inner-do-registry.d.ts +9 -0
  27. package/dist/inner-do-registry.d.ts.map +1 -1
  28. package/dist/inner-do-registry.js +35 -0
  29. package/dist/isolate-pool.d.ts +32 -24
  30. package/dist/isolate-pool.d.ts.map +1 -1
  31. package/dist/isolate-pool.js +78 -54
  32. package/dist/process-fabric.d.ts +42 -23
  33. package/dist/process-fabric.d.ts.map +1 -1
  34. package/dist/process-fabric.js +62 -6
  35. package/dist/process-host.d.ts +3 -1
  36. package/dist/process-host.d.ts.map +1 -1
  37. package/dist/process-host.js +13 -14
  38. package/dist/supervisor-props.d.ts +47 -0
  39. package/dist/supervisor-props.d.ts.map +1 -0
  40. package/dist/supervisor-props.js +37 -0
  41. package/dist/timers.d.ts +12 -0
  42. package/dist/timers.d.ts.map +1 -1
  43. package/dist/timers.js +44 -9
  44. package/dist/vendor/types.d.ts +11 -5
  45. package/dist/vendor/types.d.ts.map +1 -1
  46. package/dist/workerd-facet-host.d.ts +4 -17
  47. package/dist/workerd-facet-host.d.ts.map +1 -1
  48. package/dist/workerd-facet-host.js +120 -54
  49. package/package.json +6 -6
  50. package/src/bindings.ts +2 -0
  51. package/src/budgets.ts +96 -49
  52. package/src/connections.ts +1 -1
  53. package/src/do-calls.ts +216 -25
  54. package/src/facet-pool.ts +5 -0
  55. package/src/fanout.ts +64 -56
  56. package/src/fenced-work.ts +3 -3
  57. package/src/host-wasm.ts +41 -0
  58. package/src/image-store.ts +30 -3
  59. package/src/inner-do-registry.ts +31 -0
  60. package/src/isolate-pool.ts +108 -74
  61. package/src/process-fabric.ts +88 -30
  62. package/src/process-host.ts +16 -14
  63. package/src/supervisor-props.ts +56 -0
  64. package/src/timers.ts +45 -9
  65. package/src/vendor/types.ts +11 -5
  66. package/src/workerd-facet-host.ts +123 -58
@@ -0,0 +1,31 @@
1
+ /**
2
+ * host-wasm.ts — what the fabric knows about a WebAssembly.Module a host
3
+ * hands a dynamic worker already compiled.
4
+ *
5
+ * workerd accepts a compiled module in a Worker Loader module map and lets
6
+ * the dynamic worker share its compiled code (src/workerd/api/
7
+ * worker-loader.c++, extractWasmModuleContent), so a host that bundles a
8
+ * fixed wasm (esbuild's, say) hands that module over instead of fetching a
9
+ * second copy of the bytes and having the guest compile them again. Two
10
+ * things the fabric needs about a member are unreadable from JS for a
11
+ * Module:
12
+ * - its size: the module's wire bytes still count toward the 64 MiB
13
+ * dynamic-worker code limit (worker-loader.c++ sums every member), and
14
+ * assertModuleMapWithinCodeLimit must count them;
15
+ * - its identity: a pool folds its wasm into the loader cache key, and a
16
+ * Module has no bytes to fingerprint.
17
+ * The host that owns the module states both once, here.
18
+ */
19
+ const described = new WeakMap();
20
+ /** Record what `module` is, and return it. */
21
+ export function describeHostWasm(module, identity) {
22
+ if (!Number.isSafeInteger(identity.bytes) || identity.bytes <= 0) {
23
+ throw new RangeError(`describeHostWasm: '${identity.id}' needs its wire size in bytes, got ${identity.bytes}`);
24
+ }
25
+ described.set(module, identity);
26
+ return module;
27
+ }
28
+ /** What a host said `module` is, or undefined for a module nobody described. */
29
+ export function hostWasmIdentity(module) {
30
+ return described.get(module);
31
+ }
@@ -105,7 +105,7 @@ export declare class ImageStore {
105
105
  * Writing the sources here, once, is what lets the session stop holding
106
106
  * them: after this returns, the only thing it keeps is a path.
107
107
  */
108
- materialize(pid: number, images: AsyncIterable<readonly [string, string]> | Iterable<readonly [string, string]>, pacer: TurnBudget): Promise<Record<string, string>>;
108
+ materialize(pid: number, images: AsyncIterable<readonly [string, string | readonly string[]]> | Iterable<readonly [string, string | readonly string[]]>, pacer: TurnBudget): Promise<Record<string, string>>;
109
109
  /**
110
110
  * Drop every image no running process boots from.
111
111
  *
@@ -1 +1 @@
1
- {"version":3,"file":"image-store.d.ts","sourceRoot":"","sources":["../src/image-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,QAA0D,CAAC;AAErG;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,wEAAwE;IACxE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACjD,kDAAkD;IAClD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAClE,2EAA2E;IAC3E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,qDAAqD;IACrD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,qBAAa,UAAU;IAanB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAbzB,8EAA8E;IAC9E,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;;;OAMG;gBAEgB,KAAK,EAAE,MAAM,cAAc,EAC3B,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO;IAGnD;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,IAAI,IAAI;IAOjB;;;;;;;;;;;;;;;;OAgBG;IACG,WAAW,CACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACtF,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAuDlC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,KAAK;CAgBd"}
1
+ {"version":3,"file":"image-store.d.ts","sourceRoot":"","sources":["../src/image-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;GASG;AACH,eAAO,MAAM,6BAA6B,QAA0D,CAAC;AAErG;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,iEAAiE;IACjE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,wEAAwE;IACxE,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IACjD,kDAAkD;IAClD,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAClE,2EAA2E;IAC3E,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC5B,qDAAqD;IACrD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;;;;;;GAOG;AACH,qBAAa,UAAU;IAanB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAbzB,8EAA8E;IAC9E,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;;;OAMG;gBAEgB,KAAK,EAAE,MAAM,cAAc,EAC3B,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO;IAGnD;;;;;;;;;;;;;;;;OAgBG;IACH,SAAS,IAAI,IAAI;IAOjB;;;;;;;;;;;;;;;;OAgBG;IACG,WAAW,CACf,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,EAC9H,KAAK,EAAE,UAAU,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAuDlC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,KAAK;CAgBd"}
@@ -111,12 +111,12 @@ export class ImageStore {
111
111
  fs.mkdirp(FACET_IMAGE_DIR);
112
112
  let count = 0;
113
113
  for await (const [moduleName, source] of images) {
114
- const path = facetImagePath(await facetImageDigest(source));
114
+ const bytes = typeof source === 'string' ? new TextEncoder().encode(source) : encodeParts(source);
115
+ const path = facetImagePath(await facetImageDigest(bytes));
115
116
  paths[moduleName] = path;
116
117
  rooted.push(path);
117
118
  count++;
118
119
  const stored = path.replace(/^\/+/, '');
119
- const bytes = new TextEncoder().encode(source);
120
120
  console.log('[image-store] pid=' + pid + ' image ' + count + ' ' + moduleName + ' → '
121
121
  + path.slice(-12) + ' ' + bytes.byteLength + ' bytes, slice=' + FACET_IMAGE_WRITE_SLICE_BYTES
122
122
  + ' turns=' + pacer.chunks);
@@ -190,3 +190,35 @@ export class ImageStore {
190
190
  }
191
191
  }
192
192
  }
193
+ /**
194
+ * UTF-8 bytes of an image given as ordered parts, encoded into one buffer a
195
+ * part at a time: joining the parts first would hold the whole image twice as
196
+ * text.
197
+ */
198
+ function encodeParts(parts) {
199
+ let length = 0;
200
+ for (const part of parts) {
201
+ for (let i = 0; i < part.length; i++) {
202
+ const code = part.charCodeAt(i);
203
+ if (code < 0x80)
204
+ length += 1;
205
+ else if (code < 0x800)
206
+ length += 2;
207
+ else if (code >= 0xd800 && code <= 0xdbff && (part.charCodeAt(i + 1) & 0xfc00) === 0xdc00) {
208
+ length += 4;
209
+ i++;
210
+ }
211
+ else
212
+ length += 3;
213
+ }
214
+ }
215
+ const bytes = new Uint8Array(length);
216
+ const encoder = new TextEncoder();
217
+ let offset = 0;
218
+ for (const part of parts) {
219
+ const encoded = encoder.encode(part);
220
+ bytes.set(encoded, offset);
221
+ offset += encoded.byteLength;
222
+ }
223
+ return bytes;
224
+ }
@@ -38,4 +38,13 @@ export declare function getInnerDoClass(supervisorDoId: string, bindingName: str
38
38
  export declare function registerInnerDoClass(supervisorDoId: string, bindingName: string, cls: DurableObjectClass): void;
39
39
  /** Clear all registrations belonging to a supervisor DO (called on rebuild). */
40
40
  export declare function clearInnerDoClasses(supervisorDoId: string): void;
41
+ /**
42
+ * Record that this incarnation opens `facetName` for `bindingName`. True on the
43
+ * name's first open since the incarnation began or the binding's facets were
44
+ * last aborted: whatever still runs under it has a class this build no longer
45
+ * uses, and a get() with the new class on it resets the whole object.
46
+ */
47
+ export declare function noteInnerDoFacetOpened(ctx: DurableObjectState, bindingName: string, facetName: string): boolean;
48
+ /** Abort the facets this incarnation opened for `bindingNames`, keeping their storage, so each next request starts the class registered then. */
49
+ export declare function abortInnerDoFacets(ctx: DurableObjectState, bindingNames: Iterable<string>, reason: Error): void;
41
50
  //# sourceMappingURL=inner-do-registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"inner-do-registry.d.ts","sourceRoot":"","sources":["../src/inner-do-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE3G;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,kBAAkB,GACtB,IAAI,CAEN;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAKhE"}
1
+ {"version":3,"file":"inner-do-registry.d.ts","sourceRoot":"","sources":["../src/inner-do-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,2EAA2E;AAC3E,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAE3G;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,kBAAkB,GACtB,IAAI,CAEN;AAED,gFAAgF;AAChF,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAKhE;AAKD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAQ/G;AAED,iJAAiJ;AACjJ,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,kBAAkB,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,GAAG,IAAI,CAS/G"}
@@ -49,3 +49,38 @@ export function clearInnerDoClasses(supervisorDoId) {
49
49
  _NIMBUS_INNER_DO_CLASSES.delete(k);
50
50
  }
51
51
  }
52
+ /** Inner-DO facet names each incarnation has opened, by binding; keyed off ctx, so a new incarnation starts empty. */
53
+ const openedInnerDoFacets = new WeakMap();
54
+ /**
55
+ * Record that this incarnation opens `facetName` for `bindingName`. True on the
56
+ * name's first open since the incarnation began or the binding's facets were
57
+ * last aborted: whatever still runs under it has a class this build no longer
58
+ * uses, and a get() with the new class on it resets the whole object.
59
+ */
60
+ export function noteInnerDoFacetOpened(ctx, bindingName, facetName) {
61
+ let byBinding = openedInnerDoFacets.get(ctx);
62
+ if (!byBinding)
63
+ openedInnerDoFacets.set(ctx, byBinding = new Map());
64
+ let names = byBinding.get(bindingName);
65
+ if (!names)
66
+ byBinding.set(bindingName, names = new Set());
67
+ if (names.has(facetName))
68
+ return false;
69
+ names.add(facetName);
70
+ return true;
71
+ }
72
+ /** Abort the facets this incarnation opened for `bindingNames`, keeping their storage, so each next request starts the class registered then. */
73
+ export function abortInnerDoFacets(ctx, bindingNames, reason) {
74
+ const byBinding = openedInnerDoFacets.get(ctx);
75
+ if (!byBinding)
76
+ return;
77
+ for (const bindingName of bindingNames) {
78
+ for (const facetName of byBinding.get(bindingName) ?? []) {
79
+ try {
80
+ ctx.facets.abort(facetName, reason);
81
+ }
82
+ catch { /* already gone */ }
83
+ }
84
+ byBinding.delete(bindingName);
85
+ }
86
+ }
@@ -7,15 +7,15 @@
7
7
  * running 67 npm tarball extractions (cold-start dominates). We pin
8
8
  * each job to `slot = cursor % concurrency` and use stable loader
9
9
  * IDs `nfp:${fnHash}:slot-${i}:g${generation}`, so a pool of
10
- * concurrency=4 keeps at most 4 warm isolates rather than N fresh ones.
10
+ * concurrency=N keeps at most N warm isolates rather than one per job.
11
11
  * 2. **Nimbus defaults**: compatibilityDate = CF_COMPAT_DATE (matches
12
- * the supervisor worker), compatibilityFlags = ['nodejs_compat'],
12
+ * the supervisor worker), compatibilityFlags = GUEST_COMPAT_FLAGS,
13
13
  * globalOutbound = undefined (inherit parent network so the facet can
14
14
  * reach https://registry.npmjs.org without a proxy binding).
15
15
  * 3. **Supervisor autoinjection**. The pool grabs the embedder's
16
16
  * registered supervisor entrypoint stub (see `supervisorEntrypoint` in
17
17
  * composition.ts) and forwards it as `env.SUPERVISOR` to every facet,
18
- * same pattern as git-network-facet.ts. Callers can add more bindings
18
+ * same pattern as git/network-facet.ts. Callers can add more bindings
19
19
  * via `extraBindings`.
20
20
  * 4. **Fail-loud defaults**: timeout 60s, retries 0, onError 'throw'.
21
21
  * Caller opts in to leniency.
@@ -44,7 +44,11 @@ export interface IsolatePoolEnv {
44
44
  }
45
45
  /** Options handed to IsolatePool's constructor. */
46
46
  export interface IsolatePoolOptions {
47
- /** Maximum concurrent in-flight facets. Default 4. */
47
+ /**
48
+ * Maximum concurrent in-flight facets, each a distinct Dynamic Worker
49
+ * spent from the hosting DO's `DO_DYNAMIC_WORKER_LIMIT`. Default 1; a
50
+ * caller that wants more sizes it against that budget (Fanout does).
51
+ */
48
52
  concurrency?: number;
49
53
  /** Per-task timeout in ms. Default 60_000. */
50
54
  timeoutMs?: number;
@@ -139,15 +143,19 @@ export interface IsolatePoolOptions {
139
143
  /**
140
144
  * WebAssembly modules to ship into the facet via the LOADER's
141
145
  * `modules` map. Map keys are module specifier paths (e.g.
142
- * `'esbuild.wasm'`); values are the raw bytes.
146
+ * `'esbuild.wasm'`); values are the raw bytes, or a module the host
147
+ * already holds compiled.
143
148
  *
144
- * Workerd registers each entry as `{ wasm: ArrayBuffer }` in the
145
- * worker's modules map. The pool prepends a static
149
+ * Workerd registers each entry as `{ wasm }` in the worker's modules
150
+ * map. The pool prepends a static
146
151
  * `import __NIMBUS_WASM_<id> from './<key>';` to the generated
147
- * worker.js so workerd compiles each at module-load (startup phase,
148
- * where wasm code generation is permitted). The compiled Modules
149
- * are exposed via `globalThis.__NIMBUS_WASM[<key>]` for the user
150
- * function to read at request time.
152
+ * worker.js; bytes are compiled at the facet's module-load (startup
153
+ * phase, where wasm code generation is permitted), and a compiled
154
+ * module is shared with the facet as is, its compiled code included
155
+ * (workerd src/workerd/api/worker-loader.c++,
156
+ * extractWasmModuleContent). The Modules are exposed via
157
+ * `globalThis.__NIMBUS_WASM[<key>]` for the user function to read at
158
+ * request time.
151
159
  *
152
160
  * Why this works when other paths don't:
153
161
  * - request-time `WebAssembly.compile()` — disallowed by workerd
@@ -156,15 +164,15 @@ export interface IsolatePoolOptions {
156
164
  * structured-clone refuses ("Unable to deserialize cloned data").
157
165
  * - inlining bytes in the preamble — 16 MiB string per dispatch
158
166
  * OOMs the supervisor at module-source allocation time.
159
- * - LOADER modules-map (this) — bytes ride INSIDE the worker code
160
- * blob; workerd compiles wasm during its own startup pipeline,
161
- * never crossing structured-clone, never executing JS eval.
167
+ * - LOADER modules-map (this) — the module rides INSIDE the worker
168
+ * code blob, never crossing structured-clone, never executing JS
169
+ * eval.
162
170
  *
163
- * The bytes ARE part of the loader-cache key (workerd hashes the
164
- * whole WorkerCode), so changing the wasm bytes invalidates warm
165
- * slots — desirable when the bundled wasm version changes.
171
+ * A compiled module must be described (host-wasm.ts describeHostWasm):
172
+ * its identity keys warm slots, as the bytes' fingerprint does, and its
173
+ * size counts toward the dynamic-worker code limit.
166
174
  */
167
- wasmModules?: Record<string, ArrayBuffer>;
175
+ wasmModules?: Record<string, ArrayBuffer | WebAssembly.Module>;
168
176
  }
169
177
  /** Per-call override (merged with pool defaults). */
170
178
  export interface IsolateCallOptions {
@@ -233,7 +241,7 @@ export declare function assembleLoaderWorkerModuleSource(options: LoaderWorkerMo
233
241
  * Typical use:
234
242
  *
235
243
  * const pool = new IsolatePool(env, ctx, {
236
- * concurrency: 4,
244
+ * concurrency: 2,
237
245
  * tag: 'npm-install',
238
246
  * });
239
247
  * const results = await pool.map(
@@ -273,11 +281,11 @@ export declare class IsolatePool {
273
281
  * across pool dispatches (cache-key stability).
274
282
  */
275
283
  private readonly wasmModules;
276
- /** Hash of (name + byte length + first/last bytes) of every wasm
277
- * module, folded into the loader cache key so changes invalidate
278
- * warm slots. Hashing the FULL bytes would be O(20+ MiB) per dispatch
279
- * and is unnecessary — wasm bytes are pinned at deploy time, the
280
- * length+endpoints are a strong-enough fingerprint. */
284
+ /** Hash of every constructor-time wasm module, folded into the loader
285
+ * cache key so changes invalidate warm slots: a compiled module by the
286
+ * identity its host described, bytes by name + length + first/last
287
+ * byte. Hashing the FULL bytes would be O(20+ MiB) per dispatch and is
288
+ * unnecessary — they are pinned at deploy time. */
281
289
  private readonly wasmHash;
282
290
  /**
283
291
  * Short prefix of the owning DO's id, baked into the loader.get()
@@ -1 +1 @@
1
- {"version":3,"file":"isolate-pool.d.ts","sourceRoot":"","sources":["../src/isolate-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAGH,OAAO,EAAmC,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAanF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAErE;;;;;;;;GAQG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,IAAI;IAC9B,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CACnD,CAAC,MAAM,CAAC,CAAC;AAEV,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,mDAAmD;AACnD,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAClC;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;;;;;;;;;;;;OAgBG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;OAIG;IACH,eAAe,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C;AAED,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;CAC3C;AAED,oEAAoE;AACpE,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;CACrC;AAkCD,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC1D,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,8EAA8E;AAC9E,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,+BAA+B,GACvC,MAAM,CAgFR;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,WAAW;;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D;;;;;;;;OAQG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAoC;IAC9D,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAsC;IAEtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,WAAW,CAOzB;IACH;;;;4DAIwD;IACxD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC,2EAA2E;IAC3E,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAE7B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,kBAAkB,EACvB,IAAI,CAAC,EAAE,kBAAkB;IA+G3B,wEAAwE;IACxE,IAAI,kBAAkB,IAAI,MAAM,CAE/B;IAyZD;;;OAGG;IACG,MAAM,CAAC,CAAC,EAAE,CAAC,EACf,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACrB,GAAG,EAAE,CAAC,EACN,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAatB;;;;;;;;OAQG;IACG,aAAa,CACjB,EAAE,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,EAC1E,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,kBAAkB,GACxB,OAAO,CAAC,QAAQ,CAAC;IAwBpB;;;;;OAKG;IACG,GAAG,CAAC,CAAC,EAAE,CAAC,EACZ,EAAE,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,EACrB,KAAK,EAAE,CAAC,EAAE,EACV,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAOpC;;;;;;;;;;;;;;;;OAgBG;IACG,SAAS,CAAC,CAAC,EAAE,CAAC,EAClB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,CAAC,EAAE,EACV,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IA+DpC;;;;;;;;;;;;;;OAcG;IACH,OAAO,IAAI,IAAI;CAShB"}
1
+ {"version":3,"file":"isolate-pool.d.ts","sourceRoot":"","sources":["../src/isolate-pool.ts"],"names":[],"mappings":"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"}
@@ -7,15 +7,15 @@
7
7
  * running 67 npm tarball extractions (cold-start dominates). We pin
8
8
  * each job to `slot = cursor % concurrency` and use stable loader
9
9
  * IDs `nfp:${fnHash}:slot-${i}:g${generation}`, so a pool of
10
- * concurrency=4 keeps at most 4 warm isolates rather than N fresh ones.
10
+ * concurrency=N keeps at most N warm isolates rather than one per job.
11
11
  * 2. **Nimbus defaults**: compatibilityDate = CF_COMPAT_DATE (matches
12
- * the supervisor worker), compatibilityFlags = ['nodejs_compat'],
12
+ * the supervisor worker), compatibilityFlags = GUEST_COMPAT_FLAGS,
13
13
  * globalOutbound = undefined (inherit parent network so the facet can
14
14
  * reach https://registry.npmjs.org without a proxy binding).
15
15
  * 3. **Supervisor autoinjection**. The pool grabs the embedder's
16
16
  * registered supervisor entrypoint stub (see `supervisorEntrypoint` in
17
17
  * composition.ts) and forwards it as `env.SUPERVISOR` to every facet,
18
- * same pattern as git-network-facet.ts. Callers can add more bindings
18
+ * same pattern as git/network-facet.ts. Callers can add more bindings
19
19
  * via `extraBindings`.
20
20
  * 4. **Fail-loud defaults**: timeout 60s, retries 0, onError 'throw'.
21
21
  * Caller opts in to leniency.
@@ -23,15 +23,24 @@
23
23
  * The vendored directory contains only the upstream serialization, error,
24
24
  * and binding types used by this implementation.
25
25
  */
26
- import { CF_COMPAT_DATE } from '@nimbus-sh/core/constants.js';
27
- import { hostRoute, supervisorEntrypoint } from './composition.js';
26
+ import { CF_COMPAT_DATE, GUEST_COMPAT_FLAGS } from '@nimbus-sh/core/constants.js';
27
+ import { supervisorEntrypoint } from './composition.js';
28
+ import { supervisorBindingProps, supervisorLoaderKey } from './supervisor-props.js';
28
29
  import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
29
30
  import { serializeFunction, hashSource } from './vendor/serialize.js';
30
- import { beginLoaderFetch, recordLoaderId, withDynamicWorkerCapNamed } from './budgets.js';
31
+ import { beginLoaderFetch, withDynamicWorkerCapNamed } from './budgets.js';
31
32
  import { assertModuleMapWithinCodeLimit } from './budgets.js';
32
33
  import { recordFailure, setLastFacetId, getLastRpcFrame } from '@nimbus-sh/platform/oom-discriminator.js';
33
34
  import { classifyError } from '@nimbus-sh/platform/oom-classify.js';
34
35
  import { BindingError, ExecutionError, RetryExhaustedError, TimeoutError, } from './vendor/errors.js';
36
+ import { hostWasmIdentity } from './host-wasm.js';
37
+ /**
38
+ * How long one call waits, in all, for the platform to admit it after
39
+ * "Dynamic worker concurrency limit exceeded" (doubling from 50 ms, at most
40
+ * 2 s a wait). A deployed Durable Object admitted the refused batch after a
41
+ * 6 s pause; 15 s bounds a call that would never be admitted.
42
+ */
43
+ const CAP_REFUSAL_WAIT_MS = 15_000;
35
44
  /**
36
45
  * esbuild runtime helpers re-declared at the top of every generated facet
37
46
  * module. esbuild emits `__name(fn, "fn")` wrappers around every named
@@ -99,7 +108,7 @@ export function assembleLoaderWorkerModuleSource(options) {
99
108
  * Typical use:
100
109
  *
101
110
  * const pool = new IsolatePool(env, ctx, {
102
- * concurrency: 4,
111
+ * concurrency: 2,
103
112
  * tag: 'npm-install',
104
113
  * });
105
114
  * const results = await pool.map(
@@ -138,11 +147,11 @@ export class IsolatePool {
138
147
  * across pool dispatches (cache-key stability).
139
148
  */
140
149
  wasmModules;
141
- /** Hash of (name + byte length + first/last bytes) of every wasm
142
- * module, folded into the loader cache key so changes invalidate
143
- * warm slots. Hashing the FULL bytes would be O(20+ MiB) per dispatch
144
- * and is unnecessary — wasm bytes are pinned at deploy time, the
145
- * length+endpoints are a strong-enough fingerprint. */
150
+ /** Hash of every constructor-time wasm module, folded into the loader
151
+ * cache key so changes invalidate warm slots: a compiled module by the
152
+ * identity its host described, bytes by name + length + first/last
153
+ * byte. Hashing the FULL bytes would be O(20+ MiB) per dispatch and is
154
+ * unnecessary — they are pinned at deploy time. */
146
155
  wasmHash;
147
156
  /**
148
157
  * Short prefix of the owning DO's id, baked into the loader.get()
@@ -174,7 +183,7 @@ export class IsolatePool {
174
183
  }
175
184
  this.loader = loader;
176
185
  this.ctx = ctx;
177
- this.concurrency = Math.max(1, opts?.concurrency ?? 4);
186
+ this.concurrency = Math.max(1, opts?.concurrency ?? 1);
178
187
  this.defaultTimeoutMs = opts?.timeoutMs ?? 60_000;
179
188
  this.defaultRetries = Math.max(0, opts?.retries ?? 0);
180
189
  this.tag = opts?.tag ?? 'facet';
@@ -192,15 +201,34 @@ export class IsolatePool {
192
201
  // 'esbuild_wasm') are rejected loudly because the generated worker
193
202
  // would otherwise have duplicate imports. Order is preserved.
194
203
  const wasmEntries = [];
204
+ const fingerprints = [];
195
205
  const seenIds = new Set();
196
206
  if (opts?.wasmModules) {
197
- for (const [name, bytes] of Object.entries(opts.wasmModules)) {
198
- if (!(bytes instanceof ArrayBuffer)) {
207
+ for (const [name, wasm] of Object.entries(opts.wasmModules)) {
208
+ let fingerprint;
209
+ if (wasm instanceof ArrayBuffer) {
210
+ // Name + length + first/last byte: hashing 20+ MiB of wasm per
211
+ // dispatch would be wasteful, and these bytes change only with
212
+ // the deployed bundle.
213
+ const u = new Uint8Array(wasm);
214
+ const len = u.byteLength;
215
+ fingerprint = `${name}:${len}:${len > 0 ? u[0] : 0}:${len > 0 ? u[len - 1] : 0}`;
216
+ }
217
+ else if (wasm instanceof WebAssembly.Module) {
218
+ const identity = hostWasmIdentity(wasm);
219
+ if (!identity) {
220
+ throw new BindingError(`IsolatePool: wasmModules['${name}'] is a WebAssembly.Module nobody described; ` +
221
+ 'pass it through describeHostWasm (@nimbus-sh/fabric/host-wasm.js) so warm slots ' +
222
+ 'can be keyed by it and the code limit can count it.');
223
+ }
224
+ fingerprint = `${name}:host:${identity.id}:${identity.bytes}`;
225
+ }
226
+ else {
199
227
  // Reached only when a caller broke the declared option type, so the
200
- // value is whatever it really was rather than the ArrayBuffer here.
201
- const got = bytes?.constructor?.name;
202
- throw new BindingError(`IsolatePool: wasmModules['${name}'] must be ArrayBuffer ` +
203
- `(got ${got || typeof bytes}).`);
228
+ // value is whatever it really was rather than the union here.
229
+ const got = wasm?.constructor?.name;
230
+ throw new BindingError(`IsolatePool: wasmModules['${name}'] must be an ArrayBuffer or a WebAssembly.Module ` +
231
+ `(got ${got || typeof wasm}).`);
204
232
  }
205
233
  const id = name.replace(/[^A-Za-z0-9_]/g, '_').replace(/^[^A-Za-z_]/, '_');
206
234
  if (seenIds.has(id)) {
@@ -208,29 +236,12 @@ export class IsolatePool {
208
236
  `identifier-sanitisation (id='${id}'). Pick distinct module names.`);
209
237
  }
210
238
  seenIds.add(id);
211
- wasmEntries.push({ name, id, bytes });
239
+ wasmEntries.push({ name, id, wasm });
240
+ fingerprints.push(fingerprint);
212
241
  }
213
242
  }
214
243
  this.wasmModules = wasmEntries;
215
- // Fingerprint: name + length + first/last byte of each module.
216
- // Hashing 20+ MiB of wasm per dispatch would be wasteful; this
217
- // fingerprint is bytes-stable for a given deployed bundle and only
218
- // changes when the wasm itself changes (deploy-time event).
219
- if (wasmEntries.length === 0) {
220
- this.wasmHash = '0';
221
- }
222
- else {
223
- const fp = wasmEntries
224
- .map((w) => {
225
- const u = new Uint8Array(w.bytes);
226
- const len = u.byteLength;
227
- const first = len > 0 ? u[0] : 0;
228
- const last = len > 0 ? u[len - 1] : 0;
229
- return `${w.name}:${len}:${first}:${last}`;
230
- })
231
- .join('|');
232
- this.wasmHash = hashSource(fp);
233
- }
244
+ this.wasmHash = fingerprints.length === 0 ? '0' : hashSource(fingerprints.join('|'));
234
245
  const bindings = { ...(opts?.extraBindings ?? {}) };
235
246
  this.supervisorKey = 's-none';
236
247
  if (!opts?.omitSupervisor) {
@@ -240,11 +251,11 @@ export class IsolatePool {
240
251
  // via supervisorDoIdOverride so SUPERVISOR.* RPCs route back
241
252
  // to the user's session DO, not the peer DO. Default to the
242
253
  // local ctx.id (single-DO callers and the in-DO in-DO fanout path).
243
- const supDoId = opts?.supervisorDoIdOverride ?? ctx.id.toString();
244
- const supPid = opts?.supervisorPid ?? 0;
245
- bindings.SUPERVISOR = supervisorRpc({
246
- props: { doId: supDoId, pid: supPid, route: opts?.supervisorRoute ?? hostRoute() ?? undefined },
254
+ const supervisor = supervisorBindingProps(ctx, opts?.supervisorPid ?? 0, {
255
+ doId: opts?.supervisorDoIdOverride,
256
+ route: opts?.supervisorRoute,
247
257
  });
258
+ bindings.SUPERVISOR = supervisorRpc({ props: supervisor });
248
259
  // Whatever the minted worker's env carries must be in its loader
249
260
  // cache key — workerd's loader cache survives a DO hibernation
250
261
  // wake while generation-strided pids (1000001 → 2000001) do not:
@@ -252,8 +263,9 @@ export class IsolatePool {
252
263
  // the new generation still credentialed to the dead pid, and
253
264
  // every pid-authorized RPC from it fails "process pid … does
254
265
  // not exist". doIdShort alone cannot cover this — it changes
255
- // across sessions, not across wakes of the same session.
256
- this.supervisorKey = `s${supDoId.slice(0, 12)}-${supPid}`;
266
+ // across sessions, not across wakes of the same session. So does
267
+ // the instance a binding delivers mutations to, when it names one.
268
+ this.supervisorKey = supervisorLoaderKey(`s${supervisor.doId.slice(0, 12)}-${supervisor.pid}`, supervisor);
257
269
  }
258
270
  else {
259
271
  // Supervisor entrypoint unavailable — running without ctx.exports
@@ -309,7 +321,7 @@ export class IsolatePool {
309
321
  `id='${id}') collides with another per-call key. Pick distinct names.`);
310
322
  }
311
323
  seen.add(id);
312
- out.push({ name, id, bytes });
324
+ out.push({ name, id, wasm: bytes });
313
325
  }
314
326
  return out;
315
327
  }
@@ -345,7 +357,7 @@ export class IsolatePool {
345
357
  return '0';
346
358
  const parts = [];
347
359
  for (const w of entries) {
348
- const u = new Uint8Array(w.bytes);
360
+ const u = new Uint8Array(w.wasm);
349
361
  const len = u.byteLength;
350
362
  // djb2 over the bytes. Faster than crypto.subtle.digest at small
351
363
  // sizes, deterministic, and good enough for cache-key
@@ -372,7 +384,7 @@ export class IsolatePool {
372
384
  #buildCode(fnSource, perCallWasmEntries) {
373
385
  const workerOpts = {
374
386
  compatibilityDate: CF_COMPAT_DATE,
375
- compatibilityFlags: ['nodejs_compat'],
387
+ compatibilityFlags: [...GUEST_COMPAT_FLAGS],
376
388
  // Inherit parent network so the facet can reach registry.npmjs.org.
377
389
  globalOutbound: undefined,
378
390
  env: this.bindings,
@@ -416,7 +428,7 @@ export class IsolatePool {
416
428
  // (matters only for human-readable diffs; workerd doesn't care).
417
429
  const modules = { 'worker.js': moduleSource };
418
430
  for (const w of allWasmEntries) {
419
- modules[w.name] = { wasm: w.bytes };
431
+ modules[w.name] = { wasm: w.wasm };
420
432
  }
421
433
  assertModuleMapWithinCodeLimit(modules);
422
434
  return {
@@ -516,11 +528,10 @@ export class IsolatePool {
516
528
  // binding stub once the whole pool is done, which does NOT
517
529
  // invalidate any in-flight slot's entrypoint reference.
518
530
  const stub = this.loader.get(id, async () => code);
519
- recordLoaderId(this.ctx, id);
520
531
  const entrypoint = stub.getEntrypoint();
521
532
  // Direct property call, awaited by this frame — bracketed, never
522
533
  // wrapped. See beginLoaderFetch for the measured DO-poisoning hazard.
523
- const endFetch = beginLoaderFetch(this.ctx);
534
+ const endFetch = beginLoaderFetch(this.ctx, id);
524
535
  try {
525
536
  return await invoke(entrypoint, attempt);
526
537
  }
@@ -537,6 +548,8 @@ export class IsolatePool {
537
548
  const maxAttempts = 1 + resilience.retries;
538
549
  let lastError;
539
550
  let retriedCloneRefusal = false;
551
+ let capRefusals = 0;
552
+ let capWaitedMs = 0;
540
553
  let attempt = 0;
541
554
  while (attempt < maxAttempts) {
542
555
  try {
@@ -622,6 +635,17 @@ export class IsolatePool {
622
635
  // reverse direction. This refresh targets the stale-loader case.
623
636
  continue;
624
637
  }
638
+ if (cause === 'dynamic_worker_cap' && capWaitedMs < CAP_REFUSAL_WAIT_MS) {
639
+ // The platform refused to start this call: it still counts a
640
+ // worker this Durable Object's ledger has already given back (a
641
+ // fan-out's workers stay counted for a moment after their calls
642
+ // return). Nothing ran, so the call waits, as the platform asks,
643
+ // and is sent again; it does not spend an attempt.
644
+ const delay = Math.min(CAP_REFUSAL_WAIT_MS - capWaitedMs, 50 * 2 ** capRefusals++, 2000);
645
+ capWaitedMs += delay;
646
+ await new Promise((resolve) => setTimeout(resolve, delay));
647
+ continue;
648
+ }
625
649
  if (attempt < maxAttempts - 1) {
626
650
  // 100 * 2^attempt, capped at 2s so retries don't compound waiting.
627
651
  const delay = Math.min(2000, 100 * Math.pow(2, attempt));
@@ -630,9 +654,9 @@ export class IsolatePool {
630
654
  attempt++;
631
655
  }
632
656
  }
633
- // On the way out only, so retries do not stack the annotation: a cap hit
634
- // carries the ledger — which ids hold slots, and that a keyed id never
635
- // gives one back — instead of the platform's bare message.
657
+ // On the way out only, so retries do not stack the annotation: a limit
658
+ // hit carries the ledger — which workers were in flight — instead of the
659
+ // platform's bare message.
636
660
  const named = withDynamicWorkerCapNamed(this.ctx, lastError);
637
661
  if (maxAttempts > 1) {
638
662
  throw new RetryExhaustedError(maxAttempts, named);