@nimbus-sh/fabric 0.4.0 → 0.6.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.
- package/dist/bindings.d.ts +5 -49
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +59 -27
- package/dist/fanout.d.ts +4 -11
- package/dist/fanout.d.ts.map +1 -1
- package/dist/fanout.js +38 -42
- package/dist/generation.d.ts +6 -0
- package/dist/generation.d.ts.map +1 -1
- package/dist/generation.js +6 -0
- package/dist/host-dispatch.d.ts +6 -0
- package/dist/host-dispatch.d.ts.map +1 -0
- package/dist/host-dispatch.js +27 -0
- package/dist/image-store.d.ts +11 -1
- package/dist/image-store.d.ts.map +1 -1
- package/dist/image-store.js +31 -20
- package/dist/isolate-pool.d.ts +40 -0
- package/dist/isolate-pool.d.ts.map +1 -1
- package/dist/isolate-pool.js +120 -14
- package/dist/process-host.d.ts.map +1 -1
- package/dist/process-host.js +60 -33
- package/dist/turn-budget.d.ts +4 -4
- package/dist/turn-budget.js +4 -4
- package/package.json +4 -4
- package/src/bindings.ts +57 -92
- package/src/fanout.ts +48 -67
- package/src/generation.ts +6 -0
- package/src/host-dispatch.ts +34 -0
- package/src/image-store.ts +33 -20
- package/src/isolate-pool.ts +179 -17
- package/src/process-host.ts +64 -44
- package/src/turn-budget.ts +4 -4
package/dist/isolate-pool.d.ts
CHANGED
|
@@ -75,6 +75,15 @@ export interface IsolatePoolOptions {
|
|
|
75
75
|
* not receive a Supervisor binding and do not retain user state.
|
|
76
76
|
*/
|
|
77
77
|
cacheScope?: 'session' | 'global';
|
|
78
|
+
/**
|
|
79
|
+
* Baked into the loader id. Two pools sharing tag, preamble, wasm and
|
|
80
|
+
* supervisor but differing in `scope` never reuse each other's warm
|
|
81
|
+
* workers: an interpreter scope that ENDS (REPL close, Ctrl-C reset)
|
|
82
|
+
* must not resurrect the loader-cached heap for the next owner.
|
|
83
|
+
* Default '' preserves the existing id bytes — only scoped pools get
|
|
84
|
+
* the extra segment.
|
|
85
|
+
*/
|
|
86
|
+
scope?: string;
|
|
78
87
|
/**
|
|
79
88
|
* Override the `doId` baked into the auto-injected SUPERVISOR binding.
|
|
80
89
|
* Default: `ctx.id.toString()` (the DO that constructs the pool).
|
|
@@ -235,6 +244,18 @@ export declare class IsolatePool {
|
|
|
235
244
|
private readonly defaultRetries;
|
|
236
245
|
private readonly tag;
|
|
237
246
|
private readonly slotGenerations;
|
|
247
|
+
/**
|
|
248
|
+
* Per-slot execution ownership: the tail of each slot's in-flight
|
|
249
|
+
* dispatch chain. Two dispatches on the same warm isolate at once
|
|
250
|
+
* interleave on its QueueState — map() callers used to trust the
|
|
251
|
+
* caller's slot round-robin, which could not prevent submit() (slot
|
|
252
|
+
* 0) or a second map() landing on a slot a task still occupied. Every
|
|
253
|
+
* dispatch now waits for the slot's previous execution to settle
|
|
254
|
+
* before touching it.
|
|
255
|
+
*/
|
|
256
|
+
private readonly slotTails;
|
|
257
|
+
/** Set by dispose() — queued dispatches reject instead of running. */
|
|
258
|
+
private disposed;
|
|
238
259
|
private bindings;
|
|
239
260
|
private readonly preamble;
|
|
240
261
|
private readonly preambleHash;
|
|
@@ -262,6 +283,15 @@ export declare class IsolatePool {
|
|
|
262
283
|
* 12 chars is enough entropy for DO ids to collide-free per process.
|
|
263
284
|
*/
|
|
264
285
|
private readonly doIdShort;
|
|
286
|
+
/**
|
|
287
|
+
* The supervisor identity the minted worker's env.SUPERVISOR binding
|
|
288
|
+
* bakes (doId short-form + pid), folded into the loader cache key. 's-none'
|
|
289
|
+
* when no SUPERVISOR binding was minted, so a supervisor-less pool keeps
|
|
290
|
+
* the old key shape and its warm slots stay shared.
|
|
291
|
+
*/
|
|
292
|
+
private readonly supervisorKey;
|
|
293
|
+
/** Extra loader-id segment from options.scope — see IsolatePoolOptions. */
|
|
294
|
+
private readonly scope;
|
|
265
295
|
constructor(env: unknown, ctx: DurableObjectState, opts?: IsolatePoolOptions);
|
|
266
296
|
/** Effective concurrency used when no per-call override is supplied. */
|
|
267
297
|
get defaultConcurrency(): number;
|
|
@@ -270,6 +300,16 @@ export declare class IsolatePool {
|
|
|
270
300
|
* throws TimeoutError / RetryExhaustedError / ExecutionError.
|
|
271
301
|
*/
|
|
272
302
|
submit<T, R>(fn: FacetTaskFn<T, R>, arg: T, opts?: IsolateCallOptions): Promise<Awaited<R>>;
|
|
303
|
+
/**
|
|
304
|
+
* Dispatch `fn` through the fetch transport — the pool's only
|
|
305
|
+
* cancellable path: aborting `request.signal` cancels the inner
|
|
306
|
+
* execution context at its next I/O suspension (a synchronous CPU
|
|
307
|
+
* section cannot be preempted — the platform's honest limit), the
|
|
308
|
+
* call rejects, and the slot generation bumps so the next dispatch
|
|
309
|
+
* gets a fresh interpreter. `fn` is request-shaped: it encodes and
|
|
310
|
+
* decodes its own payload.
|
|
311
|
+
*/
|
|
312
|
+
submitRequest(fn: (request: Request, env: FacetBindings) => Response | Promise<Response>, request: Request, opts?: IsolateCallOptions): Promise<Response>;
|
|
273
313
|
/**
|
|
274
314
|
* Run `fn` on every item in `items`, at most `concurrency` at a time,
|
|
275
315
|
* pinned to stable slots so warm isolates are reused.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isolate-pool.d.ts","sourceRoot":"","sources":["../src/isolate-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAgBH,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;;;;;;;;;;;;;;;;OAgBG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;;;;;;;;;;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,
|
|
1
|
+
{"version":3,"file":"isolate-pool.d.ts","sourceRoot":"","sources":["../src/isolate-pool.ts"],"names":[],"mappings":"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"}
|
package/dist/isolate-pool.js
CHANGED
|
@@ -85,7 +85,10 @@ export function assembleLoaderWorkerModuleSource(options) {
|
|
|
85
85
|
const callExpr = options.hasBindings
|
|
86
86
|
? '__fn__(...args, this.env)'
|
|
87
87
|
: '__fn__(...args)';
|
|
88
|
-
|
|
88
|
+
const requestCallExpr = options.hasBindings
|
|
89
|
+
? '__fn__(request, this.env)'
|
|
90
|
+
: '__fn__(request)';
|
|
91
|
+
lines.push('export default class extends WorkerEntrypoint {', ' execute(...args) {', ` const result = ${callExpr};`, ' if (result instanceof Promise) return result;', ' return result;', ' }', '', ' // Request/Response transport: the ONE dispatch path whose abort is', ' // real. workerd cancels the inner execution context when the request', ' // signal aborts while the fn is suspended on I/O; RPC execute() above', ' // has no equivalent (measured: Symbol.dispose on a pending RPC does', ' // not cancel). The fn is request-shaped — it encodes and decodes its', ' // own payload; nothing generic is serialized here.', ' async fetch(request) {', ' try {', ` const result = await ${requestCallExpr};`, ' if (result instanceof Response) return result;', ' return Response.json(result ?? null);', ' } catch (err) {', ' // Fail loud inside the response so an abort of the transport is', ' // not conflated with a guest exception: the caller sees 500 only', ' // for a real fn failure, and aborts arrive as fetch rejections.', ' const message = err instanceof Error && err.message ? err.message : String(err);', ' return Response.json({ __nimbusFacetError: message }, { status: 500 });', ' }', ' }', '}');
|
|
89
92
|
return lines.join('\n');
|
|
90
93
|
}
|
|
91
94
|
/**
|
|
@@ -113,6 +116,18 @@ export class IsolatePool {
|
|
|
113
116
|
defaultRetries;
|
|
114
117
|
tag;
|
|
115
118
|
slotGenerations = new Map();
|
|
119
|
+
/**
|
|
120
|
+
* Per-slot execution ownership: the tail of each slot's in-flight
|
|
121
|
+
* dispatch chain. Two dispatches on the same warm isolate at once
|
|
122
|
+
* interleave on its QueueState — map() callers used to trust the
|
|
123
|
+
* caller's slot round-robin, which could not prevent submit() (slot
|
|
124
|
+
* 0) or a second map() landing on a slot a task still occupied. Every
|
|
125
|
+
* dispatch now waits for the slot's previous execution to settle
|
|
126
|
+
* before touching it.
|
|
127
|
+
*/
|
|
128
|
+
slotTails = new Map();
|
|
129
|
+
/** Set by dispose() — queued dispatches reject instead of running. */
|
|
130
|
+
disposed = false;
|
|
116
131
|
bindings;
|
|
117
132
|
preamble;
|
|
118
133
|
preambleHash;
|
|
@@ -140,6 +155,15 @@ export class IsolatePool {
|
|
|
140
155
|
* 12 chars is enough entropy for DO ids to collide-free per process.
|
|
141
156
|
*/
|
|
142
157
|
doIdShort;
|
|
158
|
+
/**
|
|
159
|
+
* The supervisor identity the minted worker's env.SUPERVISOR binding
|
|
160
|
+
* bakes (doId short-form + pid), folded into the loader cache key. 's-none'
|
|
161
|
+
* when no SUPERVISOR binding was minted, so a supervisor-less pool keeps
|
|
162
|
+
* the old key shape and its warm slots stay shared.
|
|
163
|
+
*/
|
|
164
|
+
supervisorKey;
|
|
165
|
+
/** Extra loader-id segment from options.scope — see IsolatePoolOptions. */
|
|
166
|
+
scope;
|
|
143
167
|
constructor(env, ctx, opts) {
|
|
144
168
|
// A host hands its whole env over; the binding is claimed here and the
|
|
145
169
|
// claim is checked on the next line.
|
|
@@ -161,6 +185,7 @@ export class IsolatePool {
|
|
|
161
185
|
this.doIdShort = opts?.cacheScope === 'global'
|
|
162
186
|
? 'global'
|
|
163
187
|
: ctx.id.toString().slice(0, 12);
|
|
188
|
+
this.scope = opts?.scope ?? '';
|
|
164
189
|
// Materialise the wasm-modules table. Sanitise each name into a
|
|
165
190
|
// valid JS identifier for the static import binding; key collisions
|
|
166
191
|
// (e.g. 'esbuild.wasm' and 'esbuild_wasm' both sanitise to
|
|
@@ -207,6 +232,7 @@ export class IsolatePool {
|
|
|
207
232
|
this.wasmHash = hashSource(fp);
|
|
208
233
|
}
|
|
209
234
|
const bindings = { ...(opts?.extraBindings ?? {}) };
|
|
235
|
+
this.supervisorKey = 's-none';
|
|
210
236
|
if (!opts?.omitSupervisor) {
|
|
211
237
|
const supervisorRpc = supervisorEntrypoint();
|
|
212
238
|
if (supervisorRpc) {
|
|
@@ -215,9 +241,19 @@ export class IsolatePool {
|
|
|
215
241
|
// to the user's session DO, not the peer DO. Default to the
|
|
216
242
|
// local ctx.id (single-DO callers and the in-DO in-DO fanout path).
|
|
217
243
|
const supDoId = opts?.supervisorDoIdOverride ?? ctx.id.toString();
|
|
244
|
+
const supPid = opts?.supervisorPid ?? 0;
|
|
218
245
|
bindings.SUPERVISOR = supervisorRpc({
|
|
219
|
-
props: { doId: supDoId, pid:
|
|
246
|
+
props: { doId: supDoId, pid: supPid },
|
|
220
247
|
});
|
|
248
|
+
// Whatever the minted worker's env carries must be in its loader
|
|
249
|
+
// cache key — workerd's loader cache survives a DO hibernation
|
|
250
|
+
// wake while generation-strided pids (1000001 → 2000001) do not:
|
|
251
|
+
// a warm slot keyed without the supervisor identity returns in
|
|
252
|
+
// the new generation still credentialed to the dead pid, and
|
|
253
|
+
// every pid-authorized RPC from it fails "process pid … does
|
|
254
|
+
// 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}`;
|
|
221
257
|
}
|
|
222
258
|
else {
|
|
223
259
|
// Supervisor entrypoint unavailable — running without ctx.exports
|
|
@@ -398,8 +434,41 @@ export class IsolatePool {
|
|
|
398
434
|
/**
|
|
399
435
|
* Dispatch a single task to the slot isolate. `slotIndex` picks which
|
|
400
436
|
* warm isolate services the call; callers round-robin slots themselves.
|
|
437
|
+
* Serialized per slot: this call waits for the slot's previous
|
|
438
|
+
* execution to settle before touching the warm isolate.
|
|
439
|
+
*
|
|
440
|
+
* `invoke` is the single-attempt call against the slot's entrypoint
|
|
441
|
+
* stub. `execute` dispatches call `entrypoint.execute(...args)`;
|
|
442
|
+
* `fetch` dispatches call `entrypoint.fetch(<per-attempt Request>)` —
|
|
443
|
+
* the attempt index lets a fetch invoke mint a fresh clone for retries,
|
|
444
|
+
* since a Request's body is consumed once.
|
|
401
445
|
*/
|
|
402
|
-
async #dispatchSlot(fnSource, fnHash, slotIndex,
|
|
446
|
+
async #dispatchSlot(fnSource, fnHash, slotIndex, invoke, resilience, perCallWasm, wasAborted) {
|
|
447
|
+
// A warm slot executes one dispatch at a time: queue behind the
|
|
448
|
+
// previous owner, then record this dispatch as the new tail. The
|
|
449
|
+
// tail outlives the caller's outcome: a timeout rejects the
|
|
450
|
+
// Promise.race while runOnce's RPC is still live on the Worker, so
|
|
451
|
+
// release waits for every execution the owned body started to
|
|
452
|
+
// settle — never the caller's settle alone.
|
|
453
|
+
const previous = this.slotTails.get(slotIndex) ?? Promise.resolve();
|
|
454
|
+
let release;
|
|
455
|
+
this.slotTails.set(slotIndex, new Promise((resolve) => { release = resolve; }));
|
|
456
|
+
await previous;
|
|
457
|
+
if (this.disposed) {
|
|
458
|
+
release();
|
|
459
|
+
throw new BindingError(`IsolatePool(${this.tag}) is disposed`);
|
|
460
|
+
}
|
|
461
|
+
const inFlight = [];
|
|
462
|
+
try {
|
|
463
|
+
return await this.#dispatchSlotOwned(fnSource, fnHash, slotIndex, invoke, resilience, perCallWasm, inFlight, wasAborted);
|
|
464
|
+
}
|
|
465
|
+
finally {
|
|
466
|
+
// Do not delay the caller's own outcome — the tail releases when
|
|
467
|
+
// the RPCs the body launched have actually settled.
|
|
468
|
+
void Promise.allSettled(inFlight).then(() => release());
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async #dispatchSlotOwned(fnSource, fnHash, slotIndex, invoke, resilience, perCallWasm, inFlight, wasAborted) {
|
|
403
472
|
// Per-call wasm fingerprint. Mixed into the cache key so two calls
|
|
404
473
|
// with different bytes hit different slots (no cache poisoning).
|
|
405
474
|
// For the common case (no per-call wasm) the fingerprint is '0',
|
|
@@ -407,11 +476,11 @@ export class IsolatePool {
|
|
|
407
476
|
const perCallWasmEntries = this.#materialisePerCallWasm(perCallWasm);
|
|
408
477
|
const perCallWasmHash = this.#fingerprintWasm(perCallWasmEntries);
|
|
409
478
|
// Cache key includes the short DO id so warm isolates are scoped to
|
|
410
|
-
// ONE session
|
|
411
|
-
// a
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
const buildId = (generation) => `nfp:${this.tag}:${this.doIdShort}:${fnHash}:${this.preambleHash}:${this.wasmHash}:${perCallWasmHash}:slot-${slotIndex}:g${generation}`;
|
|
479
|
+
// ONE session (see the doIdShort field comment), and the supervisor
|
|
480
|
+
// identity so a wake of that same session can never reuse a warm
|
|
481
|
+
// worker whose SUPERVISOR binding still names the dead generation's
|
|
482
|
+
// pid. See the supervisorKey field comment for the failure mode.
|
|
483
|
+
const buildId = (generation) => `nfp:${this.tag}:${this.doIdShort}:${fnHash}:${this.preambleHash}:${this.wasmHash}:${perCallWasmHash}:${this.supervisorKey}:slot-${slotIndex}:g${generation}${this.scope ? `:${this.scope}` : ''}`;
|
|
415
484
|
let id = buildId(this.slotGenerations.get(slotIndex) ?? 0);
|
|
416
485
|
const code = this.#buildCode(fnSource, perCallWasmEntries);
|
|
417
486
|
// W5 Lever 5: record the dispatch so /api/_diag/memory shows the
|
|
@@ -453,8 +522,7 @@ export class IsolatePool {
|
|
|
453
522
|
// wrapped. See beginLoaderFetch for the measured DO-poisoning hazard.
|
|
454
523
|
const endFetch = beginLoaderFetch(this.ctx);
|
|
455
524
|
try {
|
|
456
|
-
|
|
457
|
-
return out;
|
|
525
|
+
return await invoke(entrypoint, attempt);
|
|
458
526
|
}
|
|
459
527
|
catch (err) {
|
|
460
528
|
if (err instanceof Error) {
|
|
@@ -489,8 +557,10 @@ export class IsolatePool {
|
|
|
489
557
|
// See plan in close-plan-2026-04-28.
|
|
490
558
|
let timerId;
|
|
491
559
|
try {
|
|
560
|
+
const attemptPromise = runOnce();
|
|
561
|
+
inFlight.push(attemptPromise);
|
|
492
562
|
return await Promise.race([
|
|
493
|
-
|
|
563
|
+
attemptPromise,
|
|
494
564
|
new Promise((_, reject) => {
|
|
495
565
|
timerId = setTimeout(() => reject(new TimeoutError(resilience.timeoutMs)), resilience.timeoutMs);
|
|
496
566
|
}),
|
|
@@ -501,7 +571,9 @@ export class IsolatePool {
|
|
|
501
571
|
clearTimeout(timerId);
|
|
502
572
|
}
|
|
503
573
|
}
|
|
504
|
-
|
|
574
|
+
const attemptPromise = runOnce();
|
|
575
|
+
inFlight.push(attemptPromise);
|
|
576
|
+
return await attemptPromise;
|
|
505
577
|
}
|
|
506
578
|
catch (err) {
|
|
507
579
|
lastError = err instanceof Error ? err : new Error(String(err));
|
|
@@ -524,6 +596,18 @@ export class IsolatePool {
|
|
|
524
596
|
});
|
|
525
597
|
}
|
|
526
598
|
catch { /* fail-soft */ }
|
|
599
|
+
if (wasAborted?.()) {
|
|
600
|
+
// The caller aborted this dispatch's Request. workerd cancelled
|
|
601
|
+
// the isolate's execution context wherever it was suspended —
|
|
602
|
+
// mid-syscall, mid-stream — so the interpreter's heap may hold
|
|
603
|
+
// half-applied state. Bump the slot generation: the next
|
|
604
|
+
// dispatch lands on a FRESH worker under a new id rather than
|
|
605
|
+
// reusing the abandoned one. Retrying is wrong — the user
|
|
606
|
+
// interrupted; surface the abort.
|
|
607
|
+
const generation = (this.slotGenerations.get(slotIndex) ?? 0) + 1;
|
|
608
|
+
this.slotGenerations.set(slotIndex, generation);
|
|
609
|
+
throw lastError;
|
|
610
|
+
}
|
|
527
611
|
if (cause === 'clone_refused' && !retriedCloneRefusal) {
|
|
528
612
|
retriedCloneRefusal = true;
|
|
529
613
|
const generation = (this.slotGenerations.get(slotIndex) ?? 0) + 1;
|
|
@@ -567,7 +651,28 @@ export class IsolatePool {
|
|
|
567
651
|
async submit(fn, arg, opts) {
|
|
568
652
|
const { fnSource, fnHash } = this.#prepare(fn);
|
|
569
653
|
const resilience = this.#resolve(opts);
|
|
570
|
-
return (await this.#dispatchSlot(fnSource, fnHash, 0,
|
|
654
|
+
return (await this.#dispatchSlot(fnSource, fnHash, 0, (entrypoint) => entrypoint.execute(arg), resilience, opts?.wasmModules));
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Dispatch `fn` through the fetch transport — the pool's only
|
|
658
|
+
* cancellable path: aborting `request.signal` cancels the inner
|
|
659
|
+
* execution context at its next I/O suspension (a synchronous CPU
|
|
660
|
+
* section cannot be preempted — the platform's honest limit), the
|
|
661
|
+
* call rejects, and the slot generation bumps so the next dispatch
|
|
662
|
+
* gets a fresh interpreter. `fn` is request-shaped: it encodes and
|
|
663
|
+
* decodes its own payload.
|
|
664
|
+
*/
|
|
665
|
+
async submitRequest(fn, request, opts) {
|
|
666
|
+
if (request.bodyUsed) {
|
|
667
|
+
throw new BindingError('IsolatePool.submitRequest: request body is already consumed — ' +
|
|
668
|
+
'pass an unconsumed Request so retries can re-issue it.');
|
|
669
|
+
}
|
|
670
|
+
const { fnSource, fnHash } = this.#prepare(fn);
|
|
671
|
+
const resilience = this.#resolve(opts);
|
|
672
|
+
// Every attempt fetches a clone: a Request's body is consumed once,
|
|
673
|
+
// so the caller's original stays unspent and retries get a fresh
|
|
674
|
+
// body that follows the same signal.
|
|
675
|
+
return this.#dispatchSlot(fnSource, fnHash, 0, (entrypoint) => entrypoint.fetch(request.clone()), resilience, opts?.wasmModules, () => request.signal.aborted);
|
|
571
676
|
}
|
|
572
677
|
/**
|
|
573
678
|
* Run `fn` on every item in `items`, at most `concurrency` at a time,
|
|
@@ -616,7 +721,7 @@ export class IsolatePool {
|
|
|
616
721
|
if (idx >= items.length)
|
|
617
722
|
return;
|
|
618
723
|
try {
|
|
619
|
-
const value = (await this.#dispatchSlot(fnSource, fnHash, slotIndex,
|
|
724
|
+
const value = (await this.#dispatchSlot(fnSource, fnHash, slotIndex, (entrypoint) => entrypoint.execute(items[idx]), resilience, opts?.wasmModules));
|
|
620
725
|
settled[idx] = { ok: true, value };
|
|
621
726
|
}
|
|
622
727
|
catch (err) {
|
|
@@ -655,6 +760,7 @@ export class IsolatePool {
|
|
|
655
760
|
* Safe to call more than once; idempotent.
|
|
656
761
|
*/
|
|
657
762
|
dispose() {
|
|
763
|
+
this.disposed = true;
|
|
658
764
|
if (!this.bindings)
|
|
659
765
|
return;
|
|
660
766
|
for (const key of Object.keys(this.bindings)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-host.d.ts","sourceRoot":"","sources":["../src/process-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;
|
|
1
|
+
{"version":3,"file":"process-host.d.ts","sourceRoot":"","sources":["../src/process-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AAOH,OAAO,EAGL,KAAK,WAAW,EAIhB,KAAK,kBAAkB,EAExB,MAAM,qBAAqB,CAAC;AAQ7B,4DAA4D;AAC5D,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/C;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,eAAe,EACrB,GAAG,EAAE,kBAAkB,EACvB,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,MAAM,kBAAkB,GAC7B,WAAW,CAIb;AA8ED,wBAAgB,YAAY,IAAI,MAAM,CAGrC;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5B,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;IAC5B,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;CAC7B;AAaD;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,8BAA8B,CAAC;AACvE,eAAO,MAAM,kCAAkC,yCAAyC,CAAC;AAgUzF;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAWhE"}
|
package/dist/process-host.js
CHANGED
|
@@ -66,6 +66,8 @@
|
|
|
66
66
|
import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
67
67
|
import { isTransientDoReset } from '@nimbus-sh/platform/oom-classify.js';
|
|
68
68
|
import { PEER_RETRY_BACKOFF_MS, PEER_TRANSIENT_RESET_RETRIES } from './fanout.js';
|
|
69
|
+
import { hostNamespaceBinding, hostOpDispatch } from './host-dispatch.js';
|
|
70
|
+
import { z } from 'zod/v4';
|
|
69
71
|
import { DYNAMIC_WORKER_CODE_LIMIT_BYTES } from './budgets.js';
|
|
70
72
|
import { BindingError } from './vendor/errors.js';
|
|
71
73
|
import { processes, } from './workerd-facet-host.js';
|
|
@@ -149,19 +151,6 @@ export function isolateToken() {
|
|
|
149
151
|
_isolateToken = crypto.randomUUID();
|
|
150
152
|
return _isolateToken;
|
|
151
153
|
}
|
|
152
|
-
/**
|
|
153
|
-
* Every method a hosting sibling must answer. Checked as a set at first
|
|
154
|
-
* contact: a stub that has the probe but not the router is a deployment skew,
|
|
155
|
-
* and finding that out mid-process is finding it out too late.
|
|
156
|
-
*/
|
|
157
|
-
const PROCESS_PEER_METHODS = [
|
|
158
|
-
'_rpcProcessHostProbe',
|
|
159
|
-
'_rpcHostProcess',
|
|
160
|
-
'_rpcAwaitHostedOpen',
|
|
161
|
-
'_rpcAwaitHostedBoot',
|
|
162
|
-
'_rpcRouteHostedHttp',
|
|
163
|
-
'_rpcCancelHostProcess',
|
|
164
|
-
];
|
|
165
154
|
/**
|
|
166
155
|
* Which hosted process a fetched upgrade is for. An upgrade cannot travel as
|
|
167
156
|
* RPC arguments, so the two values `_rpcRouteHostedHttp` would have taken ride
|
|
@@ -174,31 +163,66 @@ const PROCESS_PEER_METHODS = [
|
|
|
174
163
|
*/
|
|
175
164
|
export const HOSTED_WEBSOCKET_KEY_HEADER = 'x-nimbus-hosted-websocket';
|
|
176
165
|
export const HOSTED_WEBSOCKET_CAPABILITY_HEADER = 'x-nimbus-hosted-websocket-capability';
|
|
166
|
+
/**
|
|
167
|
+
* Peer stubs forward one supervisorOp entrypoint: every method the host-
|
|
168
|
+
* process surface needs is an envelope op, not a private _rpc* method.
|
|
169
|
+
*/
|
|
177
170
|
function peerNamespace(env) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
171
|
+
try {
|
|
172
|
+
return hostNamespaceBinding(env, 'ProcessFabric');
|
|
173
|
+
}
|
|
174
|
+
catch (e) {
|
|
175
|
+
if (e instanceof BindingError) {
|
|
176
|
+
throw new BindingError(e.message
|
|
177
|
+
+ " NIMBUS_PROCESS_HOST='peer' hosts every resident process on a sibling "
|
|
178
|
+
+ "Durable Object; name the host's own binding with composeFabric({ hostNamespace }).");
|
|
179
|
+
}
|
|
180
|
+
throw e;
|
|
187
181
|
}
|
|
188
|
-
return ns;
|
|
189
182
|
}
|
|
183
|
+
const PeerProbeResult = z.object({ isolateToken: z.string() });
|
|
184
|
+
const PeerOpenResult = z.object({ ok: z.boolean() });
|
|
185
|
+
const PeerBootResult = z.object({ payload: z.unknown() });
|
|
186
|
+
const PeerCancelResult = z.object({ cancelled: z.boolean() });
|
|
187
|
+
const PeerHttpResult = z.object({
|
|
188
|
+
status: z.number().int(),
|
|
189
|
+
statusText: z.string(),
|
|
190
|
+
headers: z.array(z.tuple([z.string(), z.string()])),
|
|
191
|
+
body: z.instanceof(ReadableStream).nullable(),
|
|
192
|
+
});
|
|
190
193
|
function processPeerStub(value, peerName) {
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
const dispatch = hostOpDispatch(value, `ProcessFabric peer '${peerName}'`);
|
|
195
|
+
const fetchFn = (typeof value === 'object' || typeof value === 'function') && value !== null
|
|
196
|
+
? Reflect.get(value, 'fetch')
|
|
197
|
+
: undefined;
|
|
198
|
+
if (typeof fetchFn !== 'function') {
|
|
199
|
+
throw new BindingError(`ProcessFabric: peer '${peerName}' exposes no fetch() — the hosted-websocket `
|
|
200
|
+
+ 'upgrade leg still travels as a service-binding fetch.');
|
|
193
201
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
202
|
+
const adapter = {
|
|
203
|
+
_rpcProcessHostProbe: async () => PeerProbeResult.parse(await dispatch({ op: 'processHostProbe', args: [] })),
|
|
204
|
+
_rpcHostProcess: async (boot, opts) => PeerOpenResult.parse(await dispatch({ op: 'hostProcess', args: [boot, opts] })),
|
|
205
|
+
_rpcAwaitHostedOpen: async (key) => PeerOpenResult.parse(await dispatch({ op: 'awaitHostedOpen', args: [key] })),
|
|
206
|
+
_rpcAwaitHostedBoot: async (key) => {
|
|
207
|
+
const result = PeerBootResult.parse(await dispatch({ op: 'awaitHostedBoot', args: [key] }));
|
|
208
|
+
return { payload: result.payload };
|
|
209
|
+
},
|
|
210
|
+
_rpcRouteHostedHttp: async (key, request) => PeerHttpResult.parse(await dispatch({ op: 'routeHostedHttp', args: [key, request] })),
|
|
211
|
+
_rpcCancelHostProcess: async (key) => PeerCancelResult.parse(await dispatch({ op: 'cancelHostProcess', args: [key] })),
|
|
212
|
+
fetch: async (request) => {
|
|
213
|
+
const response = await Reflect.apply(fetchFn, value, [request]);
|
|
214
|
+
if (!(response instanceof Response))
|
|
215
|
+
throw new BindingError(`ProcessFabric: peer '${peerName}' returned no Response`);
|
|
216
|
+
return response;
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
// The adapter wraps the raw stub; releasing the adapter must release it —
|
|
220
|
+
// a held RPC stub pins the peer DO for the session's life.
|
|
221
|
+
const disposerKey = Reflect.get(Symbol, 'dispose');
|
|
222
|
+
if (typeof disposerKey === 'symbol') {
|
|
223
|
+
Object.defineProperty(adapter, disposerKey, { value: () => disposeRpcResource(value) });
|
|
200
224
|
}
|
|
201
|
-
return
|
|
225
|
+
return adapter;
|
|
202
226
|
}
|
|
203
227
|
class PeerProcessHost {
|
|
204
228
|
ctx;
|
|
@@ -219,6 +243,9 @@ class PeerProcessHost {
|
|
|
219
243
|
tokensInUse = new Map();
|
|
220
244
|
constructor(ctx, env) {
|
|
221
245
|
this.ctx = ctx;
|
|
246
|
+
if (env === null || (typeof env !== 'object' && typeof env !== 'function')) {
|
|
247
|
+
throw new BindingError('ProcessFabric: a peer host requires environment bindings');
|
|
248
|
+
}
|
|
222
249
|
this.ns = peerNamespace(env);
|
|
223
250
|
this.env = (env ?? {});
|
|
224
251
|
this.coordDoId = ctx.id.toString();
|
|
@@ -339,7 +366,7 @@ class PeerProcessHost {
|
|
|
339
366
|
/** One sibling name, resolved and probed. Leaks nothing on failure. */
|
|
340
367
|
async _probePlacement(pid, attempt) {
|
|
341
368
|
const peerName = `${this.coordDoId}:proc:${pid}:${attempt}`;
|
|
342
|
-
let resource;
|
|
369
|
+
let resource = null;
|
|
343
370
|
try {
|
|
344
371
|
resource = this.ns.get(this.ns.idFromName(peerName));
|
|
345
372
|
const stub = processPeerStub(resource, peerName);
|
package/dist/turn-budget.d.ts
CHANGED
|
@@ -58,10 +58,10 @@ export declare const TURN_CHUNK_MAX_BYTES = 2000000;
|
|
|
58
58
|
* spent.
|
|
59
59
|
*
|
|
60
60
|
* Callers report the work they are about to do or have just done and await
|
|
61
|
-
* the result; a pacer that is not yielding returns without suspending, so
|
|
62
|
-
*
|
|
63
|
-
* behaviour and cost. Nothing here decides WHAT the
|
|
64
|
-
* is allowed to stop.
|
|
61
|
+
* the result; a pacer that is not yielding returns without suspending, so a
|
|
62
|
+
* build small enough to fit in one chunk — most one-shot execs, every cache
|
|
63
|
+
* hit — keeps its exact behaviour and cost. Nothing here decides WHAT the
|
|
64
|
+
* launch does, only where it is allowed to stop.
|
|
65
65
|
*/
|
|
66
66
|
export declare class TurnBudget {
|
|
67
67
|
private readonly scheduler;
|
package/dist/turn-budget.js
CHANGED
|
@@ -47,10 +47,10 @@ export const TURN_CHUNK_MAX_BYTES = 2_000_000;
|
|
|
47
47
|
* spent.
|
|
48
48
|
*
|
|
49
49
|
* Callers report the work they are about to do or have just done and await
|
|
50
|
-
* the result; a pacer that is not yielding returns without suspending, so
|
|
51
|
-
*
|
|
52
|
-
* behaviour and cost. Nothing here decides WHAT the
|
|
53
|
-
* is allowed to stop.
|
|
50
|
+
* the result; a pacer that is not yielding returns without suspending, so a
|
|
51
|
+
* build small enough to fit in one chunk — most one-shot execs, every cache
|
|
52
|
+
* hit — keeps its exact behaviour and cost. Nothing here decides WHAT the
|
|
53
|
+
* launch does, only where it is allowed to stop.
|
|
54
54
|
*/
|
|
55
55
|
export class TurnBudget {
|
|
56
56
|
scheduler;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbus-sh/fabric",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "The Cloudflare half of Nimbus — Durable Object facet hosting, dynamic-worker loader pools, the resident-process fabric, and DO alarm/hibernation machinery.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
],
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -p tsconfig.json --noCheck --noEmit false --declaration true --declarationMap true --outDir dist --rootDir src",
|
|
49
|
-
"prepack": "
|
|
49
|
+
"prepack": "bun run build",
|
|
50
50
|
"typecheck": "tsc --noEmit"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@nimbus-sh/core": "^0.
|
|
54
|
-
"@nimbus-sh/platform": "^0.
|
|
53
|
+
"@nimbus-sh/core": "^0.10.0",
|
|
54
|
+
"@nimbus-sh/platform": "^0.4.0",
|
|
55
55
|
"zod": "^4.4.3"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|