@nimbus-sh/core 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.
Files changed (156) hide show
  1. package/README.md +35 -7
  2. package/dist/_shared/cache-stats.d.ts +23 -1
  3. package/dist/_shared/cache-stats.d.ts.map +1 -1
  4. package/dist/_shared/cache-stats.js +13 -1
  5. package/dist/_shared/error-text.d.ts +15 -0
  6. package/dist/_shared/error-text.d.ts.map +1 -0
  7. package/dist/_shared/error-text.js +21 -0
  8. package/dist/_shared/exports-resolver.d.ts +19 -7
  9. package/dist/_shared/exports-resolver.d.ts.map +1 -1
  10. package/dist/_shared/exports-resolver.js +1 -1
  11. package/dist/_shared/retry.d.ts.map +1 -1
  12. package/dist/_shared/retry.js +4 -2
  13. package/dist/constants.d.ts +5 -11
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/constants.js +23 -49
  16. package/dist/runtime/assets-loader.js +1 -1
  17. package/dist/runtime/esbuild-service.d.ts +4 -3
  18. package/dist/runtime/esbuild-service.d.ts.map +1 -1
  19. package/dist/runtime/esbuild-service.js +9 -4
  20. package/dist/runtime/installed-runtimes.d.ts +4 -3
  21. package/dist/runtime/installed-runtimes.d.ts.map +1 -1
  22. package/dist/runtime/long-running-handle.d.ts +3 -3
  23. package/dist/runtime/long-running-handle.d.ts.map +1 -1
  24. package/dist/runtime/os-contracts.d.ts +7 -5
  25. package/dist/runtime/os-contracts.d.ts.map +1 -1
  26. package/dist/runtime/port-registry.d.ts +28 -0
  27. package/dist/runtime/port-registry.d.ts.map +1 -1
  28. package/dist/runtime/port-registry.js +79 -9
  29. package/dist/runtime/process-logs.d.ts +9 -1
  30. package/dist/runtime/process-logs.d.ts.map +1 -1
  31. package/dist/runtime/project-detect.d.ts +6 -1
  32. package/dist/runtime/project-detect.d.ts.map +1 -1
  33. package/dist/runtime/require-resolver.d.ts.map +1 -1
  34. package/dist/runtime/require-resolver.js +3 -4
  35. package/dist/runtime/runtime-registry.d.ts +4 -3
  36. package/dist/runtime/runtime-registry.d.ts.map +1 -1
  37. package/dist/runtime/runtime-registry.js +2 -1
  38. package/dist/runtime/sqlite-runtime-fs-bridge.js +1 -5
  39. package/dist/runtime/virtual-socket-kernel.d.ts.map +1 -1
  40. package/dist/runtime/virtual-socket-kernel.generated.d.ts +2 -2
  41. package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +1 -1
  42. package/dist/runtime/virtual-socket-kernel.generated.js +3 -3
  43. package/dist/runtime/virtual-socket-kernel.js +10 -5
  44. package/dist/runtime/wasm-runner.d.ts.map +1 -1
  45. package/dist/runtime/wasm-runner.js +22 -14
  46. package/dist/shell/features.d.ts.map +1 -1
  47. package/dist/shell/features.js +5 -4
  48. package/dist/shell/unix-commands.d.ts +14 -1
  49. package/dist/shell/unix-commands.d.ts.map +1 -1
  50. package/dist/shell/unix-commands.js +115 -69
  51. package/dist/substrate/lifo/commands/net/ip.d.ts.map +1 -1
  52. package/dist/substrate/lifo/commands/system/ps.d.ts +2 -1
  53. package/dist/substrate/lifo/commands/system/ps.d.ts.map +1 -1
  54. package/dist/substrate/lifo/kernel/ServiceManager.d.ts.map +1 -1
  55. package/dist/substrate/lifo/kernel/ServiceManager.js +0 -2
  56. package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts +22 -11
  57. package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts.map +1 -1
  58. package/dist/substrate/lifo/kernel/network/NetworkStack.js +0 -2
  59. package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts +2 -1
  60. package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts.map +1 -1
  61. package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.js +3 -4
  62. package/dist/substrate/lifo/kernel/vfs/VFS.d.ts +17 -0
  63. package/dist/substrate/lifo/kernel/vfs/VFS.d.ts.map +1 -1
  64. package/dist/substrate/lifo/kernel/vfs/VFS.js +33 -0
  65. package/dist/substrate/lifo/node-compat/esbuild.d.ts.map +1 -1
  66. package/dist/substrate/lifo/node-compat/esbuild.js +2 -4
  67. package/dist/substrate/lifo/node-compat/index.d.ts.map +1 -1
  68. package/dist/substrate/lifo/node-compat/index.js +3 -2
  69. package/dist/substrate/lifo/node-compat/loose-equality.d.ts +19 -0
  70. package/dist/substrate/lifo/node-compat/loose-equality.d.ts.map +1 -0
  71. package/dist/substrate/lifo/node-compat/loose-equality.js +91 -0
  72. package/dist/substrate/lifo/node-compat/process.d.ts.map +1 -1
  73. package/dist/substrate/lifo/node-compat/process.js +6 -3
  74. package/dist/substrate/lifo/shell/Shell.d.ts +19 -0
  75. package/dist/substrate/lifo/shell/Shell.d.ts.map +1 -1
  76. package/dist/substrate/lifo/shell/Shell.js +86 -75
  77. package/dist/substrate/lifo/shell/lexer.d.ts +9 -0
  78. package/dist/substrate/lifo/shell/lexer.d.ts.map +1 -1
  79. package/dist/substrate/lifo/shell/lexer.js +40 -0
  80. package/dist/vfs/seed-project.d.ts.map +1 -1
  81. package/dist/vfs/seed-project.js +9 -3
  82. package/dist/vfs/sqlite-vfs.d.ts +63 -31
  83. package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
  84. package/dist/vfs/sqlite-vfs.js +206 -41
  85. package/package.json +3 -2
  86. package/src/_shared/cache-stats.ts +27 -1
  87. package/src/_shared/error-text.ts +21 -0
  88. package/src/_shared/exports-resolver.ts +24 -4
  89. package/src/_shared/retry.ts +5 -3
  90. package/src/constants.ts +37 -53
  91. package/src/runtime/assets-loader.ts +1 -1
  92. package/src/runtime/esbuild-service.ts +16 -11
  93. package/src/runtime/installed-runtimes.ts +4 -3
  94. package/src/runtime/long-running-handle.ts +3 -3
  95. package/src/runtime/os-contracts.ts +7 -5
  96. package/src/runtime/port-registry.ts +97 -9
  97. package/src/runtime/process-logs.ts +12 -3
  98. package/src/runtime/project-detect.ts +7 -1
  99. package/src/runtime/require-resolver.ts +7 -7
  100. package/src/runtime/runtime-registry.ts +9 -7
  101. package/src/runtime/sqlite-runtime-fs-bridge.ts +9 -6
  102. package/src/runtime/virtual-socket-kernel.generated.ts +3 -3
  103. package/src/runtime/virtual-socket-kernel.ts +10 -5
  104. package/src/runtime/wasm-runner.ts +55 -29
  105. package/src/shell/features.ts +5 -4
  106. package/src/shell/unix-commands.ts +273 -128
  107. package/src/substrate/lifo/commands/net/ip.ts +15 -15
  108. package/src/substrate/lifo/commands/system/ps.ts +2 -1
  109. package/src/substrate/lifo/kernel/ServiceManager.ts +0 -2
  110. package/src/substrate/lifo/kernel/network/NetworkStack.ts +24 -17
  111. package/src/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.ts +37 -12
  112. package/src/substrate/lifo/kernel/vfs/VFS.ts +34 -0
  113. package/src/substrate/lifo/node-compat/esbuild.ts +16 -8
  114. package/src/substrate/lifo/node-compat/index.ts +3 -2
  115. package/src/substrate/lifo/node-compat/loose-equality.ts +81 -0
  116. package/src/substrate/lifo/node-compat/process.ts +5 -2
  117. package/src/substrate/lifo/shell/Shell.ts +92 -70
  118. package/src/substrate/lifo/shell/lexer.ts +38 -0
  119. package/src/vfs/seed-project.ts +13 -6
  120. package/src/vfs/sqlite-vfs.ts +233 -77
  121. package/dist/_shared/install-phase.d.ts +0 -47
  122. package/dist/_shared/install-phase.d.ts.map +0 -1
  123. package/dist/_shared/install-phase.js +0 -1
  124. package/dist/_shared/rpc-dispose.d.ts +0 -4
  125. package/dist/_shared/rpc-dispose.d.ts.map +0 -1
  126. package/dist/_shared/rpc-dispose.js +0 -30
  127. package/dist/_shared/w7-frame.d.ts +0 -79
  128. package/dist/_shared/w7-frame.d.ts.map +0 -1
  129. package/dist/_shared/w7-frame.js +0 -839
  130. package/dist/_shared/weighted-credit-pool.d.ts +0 -61
  131. package/dist/_shared/weighted-credit-pool.d.ts.map +0 -1
  132. package/dist/_shared/weighted-credit-pool.js +0 -196
  133. package/dist/observability/diag-counters.d.ts +0 -236
  134. package/dist/observability/diag-counters.d.ts.map +0 -1
  135. package/dist/observability/diag-counters.js +0 -195
  136. package/dist/observability/heap-estimate.d.ts +0 -225
  137. package/dist/observability/heap-estimate.d.ts.map +0 -1
  138. package/dist/observability/heap-estimate.js +0 -185
  139. package/dist/observability/heavy-alloc-coord.d.ts +0 -93
  140. package/dist/observability/heavy-alloc-coord.d.ts.map +0 -1
  141. package/dist/observability/heavy-alloc-coord.js +0 -181
  142. package/dist/observability/oom-classify.d.ts +0 -138
  143. package/dist/observability/oom-classify.d.ts.map +0 -1
  144. package/dist/observability/oom-classify.js +0 -261
  145. package/dist/observability/oom-discriminator.d.ts +0 -176
  146. package/dist/observability/oom-discriminator.d.ts.map +0 -1
  147. package/dist/observability/oom-discriminator.js +0 -272
  148. package/src/_shared/install-phase.ts +0 -56
  149. package/src/_shared/rpc-dispose.ts +0 -31
  150. package/src/_shared/w7-frame.ts +0 -1012
  151. package/src/_shared/weighted-credit-pool.ts +0 -244
  152. package/src/observability/diag-counters.ts +0 -343
  153. package/src/observability/heap-estimate.ts +0 -331
  154. package/src/observability/heavy-alloc-coord.ts +0 -241
  155. package/src/observability/oom-classify.ts +0 -253
  156. package/src/observability/oom-discriminator.ts +0 -405
@@ -1,331 +0,0 @@
1
- /**
2
- * heap-estimate.ts — deterministic supervisor-heap estimator [C'.1]
3
- *
4
- * Why this module exists
5
- * ──────────────────────
6
- * `process.memoryUsage()` returns 0 for every field inside a Durable
7
- * Object class context (only dynamic-worker isolates under nodejs_compat
8
- * get the real implementation). The previous `readNodeMem` /
9
- * `sampleMemory` helpers in nimbus-session-diag.ts called it anyway and
10
- * therefore reported zero forever — useless for verifying memory-
11
- * containment work in plan §3 Track A'.
12
- *
13
- * Replacement: a deterministic estimator that sums the INSTRUMENTED
14
- * supervisor heap allocation sources — runtime counters that ARE accurate
15
- * (diag-counters.ts singleton + SqliteVFS.getStats()). Every byte it
16
- * reports has a named contributor, so a regression in any one component
17
- * is locatable.
18
- *
19
- * `estimatedBytes` is a LOWER BOUND, not a total
20
- * ─────────────────────────────────────────────
21
- * Within each instrumented component the estimator over-reports on
22
- * purpose: components carry a peak-or-current value, in-flight bytes are
23
- * counted before they are freed, and LRU bytes are the cap rather than the
24
- * live footprint.
25
- *
26
- * Across components it can only sum what is instrumented. Allocation sites
27
- * that neither bump a diag counter nor take a lease via
28
- * `acquireSupervisorAllocation` are invisible to it, and it has no way to
29
- * infer their size — so any site known to be uninstrumented is named in
30
- * HEAP_BLIND_SPOTS rather than left silently absent.
31
- *
32
- * The prefetch-bundle path used to be exactly that gap, and a large one: the
33
- * supervisor DO was reset three times under bundle construction while this
34
- * estimator reported a 9.4 MiB baseline and 35 KB of LRU. Both of its sites
35
- * are now instrumented where they allocate — the per-exec build leases the
36
- * budget it spends (`prefetchBundleBytes`) and the cross-exec cache is bounded
37
- * by bytes and reports its retained total (`prefetchCacheBytes`) — so they are
38
- * components of the breakdown rather than caveats beside it.
39
- *
40
- * `blindSpots` is still returned alongside `estimatedBytes` because a list
41
- * being empty is a claim that has to be re-earned as the system grows, not a
42
- * property of the estimator. Closing a blind spot always means instrumenting
43
- * it at its own allocation site and adding it to the breakdown — never
44
- * estimating it from here.
45
- *
46
- * Eviction-label taxonomy
47
- * ───────────────────────
48
- * Workerd distinguishes five labelled eviction reasons:
49
- * - lru → memory pressure on the runtime process
50
- * - condemned → kill (operator / abuse pipeline)
51
- * - inactive → idle eviction (70-140 s of no traffic)
52
- * - dynamic_worker → per-owner LRU cap (default 50)
53
- * - dynamic_worker_banned → Dice abuse-detection ban
54
- *
55
- * Nimbus surfaces the labels here so any tool that reads
56
- * /api/_diag/memory has a fixed, well-known taxonomy to count against.
57
- * The actual count of evictions Nimbus has observed lives in the C'.2
58
- * recovery_event ring, separate from this module.
59
- */
60
-
61
- import {
62
- PRE_BUNDLE_CONCURRENCY,
63
- PRE_BUNDLE_SLICE_CAP_BYTES,
64
- SUPERVISOR_HEAP_CEILING_BYTES,
65
- } from '../constants.js';
66
- import type { DiagCounters } from './diag-counters.js';
67
- import {
68
- readSupervisorAllocationBudget,
69
- type SupervisorAllocationBudgetStats,
70
- } from './heavy-alloc-coord.js';
71
-
72
- /**
73
- * Five labelled workerd eviction reasons. Surfaced as a constant
74
- * taxonomy in /api/_diag/memory so any consumer can count observed
75
- * events against the well-known set.
76
- *
77
- */
78
- export const WORKERD_EVICTION_LABELS = [
79
- 'lru',
80
- 'condemned',
81
- 'inactive',
82
- 'dynamic_worker',
83
- 'dynamic_worker_banned',
84
- ] as const;
85
-
86
- /**
87
- * A supervisor allocation site the estimator cannot see.
88
- *
89
- * Listing one here is a statement of fact about instrumentation, not a
90
- * measurement: the site allocates on the supervisor heap without bumping a
91
- * diag counter or taking an `acquireSupervisorAllocation` lease, so its
92
- * live size is unknown to this module. Only `capBytes` — the bound the
93
- * site enforces on itself — is knowable from here.
94
- */
95
- export interface HeapBlindSpot {
96
- /** Module-qualified allocation site. */
97
- source: string;
98
- /**
99
- * Upper bound the site enforces per retained item, or null when nothing
100
- * bounds its total bytes. A null here is the important case: it means no
101
- * finite worst case can be stated for the supervisor at all.
102
- */
103
- capBytes: number | null;
104
- /** What allocates, and why the estimator cannot observe it. */
105
- reason: string;
106
- }
107
-
108
- /**
109
- * Supervisor allocation sites known to be unaccounted for.
110
- *
111
- * Empty: both former entries were the prefetch-bundle path in
112
- * facets/manager.ts, and both are now instrumented at their own allocation
113
- * sites rather than described from here. `buildPrefetchBundle` takes an
114
- * `acquireSupervisorAllocation` lease and reports
115
- * `breakdown.prefetchBundleBytes`; `prefetchBundleCache` is bounded by
116
- * PREFETCH_CACHE_MAX_BYTES instead of by entry count and reports its live
117
- * retained total as `breakdown.prefetchCacheBytes`.
118
- *
119
- * An empty list says nothing is CURRENTLY known to be missing — it is not a
120
- * proof of completeness, which no list can be. Adding an entry is how a newly
121
- * discovered gap stays visible in /api/_diag/memory until it is closed the
122
- * same way: instrumented where it allocates, then folded into HeapBreakdown.
123
- */
124
- export const HEAP_BLIND_SPOTS: readonly HeapBlindSpot[] = [];
125
-
126
- /**
127
- * Supervisor heap snapshot. Components are PEAK-OR-CURRENT bytes from
128
- * runtime counters; the estimator never calls process.memoryUsage().
129
- */
130
- export interface HeapEstimate {
131
- /**
132
- * Sum of the instrumented components below. Always equals the breakdown
133
- * sum, and is a LOWER BOUND on supervisor heap — see `blindSpots`.
134
- */
135
- estimatedBytes: number;
136
- /** SUPERVISOR_HEAP_CEILING_BYTES (constant — 64 MiB by design). */
137
- ceilingBytes: number;
138
- /**
139
- * estimatedBytes / ceilingBytes × 100, one decimal place. Shares
140
- * estimatedBytes' lower-bound caveat: a low value does NOT mean the
141
- * supervisor has headroom, only that instrumented sources are small.
142
- */
143
- percentOfCeiling: number;
144
- /** Per-source byte attribution for the instrumented sources. */
145
- breakdown: HeapBreakdown;
146
- /**
147
- * Allocation sites this estimate does not cover. Non-empty means
148
- * `estimatedBytes` cannot be read as a total. See HEAP_BLIND_SPOTS.
149
- */
150
- blindSpots: readonly HeapBlindSpot[];
151
- /**
152
- * Worst-case bytes the blind spots could add, or null when any of them
153
- * is unbounded — in which case no finite worst case exists and only
154
- * direct instrumentation can answer the headroom question.
155
- */
156
- blindSpotCeilingBytes: number | null;
157
- /**
158
- * Shared transient-allocation budget occupancy. Named contributors overlap
159
- * this total; any otherwise-unattributed occupancy is included in
160
- * breakdown.unattributedReservationBytes.
161
- */
162
- allocationBudget: SupervisorAllocationBudgetStats;
163
- }
164
-
165
- export interface HeapBreakdown {
166
- /** Static module bundle + runtime baseline. Constant per build. */
167
- supervisorBaselineBytes: number;
168
- /** SqliteVFS LRU cache hot-byte count (sqlite-vfs.ts cache.hotBytes). */
169
- vfsLruBytes: number;
170
- /** SqliteVFS current logical retained write payload bytes. */
171
- vfsInFlightBytes: number;
172
- /** Pre-bundle slice bytes resident in supervisor heap (peak across
173
- * in-flight pool slots × SLICE_CAP_BYTES). Drops to 0 once
174
- * A'.2/A'.3 stream the slice through ReadableStream-over-RPC. */
175
- preBundleSliceBytes: number;
176
- /**
177
- * In-flight supervisor RPC payload bytes (Phase 2 A'.2).
178
- *
179
- * Sum of bytes claimed by DO-side byte-returning filesystem RPC handlers
180
- * between payload allocation and exit. Tracked by
181
- * `inFlightRpcPayloadBytes` in src/observability/diag-counters.ts; bumped
182
- * after shared-budget admission and debited in `finally`.
183
- *
184
- * At idle this is 0. Under load it should stay bounded by the
185
- * shared allocation budget.
186
- * Persistent non-zero readings here mean an RPC handler isn't
187
- * decrementing on its failure path — a leak worth fixing.
188
- */
189
- streamingBuffersBytes: number;
190
- /**
191
- * Bytes reserved by prefetch-bundle builds in flight (diag-counters
192
- * `prefetchBundleBytes`). Transient: each build leases the enrichment budget
193
- * for its duration and releases it on the way out, success or failure.
194
- */
195
- prefetchBundleBytes: number;
196
- /**
197
- * Bytes retained by the FacetManager's prefetch-bundle LRU (diag-counters
198
- * `prefetchCacheBytes`). Unlike every other component here these persist
199
- * across execs, and unlike the transient ones they are a measurement of what
200
- * the cache holds rather than a reservation it might use.
201
- */
202
- prefetchCacheBytes: number;
203
- /**
204
- * Shared-budget occupancy not already represented by the named transient
205
- * counters. This covers full-budget owners and keeps cross-DO module-local
206
- * reservations visible without double-counting read/write payloads.
207
- */
208
- unattributedReservationBytes: number;
209
- }
210
-
211
- /**
212
- * Inputs the estimator needs from the SqliteVFS layer. Kept narrow
213
- * (just the two fields we actually consume) to avoid a circular import
214
- * between observability/ and the VFS module.
215
- */
216
- export interface VfsHeapInputs {
217
- /** SqliteVFS.getStats().cache.hotBytes — actual LRU memory in use. */
218
- cacheHotBytes: number;
219
- /** Current sum of in-flight write payloads (or 0 if none). */
220
- inFlightWriteBytes: number;
221
- }
222
-
223
- // ── Architectural constants for non-counter contributors ────────────────
224
- //
225
- // Each constant below has a comment explaining the source-of-truth and
226
- // when it is expected to change (typically: when a Track A' wave lands).
227
- // They are NOT runtime-measured because the underlying values aren't
228
- // observable from JS — they're properties of the worker bundle itself.
229
-
230
- /**
231
- * Static supervisor baseline. The compiled worker bundle resident in V8
232
- * isolate memory: module sources, class definitions, top-level imports,
233
- * lookup tables.
234
- *
235
- * Phase 2 A'.5 dropped the 16 MiB esbuild-wasm base64 string from the
236
- * generated module (it now lives in env.ASSETS), shrinking the worker
237
- * bundle by ~21 MiB UTF-16-resident. Empirical baseline post-A'.5 is
238
- * ~9 MiB (verified via the heap-estimator probe + bundle size).
239
- *
240
- * This is constant across a deploy. A bundle-size shrink (removing an
241
- * unused dependency, splitting another big constant out to assets)
242
- * would lower this further.
243
- */
244
- const SUPERVISOR_BASELINE_BYTES = 9 * 1024 * 1024;
245
-
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
- /**
273
- * Build a heap estimate from runtime counters + VFS inputs.
274
- *
275
- * Deterministic and I/O-free. Called from the /api/_diag/memory request
276
- * handler; the allocation-budget snapshot is process-local state.
277
- */
278
- export function estimateSupervisorHeap(
279
- c: DiagCounters,
280
- vfs: VfsHeapInputs,
281
- ): HeapEstimate {
282
- const allocationBudget = readSupervisorAllocationBudget();
283
- const preBundleSliceBytes = estimatePreBundleSliceBytes(c);
284
- // The prefetch build holds a lease, so its bytes are already inside
285
- // allocationBudget.current — naming it here moves it out of the
286
- // unattributed remainder rather than adding to the total twice.
287
- const transientAttributedBytes =
288
- vfs.inFlightWriteBytes +
289
- preBundleSliceBytes +
290
- c.inFlightRpcPayloadBytes +
291
- c.prefetchBundleBytes;
292
- const breakdown: HeapBreakdown = {
293
- supervisorBaselineBytes: SUPERVISOR_BASELINE_BYTES,
294
- vfsLruBytes: vfs.cacheHotBytes,
295
- vfsInFlightBytes: vfs.inFlightWriteBytes,
296
- preBundleSliceBytes,
297
- streamingBuffersBytes: c.inFlightRpcPayloadBytes,
298
- prefetchBundleBytes: c.prefetchBundleBytes,
299
- prefetchCacheBytes: c.prefetchCacheBytes,
300
- unattributedReservationBytes: Math.max(
301
- 0,
302
- allocationBudget.current - transientAttributedBytes,
303
- ),
304
- };
305
-
306
- const estimatedBytes =
307
- breakdown.supervisorBaselineBytes +
308
- breakdown.vfsLruBytes +
309
- breakdown.vfsInFlightBytes +
310
- breakdown.preBundleSliceBytes +
311
- breakdown.streamingBuffersBytes +
312
- breakdown.prefetchBundleBytes +
313
- breakdown.prefetchCacheBytes +
314
- breakdown.unattributedReservationBytes;
315
-
316
- const percentOfCeiling = Math.round(
317
- (estimatedBytes / SUPERVISOR_HEAP_CEILING_BYTES) * 1000,
318
- ) / 10;
319
-
320
- return {
321
- estimatedBytes,
322
- ceilingBytes: SUPERVISOR_HEAP_CEILING_BYTES,
323
- percentOfCeiling,
324
- breakdown,
325
- blindSpots: HEAP_BLIND_SPOTS,
326
- blindSpotCeilingBytes: HEAP_BLIND_SPOTS.some((s) => s.capBytes === null)
327
- ? null
328
- : HEAP_BLIND_SPOTS.reduce((sum, s) => sum + (s.capBytes ?? 0), 0),
329
- allocationBudget,
330
- };
331
- }
@@ -1,241 +0,0 @@
1
- /**
2
- * Supervisor-local byte-budget back-pressure for transient allocations.
3
- *
4
- * A DO can share its isolate's 128 MiB platform ceiling with peer DOs, so
5
- * independent "safe" allocations cannot each assume the full ceiling.
6
- * Module scope gives every allocator in one supervisor isolate a single FIFO
7
- * budget. Full-budget owners are exclusive; weighted owners may overlap only
8
- * while their retained-byte claims fit together.
9
- */
10
-
11
- import {
12
- CHUNK_SIZE,
13
- SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES,
14
- SUPERVISOR_READ_RESERVE_BYTES,
15
- } from '../constants.js';
16
- import {
17
- WeightedCreditPool,
18
- type ResizableCreditLease,
19
- type WeightedCreditPoolOptions,
20
- } from '../_shared/weighted-credit-pool.js';
21
-
22
- export interface SupervisorAllocationBudgetStats {
23
- readonly capacity: number;
24
- readonly current: number;
25
- readonly peak: number;
26
- readonly queued: number;
27
- }
28
-
29
- interface AllocationBudgetLifecycle {
30
- onActive?: () => void;
31
- onIdle?: () => void;
32
- }
33
-
34
- /**
35
- * Reusable contract behind the supervisor singleton. A separate instance is
36
- * useful in tests and keeps the invariant independent of Nimbus internals.
37
- */
38
- export class SupervisorAllocationBudget {
39
- private readonly credits: WeightedCreditPool;
40
- private active = false;
41
- /**
42
- * Leases that drive the disposable-cache lifecycle. Occupancy cannot stand
43
- * in for this: a read holds credit too, and a read must not sacrifice the
44
- * cache it is filling.
45
- */
46
- private lifecycleHolders = 0;
47
-
48
- constructor(
49
- readonly capacity: number,
50
- private readonly lifecycle: AllocationBudgetLifecycle = {},
51
- reserve: WeightedCreditPoolOptions = {},
52
- ) {
53
- this.credits = new WeightedCreditPool(capacity, reserve);
54
- }
55
-
56
- get stats(): SupervisorAllocationBudgetStats {
57
- return {
58
- capacity: this.capacity,
59
- ...this.credits.stats,
60
- };
61
- }
62
-
63
- /**
64
- * Reserve bytes and, while held, mark the budget active so observers can
65
- * free heap headroom. For owners whose payload is what the headroom is for:
66
- * installs, clones, pre-bundles, boot payloads, streamed writes.
67
- */
68
- acquire(bytes: number, signal?: AbortSignal): Promise<ResizableCreditLease> {
69
- return this._acquire(bytes, signal, true);
70
- }
71
-
72
- /**
73
- * Reserve bytes WITHOUT driving the cache lifecycle, for owners that are
74
- * filling the disposable cache rather than competing with it. Shrinking a
75
- * chunk cache to serve a chunk read is circular: sequentially the budget
76
- * empties between every read, so an occupancy-edged observer fires once per
77
- * read and the cache is pinned at its shrunk floor for the whole workload,
78
- * never warming. The byte credit is still taken — back-pressure is what
79
- * stops a large read overlapping a large write.
80
- */
81
- acquireWithoutLifecycle(bytes: number, signal?: AbortSignal): Promise<ResizableCreditLease> {
82
- return this._acquire(bytes, signal, false);
83
- }
84
-
85
- private async _acquire(
86
- bytes: number,
87
- signal: AbortSignal | undefined,
88
- drivesLifecycle: boolean,
89
- ): Promise<ResizableCreditLease> {
90
- const credit = await this.credits.acquire(bytes, signal);
91
- if (drivesLifecycle) {
92
- this.lifecycleHolders++;
93
- if (!this.active) {
94
- this.active = true;
95
- this.lifecycle.onActive?.();
96
- }
97
- }
98
-
99
- let released = false;
100
- return {
101
- get bytes() {
102
- return credit.bytes;
103
- },
104
- shrinkTo: (nextBytes: number) => {
105
- credit.shrinkTo(nextBytes);
106
- },
107
- release: () => {
108
- if (released) return;
109
- released = true;
110
- credit.release();
111
- if (!drivesLifecycle) return;
112
- this.lifecycleHolders--;
113
- if (this.lifecycleHolders === 0 && this.active) {
114
- this.active = false;
115
- this.lifecycle.onIdle?.();
116
- }
117
- },
118
- };
119
- }
120
- }
121
-
122
- /**
123
- * A registered observer (typically a SqliteVFS) receives shrink/restore
124
- * signals when the shared budget transitions idle↔active.
125
- */
126
- interface AllocObserver {
127
- /** Called when budget occupancy transitions idle → active. */
128
- onAcquire?: () => void;
129
- /** Called when budget occupancy transitions active → idle. */
130
- onRelease?: () => void;
131
- }
132
-
133
- interface Coord {
134
- observers: Set<AllocObserver>;
135
- active: boolean;
136
- }
137
-
138
- const coord: Coord = { observers: new Set(), active: false };
139
-
140
- function getCoord(): Coord {
141
- return coord;
142
- }
143
-
144
- function errorMessage(error: unknown): string {
145
- return error instanceof Error ? error.message : String(error);
146
- }
147
-
148
- /**
149
- * Register an observer that fires when the shared allocation budget
150
- * transitions idle → active and active → idle.
151
- * Returns an unsubscribe function. Idempotent: registering the same
152
- * observer twice is a no-op (Set semantics).
153
- *
154
- * NimbusSession uses this to shrink the disposable VFS LRU while transient
155
- * payloads are admitted.
156
- */
157
- export function registerAllocObserver(o: AllocObserver): () => void {
158
- const c = getCoord();
159
- const isNew = !c.observers.has(o);
160
- c.observers.add(o);
161
- if (isNew && c.active) {
162
- try { o.onAcquire?.(); } catch (error) {
163
- // eslint-disable-next-line no-console
164
- console.error('[heavy-alloc-coord] observer.onAcquire threw:', errorMessage(error));
165
- }
166
- }
167
- return () => { c.observers.delete(o); };
168
- }
169
-
170
- function fireOnAcquire(): void {
171
- const c = getCoord();
172
- c.active = true;
173
- for (const o of c.observers) {
174
- try { o.onAcquire?.(); } catch (error) {
175
- // Observer errors must NOT break the heavy-alloc protocol.
176
- // Log and continue.
177
- // eslint-disable-next-line no-console
178
- console.error('[heavy-alloc-coord] observer.onAcquire threw:', errorMessage(error));
179
- }
180
- }
181
- }
182
-
183
- function fireOnRelease(): void {
184
- const c = getCoord();
185
- c.active = false;
186
- for (const o of c.observers) {
187
- try { o.onRelease?.(); } catch (error) {
188
- // eslint-disable-next-line no-console
189
- console.error('[heavy-alloc-coord] observer.onRelease threw:', errorMessage(error));
190
- }
191
- }
192
- }
193
-
194
- const supervisorAllocationBudget = new SupervisorAllocationBudget(
195
- SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES,
196
- {
197
- onActive: fireOnAcquire,
198
- onIdle: fireOnRelease,
199
- },
200
- // A chunk-sized read draws on this rather than queueing behind a
201
- // multi-megabyte owner for a wait unrelated to its own cost.
202
- { smallRequestBytes: CHUNK_SIZE, reserve: SUPERVISOR_READ_RESERVE_BYTES },
203
- );
204
-
205
- /**
206
- * Reserve an exact number of supervisor-resident bytes.
207
- */
208
- export function acquireSupervisorAllocation(
209
- bytes: number,
210
- signal?: AbortSignal,
211
- ): Promise<ResizableCreditLease> {
212
- return supervisorAllocationBudget.acquire(bytes, signal);
213
- }
214
-
215
- /**
216
- * Reserve bytes for a filesystem READ. Takes the same byte credit as any
217
- * other owner but does not shrink the disposable VFS cache, which reads are
218
- * there to fill.
219
- */
220
- export function acquireSupervisorReadAllocation(
221
- bytes: number,
222
- signal?: AbortSignal,
223
- ): Promise<ResizableCreditLease> {
224
- return supervisorAllocationBudget.acquireWithoutLifecycle(bytes, signal);
225
- }
226
-
227
- /**
228
- * Reserve the full budget for an allocation whose retained size is not known
229
- * accurately enough to overlap safely with other heavy work.
230
- */
231
- export async function acquireHeavyAlloc(signal?: AbortSignal): Promise<() => void> {
232
- const lease = await acquireSupervisorAllocation(
233
- SUPERVISOR_IN_FLIGHT_ALLOCATION_BUDGET_BYTES,
234
- signal,
235
- );
236
- return lease.release;
237
- }
238
-
239
- export function readSupervisorAllocationBudget(): SupervisorAllocationBudgetStats {
240
- return supervisorAllocationBudget.stats;
241
- }