@nimbus-sh/platform 0.8.2 → 0.14.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/composition.d.ts +3 -2
- package/dist/composition.d.ts.map +1 -1
- package/dist/composition.js +34 -19
- package/dist/crc32.d.ts +5 -5
- package/dist/crc32.d.ts.map +1 -1
- package/dist/crc32.js +8 -12
- package/dist/diag-counters.d.ts +18 -10
- package/dist/diag-counters.d.ts.map +1 -1
- package/dist/diag-counters.js +7 -20
- package/dist/heap-estimate.d.ts +5 -20
- package/dist/heap-estimate.d.ts.map +1 -1
- package/dist/heap-estimate.js +6 -33
- package/dist/heavy-alloc-coord.d.ts +131 -11
- package/dist/heavy-alloc-coord.d.ts.map +1 -1
- package/dist/heavy-alloc-coord.js +241 -22
- package/dist/hex.d.ts +20 -0
- package/dist/hex.d.ts.map +1 -0
- package/dist/hex.js +29 -0
- package/dist/install-phase.d.ts +1 -15
- package/dist/install-phase.d.ts.map +1 -1
- package/dist/lexical-path.d.ts +11 -0
- package/dist/lexical-path.d.ts.map +1 -0
- package/dist/lexical-path.js +19 -0
- package/dist/limits.d.ts +4 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +25 -0
- package/dist/module-source.d.ts.map +1 -1
- package/dist/module-source.js +2 -1
- package/dist/oom-classify.d.ts +4 -0
- package/dist/oom-classify.d.ts.map +1 -1
- package/dist/oom-classify.js +6 -0
- package/dist/realm-builtin.d.ts +2 -0
- package/dist/realm-builtin.d.ts.map +1 -0
- package/dist/realm-builtin.js +11 -0
- package/dist/utf8.d.ts +21 -1
- package/dist/utf8.d.ts.map +1 -1
- package/dist/utf8.js +73 -1
- package/dist/w7-frame.d.ts +92 -17
- package/dist/w7-frame.d.ts.map +1 -1
- package/dist/w7-frame.js +87 -35
- package/dist/wave-writer.d.ts +11 -6
- package/dist/wave-writer.d.ts.map +1 -1
- package/dist/wave-writer.js +28 -29
- package/dist/weighted-credit-pool.d.ts +43 -0
- package/dist/weighted-credit-pool.d.ts.map +1 -1
- package/dist/weighted-credit-pool.js +250 -46
- package/dist/worker-code.d.ts +48 -0
- package/dist/worker-code.d.ts.map +1 -0
- package/dist/worker-code.js +10 -0
- package/package.json +2 -2
- package/src/composition.ts +41 -21
- package/src/crc32.ts +9 -12
- package/src/diag-counters.ts +23 -22
- package/src/heap-estimate.ts +11 -40
- package/src/heavy-alloc-coord.ts +275 -20
- package/src/hex.ts +29 -0
- package/src/install-phase.ts +1 -15
- package/src/lexical-path.ts +17 -0
- package/src/limits.ts +29 -0
- package/src/module-source.ts +3 -1
- package/src/oom-classify.ts +8 -0
- package/src/realm-builtin.ts +22 -0
- package/src/utf8.ts +75 -1
- package/src/w7-frame.ts +152 -53
- package/src/wave-writer.ts +28 -27
- package/src/weighted-credit-pool.ts +256 -51
- package/src/worker-code.ts +38 -0
package/src/composition.ts
CHANGED
|
@@ -33,9 +33,11 @@
|
|
|
33
33
|
* the Durable Object classes, which is what lets the pool be unit-tested
|
|
34
34
|
* in a plain Node/Bun process.
|
|
35
35
|
*/
|
|
36
|
+
import type { WorkerCode } from './worker-code.js';
|
|
37
|
+
|
|
36
38
|
export type EntrypointLoopbackFactory = <Stub = unknown>(options: { props: object }) => Stub;
|
|
37
39
|
export type CtxExports = Record<string, EntrypointLoopbackFactory | undefined>;
|
|
38
|
-
export type StagedBootAssembler = (env: unknown, stage: unknown) => Promise<
|
|
40
|
+
export type StagedBootAssembler = (env: unknown, stage: unknown) => Promise<WorkerCode>;
|
|
39
41
|
|
|
40
42
|
export interface FabricComposition {
|
|
41
43
|
supervisorEntrypoint: string;
|
|
@@ -67,11 +69,17 @@ let composition: FabricComposition | null = null;
|
|
|
67
69
|
let ctxExports: CtxExports | null = null;
|
|
68
70
|
|
|
69
71
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
72
|
+
* The compatibility flags a host composes the fabric under, refused without.
|
|
73
|
+
* Composing refuses wherever it composes (module scope: the Worker fails at
|
|
74
|
+
* startup; NimbusWorkspace.create: that call throws), naming the fix. Off
|
|
75
|
+
* workerd (bun, node) there is no `Cloudflare` global to read, and neither
|
|
76
|
+
* flag has anything to act on.
|
|
77
|
+
*
|
|
78
|
+
* `enhanced_error_serialization`: a program's filesystem errors reach it
|
|
79
|
+
* across workerd RPC, from the host that answers its supervisor binding, and
|
|
80
|
+
* the program branches on the POSIX `code` each one carries. workerd carries
|
|
81
|
+
* an error's own properties across RPC only when the sending AND the
|
|
82
|
+
* receiving isolate have the flag, on by compatibility date from 2026-04-21:
|
|
75
83
|
* the serializer writes a native error as a host object with its own
|
|
76
84
|
* properties only under the flag (src/workerd/jsg/ser.c++,
|
|
77
85
|
* Serializer::IsHostObject), and the receiver reads that detail back only
|
|
@@ -79,22 +87,34 @@ let ctxExports: CtxExports | null = null;
|
|
|
79
87
|
* https://developers.cloudflare.com/workers/runtime-apis/rpc/error-handling/.
|
|
80
88
|
* Programs load at CF_COMPAT_DATE, past it. The host runs at its embedder's
|
|
81
89
|
* date, and below it every ENOENT would reach every program as a bare message
|
|
82
|
-
* it maps to EIO.
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* is
|
|
90
|
+
* it maps to EIO. The flag alone keeps every other behavior of that date.
|
|
91
|
+
*
|
|
92
|
+
* `enable_request_signal`, which no compatibility date enables: a process
|
|
93
|
+
* owns work for a response until its client is done with it, and a client
|
|
94
|
+
* that goes away is seen only as the incoming request's `signal` aborting
|
|
95
|
+
* (src/workerd/io/worker-entrypoint.c++, "The client has disconnected").
|
|
96
|
+
* Without it a response whose client left keeps its process's work, and the
|
|
97
|
+
* host that runs it, held until the process writes again, or for good.
|
|
87
98
|
*/
|
|
88
|
-
function
|
|
99
|
+
function requireHostFlags(): void {
|
|
89
100
|
// `typeof` of an undeclared global is 'undefined', not a ReferenceError.
|
|
90
101
|
if (typeof Cloudflare === 'undefined') return;
|
|
91
|
-
if (Cloudflare.compatibilityFlags.enhanced_error_serialization
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
102
|
+
if (Cloudflare.compatibilityFlags.enhanced_error_serialization !== true) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
'fabric: this Worker runs without the enhanced_error_serialization compatibility flag, so the '
|
|
105
|
+
+ "code on a program's filesystem error (ENOENT, EEXIST, ...) would not survive the RPC back "
|
|
106
|
+
+ 'to it. Add "enhanced_error_serialization" to compatibility_flags in your wrangler config, '
|
|
107
|
+
+ 'or set compatibility_date to 2026-04-21 or later.',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
if (Cloudflare.compatibilityFlags.enable_request_signal !== true) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
'fabric: this Worker runs without the enable_request_signal compatibility flag, so a process '
|
|
113
|
+
+ 'cannot see a client leave the response it is serving, and keeps that work held. Add '
|
|
114
|
+
+ '"enable_request_signal" to compatibility_flags in your wrangler config; no compatibility '
|
|
115
|
+
+ 'date enables it.',
|
|
116
|
+
);
|
|
117
|
+
}
|
|
98
118
|
}
|
|
99
119
|
|
|
100
120
|
/**
|
|
@@ -102,10 +122,10 @@ function requireEnhancedErrorSerialization(): void {
|
|
|
102
122
|
* a second call with different values throws, naming both, so an embedder
|
|
103
123
|
* whose composition lost to an earlier import learns it at startup rather
|
|
104
124
|
* than from a facet that reached the wrong host. A host without
|
|
105
|
-
* `enhanced_error_serialization` is refused.
|
|
125
|
+
* `enhanced_error_serialization` or `enable_request_signal` is refused.
|
|
106
126
|
*/
|
|
107
127
|
export function composeFabric(value: FabricComposition): void {
|
|
108
|
-
|
|
128
|
+
requireHostFlags();
|
|
109
129
|
if (!composition) {
|
|
110
130
|
composition = value;
|
|
111
131
|
return;
|
package/src/crc32.ts
CHANGED
|
@@ -9,26 +9,23 @@
|
|
|
9
9
|
* Measured 2026-09-24 (5k files x 2 KiB; 64 MiB in 1 MiB buffers):
|
|
10
10
|
* node:zlib's crc32 is 5-40x faster than any JavaScript loop in bun, node and
|
|
11
11
|
* workerd (compat 2026-04-01, nodejs_compat), but each call costs ~0.14 us in
|
|
12
|
-
* workerd, so below 128 bytes the table loop wins there. It is
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* workerd, so below 128 bytes the table loop wins there. It is the realm's
|
|
13
|
+
* own (realmBuiltin), like core's incremental SHA-256's, so this module still
|
|
14
|
+
* loads where node:zlib is absent (a browser, workerd without nodejs_compat).
|
|
15
|
+
* The JavaScript path is slicing-by-8: 2-3x the byte-at-a-time table loop in
|
|
16
|
+
* bun and node, and level with it in workerd.
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { realmBuiltin } from './realm-builtin.js';
|
|
20
|
+
|
|
19
21
|
type NodeZlib = { crc32?(data: Uint8Array, value?: number): number };
|
|
20
|
-
type BuiltinModules = { process?: { getBuiltinModule?(id: 'node:zlib'): NodeZlib | undefined } };
|
|
21
22
|
|
|
22
23
|
/** Below this many bytes the native call's overhead exceeds the JavaScript loop's work (workerd). */
|
|
23
24
|
const CRC_NATIVE_MIN_BYTES = 128;
|
|
24
25
|
|
|
25
26
|
const nativeCrc32 = (() => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return typeof crc32 === 'function' ? crc32 : null;
|
|
29
|
-
} catch {
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
27
|
+
const crc32 = realmBuiltin<NodeZlib>('node:zlib')?.crc32;
|
|
28
|
+
return typeof crc32 === 'function' ? crc32 : null;
|
|
32
29
|
})();
|
|
33
30
|
|
|
34
31
|
/** Eight 256-entry tables: table k maps a byte to its CRC after k further zero bytes. */
|
package/src/diag-counters.ts
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
// (parsing package-lock.json). See _shared/install-phase.ts for full
|
|
34
34
|
// semantics documentation.
|
|
35
35
|
import type { InstallPhase } from './install-phase.js';
|
|
36
|
+
import { readSupervisorAllocationAttribution, type AllocationAttribution, type SupervisorAllocationLease } from './heavy-alloc-coord.js';
|
|
36
37
|
|
|
37
38
|
export interface DiagCounters {
|
|
38
39
|
/** Top-level install phase. */
|
|
@@ -62,13 +63,22 @@ export interface DiagCounters {
|
|
|
62
63
|
* `buildPrefetchBundle` accumulates raw VFS file contents into an in-heap
|
|
63
64
|
* bundle object, and used to do so with nothing observing it: the estimator
|
|
64
65
|
* read 9.4 MiB while those bytes were resetting the DO. Each build now takes
|
|
65
|
-
* an `acquireSupervisorAllocation` lease for the budget it may use
|
|
66
|
-
*
|
|
67
|
-
* rather than landing in
|
|
66
|
+
* an `acquireSupervisorAllocation` lease for the budget it may use, counted
|
|
67
|
+
* here while it is held (attributeAllocation), so the reservation is
|
|
68
|
+
* ATTRIBUTED to the bundle path rather than landing in
|
|
69
|
+
* `unattributedReservationBytes`.
|
|
68
70
|
*
|
|
69
71
|
* Surfaced as `breakdown.prefetchBundleBytes`. Back to zero between builds.
|
|
70
72
|
*/
|
|
71
73
|
prefetchBundleBytes: number;
|
|
74
|
+
/**
|
|
75
|
+
* Bytes the slices of pre-bundles in flight hold: each build's lease
|
|
76
|
+
* (browser-prebundle.ts), counted while it is held (attributeAllocation),
|
|
77
|
+
* after it shrinks to the slice it walked.
|
|
78
|
+
*
|
|
79
|
+
* Surfaced as `breakdown.preBundleSliceBytes`. Back to zero between builds.
|
|
80
|
+
*/
|
|
81
|
+
preBundleSliceBytes: number;
|
|
72
82
|
/**
|
|
73
83
|
* Bytes retained by the FacetManager's prefetch-bundle LRU right now.
|
|
74
84
|
*
|
|
@@ -145,10 +155,9 @@ export interface DiagCounters {
|
|
|
145
155
|
};
|
|
146
156
|
}
|
|
147
157
|
|
|
148
|
-
const _counters: DiagCounters = {
|
|
158
|
+
const _counters: Omit<DiagCounters, 'prefetchBundleBytes' | 'preBundleSliceBytes'> = {
|
|
149
159
|
installPhase: 'idle',
|
|
150
160
|
inFlightRpcPayloadBytes: 0,
|
|
151
|
-
prefetchBundleBytes: 0,
|
|
152
161
|
prefetchCacheBytes: 0,
|
|
153
162
|
supervisorAnsweredBytes: 0,
|
|
154
163
|
installFacet: {
|
|
@@ -174,7 +183,7 @@ const _counters: DiagCounters = {
|
|
|
174
183
|
|
|
175
184
|
/** Read a snapshot — caller-side mutations don't affect the singleton. */
|
|
176
185
|
export function readDiagCounters(): DiagCounters {
|
|
177
|
-
return { ..._counters };
|
|
186
|
+
return { ..._counters, ...readSupervisorAllocationAttribution() };
|
|
178
187
|
}
|
|
179
188
|
|
|
180
189
|
/** Count `bytes` the session answered a process's supervisor call with. */
|
|
@@ -229,24 +238,16 @@ export function rpcPayloadEnd(bytes: number): void {
|
|
|
229
238
|
}
|
|
230
239
|
}
|
|
231
240
|
|
|
241
|
+
/** The counters that name the live allocation leases of one owner. */
|
|
242
|
+
export type AllocationOwner = AllocationAttribution;
|
|
243
|
+
|
|
232
244
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* `prefetchBundleEnd` in the matching `finally` so it returns to zero whether
|
|
236
|
-
* the build succeeded or threw.
|
|
245
|
+
* Attribution belongs to the budget's actual lease. The public gauges are
|
|
246
|
+
* derived from its holders, never maintained in a second counter registry.
|
|
237
247
|
*/
|
|
238
|
-
export function
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
_counters.prefetchBundleBytes += n;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/** Release an in-flight prefetch-bundle reservation. Floors at 0. */
|
|
245
|
-
export function prefetchBundleEnd(bytes: number): void {
|
|
246
|
-
const n = Number(bytes);
|
|
247
|
-
if (!Number.isFinite(n) || n <= 0) return;
|
|
248
|
-
_counters.prefetchBundleBytes -= n;
|
|
249
|
-
if (_counters.prefetchBundleBytes < 0) _counters.prefetchBundleBytes = 0;
|
|
248
|
+
export function attributeAllocation(owner: AllocationOwner, lease: SupervisorAllocationLease): SupervisorAllocationLease {
|
|
249
|
+
lease.attribute(owner);
|
|
250
|
+
return lease;
|
|
250
251
|
}
|
|
251
252
|
|
|
252
253
|
/**
|
package/src/heap-estimate.ts
CHANGED
|
@@ -58,11 +58,7 @@
|
|
|
58
58
|
* recovery_event ring, separate from this module.
|
|
59
59
|
*/
|
|
60
60
|
|
|
61
|
-
import {
|
|
62
|
-
PRE_BUNDLE_CONCURRENCY,
|
|
63
|
-
PRE_BUNDLE_SLICE_CAP_BYTES,
|
|
64
|
-
SUPERVISOR_HEAP_CEILING_BYTES,
|
|
65
|
-
} from './limits.js';
|
|
61
|
+
import { SUPERVISOR_HEAP_CEILING_BYTES } from './limits.js';
|
|
66
62
|
import type { DiagCounters } from './diag-counters.js';
|
|
67
63
|
import {
|
|
68
64
|
readSupervisorAllocationBudget,
|
|
@@ -169,9 +165,11 @@ export interface HeapBreakdown {
|
|
|
169
165
|
vfsLruBytes: number;
|
|
170
166
|
/** SqliteVFS current logical retained write payload bytes. */
|
|
171
167
|
vfsInFlightBytes: number;
|
|
172
|
-
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
168
|
+
/**
|
|
169
|
+
* Bytes the slices of pre-bundles in flight hold (diag-counters
|
|
170
|
+
* `preBundleSliceBytes`): each build's lease, from its worst case down to
|
|
171
|
+
* the slice it walked, released as the build ends, however it ends.
|
|
172
|
+
*/
|
|
175
173
|
preBundleSliceBytes: number;
|
|
176
174
|
/**
|
|
177
175
|
* In-flight supervisor RPC payload bytes (Phase 2 A'.2).
|
|
@@ -243,32 +241,6 @@ export interface VfsHeapInputs {
|
|
|
243
241
|
*/
|
|
244
242
|
const SUPERVISOR_BASELINE_BYTES = 9 * 1024 * 1024;
|
|
245
243
|
|
|
246
|
-
/**
|
|
247
|
-
* Estimate pre-bundle slice bytes resident in the supervisor heap.
|
|
248
|
-
*
|
|
249
|
-
* Pre A'.2: the supervisor builds the slice in heap (up to
|
|
250
|
-
* SLICE_CAP_BYTES = 28 MiB per concurrent slot) before passing to the
|
|
251
|
-
* pre-bundle facet. With PRE_BUNDLE_CONCURRENCY = 1 the cap is 28 MiB.
|
|
252
|
-
*
|
|
253
|
-
* Diag counters track pre-bundle attempts/completions but not
|
|
254
|
-
* in-flight slice bytes specifically. Until A'.2 streams the slice,
|
|
255
|
-
* we treat the slice as PRESENT in supervisor heap iff the pre-bundle
|
|
256
|
-
* phase is active AND a slot is in flight (no completion yet for the
|
|
257
|
-
* current attempted count).
|
|
258
|
-
*
|
|
259
|
-
* After A'.2 lands the slice flows via ReadableStream-over-RPC and
|
|
260
|
-
* supervisor never holds the bytes — this contribution drops to 0.
|
|
261
|
-
*/
|
|
262
|
-
export function estimatePreBundleSliceBytes(c: DiagCounters): number {
|
|
263
|
-
// Conservative: if the most recent batch attempted >0 specs and the
|
|
264
|
-
// batch hasn't fully completed, assume one slice's worth of bytes
|
|
265
|
-
// are in flight.
|
|
266
|
-
const f = c.preBundleFacet;
|
|
267
|
-
const inFlight = f.attempted - f.bundlesCompleted - f.errors - f.skipped;
|
|
268
|
-
if (inFlight <= 0) return 0;
|
|
269
|
-
return Math.min(inFlight, PRE_BUNDLE_CONCURRENCY) * PRE_BUNDLE_SLICE_CAP_BYTES;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
244
|
/**
|
|
273
245
|
* Build a heap estimate from runtime counters + VFS inputs.
|
|
274
246
|
*
|
|
@@ -280,20 +252,19 @@ export function estimateSupervisorHeap(
|
|
|
280
252
|
vfs: VfsHeapInputs,
|
|
281
253
|
): HeapEstimate {
|
|
282
254
|
const allocationBudget = readSupervisorAllocationBudget();
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
// unattributed remainder rather than adding to the total twice.
|
|
255
|
+
// The prefetch build and the pre-bundle hold leases, so their bytes are
|
|
256
|
+
// already inside allocationBudget.current — naming them here moves them out
|
|
257
|
+
// of the unattributed remainder rather than adding to the total twice.
|
|
287
258
|
const transientAttributedBytes =
|
|
288
259
|
vfs.inFlightWriteBytes +
|
|
289
|
-
preBundleSliceBytes +
|
|
260
|
+
c.preBundleSliceBytes +
|
|
290
261
|
c.inFlightRpcPayloadBytes +
|
|
291
262
|
c.prefetchBundleBytes;
|
|
292
263
|
const breakdown: HeapBreakdown = {
|
|
293
264
|
supervisorBaselineBytes: SUPERVISOR_BASELINE_BYTES,
|
|
294
265
|
vfsLruBytes: vfs.cacheHotBytes,
|
|
295
266
|
vfsInFlightBytes: vfs.inFlightWriteBytes,
|
|
296
|
-
preBundleSliceBytes,
|
|
267
|
+
preBundleSliceBytes: c.preBundleSliceBytes,
|
|
297
268
|
streamingBuffersBytes: c.inFlightRpcPayloadBytes,
|
|
298
269
|
prefetchBundleBytes: c.prefetchBundleBytes,
|
|
299
270
|
prefetchCacheBytes: c.prefetchCacheBytes,
|