@nimbus-sh/fabric 0.8.0 → 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.
- package/README.md +26 -11
- package/dist/budgets.d.ts +50 -29
- package/dist/budgets.d.ts.map +1 -1
- package/dist/budgets.js +88 -40
- package/dist/do-calls.d.ts +20 -0
- package/dist/do-calls.d.ts.map +1 -1
- package/dist/do-calls.js +24 -11
- package/dist/fanout.d.ts +40 -32
- package/dist/fanout.d.ts.map +1 -1
- package/dist/fanout.js +46 -50
- package/dist/host-wasm.d.ts +29 -0
- package/dist/host-wasm.d.ts.map +1 -0
- package/dist/host-wasm.js +31 -0
- package/dist/image-store.d.ts +1 -1
- package/dist/image-store.d.ts.map +1 -1
- package/dist/image-store.js +33 -1
- package/dist/isolate-pool.d.ts +31 -23
- package/dist/isolate-pool.d.ts.map +1 -1
- package/dist/isolate-pool.js +68 -46
- package/dist/process-fabric.d.ts +26 -11
- package/dist/process-fabric.d.ts.map +1 -1
- package/dist/process-fabric.js +44 -0
- package/dist/timers.d.ts +12 -0
- package/dist/timers.d.ts.map +1 -1
- package/dist/timers.js +44 -9
- package/dist/vendor/types.d.ts +11 -5
- package/dist/vendor/types.d.ts.map +1 -1
- package/dist/workerd-facet-host.d.ts.map +1 -1
- package/dist/workerd-facet-host.js +30 -30
- package/package.json +4 -4
- package/src/budgets.ts +96 -49
- package/src/do-calls.ts +45 -11
- package/src/fanout.ts +60 -53
- package/src/host-wasm.ts +41 -0
- package/src/image-store.ts +29 -2
- package/src/isolate-pool.ts +98 -66
- package/src/process-fabric.ts +55 -13
- package/src/timers.ts +45 -9
- package/src/vendor/types.ts +11 -5
- package/src/workerd-facet-host.ts +32 -31
package/src/process-fabric.ts
CHANGED
|
@@ -133,6 +133,14 @@ export const ResidentCodeSpecSchema = z.object({
|
|
|
133
133
|
* image store below and the spec names it.
|
|
134
134
|
*/
|
|
135
135
|
vfsTextModules: z.record(z.string(), z.string()).optional(),
|
|
136
|
+
/**
|
|
137
|
+
* VFS paths of generated CommonJS PACKS (encodeCommonJsPack): each image
|
|
138
|
+
* carries many `{ cjs }` modules, and the map gains every one of them at
|
|
139
|
+
* load. A node process's module cells travel this way — one module per
|
|
140
|
+
* file, so the guest's registry compiles only what the program requires,
|
|
141
|
+
* and one image per launch, so the boot spec names a path and not thousands.
|
|
142
|
+
*/
|
|
143
|
+
vfsCommonJsPacks: z.array(z.string()).optional(),
|
|
136
144
|
/**
|
|
137
145
|
* The isolate's exact `env`: one entry per binding the embedder minted,
|
|
138
146
|
* carried by reference so loopback stubs survive untouched. Defined —
|
|
@@ -265,7 +273,7 @@ export async function residentLoaderConfig(
|
|
|
265
273
|
spec: ResidentCodeSpec,
|
|
266
274
|
disk: ResidentDiskReader,
|
|
267
275
|
): Promise<Record<string, unknown>> {
|
|
268
|
-
const resolved: Record<string, string | { wasm: ArrayBuffer }> = {};
|
|
276
|
+
const resolved: Record<string, string | { wasm: ArrayBuffer } | { cjs: string }> = {};
|
|
269
277
|
for (const [moduleName, path] of Object.entries(spec.vfsWasmModules ?? {})) {
|
|
270
278
|
const bytes = await disk.readFile(path);
|
|
271
279
|
// The read's own buffer when it fits exactly, and only otherwise a copy.
|
|
@@ -283,6 +291,9 @@ export async function residentLoaderConfig(
|
|
|
283
291
|
for (const [moduleName, path] of Object.entries(spec.vfsTextModules ?? {})) {
|
|
284
292
|
resolved[moduleName] = await readFacetImage(disk, path);
|
|
285
293
|
}
|
|
294
|
+
for (const path of spec.vfsCommonJsPacks ?? []) {
|
|
295
|
+
Object.assign(resolved, decodeCommonJsPack(await readFacetImage(disk, path)));
|
|
296
|
+
}
|
|
286
297
|
return {
|
|
287
298
|
compatibilityDate: spec.compatibilityDate,
|
|
288
299
|
compatibilityFlags: spec.compatibilityFlags,
|
|
@@ -293,6 +304,40 @@ export async function residentLoaderConfig(
|
|
|
293
304
|
};
|
|
294
305
|
}
|
|
295
306
|
|
|
307
|
+
/**
|
|
308
|
+
* One image holding many `{ cjs }` modules: a JSON index of `[name, length]`
|
|
309
|
+
* rows, a newline, and the module texts back to back. Lengths are UTF-16 code
|
|
310
|
+
* units, the unit the decoded text is sliced in, so decoding copies nothing:
|
|
311
|
+
* each module is a slice of the one string read.
|
|
312
|
+
*
|
|
313
|
+
* Encoded as its parts, in order, never joined: the image store encodes them
|
|
314
|
+
* straight into the image's bytes, and a joined copy would be a second full
|
|
315
|
+
* copy of the program's code on the coordinator.
|
|
316
|
+
*/
|
|
317
|
+
export function encodeCommonJsPack(modules: Record<string, string>): string[] {
|
|
318
|
+
const index: [string, number][] = [];
|
|
319
|
+
const parts: string[] = [''];
|
|
320
|
+
for (const [name, text] of Object.entries(modules)) {
|
|
321
|
+
index.push([name, text.length]);
|
|
322
|
+
parts.push(text);
|
|
323
|
+
}
|
|
324
|
+
parts[0] = JSON.stringify(index) + '\n';
|
|
325
|
+
return parts;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function decodeCommonJsPack(pack: string): Record<string, { cjs: string }> {
|
|
329
|
+
const newline = pack.indexOf('\n');
|
|
330
|
+
const index = z.array(z.tuple([z.string(), z.number().int().nonnegative()])).parse(JSON.parse(pack.slice(0, newline)));
|
|
331
|
+
const modules: Record<string, { cjs: string }> = {};
|
|
332
|
+
let offset = newline + 1;
|
|
333
|
+
for (const [name, length] of index) {
|
|
334
|
+
modules[name] = { cjs: pack.slice(offset, offset + length) };
|
|
335
|
+
offset += length;
|
|
336
|
+
}
|
|
337
|
+
if (offset !== pack.length) throw new Error(`Nimbus: CommonJS pack holds ${pack.length - offset} bytes its index does not name`);
|
|
338
|
+
return modules;
|
|
339
|
+
}
|
|
340
|
+
|
|
296
341
|
/**
|
|
297
342
|
* Read one content-addressed facet image and verify it against the digest its
|
|
298
343
|
* path claims. Content addressing is only a guarantee if the bytes are checked
|
|
@@ -428,17 +473,14 @@ export interface ProcessImageDelivery {
|
|
|
428
473
|
* across one. A peer-hosted process can only ever receive an image
|
|
429
474
|
* through `moduleCeilingBytes` below, or by streaming it.
|
|
430
475
|
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
* workerd
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
* destination of a DIFFERENT class had all 500 seeded files readable from the
|
|
440
|
-
* destination's CONSTRUCTOR. No compat-date gate. So calling it is a
|
|
441
|
-
* lockfile-and-types problem, not a platform one.
|
|
476
|
+
* Present in production, and since the pins moved to
|
|
477
|
+
* `@cloudflare/workers-types` 5.20260928.1 and workerd 1.20260926.1 also in
|
|
478
|
+
* the type checker and `wrangler dev` (DurableObjectFacets.clone;
|
|
479
|
+
* src/workerd/api/actor-state.h). Before that the types declared no `clone`
|
|
480
|
+
* and the pinned workerd 1.20260603.1 lacked it, while on a live Worker the
|
|
481
|
+
* binding enumerated `["abort","clone","constructor","delete","get"]` and a
|
|
482
|
+
* clone into a destination of a DIFFERENT class had all 500 seeded files
|
|
483
|
+
* readable from the destination's CONSTRUCTOR. No compat-date gate.
|
|
442
484
|
*
|
|
443
485
|
* The hazard that comes with it, measured rather than assumed: ANY `src`
|
|
444
486
|
* that does not resolve to a populated facet — a typo, a name not created
|
|
@@ -488,7 +530,7 @@ export interface OneShotCodeSpec {
|
|
|
488
530
|
compatibilityDate: string;
|
|
489
531
|
compatibilityFlags: string[];
|
|
490
532
|
mainModule: string;
|
|
491
|
-
modules: Record<string, string | { wasm: ArrayBuffer }>;
|
|
533
|
+
modules: Record<string, string | { wasm: ArrayBuffer } | { cjs: string }>;
|
|
492
534
|
}
|
|
493
535
|
|
|
494
536
|
/**
|
package/src/timers.ts
CHANGED
|
@@ -58,9 +58,12 @@ export const TIMER_REASONS_KEY = 'w1_next_alarm_reasons';
|
|
|
58
58
|
* The host instance carrying the per-instance timer chain. The field lives on
|
|
59
59
|
* the embedder's DO instance so one chain serializes every timer-map
|
|
60
60
|
* read-modify-write for that instance (see {@link Timers.schedule}).
|
|
61
|
+
* `_timerEpoch` counts {@link Timers.reset}s: a schedule or dispatch writes
|
|
62
|
+
* only while the epoch it was requested in is still current.
|
|
61
63
|
*/
|
|
62
64
|
export interface TimerHost {
|
|
63
65
|
_timerChain?: Promise<unknown>;
|
|
66
|
+
_timerEpoch?: number;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
/**
|
|
@@ -159,6 +162,7 @@ export class Timers {
|
|
|
159
162
|
arms.push({ reason, whenMs });
|
|
160
163
|
return Promise.resolve(true);
|
|
161
164
|
}
|
|
165
|
+
const epoch = host._timerEpoch ?? 0;
|
|
162
166
|
// Serialize every read-modify-write of the reasons map through one
|
|
163
167
|
// per-instance chain: two schedulers firing back-to-back from one activity
|
|
164
168
|
// hook would otherwise interleave their get→put cycles and silently drop
|
|
@@ -170,15 +174,20 @@ export class Timers {
|
|
|
170
174
|
const existing = (await ctx.storage.get(TIMER_REASONS_KEY)) as
|
|
171
175
|
| Record<string, number>
|
|
172
176
|
| undefined;
|
|
177
|
+
// Requested before a reset: it belongs to the timers the reset voided.
|
|
178
|
+
if ((host._timerEpoch ?? 0) !== epoch) return false;
|
|
173
179
|
const map: Record<string, number> = { ...(existing || {}) };
|
|
174
180
|
// Earliest-deadline-first: only update if new request is sooner or
|
|
175
181
|
// this reason has no pending entry.
|
|
182
|
+
let written: Promise<unknown> | undefined;
|
|
176
183
|
if (!(reason in map) || whenMs < map[reason]) {
|
|
177
184
|
map[reason] = whenMs;
|
|
178
|
-
|
|
185
|
+
written = ctx.storage.put(TIMER_REASONS_KEY, map);
|
|
179
186
|
}
|
|
180
|
-
|
|
181
|
-
|
|
187
|
+
// Issued in the turn the epoch was checked in: a reset after this
|
|
188
|
+
// point wipes and disarms after these, never before.
|
|
189
|
+
setAlarmFn.call(ctx.storage, Math.min(...Object.values(map)));
|
|
190
|
+
await written;
|
|
182
191
|
return true;
|
|
183
192
|
} catch (e) {
|
|
184
193
|
console.warn('[nimbus/W1] timers.schedule threw:', errorText(e));
|
|
@@ -190,6 +199,18 @@ export class Timers {
|
|
|
190
199
|
return chained;
|
|
191
200
|
}
|
|
192
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Void every timer of this instance: a schedule or dispatch already
|
|
204
|
+
* requested — still queued on the chain, or a dispatch whose handlers are
|
|
205
|
+
* running — writes no reason and arms no alarm from here on. For a
|
|
206
|
+
* deliberate end of the actor's state (a session destroy): call it before
|
|
207
|
+
* wiping storage and deleting the alarm, so nothing in flight writes the
|
|
208
|
+
* map back or re-arms after the wipe. Requests made after it proceed.
|
|
209
|
+
*/
|
|
210
|
+
reset(): void {
|
|
211
|
+
this.host._timerEpoch = (this.host._timerEpoch ?? 0) + 1;
|
|
212
|
+
}
|
|
213
|
+
|
|
193
214
|
/**
|
|
194
215
|
* Multi-reason timer dispatcher. Called from the DO's `alarm()` handler
|
|
195
216
|
* with the embedder's handler map.
|
|
@@ -216,19 +237,28 @@ export class Timers {
|
|
|
216
237
|
alarmInfo?: TimerAlarmInfo,
|
|
217
238
|
): Promise<void> {
|
|
218
239
|
const { host, ctx } = this;
|
|
240
|
+
const epoch = host._timerEpoch ?? 0;
|
|
241
|
+
const current = (): boolean => (host._timerEpoch ?? 0) === epoch;
|
|
219
242
|
// Same serialization as schedule: the dispatcher's read→handlers→write
|
|
220
243
|
// cycle must not interleave with an activity-hook schedule.
|
|
221
244
|
const chained = (host._timerChain ?? Promise.resolve()).then(
|
|
222
|
-
() => dispatchBody(ctx, handlers, onLegacyAlarm, alarmInfo),
|
|
223
|
-
() => dispatchBody(ctx, handlers, onLegacyAlarm, alarmInfo),
|
|
245
|
+
() => dispatchBody(ctx, current, handlers, onLegacyAlarm, alarmInfo),
|
|
246
|
+
() => dispatchBody(ctx, current, handlers, onLegacyAlarm, alarmInfo),
|
|
224
247
|
);
|
|
225
248
|
host._timerChain = chained;
|
|
226
249
|
return chained;
|
|
227
250
|
}
|
|
228
251
|
}
|
|
229
252
|
|
|
253
|
+
/**
|
|
254
|
+
* One dispatch. `current` answers whether the epoch it was requested in still
|
|
255
|
+
* stands: once a reset voids it, no further handler runs and nothing is
|
|
256
|
+
* written or armed — the reset's caller is wiping this state, and a write
|
|
257
|
+
* after the wipe would outlive it.
|
|
258
|
+
*/
|
|
230
259
|
async function dispatchBody(
|
|
231
260
|
ctx: TimerContext,
|
|
261
|
+
current: () => boolean,
|
|
232
262
|
handlers: TimerHandlers,
|
|
233
263
|
onLegacyAlarm?: () => void,
|
|
234
264
|
alarmInfo?: TimerAlarmInfo,
|
|
@@ -242,6 +272,7 @@ async function dispatchBody(
|
|
|
242
272
|
const existing = (await ctx?.storage?.get?.(TIMER_REASONS_KEY)) as
|
|
243
273
|
| Record<string, number>
|
|
244
274
|
| undefined;
|
|
275
|
+
if (!current()) return;
|
|
245
276
|
const map: Record<string, number> = { ...(existing || {}) };
|
|
246
277
|
const hadMap = Object.keys(map).length > 0;
|
|
247
278
|
if (!hadMap) {
|
|
@@ -255,6 +286,7 @@ async function dispatchBody(
|
|
|
255
286
|
if (when <= now) fired.push(reason);
|
|
256
287
|
}
|
|
257
288
|
for (const reason of fired) {
|
|
289
|
+
if (!current()) return;
|
|
258
290
|
delete map[reason];
|
|
259
291
|
const handler = handlers[reason];
|
|
260
292
|
// Unknown reasons silently dropped (forward-compat).
|
|
@@ -269,20 +301,24 @@ async function dispatchBody(
|
|
|
269
301
|
}
|
|
270
302
|
}
|
|
271
303
|
}
|
|
304
|
+
// A reset while the handlers ran: the map read above is gone, and
|
|
305
|
+
// writing it back — or arming for it — would revive what was ended.
|
|
306
|
+
if (!current()) return;
|
|
272
307
|
// Fold the in-dispatch arms, earliest-deadline-first per reason.
|
|
273
308
|
for (const arm of arms) {
|
|
274
309
|
if (!(arm.reason in map) || arm.whenMs < map[arm.reason]) {
|
|
275
310
|
map[arm.reason] = arm.whenMs;
|
|
276
311
|
}
|
|
277
312
|
}
|
|
278
|
-
// Re-arm or clear
|
|
313
|
+
// Re-arm or clear, issued in the turn the epoch was checked in: a reset
|
|
314
|
+
// after this point wipes and disarms after these, never before.
|
|
279
315
|
const setAlarmFn = ctx?.storage?.setAlarm;
|
|
280
316
|
if (Object.keys(map).length > 0) {
|
|
281
|
-
|
|
282
|
-
const earliest = Math.min(...Object.values(map));
|
|
317
|
+
const written = ctx.storage.put(TIMER_REASONS_KEY, map);
|
|
283
318
|
if (typeof setAlarmFn === 'function') {
|
|
284
|
-
setAlarmFn.call(ctx.storage,
|
|
319
|
+
setAlarmFn.call(ctx.storage, Math.min(...Object.values(map)));
|
|
285
320
|
}
|
|
321
|
+
await written;
|
|
286
322
|
} else if (hadMap) {
|
|
287
323
|
try { await ctx.storage.delete(TIMER_REASONS_KEY); } catch {}
|
|
288
324
|
// No remaining reasons → no setAlarm call → DO becomes
|
package/src/vendor/types.ts
CHANGED
|
@@ -9,12 +9,18 @@ export interface TextModule { text: string }
|
|
|
9
9
|
export interface DataModule { data: ArrayBuffer }
|
|
10
10
|
export interface JsonModule { json: unknown }
|
|
11
11
|
/**
|
|
12
|
-
* A
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* A WebAssembly module, importable from a facet by module name. Nimbus ships
|
|
13
|
+
* these on every path — sql.js for node:sqlite, the interpreter images for
|
|
14
|
+
* python and ruby — so the omission here was the type lagging the API, not a
|
|
15
|
+
* kind the loader lacks. The loader takes either the bytes, which it
|
|
16
|
+
* compiles, or a module the caller holds compiled already, whose compiled
|
|
17
|
+
* code the dynamic worker then shares (workerd src/workerd/api/
|
|
18
|
+
* worker-loader.c++, extractWasmModuleContent). A module handed over this
|
|
19
|
+
* way should be described with describeHostWasm (host-wasm.ts): the
|
|
20
|
+
* code-size budget and the loader cache key cannot read a Module's size or
|
|
21
|
+
* identity from JS.
|
|
16
22
|
*/
|
|
17
|
-
export interface WasmModule { wasm: ArrayBuffer }
|
|
23
|
+
export interface WasmModule { wasm: ArrayBuffer | WebAssembly.Module }
|
|
18
24
|
|
|
19
25
|
/** Plain string = type inferred from file extension (.js or .py). */
|
|
20
26
|
export type ModuleContent =
|
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
facetNameCount,
|
|
29
29
|
facetNameCountDurable,
|
|
30
30
|
recordFacetNameMinted,
|
|
31
|
-
recordLoaderId,
|
|
32
31
|
withDynamicWorkerCapNamed,
|
|
33
32
|
withFacetBudgetNamed,
|
|
34
33
|
} from './budgets.js';
|
|
@@ -121,9 +120,10 @@ interface FacetContainer {
|
|
|
121
120
|
abort(name: string, reason?: unknown): void;
|
|
122
121
|
delete(name: string): void;
|
|
123
122
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* {@link cloneStorage}, the one way the
|
|
123
|
+
* Declared by `@cloudflare/workers-types` 5 and present in workerd
|
|
124
|
+
* ≥ 1.20260926.1 and in production; an embedder's older local workerd
|
|
125
|
+
* (≤ 1.20260603.1) lacks it — see {@link cloneStorage}, the one way the
|
|
126
|
+
* fabric calls it.
|
|
127
127
|
*/
|
|
128
128
|
clone?(src: string, dst: string): void;
|
|
129
129
|
}
|
|
@@ -212,7 +212,7 @@ export async function cloneStorage(
|
|
|
212
212
|
if (typeof facets.clone !== 'function') {
|
|
213
213
|
throw new Error(
|
|
214
214
|
'Nimbus: ctx.facets.clone is unavailable in this runtime; the reflink image '
|
|
215
|
-
+ 'path needs
|
|
215
|
+
+ 'path needs workerd 1.20260926.1 or later, or deployed Cloudflare workerd',
|
|
216
216
|
);
|
|
217
217
|
}
|
|
218
218
|
const { src, dst } = clone;
|
|
@@ -471,10 +471,13 @@ function spawnResident(
|
|
|
471
471
|
);
|
|
472
472
|
}
|
|
473
473
|
evaluated = true;
|
|
474
|
-
return { class: residentProcessClass(
|
|
474
|
+
return { class: residentProcessClass(env, disk, supervisor, params, loaderKey) };
|
|
475
475
|
};
|
|
476
476
|
const book = slotBook(ctx);
|
|
477
477
|
const ledger = sessionLedger(ctx);
|
|
478
|
+
// A warm worker keeps the SUPERVISOR binding it was built with, and the
|
|
479
|
+
// loader outlives this instance.
|
|
480
|
+
const loaderKey = supervisorLoaderKey(params.workerKey, supervisor);
|
|
478
481
|
let facet: ResidentFacetStub;
|
|
479
482
|
try {
|
|
480
483
|
// N18: the fill is admitted, and recorded under the facet's name, before
|
|
@@ -490,6 +493,12 @@ function spawnResident(
|
|
|
490
493
|
throw withFacetBudgetNamed(facetNameCount(ctx), error);
|
|
491
494
|
}
|
|
492
495
|
if (explicit) book.live.add(name);
|
|
496
|
+
// The facet's worker is one Dynamic Worker in flight for as long as the
|
|
497
|
+
// process is resident, not only while a call is open: its WebSockets and
|
|
498
|
+
// streamed responses outlive the calls the ledger could bracket, and a
|
|
499
|
+
// request can reach it at any moment. Held from here to `release`, so no
|
|
500
|
+
// fan-out spends the slot a running process needs.
|
|
501
|
+
const endResidency = beginLoaderFetch(ctx, loaderKey);
|
|
493
502
|
facetOfPid(ctx).set(params.pid, name);
|
|
494
503
|
|
|
495
504
|
let disposed = false;
|
|
@@ -498,6 +507,7 @@ function spawnResident(
|
|
|
498
507
|
disposed = true;
|
|
499
508
|
released = true;
|
|
500
509
|
facetOfPid(ctx).delete(params.pid);
|
|
510
|
+
endResidency();
|
|
501
511
|
try { facets.abort(name, new Error('Nimbus: resident process released')); } catch { /* already gone */ }
|
|
502
512
|
if (explicit) book.live.delete(name);
|
|
503
513
|
// The two release classes: an ephemeral facet's SQLite is slot-reuse
|
|
@@ -564,11 +574,11 @@ function spawnResident(
|
|
|
564
574
|
* the hosting DO's heap.
|
|
565
575
|
*/
|
|
566
576
|
function residentProcessClass(
|
|
567
|
-
ctx: DurableObjectState,
|
|
568
577
|
env: ResidentFacetEnv,
|
|
569
578
|
disk: () => ResidentDiskReader,
|
|
570
579
|
supervisor: ResidentSupervisorProps,
|
|
571
580
|
params: ProcessHostParams,
|
|
581
|
+
loaderKey: string,
|
|
572
582
|
): unknown {
|
|
573
583
|
const loader = env.LOADER;
|
|
574
584
|
if (!loader || typeof loader.get !== 'function') {
|
|
@@ -577,18 +587,9 @@ function residentProcessClass(
|
|
|
577
587
|
+ 'the Worker Loader binding; add it via worker_loaders in wrangler.jsonc.',
|
|
578
588
|
);
|
|
579
589
|
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
try {
|
|
584
|
-
const worker = loader
|
|
585
|
-
.get(loaderKey, () => residentWorkerConfig(env, disk, supervisor, params.boot))
|
|
586
|
-
.getDurableObjectClass(RESIDENT_PROCESS_CLASS);
|
|
587
|
-
recordLoaderId(ctx, loaderKey);
|
|
588
|
-
return worker;
|
|
589
|
-
} catch (error) {
|
|
590
|
-
throw withDynamicWorkerCapNamed(ctx, error);
|
|
591
|
-
}
|
|
590
|
+
return loader
|
|
591
|
+
.get(loaderKey, () => residentWorkerConfig(env, disk, supervisor, params.boot))
|
|
592
|
+
.getDurableObjectClass(RESIDENT_PROCESS_CLASS);
|
|
592
593
|
}
|
|
593
594
|
|
|
594
595
|
async function runOneShot<T>(
|
|
@@ -638,22 +639,22 @@ async function runOneShot<T>(
|
|
|
638
639
|
throw new Error('Nimbus: one-shot runtime entrypoint has no fetch method');
|
|
639
640
|
}
|
|
640
641
|
params.onLoaded?.();
|
|
641
|
-
// The unkeyed worker is
|
|
642
|
-
//
|
|
643
|
-
// never wrapped: see beginLoaderFetch for the
|
|
644
|
-
// hazard, and the pipelined-`fetch.call` note above
|
|
645
|
-
|
|
646
|
-
|
|
642
|
+
// The unkeyed worker is one distinct dynamic worker in flight until its
|
|
643
|
+
// response is consumed (the body streams from it), keyed by this run's
|
|
644
|
+
// writer id — bracketed, never wrapped: see beginLoaderFetch for the
|
|
645
|
+
// measured DO-poisoning hazard, and the pipelined-`fetch.call` note above
|
|
646
|
+
// for its sibling.
|
|
647
|
+
const endFetch = beginLoaderFetch(ctx, `one-shot:${params.writerId}`);
|
|
647
648
|
try {
|
|
648
|
-
response = await ep.fetch(params.request);
|
|
649
|
+
const response = await ep.fetch(params.request);
|
|
650
|
+
try {
|
|
651
|
+
return await consume(response);
|
|
652
|
+
} finally {
|
|
653
|
+
disposeRpcResource(response);
|
|
654
|
+
}
|
|
649
655
|
} finally {
|
|
650
656
|
endFetch();
|
|
651
657
|
}
|
|
652
|
-
try {
|
|
653
|
-
return await consume(response);
|
|
654
|
-
} finally {
|
|
655
|
-
disposeRpcResource(response);
|
|
656
|
-
}
|
|
657
658
|
} catch (error) {
|
|
658
659
|
throw withDynamicWorkerCapNamed(ctx, error);
|
|
659
660
|
} finally {
|