@camstack/system 1.2.59 → 1.2.61
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/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/prefix-range.d.ts +50 -0
- package/dist/builtins/sqlite-storage/query-bounds.d.ts +116 -0
- package/dist/builtins/sqlite-storage/sqlite-pragmas.d.ts +12 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +38 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +281 -19
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +281 -19
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-CV6XTApk.js → dist-BN681PgO.js} +974 -18
- package/dist/{dist-BqJJWCS8.mjs → dist-L6QPJWsr.mjs} +974 -18
- package/dist/index.d.ts +2 -2
- package/dist/index.js +131 -9
- package/dist/index.mjs +126 -9
- package/dist/kernel/heap-watch.d.ts +48 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { startHeapWatch, buildHeapSample, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, } from './kernel/heap-watch.js';
|
|
2
|
-
export type { HeapSample, HeapWatchSink } from './kernel/heap-watch.js';
|
|
1
|
+
export { startHeapWatch, buildHeapSample, createV8Reclaimer, shouldReclaim, strandedMb, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, HEAP_RECLAIM_TRIGGER_MB, HEAP_RECLAIM_MIN_INTERVAL_MS, } from './kernel/heap-watch.js';
|
|
2
|
+
export type { HeapSample, HeapWatchSink, HeapReclaimer, HeapReclaimOptions, } from './kernel/heap-watch.js';
|
|
3
3
|
export { ensureBinary, downloadBinary, findInPath, getPlatformInfo, buildBinaryPath, ensureFfmpeg, getFfmpegDownloadUrl, ensurePython, installPythonPackages, installPythonRequirements, getPythonDownloadUrl, PYTHON_VERSION, } from '@camstack/types/node';
|
|
4
4
|
export type { PlatformInfo } from '@camstack/types/node';
|
|
5
5
|
export { ALL_CAPABILITY_DEFINITIONS, METHOD_ACCESS_MAP, expandCapMethods, isArrayOutputSchema, isCollectionArrayMethod, addonSettingsCapability, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, } from '@camstack/types';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("./dist-
|
|
3
|
+
const require_dist = require("./dist-BN681PgO.js");
|
|
4
4
|
const require_model_download_service = require("./model-download-service-Cpc6SWzV.js");
|
|
5
5
|
const require_manifest_python_deps = require("./manifest-python-deps-d4acYXVV.js");
|
|
6
6
|
const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
|
|
@@ -30,6 +30,8 @@ require("./builtins/device-manager/index.js");
|
|
|
30
30
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
31
31
|
let node_v8 = require("node:v8");
|
|
32
32
|
node_v8 = require_chunk.__toESM(node_v8);
|
|
33
|
+
let node_vm = require("node:vm");
|
|
34
|
+
node_vm = require_chunk.__toESM(node_vm);
|
|
33
35
|
let _camstack_types_node = require("@camstack/types/node");
|
|
34
36
|
let zod = require("zod");
|
|
35
37
|
let node_crypto = require("node:crypto");
|
|
@@ -43,8 +45,6 @@ let _camstack_types_addon = require("@camstack/types/addon");
|
|
|
43
45
|
let node_child_process = require("node:child_process");
|
|
44
46
|
let node_util = require("node:util");
|
|
45
47
|
node_util = require_chunk.__toESM(node_util);
|
|
46
|
-
let node_vm = require("node:vm");
|
|
47
|
-
node_vm = require_chunk.__toESM(node_vm);
|
|
48
48
|
let node_os = require("node:os");
|
|
49
49
|
node_os = require_chunk.__toESM(node_os);
|
|
50
50
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -60,13 +60,15 @@ path = require_chunk.__toESM(path, 1);
|
|
|
60
60
|
let module$1 = require("module");
|
|
61
61
|
//#region src/kernel/heap-watch.ts
|
|
62
62
|
/**
|
|
63
|
-
* heap-watch — hub-main's own memory, on the record.
|
|
63
|
+
* heap-watch — hub-main's own memory, on the record, and returned to the OS.
|
|
64
64
|
*
|
|
65
65
|
* ## Why this exists
|
|
66
66
|
*
|
|
67
|
-
* hub-main
|
|
68
|
-
*
|
|
69
|
-
*
|
|
67
|
+
* hub-main used to be launched with `--max-old-space-size=1536` (docker-entrypoint;
|
|
68
|
+
* the cap was removed on 2026-08-02, so the live ceiling is now V8's own default —
|
|
69
|
+
* 4288MB in this container — and this module reads it at runtime rather than
|
|
70
|
+
* assuming a number). It logged NOTHING about its own heap. When it died on
|
|
71
|
+
* 2026-08-01/02 the only trace was V8's own stderr line:
|
|
70
72
|
*
|
|
71
73
|
* FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
|
|
72
74
|
*
|
|
@@ -94,6 +96,41 @@ let module$1 = require("module");
|
|
|
94
96
|
* `--max-old-space-size` and are most of the gap between a 1.5GB cap and the
|
|
95
97
|
* ~2.05GB RSS observed live: a diagnosis that only looks at the JS heap
|
|
96
98
|
* misreads that process by 500MB.
|
|
99
|
+
*
|
|
100
|
+
* ## And then the heartbeat explained the "off-heap leak", which was not one
|
|
101
|
+
*
|
|
102
|
+
* 2026-08-08, measured on the live hub over 1019 consecutive samples and seven
|
|
103
|
+
* separate boots: **hub-main's RSS is a HIGH-WATER MARK, not a leak.** In every
|
|
104
|
+
* one of those 1019 samples RSS was >= the running maximum of
|
|
105
|
+
* `heapTotal + external`, never once below it, and
|
|
106
|
+
* `RSS_peak - peak(heapTotal + external)` landed between 874MB and 1639MB on
|
|
107
|
+
* every boot — a roughly constant native floor on top of a number that only
|
|
108
|
+
* ever goes up. RSS tracked the highest point the process ever reached and
|
|
109
|
+
* stayed there: 3.3GB -> 4.5GB -> 5.2GB, and 7.8GB on the worst boot.
|
|
110
|
+
*
|
|
111
|
+
* Two things push that peak, and neither is a retained object:
|
|
112
|
+
*
|
|
113
|
+
* - the JS heap sawtooths to 2.0-2.8GB because the ceiling is 4288MB and
|
|
114
|
+
* nothing creates pressure before then;
|
|
115
|
+
* - `arrayBuffers` accumulates DEAD Buffers — up to 3.65GB — because V8 only
|
|
116
|
+
* sweeps them on a major GC. Eleven collapses were captured, and every
|
|
117
|
+
* single one coincides with `heapUsed` dropping to its ~650MB floor. No code
|
|
118
|
+
* released a reference at 13:23:03; a mark-compact ran.
|
|
119
|
+
*
|
|
120
|
+
* At the peak V8 has committed the pages, and **an ordinary major GC does not
|
|
121
|
+
* give them back.** Proven on the production hub: `smaps` showed 10,819 mapped
|
|
122
|
+
* 256KB pages (V8's `kRegularPageSize`) holding 2704MB while V8 itself reported
|
|
123
|
+
* `total_physical_size` of only 846MB — ~7,800 pages committed, resident, and
|
|
124
|
+
* outside V8's own accounting. One `gc({ flavor: 'last-resort' })` took 417ms
|
|
125
|
+
* and returned RSS 3355MB -> 1237MB, pages 10,819 -> 2,963.
|
|
126
|
+
*
|
|
127
|
+
* So the fix for the "~3GB/2h off-heap growth" is not a retainer hunt. It is to
|
|
128
|
+
* ask V8 for the pages back when the process is visibly holding more than it is
|
|
129
|
+
* using — which is what `shouldReclaim` decides and what the heartbeat now does.
|
|
130
|
+
*
|
|
131
|
+
* The cost is a 250-450ms stop-the-world on the hub's router process, bounded to
|
|
132
|
+
* once per `HEAP_RECLAIM_MIN_INTERVAL_MS`. The alternative, measured, is a
|
|
133
|
+
* ratchet to 7.8GB and the OOM that ends it.
|
|
97
134
|
*/
|
|
98
135
|
/** Heartbeat period. Slow enough to be free, fast enough that the sample before
|
|
99
136
|
* a fatal is recent. */
|
|
@@ -101,6 +138,20 @@ var HEAP_WATCH_INTERVAL_MS = 6e4;
|
|
|
101
138
|
/** Warn above this share of the heap ceiling. Chosen so the warning precedes
|
|
102
139
|
* the fatal by more than one interval under realistic growth. */
|
|
103
140
|
var HEAP_WATCH_WARN_RATIO = .8;
|
|
141
|
+
/**
|
|
142
|
+
* Reclaim once the process holds this many MB beyond what it is USING.
|
|
143
|
+
*
|
|
144
|
+
* Live calibration, both readings from the same hub minutes apart: 2491MB
|
|
145
|
+
* stranded before the reclaim, 438MB after. 438MB is this process's irreducible
|
|
146
|
+
* native floor (sqlite pages, libvips, sharp, thread stacks, node's own text) —
|
|
147
|
+
* collecting against it would buy nothing and cost a stall every minute. 1536
|
|
148
|
+
* sits clear of that floor and still fires ~1.1GB before the ratchet is worth
|
|
149
|
+
* anything, so it triggers on the real condition and stops as soon as it is fixed.
|
|
150
|
+
*/
|
|
151
|
+
var HEAP_RECLAIM_TRIGGER_MB = 1536;
|
|
152
|
+
/** Floor on how often the stop-the-world may run. 250-450ms measured, so ten
|
|
153
|
+
* minutes keeps the worst case under 0.1% of the router's time. */
|
|
154
|
+
var HEAP_RECLAIM_MIN_INTERVAL_MS = 6e5;
|
|
104
155
|
var MB = (bytes) => Math.round(bytes / 1048576);
|
|
105
156
|
/**
|
|
106
157
|
* Build a sample from raw readings. Pure so the thresholds are testable without
|
|
@@ -123,6 +174,47 @@ var consoleSink = {
|
|
|
123
174
|
info: (line) => console.log(line),
|
|
124
175
|
warn: (line) => console.warn(line)
|
|
125
176
|
};
|
|
177
|
+
/**
|
|
178
|
+
* RSS the process is holding but not using.
|
|
179
|
+
*
|
|
180
|
+
* The subtrahend is heapUSED, not heapTotal, deliberately: the pages V8
|
|
181
|
+
* committed at a sawtooth peak and no longer needs are counted inside heapTotal,
|
|
182
|
+
* and they are precisely what this number exists to see.
|
|
183
|
+
*/
|
|
184
|
+
function strandedMb(s) {
|
|
185
|
+
return s.rssMb - s.heapUsedMb - s.externalMb;
|
|
186
|
+
}
|
|
187
|
+
/** Whether it is worth paying a stop-the-world to get those pages back. */
|
|
188
|
+
function shouldReclaim(s, triggerMb = HEAP_RECLAIM_TRIGGER_MB) {
|
|
189
|
+
return strandedMb(s) > triggerMb;
|
|
190
|
+
}
|
|
191
|
+
function isGcFunction(value) {
|
|
192
|
+
return typeof value === "function";
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Build the reclaimer without requiring `--expose-gc` on argv.
|
|
196
|
+
*
|
|
197
|
+
* The flag is turned on, the function captured, and the flag turned straight
|
|
198
|
+
* back off — a global `gc` left reachable by application code is a footgun, and
|
|
199
|
+
* requiring an argv change would mean this cannot ship without a new image.
|
|
200
|
+
* Verified against the live hub before being written: 266ms, RSS 1453 -> 1237.
|
|
201
|
+
*
|
|
202
|
+
* Returns undefined if V8 declines, so the caller keeps a heartbeat either way.
|
|
203
|
+
*/
|
|
204
|
+
function createV8Reclaimer() {
|
|
205
|
+
try {
|
|
206
|
+
node_v8.setFlagsFromString("--expose-gc");
|
|
207
|
+
const gc = node_vm.runInNewContext("gc");
|
|
208
|
+
node_v8.setFlagsFromString("--no-expose-gc");
|
|
209
|
+
if (!isGcFunction(gc)) return void 0;
|
|
210
|
+
return () => gc({
|
|
211
|
+
execution: "sync",
|
|
212
|
+
flavor: "last-resort"
|
|
213
|
+
});
|
|
214
|
+
} catch {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
126
218
|
function format$1(label, s) {
|
|
127
219
|
return `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
|
|
128
220
|
}
|
|
@@ -132,13 +224,38 @@ function format$1(label, s) {
|
|
|
132
224
|
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
133
225
|
* a diagnostic that can crash the process it watches is worse than none.
|
|
134
226
|
*/
|
|
135
|
-
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS) {
|
|
227
|
+
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions) {
|
|
228
|
+
const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
|
|
229
|
+
const now = reclaimOptions?.now ?? (() => Date.now());
|
|
230
|
+
const triggerMb = reclaimOptions?.triggerMb ?? 1536;
|
|
231
|
+
const minIntervalMs = reclaimOptions?.minIntervalMs ?? 6e5;
|
|
232
|
+
let lastReclaimAt = Number.NEGATIVE_INFINITY;
|
|
233
|
+
const read = () => {
|
|
234
|
+
const limit = reclaimOptions?.heapLimitBytes ?? node_v8.getHeapStatistics().heap_size_limit;
|
|
235
|
+
return buildHeapSample(readMemory(), limit);
|
|
236
|
+
};
|
|
237
|
+
const maybeReclaim = (sample) => {
|
|
238
|
+
if (reclaimOptions === void 0) return;
|
|
239
|
+
if (!shouldReclaim(sample, triggerMb)) return;
|
|
240
|
+
if (now() - lastReclaimAt < minIntervalMs) return;
|
|
241
|
+
lastReclaimAt = now();
|
|
242
|
+
const startedAt = now();
|
|
243
|
+
try {
|
|
244
|
+
reclaimOptions.reclaim();
|
|
245
|
+
} catch (error) {
|
|
246
|
+
sink.warn(`[mem] reclaim failed — ${error instanceof Error ? error.message : String(error)}`);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const after = read();
|
|
250
|
+
sink.info(`[mem] reclaim ${label} stranded=${strandedMb(sample)}MB rss=${sample.rssMb}MB→${after.rssMb}MB freed=${sample.rssMb - after.rssMb}MB arrayBuffers=${sample.arrayBuffersMb}MB→${after.arrayBuffersMb}MB took=${now() - startedAt}ms`);
|
|
251
|
+
};
|
|
136
252
|
const tick = () => {
|
|
137
253
|
try {
|
|
138
|
-
const sample =
|
|
254
|
+
const sample = read();
|
|
139
255
|
const line = format$1(label, sample);
|
|
140
256
|
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
141
257
|
else sink.info(line);
|
|
258
|
+
maybeReclaim(sample);
|
|
142
259
|
} catch {}
|
|
143
260
|
};
|
|
144
261
|
const timer = setInterval(tick, intervalMs);
|
|
@@ -94566,6 +94683,8 @@ exports.FsStorageBackend = FsStorageBackend;
|
|
|
94566
94683
|
exports.HEALTH_MONITOR_GRACE_PERIOD_MS = HEALTH_MONITOR_GRACE_PERIOD_MS;
|
|
94567
94684
|
exports.HEALTH_MONITOR_RETRY_INTERVALS_MS = HEALTH_MONITOR_RETRY_INTERVALS_MS;
|
|
94568
94685
|
exports.HEALTH_MONITOR_TICK_MS = HEALTH_MONITOR_TICK_MS;
|
|
94686
|
+
exports.HEAP_RECLAIM_MIN_INTERVAL_MS = HEAP_RECLAIM_MIN_INTERVAL_MS;
|
|
94687
|
+
exports.HEAP_RECLAIM_TRIGGER_MB = HEAP_RECLAIM_TRIGGER_MB;
|
|
94569
94688
|
exports.HEAP_WATCH_INTERVAL_MS = HEAP_WATCH_INTERVAL_MS;
|
|
94570
94689
|
exports.HEAP_WATCH_WARN_RATIO = HEAP_WATCH_WARN_RATIO;
|
|
94571
94690
|
exports.HUB_CAP_FWD_ACTION = require_manifest_python_deps.HUB_CAP_FWD_ACTION;
|
|
@@ -94687,6 +94806,7 @@ exports.createUdsEventBridge = require_manifest_python_deps.createUdsEventBridge
|
|
|
94687
94806
|
exports.createUdsEventBus = require_manifest_python_deps.createUdsEventBus;
|
|
94688
94807
|
exports.createUdsLogger = require_manifest_python_deps.createUdsLogger;
|
|
94689
94808
|
exports.createUdsLoggerWithControl = require_manifest_python_deps.createUdsLoggerWithControl;
|
|
94809
|
+
exports.createV8Reclaimer = createV8Reclaimer;
|
|
94690
94810
|
exports.deleteModelFromDisk = require_model_download_service.deleteModelFromDisk;
|
|
94691
94811
|
exports.deriveAgentListenPort = deriveAgentListenPort;
|
|
94692
94812
|
exports.describeProviderKindDrift = describeProviderKindDrift;
|
|
@@ -94809,7 +94929,9 @@ exports.scopesAllowDeviceCap = scopesAllowDeviceCap;
|
|
|
94809
94929
|
exports.serializeTypedArrays = require_manifest_python_deps.serializeTypedArrays;
|
|
94810
94930
|
exports.setHubConnected = require_manifest_python_deps.setHubConnected;
|
|
94811
94931
|
exports.setNodeEventInterest = require_manifest_python_deps.setNodeEventInterest;
|
|
94932
|
+
exports.shouldReclaim = shouldReclaim;
|
|
94812
94933
|
exports.startHeapWatch = startHeapWatch;
|
|
94934
|
+
exports.strandedMb = strandedMb;
|
|
94813
94935
|
exports.stripCamstackDeps = stripCamstackDeps;
|
|
94814
94936
|
exports.subscribePassthrough = require_manifest_python_deps.subscribePassthrough;
|
|
94815
94937
|
exports.udsChildLogToWorkerEntry = require_manifest_python_deps.udsChildLogToWorkerEntry;
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { $ as procedureAuthKey, Ct as asJsonObject$1, Dt as emitDownForOwnedCaps, Et as createEvent, Ft as readinessKey, H as kebabToCamel, It as resolveCapMount, K as logLevelAtMost, L as isArrayOutputSchema, Lt as scopeKey, Nt as parseJsonObject, P as extractNestedAddonId, Pt as parseJsonUnknown$1, R as isCollectionArrayMethod, Tt as asString$1, U as lifecycleJobSchema, V as isVoidInput, X as objectInputDeclaresAddonId, bt as ReadinessRegistry, c as METHOD_ACCESS_MAP, ft as errMsg$1, gt as DEVICE_STATUS_METHOD, h as addonSettingsCapability, ht as DEVICE_SETTINGS_CONTRIBUTION_METHODS, kt as expandCapMethods, l as RUNTIME_DEFAULTS, mt as DATAPLANE_SECRET_HEADER$1, q as looseSchema, t as ALL_CAPABILITY_DEFINITIONS, wt as asNumber, xt as ReadinessTimeoutError, z as isObjectInput, zt as EventCategory$1 } from "./dist-
|
|
2
|
+
import { $ as procedureAuthKey, Ct as asJsonObject$1, Dt as emitDownForOwnedCaps, Et as createEvent, Ft as readinessKey, H as kebabToCamel, It as resolveCapMount, K as logLevelAtMost, L as isArrayOutputSchema, Lt as scopeKey, Nt as parseJsonObject, P as extractNestedAddonId, Pt as parseJsonUnknown$1, R as isCollectionArrayMethod, Tt as asString$1, U as lifecycleJobSchema, V as isVoidInput, X as objectInputDeclaresAddonId, bt as ReadinessRegistry, c as METHOD_ACCESS_MAP, ft as errMsg$1, gt as DEVICE_STATUS_METHOD, h as addonSettingsCapability, ht as DEVICE_SETTINGS_CONTRIBUTION_METHODS, kt as expandCapMethods, l as RUNTIME_DEFAULTS, mt as DATAPLANE_SECRET_HEADER$1, q as looseSchema, t as ALL_CAPABILITY_DEFINITIONS, wt as asNumber, xt as ReadinessTimeoutError, z as isObjectInput, zt as EventCategory$1 } from "./dist-L6QPJWsr.mjs";
|
|
3
3
|
import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-D8B-4ktF.mjs";
|
|
4
4
|
import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-CX5tGRqf.mjs";
|
|
5
5
|
import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BkP504Vq.mjs";
|
|
@@ -28,6 +28,7 @@ import { DeviceManagerAddon } from "./builtins/device-manager/device-manager.add
|
|
|
28
28
|
import "./builtins/device-manager/index.mjs";
|
|
29
29
|
import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
|
|
30
30
|
import * as v8 from "node:v8";
|
|
31
|
+
import * as vm from "node:vm";
|
|
31
32
|
import { PYTHON_VERSION, buildBinaryPath, downloadBinary, ensureBinary, ensureFfmpeg, ensurePython, findInPath, getFfmpegDownloadUrl, getPlatformInfo, getPythonDownloadUrl, installPythonPackages, installPythonRequirements } from "@camstack/types/node";
|
|
32
33
|
import { z } from "zod";
|
|
33
34
|
import { X509Certificate, createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -40,7 +41,6 @@ import { asJsonObject, errMsg, parseJsonUnknown } from "@camstack/types/addon";
|
|
|
40
41
|
import { execFile, spawn } from "node:child_process";
|
|
41
42
|
import * as util$10 from "node:util";
|
|
42
43
|
import { promisify } from "node:util";
|
|
43
|
-
import * as vm from "node:vm";
|
|
44
44
|
import * as os$18 from "node:os";
|
|
45
45
|
import { lstat, readdir, readlink, realpath } from "node:fs/promises";
|
|
46
46
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -52,13 +52,15 @@ import "path";
|
|
|
52
52
|
import { createRequire } from "module";
|
|
53
53
|
//#region src/kernel/heap-watch.ts
|
|
54
54
|
/**
|
|
55
|
-
* heap-watch — hub-main's own memory, on the record.
|
|
55
|
+
* heap-watch — hub-main's own memory, on the record, and returned to the OS.
|
|
56
56
|
*
|
|
57
57
|
* ## Why this exists
|
|
58
58
|
*
|
|
59
|
-
* hub-main
|
|
60
|
-
*
|
|
61
|
-
*
|
|
59
|
+
* hub-main used to be launched with `--max-old-space-size=1536` (docker-entrypoint;
|
|
60
|
+
* the cap was removed on 2026-08-02, so the live ceiling is now V8's own default —
|
|
61
|
+
* 4288MB in this container — and this module reads it at runtime rather than
|
|
62
|
+
* assuming a number). It logged NOTHING about its own heap. When it died on
|
|
63
|
+
* 2026-08-01/02 the only trace was V8's own stderr line:
|
|
62
64
|
*
|
|
63
65
|
* FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
|
|
64
66
|
*
|
|
@@ -86,6 +88,41 @@ import { createRequire } from "module";
|
|
|
86
88
|
* `--max-old-space-size` and are most of the gap between a 1.5GB cap and the
|
|
87
89
|
* ~2.05GB RSS observed live: a diagnosis that only looks at the JS heap
|
|
88
90
|
* misreads that process by 500MB.
|
|
91
|
+
*
|
|
92
|
+
* ## And then the heartbeat explained the "off-heap leak", which was not one
|
|
93
|
+
*
|
|
94
|
+
* 2026-08-08, measured on the live hub over 1019 consecutive samples and seven
|
|
95
|
+
* separate boots: **hub-main's RSS is a HIGH-WATER MARK, not a leak.** In every
|
|
96
|
+
* one of those 1019 samples RSS was >= the running maximum of
|
|
97
|
+
* `heapTotal + external`, never once below it, and
|
|
98
|
+
* `RSS_peak - peak(heapTotal + external)` landed between 874MB and 1639MB on
|
|
99
|
+
* every boot — a roughly constant native floor on top of a number that only
|
|
100
|
+
* ever goes up. RSS tracked the highest point the process ever reached and
|
|
101
|
+
* stayed there: 3.3GB -> 4.5GB -> 5.2GB, and 7.8GB on the worst boot.
|
|
102
|
+
*
|
|
103
|
+
* Two things push that peak, and neither is a retained object:
|
|
104
|
+
*
|
|
105
|
+
* - the JS heap sawtooths to 2.0-2.8GB because the ceiling is 4288MB and
|
|
106
|
+
* nothing creates pressure before then;
|
|
107
|
+
* - `arrayBuffers` accumulates DEAD Buffers — up to 3.65GB — because V8 only
|
|
108
|
+
* sweeps them on a major GC. Eleven collapses were captured, and every
|
|
109
|
+
* single one coincides with `heapUsed` dropping to its ~650MB floor. No code
|
|
110
|
+
* released a reference at 13:23:03; a mark-compact ran.
|
|
111
|
+
*
|
|
112
|
+
* At the peak V8 has committed the pages, and **an ordinary major GC does not
|
|
113
|
+
* give them back.** Proven on the production hub: `smaps` showed 10,819 mapped
|
|
114
|
+
* 256KB pages (V8's `kRegularPageSize`) holding 2704MB while V8 itself reported
|
|
115
|
+
* `total_physical_size` of only 846MB — ~7,800 pages committed, resident, and
|
|
116
|
+
* outside V8's own accounting. One `gc({ flavor: 'last-resort' })` took 417ms
|
|
117
|
+
* and returned RSS 3355MB -> 1237MB, pages 10,819 -> 2,963.
|
|
118
|
+
*
|
|
119
|
+
* So the fix for the "~3GB/2h off-heap growth" is not a retainer hunt. It is to
|
|
120
|
+
* ask V8 for the pages back when the process is visibly holding more than it is
|
|
121
|
+
* using — which is what `shouldReclaim` decides and what the heartbeat now does.
|
|
122
|
+
*
|
|
123
|
+
* The cost is a 250-450ms stop-the-world on the hub's router process, bounded to
|
|
124
|
+
* once per `HEAP_RECLAIM_MIN_INTERVAL_MS`. The alternative, measured, is a
|
|
125
|
+
* ratchet to 7.8GB and the OOM that ends it.
|
|
89
126
|
*/
|
|
90
127
|
/** Heartbeat period. Slow enough to be free, fast enough that the sample before
|
|
91
128
|
* a fatal is recent. */
|
|
@@ -93,6 +130,20 @@ var HEAP_WATCH_INTERVAL_MS = 6e4;
|
|
|
93
130
|
/** Warn above this share of the heap ceiling. Chosen so the warning precedes
|
|
94
131
|
* the fatal by more than one interval under realistic growth. */
|
|
95
132
|
var HEAP_WATCH_WARN_RATIO = .8;
|
|
133
|
+
/**
|
|
134
|
+
* Reclaim once the process holds this many MB beyond what it is USING.
|
|
135
|
+
*
|
|
136
|
+
* Live calibration, both readings from the same hub minutes apart: 2491MB
|
|
137
|
+
* stranded before the reclaim, 438MB after. 438MB is this process's irreducible
|
|
138
|
+
* native floor (sqlite pages, libvips, sharp, thread stacks, node's own text) —
|
|
139
|
+
* collecting against it would buy nothing and cost a stall every minute. 1536
|
|
140
|
+
* sits clear of that floor and still fires ~1.1GB before the ratchet is worth
|
|
141
|
+
* anything, so it triggers on the real condition and stops as soon as it is fixed.
|
|
142
|
+
*/
|
|
143
|
+
var HEAP_RECLAIM_TRIGGER_MB = 1536;
|
|
144
|
+
/** Floor on how often the stop-the-world may run. 250-450ms measured, so ten
|
|
145
|
+
* minutes keeps the worst case under 0.1% of the router's time. */
|
|
146
|
+
var HEAP_RECLAIM_MIN_INTERVAL_MS = 6e5;
|
|
96
147
|
var MB = (bytes) => Math.round(bytes / 1048576);
|
|
97
148
|
/**
|
|
98
149
|
* Build a sample from raw readings. Pure so the thresholds are testable without
|
|
@@ -115,6 +166,47 @@ var consoleSink = {
|
|
|
115
166
|
info: (line) => console.log(line),
|
|
116
167
|
warn: (line) => console.warn(line)
|
|
117
168
|
};
|
|
169
|
+
/**
|
|
170
|
+
* RSS the process is holding but not using.
|
|
171
|
+
*
|
|
172
|
+
* The subtrahend is heapUSED, not heapTotal, deliberately: the pages V8
|
|
173
|
+
* committed at a sawtooth peak and no longer needs are counted inside heapTotal,
|
|
174
|
+
* and they are precisely what this number exists to see.
|
|
175
|
+
*/
|
|
176
|
+
function strandedMb(s) {
|
|
177
|
+
return s.rssMb - s.heapUsedMb - s.externalMb;
|
|
178
|
+
}
|
|
179
|
+
/** Whether it is worth paying a stop-the-world to get those pages back. */
|
|
180
|
+
function shouldReclaim(s, triggerMb = HEAP_RECLAIM_TRIGGER_MB) {
|
|
181
|
+
return strandedMb(s) > triggerMb;
|
|
182
|
+
}
|
|
183
|
+
function isGcFunction(value) {
|
|
184
|
+
return typeof value === "function";
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Build the reclaimer without requiring `--expose-gc` on argv.
|
|
188
|
+
*
|
|
189
|
+
* The flag is turned on, the function captured, and the flag turned straight
|
|
190
|
+
* back off — a global `gc` left reachable by application code is a footgun, and
|
|
191
|
+
* requiring an argv change would mean this cannot ship without a new image.
|
|
192
|
+
* Verified against the live hub before being written: 266ms, RSS 1453 -> 1237.
|
|
193
|
+
*
|
|
194
|
+
* Returns undefined if V8 declines, so the caller keeps a heartbeat either way.
|
|
195
|
+
*/
|
|
196
|
+
function createV8Reclaimer() {
|
|
197
|
+
try {
|
|
198
|
+
v8.setFlagsFromString("--expose-gc");
|
|
199
|
+
const gc = vm.runInNewContext("gc");
|
|
200
|
+
v8.setFlagsFromString("--no-expose-gc");
|
|
201
|
+
if (!isGcFunction(gc)) return void 0;
|
|
202
|
+
return () => gc({
|
|
203
|
+
execution: "sync",
|
|
204
|
+
flavor: "last-resort"
|
|
205
|
+
});
|
|
206
|
+
} catch {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
118
210
|
function format$1(label, s) {
|
|
119
211
|
return `[mem] ${label} rss=${s.rssMb}MB heapUsed=${s.heapUsedMb}MB heapTotal=${s.heapTotalMb}MB heapLimit=${s.heapLimitMb}MB used=${Math.round(s.usedRatio * 100)}% external=${s.externalMb}MB arrayBuffers=${s.arrayBuffersMb}MB`;
|
|
120
212
|
}
|
|
@@ -124,13 +216,38 @@ function format$1(label, s) {
|
|
|
124
216
|
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
125
217
|
* a diagnostic that can crash the process it watches is worse than none.
|
|
126
218
|
*/
|
|
127
|
-
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS) {
|
|
219
|
+
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions) {
|
|
220
|
+
const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
|
|
221
|
+
const now = reclaimOptions?.now ?? (() => Date.now());
|
|
222
|
+
const triggerMb = reclaimOptions?.triggerMb ?? 1536;
|
|
223
|
+
const minIntervalMs = reclaimOptions?.minIntervalMs ?? 6e5;
|
|
224
|
+
let lastReclaimAt = Number.NEGATIVE_INFINITY;
|
|
225
|
+
const read = () => {
|
|
226
|
+
const limit = reclaimOptions?.heapLimitBytes ?? v8.getHeapStatistics().heap_size_limit;
|
|
227
|
+
return buildHeapSample(readMemory(), limit);
|
|
228
|
+
};
|
|
229
|
+
const maybeReclaim = (sample) => {
|
|
230
|
+
if (reclaimOptions === void 0) return;
|
|
231
|
+
if (!shouldReclaim(sample, triggerMb)) return;
|
|
232
|
+
if (now() - lastReclaimAt < minIntervalMs) return;
|
|
233
|
+
lastReclaimAt = now();
|
|
234
|
+
const startedAt = now();
|
|
235
|
+
try {
|
|
236
|
+
reclaimOptions.reclaim();
|
|
237
|
+
} catch (error) {
|
|
238
|
+
sink.warn(`[mem] reclaim failed — ${error instanceof Error ? error.message : String(error)}`);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const after = read();
|
|
242
|
+
sink.info(`[mem] reclaim ${label} stranded=${strandedMb(sample)}MB rss=${sample.rssMb}MB→${after.rssMb}MB freed=${sample.rssMb - after.rssMb}MB arrayBuffers=${sample.arrayBuffersMb}MB→${after.arrayBuffersMb}MB took=${now() - startedAt}ms`);
|
|
243
|
+
};
|
|
128
244
|
const tick = () => {
|
|
129
245
|
try {
|
|
130
|
-
const sample =
|
|
246
|
+
const sample = read();
|
|
131
247
|
const line = format$1(label, sample);
|
|
132
248
|
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
133
249
|
else sink.info(line);
|
|
250
|
+
maybeReclaim(sample);
|
|
134
251
|
} catch {}
|
|
135
252
|
};
|
|
136
253
|
const timer = setInterval(tick, intervalMs);
|
|
@@ -94510,4 +94627,4 @@ var LifecycleJobEngine = class {
|
|
|
94510
94627
|
}
|
|
94511
94628
|
};
|
|
94512
94629
|
//#endregion
|
|
94513
|
-
export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CoreBlocksAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LivenessMonitorAddon, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, LokiDestination, LokiLoggingAddon, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NATIVE_SCAN_DEPTH, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, TRADITIONAL_NATIVE_PACKAGES, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, addonSettingsCapability, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildHeapSample, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterEventTopic, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureAddonNativePrebuilds, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensureNativePrebuilds, ensurePython, ensureTlsCert, expandCapMethods, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hasDotNode, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, setNodeEventInterest, startHeapWatch, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
|
|
94630
|
+
export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CoreBlocksAddon, CustomActionRegistry, DEFAULT_DATA_PATH, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HEAP_RECLAIM_MIN_INTERVAL_MS, HEAP_RECLAIM_TRIGGER_MB, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LifecycleJobEngine, LifecycleStateMachine, LivenessMonitorAddon, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, LokiDestination, LokiLoggingAddon, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NATIVE_SCAN_DEPTH, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, PYTHON_VERSION, PipelineRunner, PipelineValidator, PythonEnvManager, RESTART_MARKER_FILE, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, TRADITIONAL_NATIVE_PACKAGES, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, __resetCapUsageRegistryForTests, adaptBrokerToCluster, addonSettingsCapability, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildHeapSample, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, capActionName, capActionSuffix, capBareAction, capServiceName, classifyCapRoute, clearPendingRestart, clusterEventTopic, clusterSecretMatches, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonService, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCoreCapService, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createKernelHwAccel, createLocalTransport, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, createV8Reclaimer, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, detectWorkspacePackagesDir, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, encodeFrame, ensureAddonNativePrebuilds, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensureNativePrebuilds, ensurePython, ensureTlsCert, expandCapMethods, fetchJson, findInPath, formatLogLine, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hasDotNode, hashClusterSecret, installManifestNativeDeps, installManifestPythonDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcChildLink, ipcParentLink, isAddonDeploySource, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, parseCapAction, parseRangeHeader, parseTokenizedUrl, proxyToUpstream, readPendingRestart, readinessKey, registerEventBusService, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runNpm, scheduleSelfRestart, scopeKey, scopesAllowDeviceCap, serializeTypedArrays, setHubConnected, setNodeEventInterest, shouldReclaim, startHeapWatch, strandedMb, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, writePendingRestart };
|
|
@@ -4,6 +4,20 @@ export declare const HEAP_WATCH_INTERVAL_MS = 60000;
|
|
|
4
4
|
/** Warn above this share of the heap ceiling. Chosen so the warning precedes
|
|
5
5
|
* the fatal by more than one interval under realistic growth. */
|
|
6
6
|
export declare const HEAP_WATCH_WARN_RATIO = 0.8;
|
|
7
|
+
/**
|
|
8
|
+
* Reclaim once the process holds this many MB beyond what it is USING.
|
|
9
|
+
*
|
|
10
|
+
* Live calibration, both readings from the same hub minutes apart: 2491MB
|
|
11
|
+
* stranded before the reclaim, 438MB after. 438MB is this process's irreducible
|
|
12
|
+
* native floor (sqlite pages, libvips, sharp, thread stacks, node's own text) —
|
|
13
|
+
* collecting against it would buy nothing and cost a stall every minute. 1536
|
|
14
|
+
* sits clear of that floor and still fires ~1.1GB before the ratchet is worth
|
|
15
|
+
* anything, so it triggers on the real condition and stops as soon as it is fixed.
|
|
16
|
+
*/
|
|
17
|
+
export declare const HEAP_RECLAIM_TRIGGER_MB = 1536;
|
|
18
|
+
/** Floor on how often the stop-the-world may run. 250-450ms measured, so ten
|
|
19
|
+
* minutes keeps the worst case under 0.1% of the router's time. */
|
|
20
|
+
export declare const HEAP_RECLAIM_MIN_INTERVAL_MS = 600000;
|
|
7
21
|
export interface HeapSample {
|
|
8
22
|
readonly rssMb: number;
|
|
9
23
|
readonly heapUsedMb: number;
|
|
@@ -27,10 +41,43 @@ export interface HeapWatchSink {
|
|
|
27
41
|
readonly info: (line: string) => void;
|
|
28
42
|
readonly warn: (line: string) => void;
|
|
29
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* RSS the process is holding but not using.
|
|
46
|
+
*
|
|
47
|
+
* The subtrahend is heapUSED, not heapTotal, deliberately: the pages V8
|
|
48
|
+
* committed at a sawtooth peak and no longer needs are counted inside heapTotal,
|
|
49
|
+
* and they are precisely what this number exists to see.
|
|
50
|
+
*/
|
|
51
|
+
export declare function strandedMb(s: HeapSample): number;
|
|
52
|
+
/** Whether it is worth paying a stop-the-world to get those pages back. */
|
|
53
|
+
export declare function shouldReclaim(s: HeapSample, triggerMb?: number): boolean;
|
|
54
|
+
/** A full collection that also asks V8 to hand the pages back to the OS. */
|
|
55
|
+
export type HeapReclaimer = () => void;
|
|
56
|
+
/**
|
|
57
|
+
* Build the reclaimer without requiring `--expose-gc` on argv.
|
|
58
|
+
*
|
|
59
|
+
* The flag is turned on, the function captured, and the flag turned straight
|
|
60
|
+
* back off — a global `gc` left reachable by application code is a footgun, and
|
|
61
|
+
* requiring an argv change would mean this cannot ship without a new image.
|
|
62
|
+
* Verified against the live hub before being written: 266ms, RSS 1453 -> 1237.
|
|
63
|
+
*
|
|
64
|
+
* Returns undefined if V8 declines, so the caller keeps a heartbeat either way.
|
|
65
|
+
*/
|
|
66
|
+
export declare function createV8Reclaimer(): HeapReclaimer | undefined;
|
|
67
|
+
/** Wiring for the reclaim pass. `readMemory`/`heapLimitBytes` exist so a test
|
|
68
|
+
* can reach a gigabyte-scale state without allocating one. */
|
|
69
|
+
export interface HeapReclaimOptions {
|
|
70
|
+
readonly reclaim: HeapReclaimer;
|
|
71
|
+
readonly readMemory?: () => NodeJS.MemoryUsage;
|
|
72
|
+
readonly heapLimitBytes?: number;
|
|
73
|
+
readonly triggerMb?: number;
|
|
74
|
+
readonly minIntervalMs?: number;
|
|
75
|
+
readonly now?: () => number;
|
|
76
|
+
}
|
|
30
77
|
/**
|
|
31
78
|
* Start the heartbeat. Returns a stop function.
|
|
32
79
|
*
|
|
33
80
|
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
34
81
|
* a diagnostic that can crash the process it watches is worse than none.
|
|
35
82
|
*/
|
|
36
|
-
export declare function startHeapWatch(label?: string, sink?: HeapWatchSink, intervalMs?: number): () => void;
|
|
83
|
+
export declare function startHeapWatch(label?: string, sink?: HeapWatchSink, intervalMs?: number, reclaimOptions?: HeapReclaimOptions): () => void;
|