@camstack/system 1.2.123 → 1.2.125
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/addon-runner.js +224 -224
- package/dist/addon-runner.mjs +224 -224
- 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-bindings-store.d.ts +20 -0
- package/dist/builtins/device-manager/device-linked-devices.d.ts +16 -0
- package/dist/builtins/device-manager/device-manager.addon.d.ts +6 -0
- package/dist/builtins/device-manager/device-manager.addon.js +73 -7
- package/dist/builtins/device-manager/device-manager.addon.mjs +73 -7
- 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/sqlite-settings.addon.js +2 -2
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
- 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-DnhGRFEn.js → dist-CIDgcxPk.js} +97 -21
- package/dist/{dist-CEhrHp3u.mjs → dist-CvJrtLkw.mjs} +97 -21
- package/dist/index.js +43 -4
- package/dist/index.mjs +43 -4
- package/dist/kernel/heap-watch.d.ts +12 -9
- package/dist/kernel/moleculer/process-service.d.ts +12 -1
- package/dist/kernel/moleculer/runner-native-allocator.d.ts +95 -0
- package/dist/{manifest-python-deps-B3_4YiDK.js → manifest-python-deps-BTkFwAk_.js} +15 -12
- package/dist/{manifest-python-deps-DIIXlR1x.mjs → manifest-python-deps-Dmcnb287.mjs} +15 -12
- package/dist/{retired-settings-keys-VAyOhu_a.mjs → retired-settings-keys-B1aZcoS0.mjs} +1 -1
- package/dist/{retired-settings-keys-D6Jy_SsO.js → retired-settings-keys-D7c0n9jO.js} +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native-allocator environment for a forked addon runner.
|
|
3
|
+
*
|
|
4
|
+
* ## The half of a runner's memory that no V8 flag reaches
|
|
5
|
+
*
|
|
6
|
+
* `--max-old-space-size` bounds V8's old space and nothing else. It does not
|
|
7
|
+
* bound `external`, it does not bound `arrayBuffers`, and it does not bound what
|
|
8
|
+
* a native addon (`sharp`/`libvips`, decoders, codecs) allocates through `malloc`.
|
|
9
|
+
*
|
|
10
|
+
* Measured on the live hub, 2026-08-25, over a ~6h window of `[mem]` heartbeats:
|
|
11
|
+
*
|
|
12
|
+
* hub/pipeline-analytics rss 309-7334MB heapTotal max 343MB external max 5479MB
|
|
13
|
+
*
|
|
14
|
+
* It already carried `--max-old-space-size=1024`, so the flag was in force the
|
|
15
|
+
* whole time and the process still reached 7.3GB — because 96% of it was never
|
|
16
|
+
* V8's to bound. Its reclaim line names the shape exactly:
|
|
17
|
+
*
|
|
18
|
+
* reclaim hub/pipeline-analytics stranded=2552MB rss=2765MB→2647MB
|
|
19
|
+
* freed=118MB arrayBuffers=81MB→29MB took=58ms
|
|
20
|
+
*
|
|
21
|
+
* The `arrayBuffers` FLOOR returns to ~1MB every cycle, so this is not a leak.
|
|
22
|
+
* It is glibc holding freed pages: with `MALLOC_ARENA_MAX` unset, glibc opens up
|
|
23
|
+
* to `8 × ncpu` arenas — 160 on this 20-core host — and each keeps its own free
|
|
24
|
+
* list, which is returned to the OS only when the top of that arena is free.
|
|
25
|
+
*
|
|
26
|
+
* ## Why this matters more than the cgroup number suggests
|
|
27
|
+
*
|
|
28
|
+
* The container is capped at 24GiB and `memory.current` rides at 21-24GB. Near
|
|
29
|
+
* the limit the kernel performs direct reclaim IN THE CONTEXT OF THE TASK THAT
|
|
30
|
+
* ALLOCATES, so every process in the container stalls together — measured as
|
|
31
|
+
* four idle, mutually unrelated runners sharing one ~39s stall in the same
|
|
32
|
+
* sample. Returning stranded pages is therefore not a tidiness win; it is what
|
|
33
|
+
* keeps unrelated runners off the direct-reclaim path.
|
|
34
|
+
*
|
|
35
|
+
* ## Why `VIPS_CONCURRENCY` is welded to it
|
|
36
|
+
*
|
|
37
|
+
* `sharp` reads `MALLOC_ARENA_MAX` in its NATIVE binding (not in JS — grepping
|
|
38
|
+
* the bundle finds nothing). Unset on glibc, sharp pins libvips concurrency to 1
|
|
39
|
+
* as its own fragmentation defence; the moment the variable appears it restores
|
|
40
|
+
* concurrency to `availableParallelism()`. Verified in the live container:
|
|
41
|
+
*
|
|
42
|
+
* MALLOC_ARENA_MAX unset → sharp.concurrency() === 1
|
|
43
|
+
* MALLOC_ARENA_MAX=2 → sharp.concurrency() === 20
|
|
44
|
+
* MALLOC_ARENA_MAX=2 VIPS_CONCURRENCY=1 → sharp.concurrency() === 1
|
|
45
|
+
*
|
|
46
|
+
* Shipping the arena bound alone would therefore have quietly shipped a 1→20
|
|
47
|
+
* libvips threading change to the busiest addon on a host already at ~75% CPU,
|
|
48
|
+
* under the banner of a memory fix. This module ships the allocator change and
|
|
49
|
+
* ONLY the allocator change; raising libvips concurrency is a separate decision.
|
|
50
|
+
*
|
|
51
|
+
* ## What it costs, measured
|
|
52
|
+
*
|
|
53
|
+
* Same container, same 12-image corpus, same pipeline as analytics (decode →
|
|
54
|
+
* extract → resize → encode → raw), 3 interleaved pairs so host load cancels,
|
|
55
|
+
* concurrency pinned to 1 on both arms:
|
|
56
|
+
*
|
|
57
|
+
* arenas default : mean 51.9ms p50 60.0ms p90 67.3ms p99 70.7ms rss 505MB
|
|
58
|
+
* arenas=2 : mean 51.5ms p50 57.6ms p90 67.2ms p99 72.9ms rss 354MB
|
|
59
|
+
*
|
|
60
|
+
* Latency is unchanged inside noise (-0.8% mean, +3.1% p99); the same work is
|
|
61
|
+
* done in 30% less RSS.
|
|
62
|
+
*/
|
|
63
|
+
/** Bounded glibc arenas per forked runner. */
|
|
64
|
+
export declare const RUNNER_MALLOC_ARENA_MAX_DEFAULT = 2;
|
|
65
|
+
/**
|
|
66
|
+
* libvips worker threads per forked runner. `1` is not a tuning choice — it is
|
|
67
|
+
* sharp's OWN default on glibc, pinned here so that bounding the arenas does not
|
|
68
|
+
* change it as a side effect. See the module header.
|
|
69
|
+
*/
|
|
70
|
+
export declare const RUNNER_VIPS_CONCURRENCY_DEFAULT = 1;
|
|
71
|
+
/**
|
|
72
|
+
* The native-allocator variables to add to a runner's environment. Both keys are
|
|
73
|
+
* present or both are absent — see the `VIPS_CONCURRENCY` note in the module
|
|
74
|
+
* header for why one without the other is the dangerous configuration.
|
|
75
|
+
*/
|
|
76
|
+
export interface RunnerNativeAllocatorEnv {
|
|
77
|
+
readonly MALLOC_ARENA_MAX?: string;
|
|
78
|
+
readonly VIPS_CONCURRENCY?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Resolve the native-allocator env for a child runner from the parent's env.
|
|
82
|
+
*
|
|
83
|
+
* Precedence, highest first:
|
|
84
|
+
* 1. `CAMSTACK_RUNNER_NATIVE_ALLOCATOR=off` — kill switch, returns nothing and
|
|
85
|
+
* restores the glibc/sharp defaults. Recoverable without a rebuild.
|
|
86
|
+
* 2. A value the operator already put in the environment (`MALLOC_ARENA_MAX`,
|
|
87
|
+
* `VIPS_CONCURRENCY`) — inherited verbatim, never overwritten.
|
|
88
|
+
* 3. `CAMSTACK_RUNNER_MALLOC_ARENA_MAX` — the CamStack override.
|
|
89
|
+
* 4. The defaults above.
|
|
90
|
+
*
|
|
91
|
+
* A nonsense override falls back to the default rather than being passed through:
|
|
92
|
+
* glibc reads `MALLOC_ARENA_MAX=0` as "unbounded", which is the very state this
|
|
93
|
+
* exists to leave.
|
|
94
|
+
*/
|
|
95
|
+
export declare function runnerNativeAllocatorEnv(parentEnv: NodeJS.ProcessEnv): RunnerNativeAllocatorEnv;
|
|
@@ -311,7 +311,7 @@ function format(label, s, loop) {
|
|
|
311
311
|
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
312
312
|
* a diagnostic that can crash the process it watches is worse than none.
|
|
313
313
|
*/
|
|
314
|
-
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv) {
|
|
314
|
+
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv, announceCeilingOrigin = true) {
|
|
315
315
|
const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
|
|
316
316
|
const now = reclaimOptions?.now ?? (() => Date.now());
|
|
317
317
|
const triggerMb = reclaimOptions?.triggerMb ?? 1024;
|
|
@@ -392,7 +392,7 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
392
392
|
const timer = setInterval(tick, probeIntervalMs);
|
|
393
393
|
timer.unref?.();
|
|
394
394
|
tick();
|
|
395
|
-
if (heapCeilingOrigin(execArgv) === "v8-default") sink.info(`[mem] ${label} heap ceiling is V8's DEFAULT (${read().heapLimitMb}MB) — no --max-old-space-size on argv. Nothing here CHOSE that number; it is derived from host RAM and moves with it.`);
|
|
395
|
+
if (announceCeilingOrigin && heapCeilingOrigin(execArgv) === "v8-default") sink.info(`[mem] ${label} heap ceiling is V8's DEFAULT (${read().heapLimitMb}MB) — no --max-old-space-size on argv. Nothing here CHOSE that number; it is derived from host RAM and moves with it.`);
|
|
396
396
|
let stopped = false;
|
|
397
397
|
return () => {
|
|
398
398
|
if (stopped) return;
|
|
@@ -422,14 +422,17 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
|
|
|
422
422
|
*
|
|
423
423
|
* ## Why heavy runners get hub-main's mechanism
|
|
424
424
|
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
* `
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
425
|
+
* Because a heap ceiling cannot reach what actually strands in them. Every
|
|
426
|
+
* media-path runner now carries an old-space ceiling sized from its own
|
|
427
|
+
* measured peak (`addon-pipeline` manifest + its `manifest-heap-ceilings` spec),
|
|
428
|
+
* and that changes nothing here: what a heavy runner strands is NATIVE —
|
|
429
|
+
* `external`, `arrayBuffers`, glibc arena free lists — none of which
|
|
430
|
+
* `--max-old-space-size` bounds. Measured 2026-08-25, `hub/pipeline-analytics`
|
|
431
|
+
* reported `stranded=2552MB` while holding an 81MB V8 heap under a 1024MB
|
|
432
|
+
* ceiling that was in force the whole time. hub-main had exactly this shape and
|
|
433
|
+
* its reclaimer is proven live — 11 passes in 4h, 375–1652MB returned per pass
|
|
434
|
+
* at 550–680ms each. Same mechanism, same `shouldReclaim` stranded-over-trigger
|
|
435
|
+
* gate; never a second implementation.
|
|
433
436
|
*
|
|
434
437
|
* ## The stall, priced for the runners that get it
|
|
435
438
|
*
|
|
@@ -447,13 +450,13 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
|
|
|
447
450
|
function startRunnerHeapWatch(options) {
|
|
448
451
|
if (options.heapProfile !== "heavy") return void 0;
|
|
449
452
|
const intervalMs = options.intervalMs ?? 3e5;
|
|
450
|
-
if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs);
|
|
453
|
+
if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs, void 0, void 0, void 0, false);
|
|
451
454
|
let reclaimOptions = options.reclaimOptions;
|
|
452
455
|
if (reclaimOptions === void 0) {
|
|
453
456
|
const reclaimer = createV8Reclaimer();
|
|
454
457
|
reclaimOptions = reclaimer === void 0 ? void 0 : { reclaim: reclaimer };
|
|
455
458
|
}
|
|
456
|
-
return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions);
|
|
459
|
+
return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions, void 0, void 0, true);
|
|
457
460
|
}
|
|
458
461
|
//#endregion
|
|
459
462
|
//#region src/kernel/moleculer/addon-data-plane-facility.ts
|
|
@@ -307,7 +307,7 @@ function format(label, s, loop) {
|
|
|
307
307
|
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
308
308
|
* a diagnostic that can crash the process it watches is worse than none.
|
|
309
309
|
*/
|
|
310
|
-
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv) {
|
|
310
|
+
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions, loopDelay = createLoopDelayMeter(), execArgv = process.execArgv, announceCeilingOrigin = true) {
|
|
311
311
|
const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
|
|
312
312
|
const now = reclaimOptions?.now ?? (() => Date.now());
|
|
313
313
|
const triggerMb = reclaimOptions?.triggerMb ?? 1024;
|
|
@@ -388,7 +388,7 @@ function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEA
|
|
|
388
388
|
const timer = setInterval(tick, probeIntervalMs);
|
|
389
389
|
timer.unref?.();
|
|
390
390
|
tick();
|
|
391
|
-
if (heapCeilingOrigin(execArgv) === "v8-default") sink.info(`[mem] ${label} heap ceiling is V8's DEFAULT (${read().heapLimitMb}MB) — no --max-old-space-size on argv. Nothing here CHOSE that number; it is derived from host RAM and moves with it.`);
|
|
391
|
+
if (announceCeilingOrigin && heapCeilingOrigin(execArgv) === "v8-default") sink.info(`[mem] ${label} heap ceiling is V8's DEFAULT (${read().heapLimitMb}MB) — no --max-old-space-size on argv. Nothing here CHOSE that number; it is derived from host RAM and moves with it.`);
|
|
392
392
|
let stopped = false;
|
|
393
393
|
return () => {
|
|
394
394
|
if (stopped) return;
|
|
@@ -418,14 +418,17 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
|
|
|
418
418
|
*
|
|
419
419
|
* ## Why heavy runners get hub-main's mechanism
|
|
420
420
|
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
* `
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
421
|
+
* Because a heap ceiling cannot reach what actually strands in them. Every
|
|
422
|
+
* media-path runner now carries an old-space ceiling sized from its own
|
|
423
|
+
* measured peak (`addon-pipeline` manifest + its `manifest-heap-ceilings` spec),
|
|
424
|
+
* and that changes nothing here: what a heavy runner strands is NATIVE —
|
|
425
|
+
* `external`, `arrayBuffers`, glibc arena free lists — none of which
|
|
426
|
+
* `--max-old-space-size` bounds. Measured 2026-08-25, `hub/pipeline-analytics`
|
|
427
|
+
* reported `stranded=2552MB` while holding an 81MB V8 heap under a 1024MB
|
|
428
|
+
* ceiling that was in force the whole time. hub-main had exactly this shape and
|
|
429
|
+
* its reclaimer is proven live — 11 passes in 4h, 375–1652MB returned per pass
|
|
430
|
+
* at 550–680ms each. Same mechanism, same `shouldReclaim` stranded-over-trigger
|
|
431
|
+
* gate; never a second implementation.
|
|
429
432
|
*
|
|
430
433
|
* ## The stall, priced for the runners that get it
|
|
431
434
|
*
|
|
@@ -443,13 +446,13 @@ var RUNNER_HEAP_WATCH_INTERVAL_MS = 3e5;
|
|
|
443
446
|
function startRunnerHeapWatch(options) {
|
|
444
447
|
if (options.heapProfile !== "heavy") return void 0;
|
|
445
448
|
const intervalMs = options.intervalMs ?? 3e5;
|
|
446
|
-
if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs);
|
|
449
|
+
if (options.reclaimSwitch === "off") return startHeapWatch(options.label, options.sink, intervalMs, void 0, void 0, void 0, false);
|
|
447
450
|
let reclaimOptions = options.reclaimOptions;
|
|
448
451
|
if (reclaimOptions === void 0) {
|
|
449
452
|
const reclaimer = createV8Reclaimer();
|
|
450
453
|
reclaimOptions = reclaimer === void 0 ? void 0 : { reclaim: reclaimer };
|
|
451
454
|
}
|
|
452
|
-
return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions);
|
|
455
|
+
return startHeapWatch(options.label, options.sink, intervalMs, reclaimOptions, void 0, void 0, true);
|
|
453
456
|
}
|
|
454
457
|
//#endregion
|
|
455
458
|
//#region src/kernel/moleculer/addon-data-plane-facility.ts
|