@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.
- package/README.md +26 -11
- package/dist/bindings.d.ts +1 -0
- package/dist/bindings.d.ts.map +1 -1
- package/dist/bindings.js +2 -0
- package/dist/budgets.d.ts +50 -29
- package/dist/budgets.d.ts.map +1 -1
- package/dist/budgets.js +88 -40
- package/dist/connections.d.ts +1 -1
- package/dist/connections.js +1 -1
- package/dist/do-calls.d.ts +91 -9
- package/dist/do-calls.d.ts.map +1 -1
- package/dist/do-calls.js +161 -22
- package/dist/facet-pool.d.ts +3 -0
- package/dist/facet-pool.d.ts.map +1 -1
- package/dist/facet-pool.js +3 -0
- package/dist/fanout.d.ts +40 -32
- package/dist/fanout.d.ts.map +1 -1
- package/dist/fanout.js +50 -53
- package/dist/fenced-work.d.ts +3 -3
- 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 +34 -2
- package/dist/inner-do-registry.d.ts +9 -0
- package/dist/inner-do-registry.d.ts.map +1 -1
- package/dist/inner-do-registry.js +35 -0
- package/dist/isolate-pool.d.ts +32 -24
- package/dist/isolate-pool.d.ts.map +1 -1
- package/dist/isolate-pool.js +78 -54
- package/dist/process-fabric.d.ts +42 -23
- package/dist/process-fabric.d.ts.map +1 -1
- package/dist/process-fabric.js +62 -6
- package/dist/process-host.d.ts +3 -1
- package/dist/process-host.d.ts.map +1 -1
- package/dist/process-host.js +13 -14
- package/dist/supervisor-props.d.ts +47 -0
- package/dist/supervisor-props.d.ts.map +1 -0
- package/dist/supervisor-props.js +37 -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 +4 -17
- package/dist/workerd-facet-host.d.ts.map +1 -1
- package/dist/workerd-facet-host.js +120 -54
- package/package.json +6 -6
- package/src/bindings.ts +2 -0
- package/src/budgets.ts +96 -49
- package/src/connections.ts +1 -1
- package/src/do-calls.ts +216 -25
- package/src/facet-pool.ts +5 -0
- package/src/fanout.ts +64 -56
- package/src/fenced-work.ts +3 -3
- package/src/host-wasm.ts +41 -0
- package/src/image-store.ts +30 -3
- package/src/inner-do-registry.ts +31 -0
- package/src/isolate-pool.ts +108 -74
- package/src/process-fabric.ts +88 -30
- package/src/process-host.ts +16 -14
- package/src/supervisor-props.ts +56 -0
- package/src/timers.ts +45 -9
- package/src/vendor/types.ts +11 -5
- package/src/workerd-facet-host.ts +123 -58
package/dist/vendor/types.d.ts
CHANGED
|
@@ -17,13 +17,19 @@ export interface JsonModule {
|
|
|
17
17
|
json: unknown;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* A
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* A WebAssembly module, importable from a facet by module name. Nimbus ships
|
|
21
|
+
* these on every path — sql.js for node:sqlite, the interpreter images for
|
|
22
|
+
* python and ruby — so the omission here was the type lagging the API, not a
|
|
23
|
+
* kind the loader lacks. The loader takes either the bytes, which it
|
|
24
|
+
* compiles, or a module the caller holds compiled already, whose compiled
|
|
25
|
+
* code the dynamic worker then shares (workerd src/workerd/api/
|
|
26
|
+
* worker-loader.c++, extractWasmModuleContent). A module handed over this
|
|
27
|
+
* way should be described with describeHostWasm (host-wasm.ts): the
|
|
28
|
+
* code-size budget and the loader cache key cannot read a Module's size or
|
|
29
|
+
* identity from JS.
|
|
24
30
|
*/
|
|
25
31
|
export interface WasmModule {
|
|
26
|
-
wasm: ArrayBuffer;
|
|
32
|
+
wasm: ArrayBuffer | WebAssembly.Module;
|
|
27
33
|
}
|
|
28
34
|
/** Plain string = type inferred from file extension (.js or .py). */
|
|
29
35
|
export type ModuleContent = string | JsModule | CjsModule | PyModule | TextModule | DataModule | JsonModule | WasmModule;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/vendor/types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IAAG,EAAE,EAAE,MAAM,CAAA;CAAE;AACxC,MAAM,WAAW,SAAS;IAAG,GAAG,EAAE,MAAM,CAAA;CAAE;AAC1C,MAAM,WAAW,QAAQ;IAAG,EAAE,EAAE,MAAM,CAAA;CAAE;AACxC,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,MAAM,CAAA;CAAE;AAC5C,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,WAAW,CAAA;CAAE;AACjD,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,OAAO,CAAA;CAAE;AAC7C
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/vendor/types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,QAAQ;IAAG,EAAE,EAAE,MAAM,CAAA;CAAE;AACxC,MAAM,WAAW,SAAS;IAAG,GAAG,EAAE,MAAM,CAAA;CAAE;AAC1C,MAAM,WAAW,QAAQ;IAAG,EAAE,EAAE,MAAM,CAAA;CAAE;AACxC,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,MAAM,CAAA;CAAE;AAC5C,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,WAAW,CAAA;CAAE;AACjD,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,OAAO,CAAA;CAAE;AAC7C;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,UAAU;IAAG,IAAI,EAAE,WAAW,GAAG,WAAW,CAAC,MAAM,CAAA;CAAE;AAEtE,qEAAqE;AACrE,MAAM,MAAM,aAAa,GACrB,MAAM,GACN,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,UAAU,GACV,UAAU,GACV,UAAU,GACV,UAAU,CAAC;AAEf,MAAM,WAAW,UAAU;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE9B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CAClE;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,UAAU;IACzB,aAAa,IAAI,cAAc,CAAC;IAChC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,cAAc,CAAC;CACvE;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC/C;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;AAExD,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,eAAe,EAAE,eAAe,GAAG,UAAU,CAAC;CAC/D"}
|
|
@@ -24,11 +24,7 @@ export interface NimbusCtxExports {
|
|
|
24
24
|
key: string;
|
|
25
25
|
name: string | null;
|
|
26
26
|
depth: number;
|
|
27
|
-
supervisor:
|
|
28
|
-
doId: string;
|
|
29
|
-
pid: number;
|
|
30
|
-
writerId: string;
|
|
31
|
-
};
|
|
27
|
+
supervisor: ResidentSupervisorProps;
|
|
32
28
|
stage?: unknown;
|
|
33
29
|
};
|
|
34
30
|
}) => LoadedWorkerEntrypointStub;
|
|
@@ -40,11 +36,7 @@ export declare function getNimbusCtxExports(): NimbusCtxExports;
|
|
|
40
36
|
* request rather than leaving it resident: their module map is assembled in
|
|
41
37
|
* that stateless entrypoint's own isolate, never in a session DO.
|
|
42
38
|
*/
|
|
43
|
-
export declare function createLoadedWorkerEntrypoint(ctxExports: NimbusCtxExports, supervisor:
|
|
44
|
-
doId: string;
|
|
45
|
-
pid: number;
|
|
46
|
-
writerId: string;
|
|
47
|
-
}, stage: unknown, name?: string | null): Promise<LoadedWorkerEntrypointStub>;
|
|
39
|
+
export declare function createLoadedWorkerEntrypoint(ctxExports: NimbusCtxExports, supervisor: ResidentSupervisorProps, stage: unknown, name?: string | null): Promise<LoadedWorkerEntrypointStub>;
|
|
48
40
|
/** What an unkeyed `LOADER.load` hands back. */
|
|
49
41
|
interface LoadedWorkerStub {
|
|
50
42
|
getEntrypoint(): LoadedWorkerEntrypointStub;
|
|
@@ -133,13 +125,8 @@ export declare function cloneStorage(ctx: DurableObjectState, clone: {
|
|
|
133
125
|
export declare function residentFacetName(slot: number): string;
|
|
134
126
|
/** The prefix every durable application's facet name carries. */
|
|
135
127
|
export declare const DURABLE_FACET_NAME_PREFIX = "app-slot-";
|
|
136
|
-
/**
|
|
137
|
-
|
|
138
|
-
* storage. `spawnResident` releases ephemeral processes with abort+delete
|
|
139
|
-
* (storage is slot-reuse hygiene) and durable ones with abort alone (the
|
|
140
|
-
* storage IS the durable application's state); explicit removal arrives here
|
|
141
|
-
* through the coordinator's durable-slot book, owner-checked.
|
|
142
|
-
*/
|
|
128
|
+
/** The facet a running resident process `pid` lives in on this actor, for its storage ledger row. */
|
|
129
|
+
export declare function residentFacetOf(ctx: DurableObjectState, pid: number): string | undefined;
|
|
143
130
|
export declare function deleteFacetStorage(ctx: DurableObjectState, name: string): void;
|
|
144
131
|
/**
|
|
145
132
|
* What `processes(ctx, env).spawn` hands back: a running process, minus its placement.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workerd-facet-host.d.ts","sourceRoot":"","sources":["../src/workerd-facet-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"workerd-facet-host.d.ts","sourceRoot":"","sources":["../src/workerd-facet-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,9 +13,11 @@
|
|
|
13
13
|
* `HostedProcess` and never imports this file.
|
|
14
14
|
*/
|
|
15
15
|
import { disposeRpcResource } from '@nimbus-sh/platform/rpc-dispose.js';
|
|
16
|
+
import { StorageLedger, forgetFacetStorage } from '@nimbus-sh/core/runtime/storage-ledger.js';
|
|
16
17
|
import { getCtxExports, stagedBootAssembler, supervisorEntrypoint, supervisorEntrypointName, } from './composition.js';
|
|
17
|
-
import { assertModuleMapWithinCodeLimit, beginLoaderFetch, facetNameCount, facetNameCountDurable, recordFacetNameMinted,
|
|
18
|
+
import { assertModuleMapWithinCodeLimit, beginLoaderFetch, facetNameCount, facetNameCountDurable, recordFacetNameMinted, withDynamicWorkerCapNamed, withFacetBudgetNamed, } from './budgets.js';
|
|
18
19
|
import { RESIDENT_PROCESS_CLASS, residentLoaderConfig, } from './process-fabric.js';
|
|
20
|
+
import { supervisorLoaderKey } from './supervisor-props.js';
|
|
19
21
|
export function getNimbusCtxExports() {
|
|
20
22
|
const ctxExports = getCtxExports();
|
|
21
23
|
if (!ctxExports || typeof ctxExports !== 'object') {
|
|
@@ -35,7 +37,9 @@ export async function createLoadedWorkerEntrypoint(ctxExports, supervisor, stage
|
|
|
35
37
|
}
|
|
36
38
|
return await ctxExports.NimbusLoadedEntrypoint({
|
|
37
39
|
props: {
|
|
38
|
-
|
|
40
|
+
// The entrypoint's loader outlives this instance, and a warm worker keeps
|
|
41
|
+
// the SUPERVISOR binding it was built with.
|
|
42
|
+
key: supervisorLoaderKey(`nimbus-process:${supervisor.doId}:${supervisor.pid}`, supervisor),
|
|
39
43
|
name,
|
|
40
44
|
depth: 0,
|
|
41
45
|
supervisor,
|
|
@@ -83,7 +87,7 @@ export async function cloneStorage(ctx, clone) {
|
|
|
83
87
|
const facets = facetContainer(ctx);
|
|
84
88
|
if (typeof facets.clone !== 'function') {
|
|
85
89
|
throw new Error('Nimbus: ctx.facets.clone is unavailable in this runtime; the reflink image '
|
|
86
|
-
+ 'path needs
|
|
90
|
+
+ 'path needs workerd 1.20260926.1 or later, or deployed Cloudflare workerd');
|
|
87
91
|
}
|
|
88
92
|
const { src, dst } = clone;
|
|
89
93
|
if (!(await clone.populated(src))) {
|
|
@@ -126,37 +130,38 @@ export const DURABLE_FACET_NAME_PREFIX = 'app-slot-';
|
|
|
126
130
|
* Slot books, per hosting actor, because the facet index is per Durable
|
|
127
131
|
* Object.
|
|
128
132
|
*
|
|
129
|
-
* Keyed weakly off `ctx`,
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* VFS epoch, in which case `invalidatedSince` can only answer poison and the
|
|
137
|
-
* whole store is dropped. A process therefore cannot boot onto a previous
|
|
138
|
-
* tenant's filesystem even when release never ran.
|
|
133
|
+
* Keyed weakly off `ctx`, so a book describes one incarnation. A facet that
|
|
134
|
+
* is still running when that incarnation ends outlives it (measured: a timer
|
|
135
|
+
* or an outgoing call keeps it going), and a `get` of its name with a new
|
|
136
|
+
* class then resets the whole object. So a fresh incarnation's first get of
|
|
137
|
+
* each name ends whatever runs there first: a minted `proc-slot-` name is
|
|
138
|
+
* deleted, which also wipes the storage a previous incarnation left, and an
|
|
139
|
+
* explicit name is aborted, which keeps it.
|
|
139
140
|
*
|
|
140
|
-
* The book
|
|
141
|
-
* allocated against DO storage instead (their owner survives a reset), so
|
|
142
|
-
* fresh incarnation's `next` starting at 0 can never collide with them even
|
|
141
|
+
* The book allocates only the `proc-slot-` space. Durable `app-slot-` names
|
|
142
|
+
* are allocated against DO storage instead (their owner survives a reset), so
|
|
143
|
+
* a fresh incarnation's `next` starting at 0 can never collide with them even
|
|
143
144
|
* before the durable ledger is adopted.
|
|
144
145
|
*/
|
|
145
146
|
const slotBooks = new WeakMap();
|
|
146
147
|
function slotBook(ctx) {
|
|
147
148
|
let book = slotBooks.get(ctx);
|
|
148
149
|
if (!book) {
|
|
149
|
-
book = { free: [], next: 0, held: new Map() };
|
|
150
|
+
book = { free: [], next: 0, held: new Map(), live: new Set() };
|
|
150
151
|
slotBooks.set(ctx, book);
|
|
151
152
|
}
|
|
152
153
|
return book;
|
|
153
154
|
}
|
|
154
|
-
/**
|
|
155
|
+
/**
|
|
156
|
+
* Take a slot for `pid`, reusing a returned one before minting a new name.
|
|
157
|
+
* `minted` names may still hold storage a previous incarnation of this actor
|
|
158
|
+
* left there, so the caller deletes it before the first get.
|
|
159
|
+
*/
|
|
155
160
|
function acquireSlot(ctx, pid) {
|
|
156
161
|
const book = slotBook(ctx);
|
|
157
162
|
const existing = book.held.get(pid);
|
|
158
163
|
if (existing !== undefined)
|
|
159
|
-
return existing;
|
|
164
|
+
return { slot: existing, minted: false };
|
|
160
165
|
const reused = book.free.length > 0;
|
|
161
166
|
const slot = reused ? book.free.shift() : book.next++;
|
|
162
167
|
book.held.set(pid, slot);
|
|
@@ -164,7 +169,7 @@ function acquireSlot(ctx, pid) {
|
|
|
164
169
|
// in the budgets ledger (see budgets.ts).
|
|
165
170
|
if (!reused)
|
|
166
171
|
recordFacetNameMinted(ctx, book.next);
|
|
167
|
-
return slot;
|
|
172
|
+
return { slot, minted: !reused };
|
|
168
173
|
}
|
|
169
174
|
/** Return `pid`'s slot to the free list. */
|
|
170
175
|
function releaseSlot(ctx, pid) {
|
|
@@ -177,14 +182,34 @@ function releaseSlot(ctx, pid) {
|
|
|
177
182
|
book.free.sort((a, b) => a - b);
|
|
178
183
|
}
|
|
179
184
|
/**
|
|
180
|
-
* Drop one facet's SQLite by name
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
+
* Drop one facet's SQLite by name, and its row in the session's storage
|
|
186
|
+
* ledger (N18) in the same step: the only way a facet database is deleted.
|
|
187
|
+
* `spawnResident` releases ephemeral processes with abort+delete (storage is
|
|
188
|
+
* slot-reuse hygiene) and durable ones with abort alone (the storage IS the
|
|
189
|
+
* durable application's state); explicit removal arrives here through the
|
|
190
|
+
* coordinator's durable-slot book, owner-checked.
|
|
185
191
|
*/
|
|
192
|
+
/** The session's storage ledger (N18), over this actor's SQL; null where it has none. */
|
|
193
|
+
function sessionLedger(ctx) {
|
|
194
|
+
const sql = ctx.storage?.sql;
|
|
195
|
+
return sql ? new StorageLedger(sql) : null;
|
|
196
|
+
}
|
|
197
|
+
const facetNames = new WeakMap();
|
|
198
|
+
function facetOfPid(ctx) {
|
|
199
|
+
let names = facetNames.get(ctx);
|
|
200
|
+
if (!names)
|
|
201
|
+
facetNames.set(ctx, names = new Map());
|
|
202
|
+
return names;
|
|
203
|
+
}
|
|
204
|
+
/** The facet a running resident process `pid` lives in on this actor, for its storage ledger row. */
|
|
205
|
+
export function residentFacetOf(ctx, pid) {
|
|
206
|
+
return facetNames.get(ctx)?.get(pid);
|
|
207
|
+
}
|
|
186
208
|
export function deleteFacetStorage(ctx, name) {
|
|
187
209
|
facetContainer(ctx).delete(name);
|
|
210
|
+
const sql = ctx.storage?.sql;
|
|
211
|
+
if (sql)
|
|
212
|
+
forgetFacetStorage(sql, name);
|
|
188
213
|
}
|
|
189
214
|
/**
|
|
190
215
|
* The process surface of one hosting actor: how a resident process comes
|
|
@@ -241,8 +266,15 @@ function spawnResident(ctx, env, disk, supervisor, params) {
|
|
|
241
266
|
throw new Error(`Nimbus: an explicit facet name must carry the '${DURABLE_FACET_NAME_PREFIX}' `
|
|
242
267
|
+ `prefix, got '${explicit.name}'`);
|
|
243
268
|
}
|
|
244
|
-
const
|
|
269
|
+
const grant = explicit ? undefined : acquireSlot(ctx, params.pid);
|
|
270
|
+
const slot = grant?.slot;
|
|
245
271
|
const name = explicit ? explicit.name : residentFacetName(slot);
|
|
272
|
+
if (grant?.minted) {
|
|
273
|
+
try {
|
|
274
|
+
deleteFacetStorage(ctx, name);
|
|
275
|
+
}
|
|
276
|
+
catch { /* nothing stored under this name */ }
|
|
277
|
+
}
|
|
246
278
|
// The start callback is the ONLY way this facet is ever created, and it
|
|
247
279
|
// fires AT MOST ONCE. Every later use goes through the stub below, so the
|
|
248
280
|
// callback running a second time means the facet was released or died —
|
|
@@ -260,10 +292,23 @@ function spawnResident(ctx, env, disk, supervisor, params) {
|
|
|
260
292
|
+ 'it is not restarted');
|
|
261
293
|
}
|
|
262
294
|
evaluated = true;
|
|
263
|
-
return { class: residentProcessClass(
|
|
295
|
+
return { class: residentProcessClass(env, disk, supervisor, params, loaderKey) };
|
|
264
296
|
};
|
|
297
|
+
const book = slotBook(ctx);
|
|
298
|
+
const ledger = sessionLedger(ctx);
|
|
299
|
+
// A warm worker keeps the SUPERVISOR binding it was built with, and the
|
|
300
|
+
// loader outlives this instance.
|
|
301
|
+
const loaderKey = supervisorLoaderKey(params.workerKey, supervisor);
|
|
265
302
|
let facet;
|
|
266
303
|
try {
|
|
304
|
+
// N18: the fill is admitted, and recorded under the facet's name, before
|
|
305
|
+
// the facet exists; a refusal leaves no facet.
|
|
306
|
+
if (ledger !== null && params.storageBytes !== undefined)
|
|
307
|
+
ledger.fill(name, params.storageBytes);
|
|
308
|
+
// get() with a new class on a facet an earlier incarnation left running resets this object.
|
|
309
|
+
if (explicit && !book.live.has(name)) {
|
|
310
|
+
facets.abort(name, new Error('Nimbus: a new incarnation takes this facet name'));
|
|
311
|
+
}
|
|
267
312
|
facet = facets.get(name, start);
|
|
268
313
|
}
|
|
269
314
|
catch (error) {
|
|
@@ -271,16 +316,29 @@ function spawnResident(ctx, env, disk, supervisor, params) {
|
|
|
271
316
|
releaseSlot(ctx, params.pid);
|
|
272
317
|
throw withFacetBudgetNamed(facetNameCount(ctx), error);
|
|
273
318
|
}
|
|
319
|
+
if (explicit)
|
|
320
|
+
book.live.add(name);
|
|
321
|
+
// The facet's worker is one Dynamic Worker in flight for as long as the
|
|
322
|
+
// process is resident, not only while a call is open: its WebSockets and
|
|
323
|
+
// streamed responses outlive the calls the ledger could bracket, and a
|
|
324
|
+
// request can reach it at any moment. Held from here to `release`, so no
|
|
325
|
+
// fan-out spends the slot a running process needs.
|
|
326
|
+
const endResidency = beginLoaderFetch(ctx, loaderKey);
|
|
327
|
+
facetOfPid(ctx).set(params.pid, name);
|
|
274
328
|
let disposed = false;
|
|
275
329
|
const release = async () => {
|
|
276
330
|
if (disposed)
|
|
277
331
|
return;
|
|
278
332
|
disposed = true;
|
|
279
333
|
released = true;
|
|
334
|
+
facetOfPid(ctx).delete(params.pid);
|
|
335
|
+
endResidency();
|
|
280
336
|
try {
|
|
281
337
|
facets.abort(name, new Error('Nimbus: resident process released'));
|
|
282
338
|
}
|
|
283
339
|
catch { /* already gone */ }
|
|
340
|
+
if (explicit)
|
|
341
|
+
book.live.delete(name);
|
|
284
342
|
// The two release classes: an ephemeral facet's SQLite is slot-reuse
|
|
285
343
|
// hygiene — the name is handed out again, so the store must not be — and
|
|
286
344
|
// a durable one's is the application itself: abort ends the process, the
|
|
@@ -288,7 +346,7 @@ function spawnResident(ctx, env, disk, supervisor, params) {
|
|
|
288
346
|
// deleteFacetStorage call ever drops it.
|
|
289
347
|
if (!explicit?.durable) {
|
|
290
348
|
try {
|
|
291
|
-
|
|
349
|
+
deleteFacetStorage(ctx, name);
|
|
292
350
|
}
|
|
293
351
|
catch { /* already gone */ }
|
|
294
352
|
}
|
|
@@ -299,7 +357,11 @@ function spawnResident(ctx, env, disk, supervisor, params) {
|
|
|
299
357
|
};
|
|
300
358
|
let started;
|
|
301
359
|
try {
|
|
302
|
-
|
|
360
|
+
// The allowance the ledger admitted, for the facet's store to keep under.
|
|
361
|
+
const startArgs = ledger !== null && params.storageBytes !== undefined && params.startArgs !== null && typeof params.startArgs === 'object'
|
|
362
|
+
? { ...params.startArgs, storage: { facet: name, grant: params.storageBytes } }
|
|
363
|
+
: params.startArgs;
|
|
364
|
+
started = facet.startProcess(startArgs);
|
|
303
365
|
}
|
|
304
366
|
catch (error) {
|
|
305
367
|
void release();
|
|
@@ -309,7 +371,18 @@ function spawnResident(ctx, env, disk, supervisor, params) {
|
|
|
309
371
|
// this one, and it is annotated AFTER awaiting the ledger — the first
|
|
310
372
|
// failure of a fresh incarnation must compare against the persisted count,
|
|
311
373
|
// not the zero its adoption read has not yet replaced.
|
|
312
|
-
started = started.
|
|
374
|
+
started = started.then((payload) => {
|
|
375
|
+
// Once the facet is up (N18) its row is the cap its store keeps under
|
|
376
|
+
// (what it measures plus what it may still grow into), or what it
|
|
377
|
+
// measures if that is more (overshoot).
|
|
378
|
+
const { databaseSize: size, storageCap: cap } = (payload ?? {});
|
|
379
|
+
const measured = typeof size === 'number' && Number.isFinite(size) ? size : null;
|
|
380
|
+
const capped = typeof cap === 'number' && Number.isFinite(cap) ? cap : null;
|
|
381
|
+
const row = capped !== null ? Math.max(capped, measured ?? 0) : measured;
|
|
382
|
+
if (ledger !== null && row !== null)
|
|
383
|
+
ledger.reportSize(name, row);
|
|
384
|
+
return payload;
|
|
385
|
+
}, async (error) => {
|
|
313
386
|
throw withFacetBudgetNamed(await facetNameCountDurable(ctx), error);
|
|
314
387
|
});
|
|
315
388
|
// A caller reads whichever of `started` and the lifecycle it needs, so keep
|
|
@@ -333,22 +406,15 @@ function spawnResident(ctx, env, disk, supervisor, params) {
|
|
|
333
406
|
* assembles its module map at most once and the bytes never stay resident in
|
|
334
407
|
* the hosting DO's heap.
|
|
335
408
|
*/
|
|
336
|
-
function residentProcessClass(
|
|
409
|
+
function residentProcessClass(env, disk, supervisor, params, loaderKey) {
|
|
337
410
|
const loader = env.LOADER;
|
|
338
411
|
if (!loader || typeof loader.get !== 'function') {
|
|
339
412
|
throw new Error('Nimbus: env.LOADER binding missing or invalid. Resident processes require '
|
|
340
413
|
+ 'the Worker Loader binding; add it via worker_loaders in wrangler.jsonc.');
|
|
341
414
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
.getDurableObjectClass(RESIDENT_PROCESS_CLASS);
|
|
346
|
-
recordLoaderId(ctx, params.workerKey);
|
|
347
|
-
return worker;
|
|
348
|
-
}
|
|
349
|
-
catch (error) {
|
|
350
|
-
throw withDynamicWorkerCapNamed(ctx, error);
|
|
351
|
-
}
|
|
415
|
+
return loader
|
|
416
|
+
.get(loaderKey, () => residentWorkerConfig(env, disk, supervisor, params.boot))
|
|
417
|
+
.getDurableObjectClass(RESIDENT_PROCESS_CLASS);
|
|
352
418
|
}
|
|
353
419
|
async function runOneShot(ctx, env, supervisor, params, consume) {
|
|
354
420
|
const loader = env.LOADER;
|
|
@@ -389,24 +455,24 @@ async function runOneShot(ctx, env, supervisor, params, consume) {
|
|
|
389
455
|
throw new Error('Nimbus: one-shot runtime entrypoint has no fetch method');
|
|
390
456
|
}
|
|
391
457
|
params.onLoaded?.();
|
|
392
|
-
// The unkeyed worker is
|
|
393
|
-
//
|
|
394
|
-
// never wrapped: see beginLoaderFetch for the
|
|
395
|
-
// hazard, and the pipelined-`fetch.call` note above
|
|
396
|
-
|
|
397
|
-
|
|
458
|
+
// The unkeyed worker is one distinct dynamic worker in flight until its
|
|
459
|
+
// response is consumed (the body streams from it), keyed by this run's
|
|
460
|
+
// writer id — bracketed, never wrapped: see beginLoaderFetch for the
|
|
461
|
+
// measured DO-poisoning hazard, and the pipelined-`fetch.call` note above
|
|
462
|
+
// for its sibling.
|
|
463
|
+
const endFetch = beginLoaderFetch(ctx, `one-shot:${params.writerId}`);
|
|
398
464
|
try {
|
|
399
|
-
response = await ep.fetch(params.request);
|
|
465
|
+
const response = await ep.fetch(params.request);
|
|
466
|
+
try {
|
|
467
|
+
return await consume(response);
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
disposeRpcResource(response);
|
|
471
|
+
}
|
|
400
472
|
}
|
|
401
473
|
finally {
|
|
402
474
|
endFetch();
|
|
403
475
|
}
|
|
404
|
-
try {
|
|
405
|
-
return await consume(response);
|
|
406
|
-
}
|
|
407
|
-
finally {
|
|
408
|
-
disposeRpcResource(response);
|
|
409
|
-
}
|
|
410
476
|
}
|
|
411
477
|
catch (error) {
|
|
412
478
|
throw withDynamicWorkerCapNamed(ctx, error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbus-sh/fabric",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "The Cloudflare half of Nimbus \u2014 Durable Object facet hosting, dynamic-worker loader pools, the resident-process fabric, and DO alarm/hibernation machinery.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -45,17 +45,17 @@
|
|
|
45
45
|
"LICENSE"
|
|
46
46
|
],
|
|
47
47
|
"scripts": {
|
|
48
|
-
"build": "tsc -p tsconfig.json --noCheck --noEmit false --declaration true --declarationMap true --outDir dist --rootDir src",
|
|
49
|
-
"
|
|
48
|
+
"build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.json --noCheck --noEmit false --declaration true --declarationMap true --outDir dist --rootDir src",
|
|
49
|
+
"prepublishOnly": "bun ../../scripts/dist-integrity.mjs --publish",
|
|
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.14.0",
|
|
54
|
+
"@nimbus-sh/platform": "^0.7.0",
|
|
55
55
|
"zod": "^4.4.3"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@cloudflare/workers-types": "^
|
|
58
|
+
"@cloudflare/workers-types": "^5.20260928.1",
|
|
59
59
|
"typescript": "^5.7.0"
|
|
60
60
|
},
|
|
61
61
|
"publishConfig": {
|
package/src/bindings.ts
CHANGED
|
@@ -324,6 +324,8 @@ const NimbusLoadedEntrypointPropsSchema = z.object({
|
|
|
324
324
|
pid: z.number().int().nonnegative(),
|
|
325
325
|
writerId: z.string().uuid(),
|
|
326
326
|
route: HostRouteSchema.optional(),
|
|
327
|
+
/** The host instance's delivery incarnation (ResidentSupervisorProps). */
|
|
328
|
+
hostIncarnation: z.string().uuid().optional(),
|
|
327
329
|
}).optional(),
|
|
328
330
|
/**
|
|
329
331
|
* Staged-artifact spec, for a ONE-SHOT run. The module map — ~23 MB for
|