@camstack/system 1.2.103 → 1.2.104
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 +15 -1
- package/dist/addon-runner.mjs +15 -1
- 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/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- 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-DHKXob1m.mjs → dist-CAzHQ03i.mjs} +20 -1
- package/dist/{dist-qnwy_iWM.js → dist-DR1VmGz6.js} +20 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +25 -312
- package/dist/index.mjs +15 -303
- package/dist/kernel/heap-watch.d.ts +67 -0
- package/dist/{manifest-python-deps-Bt_J04b0.mjs → manifest-python-deps-5ei2xVUh.mjs} +355 -1
- package/dist/{manifest-python-deps-BtVjvfvj.js → manifest-python-deps-GejnH--L.js} +422 -0
- package/package.json +1 -1
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-DR1VmGz6.js");
|
|
4
4
|
const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
|
|
5
5
|
require("./builtins/alerts/index.js");
|
|
6
6
|
const require_formatter = require("./formatter-DqAKDlvN.js");
|
|
@@ -25,7 +25,7 @@ const require_builtins_system_config_system_config_addon = require("./builtins/s
|
|
|
25
25
|
require("./builtins/system-config/index.js");
|
|
26
26
|
const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
|
|
27
27
|
const require_file_data_plane = require("./file-data-plane-DUHPHa-Y.js");
|
|
28
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
28
|
+
const require_manifest_python_deps = require("./manifest-python-deps-GejnH--L.js");
|
|
29
29
|
const require_resource_monitor = require("./resource-monitor-CdnzxBLP.js");
|
|
30
30
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
31
31
|
let zod = require("zod");
|
|
@@ -43,8 +43,6 @@ let node_fs = require("node:fs");
|
|
|
43
43
|
let node_fs$1 = require_chunk.__toESM(node_fs, 1);
|
|
44
44
|
node_fs = require_chunk.__toESM(node_fs);
|
|
45
45
|
let node_http = require("node:http");
|
|
46
|
-
let node_v8 = require("node:v8");
|
|
47
|
-
node_v8 = require_chunk.__toESM(node_v8);
|
|
48
46
|
let node_vm = require("node:vm");
|
|
49
47
|
node_vm = require_chunk.__toESM(node_vm);
|
|
50
48
|
let _camstack_types_addon = require("@camstack/types/addon");
|
|
@@ -431,304 +429,6 @@ function proxyToUpstream(opts) {
|
|
|
431
429
|
else clientReq.pipe(upstream);
|
|
432
430
|
}
|
|
433
431
|
//#endregion
|
|
434
|
-
//#region src/kernel/heap-watch.ts
|
|
435
|
-
/**
|
|
436
|
-
* heap-watch — hub-main's own memory, on the record, and returned to the OS.
|
|
437
|
-
*
|
|
438
|
-
* ## Why this exists
|
|
439
|
-
*
|
|
440
|
-
* hub-main used to be launched with `--max-old-space-size=1536` (docker-entrypoint;
|
|
441
|
-
* the cap was removed on 2026-08-02, so the live ceiling is now V8's own default —
|
|
442
|
-
* 4288MB in this container — and this module reads it at runtime rather than
|
|
443
|
-
* assuming a number). It logged NOTHING about its own heap. When it died on
|
|
444
|
-
* 2026-08-01/02 the only trace was V8's own stderr line:
|
|
445
|
-
*
|
|
446
|
-
* FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
|
|
447
|
-
*
|
|
448
|
-
* which carries no process name, no heap number and no history — the whole
|
|
449
|
-
* server restarts and the evidence is gone. Four of those in ~15h, and the one
|
|
450
|
-
* at 06:57:57 landed nine seconds after a viewer opened a WebRTC session, which
|
|
451
|
-
* is why the app sat in a permanent "loading": the hub was dying as it
|
|
452
|
-
* connected.
|
|
453
|
-
*
|
|
454
|
-
* `[mem]` reports already exist for forked addon runners (SIGUSR2 in
|
|
455
|
-
* `addon-runner.ts`) — hub-main, the one process that actually OOMs, was the
|
|
456
|
-
* one process with no telemetry at all.
|
|
457
|
-
*
|
|
458
|
-
* ## What it gives
|
|
459
|
-
*
|
|
460
|
-
* A heartbeat carrying the numbers that separate the two possible stories, and
|
|
461
|
-
* a WARN as the heap approaches the ceiling — the line that will exist BEFORE
|
|
462
|
-
* the next fatal instead of after it:
|
|
463
|
-
*
|
|
464
|
-
* - `heapUsedMb` climbing steadily between restarts → a leak;
|
|
465
|
-
* - `heapUsedMb` flat with a sharp jump under load → a burst allocation, and
|
|
466
|
-
* the timestamp says what was being served.
|
|
467
|
-
*
|
|
468
|
-
* `external`/`arrayBuffers` ride along because they are NOT bounded by
|
|
469
|
-
* `--max-old-space-size` and are most of the gap between a 1.5GB cap and the
|
|
470
|
-
* ~2.05GB RSS observed live: a diagnosis that only looks at the JS heap
|
|
471
|
-
* misreads that process by 500MB.
|
|
472
|
-
*
|
|
473
|
-
* ## And then the heartbeat explained the "off-heap leak", which was not one
|
|
474
|
-
*
|
|
475
|
-
* 2026-08-08, measured on the live hub over 1019 consecutive samples and seven
|
|
476
|
-
* separate boots: **hub-main's RSS is a HIGH-WATER MARK, not a leak.** In every
|
|
477
|
-
* one of those 1019 samples RSS was >= the running maximum of
|
|
478
|
-
* `heapTotal + external`, never once below it, and
|
|
479
|
-
* `RSS_peak - peak(heapTotal + external)` landed between 874MB and 1639MB on
|
|
480
|
-
* every boot — a roughly constant native floor on top of a number that only
|
|
481
|
-
* ever goes up. RSS tracked the highest point the process ever reached and
|
|
482
|
-
* stayed there: 3.3GB -> 4.5GB -> 5.2GB, and 7.8GB on the worst boot.
|
|
483
|
-
*
|
|
484
|
-
* Two things push that peak, and neither is a retained object:
|
|
485
|
-
*
|
|
486
|
-
* - the JS heap sawtooths to 2.0-2.8GB because the ceiling is 4288MB and
|
|
487
|
-
* nothing creates pressure before then;
|
|
488
|
-
* - `arrayBuffers` accumulates DEAD Buffers — up to 3.65GB — because V8 only
|
|
489
|
-
* sweeps them on a major GC. Eleven collapses were captured, and every
|
|
490
|
-
* single one coincides with `heapUsed` dropping to its ~650MB floor. No code
|
|
491
|
-
* released a reference at 13:23:03; a mark-compact ran.
|
|
492
|
-
*
|
|
493
|
-
* At the peak V8 has committed the pages, and **an ordinary major GC does not
|
|
494
|
-
* give them back.** Proven on the production hub: `smaps` showed 10,819 mapped
|
|
495
|
-
* 256KB pages (V8's `kRegularPageSize`) holding 2704MB while V8 itself reported
|
|
496
|
-
* `total_physical_size` of only 846MB — ~7,800 pages committed, resident, and
|
|
497
|
-
* outside V8's own accounting. One `gc({ flavor: 'last-resort' })` took 417ms
|
|
498
|
-
* and returned RSS 3355MB -> 1237MB, pages 10,819 -> 2,963.
|
|
499
|
-
*
|
|
500
|
-
* So the fix for the "~3GB/2h off-heap growth" is not a retainer hunt. It is to
|
|
501
|
-
* ask V8 for the pages back when the process is visibly holding more than it is
|
|
502
|
-
* using — which is what `shouldReclaim` decides and what the heartbeat now does.
|
|
503
|
-
*
|
|
504
|
-
* The cost is a stop-the-world on the hub's router process — 250-450ms when
|
|
505
|
-
* first measured, 654ms on the largest pass seen since — bounded to once per
|
|
506
|
-
* `HEAP_RECLAIM_MIN_INTERVAL_MS`. The alternative, measured, is a ratchet to
|
|
507
|
-
* 7.8GB and the OOM that ends it.
|
|
508
|
-
*/
|
|
509
|
-
/** Heartbeat period. Slow enough to be free, fast enough that the sample before
|
|
510
|
-
* a fatal is recent. */
|
|
511
|
-
var HEAP_WATCH_INTERVAL_MS = 6e4;
|
|
512
|
-
/** Warn above this share of the heap ceiling. Chosen so the warning precedes
|
|
513
|
-
* the fatal by more than one interval under realistic growth. */
|
|
514
|
-
var HEAP_WATCH_WARN_RATIO = .8;
|
|
515
|
-
/**
|
|
516
|
-
* Escalate above this share of the ceiling.
|
|
517
|
-
*
|
|
518
|
-
* 0.70 is 3002MB: clear of the measured routine max (2847MB) so steady state
|
|
519
|
-
* never escalates, and below {@link HEAP_WATCH_WARN_RATIO} so the fast cadence
|
|
520
|
-
* is already running by the time the warn is reachable.
|
|
521
|
-
*/
|
|
522
|
-
var HEAP_WATCH_ESCALATE_RATIO = .7;
|
|
523
|
-
/**
|
|
524
|
-
* Release escalation below this share.
|
|
525
|
-
*
|
|
526
|
-
* Hysteresis, not a second threshold: hub-main's heap sawtooths continuously,
|
|
527
|
-
* so a single ratio would flap the mode on every GC. 0.60 is 2573MB — a real
|
|
528
|
-
* retreat rather than the trough of one collection.
|
|
529
|
-
*/
|
|
530
|
-
var HEAP_WATCH_DEESCALATE_RATIO = .6;
|
|
531
|
-
/**
|
|
532
|
-
* The cadence state machine, pure so the thresholds are testable without
|
|
533
|
-
* allocating three gigabytes to reach them.
|
|
534
|
-
*/
|
|
535
|
-
function nextMode(current, usedRatio, escalateRatio = HEAP_WATCH_ESCALATE_RATIO, deescalateRatio = HEAP_WATCH_DEESCALATE_RATIO) {
|
|
536
|
-
if (current === "escalated") return usedRatio < deescalateRatio ? "steady" : "escalated";
|
|
537
|
-
return usedRatio >= escalateRatio ? "escalated" : "steady";
|
|
538
|
-
}
|
|
539
|
-
/**
|
|
540
|
-
* Reclaim once the process holds this many MB beyond what it is USING.
|
|
541
|
-
*
|
|
542
|
-
* Live calibration, both readings from the same hub minutes apart: 2491MB
|
|
543
|
-
* stranded before the reclaim, 438MB after. 438MB is this process's irreducible
|
|
544
|
-
* native floor (sqlite pages, libvips, sharp, thread stacks, node's own text) —
|
|
545
|
-
* collecting against it would buy nothing and cost a stall every minute.
|
|
546
|
-
*
|
|
547
|
-
* ## Why this came down from 1536 (2026-08-17)
|
|
548
|
-
*
|
|
549
|
-
* The trigger was originally set to fire only on an unmistakable strand. It
|
|
550
|
-
* does, and the pass on the record works exactly as designed:
|
|
551
|
-
*
|
|
552
|
-
* [mem] reclaim hub-main stranded=2365MB rss=4016MB→2368MB freed=1648MB took=654ms
|
|
553
|
-
*
|
|
554
|
-
* But the number it was protecting against changed. The host now runs with a
|
|
555
|
-
* 24GiB cgroup ceiling and **no swap**, and is OOM-killing a neighbouring VM.
|
|
556
|
-
* RSS this process is holding and not using is no longer merely untidy — it is
|
|
557
|
-
* the headroom something else needed, and the OOM is triggered by the PEAK, not
|
|
558
|
-
* by the average. A trigger of 1536 means the peak is allowed to stand 1.5GiB
|
|
559
|
-
* above the floor before anything happens.
|
|
560
|
-
*
|
|
561
|
-
* 1024 is 2.3x the measured 438MB floor, so ordinary movement around the floor
|
|
562
|
-
* can never trip it, and every pass that does fire still returns roughly
|
|
563
|
-
* `1024 - 438` ≈ **580MB minimum** — a real return for a 250-650ms stall,
|
|
564
|
-
* not a stall spent collecting noise.
|
|
565
|
-
*/
|
|
566
|
-
var HEAP_RECLAIM_TRIGGER_MB = 1024;
|
|
567
|
-
/**
|
|
568
|
-
* Floor on how often the stop-the-world may run.
|
|
569
|
-
*
|
|
570
|
-
* Ten minutes was chosen to keep the worst case under 0.1% of the router's
|
|
571
|
-
* time. The worst case is not what governs the cost, though: the reclaim only
|
|
572
|
-
* runs when {@link shouldReclaim} says the process is genuinely stranded, and
|
|
573
|
-
* after a pass it is at the ~438MB floor. Live off-heap growth was measured at
|
|
574
|
-
* roughly 3GB/2h ≈ 25MB/min, so re-stranding the ~580MB needed to qualify again
|
|
575
|
-
* takes on the order of **20 minutes** — well outside any floor considered
|
|
576
|
-
* here. In steady state this constant therefore costs nothing at all.
|
|
577
|
-
*
|
|
578
|
-
* What it does govern is a BURST, which is the only case that matters on a host
|
|
579
|
-
* with no swap: the ten-minute floor let a peak that re-formed in seconds stand
|
|
580
|
-
* for ten minutes. Two minutes bounds that, and even the pathological case of a
|
|
581
|
-
* process that re-strands instantly costs 654ms per 120s — 0.55% of the
|
|
582
|
-
* router's time, against a ~1.5GiB peak it removes each pass.
|
|
583
|
-
*/
|
|
584
|
-
var HEAP_RECLAIM_MIN_INTERVAL_MS = 12e4;
|
|
585
|
-
var MB = (bytes) => Math.round(bytes / 1048576);
|
|
586
|
-
/**
|
|
587
|
-
* Build a sample from raw readings. Pure so the thresholds are testable without
|
|
588
|
-
* allocating a gigabyte to reach them.
|
|
589
|
-
*/
|
|
590
|
-
function buildHeapSample(mem, heapLimitBytes, warnRatio = HEAP_WATCH_WARN_RATIO) {
|
|
591
|
-
const usedRatio = heapLimitBytes > 0 ? mem.heapUsed / heapLimitBytes : 0;
|
|
592
|
-
return {
|
|
593
|
-
rssMb: MB(mem.rss),
|
|
594
|
-
heapUsedMb: MB(mem.heapUsed),
|
|
595
|
-
heapTotalMb: MB(mem.heapTotal),
|
|
596
|
-
heapLimitMb: MB(heapLimitBytes),
|
|
597
|
-
externalMb: MB(mem.external),
|
|
598
|
-
arrayBuffersMb: MB(mem.arrayBuffers),
|
|
599
|
-
usedRatio: Math.round(usedRatio * 100) / 100,
|
|
600
|
-
nearLimit: usedRatio >= warnRatio
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
var consoleSink = {
|
|
604
|
-
info: (line) => console.log(line),
|
|
605
|
-
warn: (line) => console.warn(line)
|
|
606
|
-
};
|
|
607
|
-
/**
|
|
608
|
-
* RSS the process is holding but not using.
|
|
609
|
-
*
|
|
610
|
-
* The subtrahend is heapUSED, not heapTotal, deliberately: the pages V8
|
|
611
|
-
* committed at a sawtooth peak and no longer needs are counted inside heapTotal,
|
|
612
|
-
* and they are precisely what this number exists to see.
|
|
613
|
-
*/
|
|
614
|
-
function strandedMb(s) {
|
|
615
|
-
return s.rssMb - s.heapUsedMb - s.externalMb;
|
|
616
|
-
}
|
|
617
|
-
/** Whether it is worth paying a stop-the-world to get those pages back. */
|
|
618
|
-
function shouldReclaim(s, triggerMb = HEAP_RECLAIM_TRIGGER_MB) {
|
|
619
|
-
return strandedMb(s) > triggerMb;
|
|
620
|
-
}
|
|
621
|
-
function isGcFunction(value) {
|
|
622
|
-
return typeof value === "function";
|
|
623
|
-
}
|
|
624
|
-
/**
|
|
625
|
-
* Build the reclaimer without requiring `--expose-gc` on argv.
|
|
626
|
-
*
|
|
627
|
-
* The flag is turned on, the function captured, and the flag turned straight
|
|
628
|
-
* back off — a global `gc` left reachable by application code is a footgun, and
|
|
629
|
-
* requiring an argv change would mean this cannot ship without a new image.
|
|
630
|
-
* Verified against the live hub before being written: 266ms, RSS 1453 -> 1237.
|
|
631
|
-
*
|
|
632
|
-
* Returns undefined if V8 declines, so the caller keeps a heartbeat either way.
|
|
633
|
-
*/
|
|
634
|
-
function createV8Reclaimer() {
|
|
635
|
-
try {
|
|
636
|
-
node_v8.setFlagsFromString("--expose-gc");
|
|
637
|
-
const gc = node_vm.runInNewContext("gc");
|
|
638
|
-
node_v8.setFlagsFromString("--no-expose-gc");
|
|
639
|
-
if (!isGcFunction(gc)) return void 0;
|
|
640
|
-
return () => gc({
|
|
641
|
-
execution: "sync",
|
|
642
|
-
flavor: "last-resort"
|
|
643
|
-
});
|
|
644
|
-
} catch {
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
function format$1(label, s) {
|
|
649
|
-
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`;
|
|
650
|
-
}
|
|
651
|
-
/**
|
|
652
|
-
* Start the heartbeat. Returns a stop function.
|
|
653
|
-
*
|
|
654
|
-
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
655
|
-
* a diagnostic that can crash the process it watches is worse than none.
|
|
656
|
-
*/
|
|
657
|
-
function startHeapWatch(label = "hub-main", sink = consoleSink, intervalMs = HEAP_WATCH_INTERVAL_MS, reclaimOptions) {
|
|
658
|
-
const readMemory = reclaimOptions?.readMemory ?? (() => process.memoryUsage());
|
|
659
|
-
const now = reclaimOptions?.now ?? (() => Date.now());
|
|
660
|
-
const triggerMb = reclaimOptions?.triggerMb ?? 1024;
|
|
661
|
-
const minIntervalMs = reclaimOptions?.minIntervalMs ?? 12e4;
|
|
662
|
-
const fastIntervalMs = reclaimOptions?.fastIntervalMs ?? 2e3;
|
|
663
|
-
const escalateRatio = reclaimOptions?.escalateRatio ?? .7;
|
|
664
|
-
const deescalateRatio = reclaimOptions?.deescalateRatio ?? .6;
|
|
665
|
-
let lastReclaimAt = Number.NEGATIVE_INFINITY;
|
|
666
|
-
const read = () => {
|
|
667
|
-
const limit = reclaimOptions?.heapLimitBytes ?? node_v8.getHeapStatistics().heap_size_limit;
|
|
668
|
-
return buildHeapSample(readMemory(), limit);
|
|
669
|
-
};
|
|
670
|
-
const maybeReclaim = (sample) => {
|
|
671
|
-
if (reclaimOptions === void 0) return;
|
|
672
|
-
if (!shouldReclaim(sample, triggerMb)) return;
|
|
673
|
-
if (now() - lastReclaimAt < minIntervalMs) return;
|
|
674
|
-
lastReclaimAt = now();
|
|
675
|
-
const startedAt = now();
|
|
676
|
-
try {
|
|
677
|
-
reclaimOptions.reclaim();
|
|
678
|
-
} catch (error) {
|
|
679
|
-
sink.warn(`[mem] reclaim failed — ${error instanceof Error ? error.message : String(error)}`);
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const after = read();
|
|
683
|
-
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`);
|
|
684
|
-
};
|
|
685
|
-
let mode = "steady";
|
|
686
|
-
let lastLoggedAt = Number.NEGATIVE_INFINITY;
|
|
687
|
-
/**
|
|
688
|
-
* PROBING and LOGGING are separate cadences, and that separation is the whole
|
|
689
|
-
* point.
|
|
690
|
-
*
|
|
691
|
-
* The 2026-08-08 fatal escaped a 60s heartbeat because the burst began and
|
|
692
|
-
* ended between two samples. Deciding "is the heap elevated?" only when a
|
|
693
|
-
* line is due would reproduce that exactly: the process would still be read
|
|
694
|
-
* once a minute, and a 62-second climb would still be invisible. So the timer
|
|
695
|
-
* runs at the FAST period always — `process.memoryUsage()` is tens of
|
|
696
|
-
* microseconds, i.e. free at this rate — and the slow period governs only how
|
|
697
|
-
* often a steady process is allowed to say so.
|
|
698
|
-
*
|
|
699
|
-
* Never probe slower than the log cadence, or a caller passing a short
|
|
700
|
-
* `intervalMs` would silently get the long one.
|
|
701
|
-
*/
|
|
702
|
-
const probeIntervalMs = Math.min(fastIntervalMs, intervalMs);
|
|
703
|
-
const tick = () => {
|
|
704
|
-
try {
|
|
705
|
-
const sample = read();
|
|
706
|
-
const previous = mode;
|
|
707
|
-
mode = nextMode(previous, sample.usedRatio, escalateRatio, deescalateRatio);
|
|
708
|
-
const at = now();
|
|
709
|
-
const due = at - lastLoggedAt >= intervalMs;
|
|
710
|
-
if (mode === "escalated" || due) {
|
|
711
|
-
lastLoggedAt = at;
|
|
712
|
-
const line = format$1(label, sample);
|
|
713
|
-
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
714
|
-
else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
|
|
715
|
-
else sink.info(line);
|
|
716
|
-
}
|
|
717
|
-
if (previous === "escalated" && mode === "steady") sink.info(`[mem] ${label} heap back to routine — logging every ${intervalMs}ms`);
|
|
718
|
-
maybeReclaim(sample);
|
|
719
|
-
} catch {}
|
|
720
|
-
};
|
|
721
|
-
const timer = setInterval(tick, probeIntervalMs);
|
|
722
|
-
timer.unref?.();
|
|
723
|
-
tick();
|
|
724
|
-
let stopped = false;
|
|
725
|
-
return () => {
|
|
726
|
-
if (stopped) return;
|
|
727
|
-
stopped = true;
|
|
728
|
-
clearInterval(timer);
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
//#endregion
|
|
732
432
|
//#region src/lifecycle/lifecycle-state-machine.ts
|
|
733
433
|
var VALID_TRANSITIONS = {
|
|
734
434
|
stopped: ["starting", "disabled"],
|
|
@@ -94653,19 +94353,21 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
94653
94353
|
function spawnRunner(runnerId, addons, env) {
|
|
94654
94354
|
const nodeId = buildNodeId(runnerId);
|
|
94655
94355
|
const runnerPath = node_path.resolve(__dirname, "addon-runner.js");
|
|
94356
|
+
const heavy = isHeavyRunner(addons);
|
|
94656
94357
|
const childEnv = {
|
|
94657
94358
|
...process.env,
|
|
94658
94359
|
CAMSTACK_RUNNER_ID: runnerId,
|
|
94659
94360
|
CAMSTACK_RUNNER_ADDONS: JSON.stringify(addons),
|
|
94660
94361
|
CAMSTACK_PARENT_NODE_ID: parentNodeId,
|
|
94661
94362
|
CAMSTACK_DATA_DIR: dataDir,
|
|
94363
|
+
CAMSTACK_RUNNER_HEAP_PROFILE: heavy ? "heavy" : "light",
|
|
94662
94364
|
CAMSTACK_LOG_LEVEL: process.env["CAMSTACK_LOG_LEVEL"] ?? "info",
|
|
94663
94365
|
...parentTcpPort !== void 0 ? { CAMSTACK_PARENT_TCP_PORT: String(parentTcpPort) } : {},
|
|
94664
94366
|
...parentUdsPath !== void 0 ? { CAMSTACK_PARENT_UDS_PATH: parentUdsPath } : {},
|
|
94665
94367
|
...env
|
|
94666
94368
|
};
|
|
94667
94369
|
const heapFlags = runnerHeapFlags(addons);
|
|
94668
|
-
capturedBroker?.logger.info(`[${runnerId}] heap profile: ${
|
|
94370
|
+
capturedBroker?.logger.info(`[${runnerId}] heap profile: ${heavy ? "heavy" : "light"} flags=[${heapFlags.join(" ")}]`);
|
|
94669
94371
|
const child = spawnFn(process.execPath, [...heapFlags, runnerPath], {
|
|
94670
94372
|
env: childEnv,
|
|
94671
94373
|
stdio: [
|
|
@@ -94709,6 +94411,15 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
94709
94411
|
capturedBroker?.logger.error(`[${runnerId}] Crash circuit-breaker tripped — ${decision.crashesInWindow} crashes in ${CRASH_WINDOW_MS}ms. Not respawning.`);
|
|
94710
94412
|
processes.set(runnerId, entry);
|
|
94711
94413
|
if (capturedBroker) forceMoleculerDisconnect(capturedBroker, entry.nodeId);
|
|
94414
|
+
emitRunnerEvent(require_dist.EventCategory.AddonRunnerFailed, runnerId, {
|
|
94415
|
+
runnerId,
|
|
94416
|
+
nodeId: parentNodeId,
|
|
94417
|
+
addonIds: entry.runnerAddons.map((addon) => addon.addonId),
|
|
94418
|
+
crashesInWindow: decision.crashesInWindow,
|
|
94419
|
+
windowMs: CRASH_WINDOW_MS,
|
|
94420
|
+
...code !== null ? { exitCode: code } : {},
|
|
94421
|
+
...signal !== null ? { signal } : {}
|
|
94422
|
+
});
|
|
94712
94423
|
for (const addon of entry.runnerAddons) emitRunnerEvent(require_dist.EventCategory.AddonCrashed, addon.addonId, {
|
|
94713
94424
|
addonId: addon.addonId,
|
|
94714
94425
|
error: `Crash circuit-breaker tripped after ${decision.crashesInWindow} crashes in ${CRASH_WINDOW_MS}ms`
|
|
@@ -95435,10 +95146,10 @@ exports.FsStorageBackend = FsStorageBackend;
|
|
|
95435
95146
|
exports.HEALTH_MONITOR_GRACE_PERIOD_MS = HEALTH_MONITOR_GRACE_PERIOD_MS;
|
|
95436
95147
|
exports.HEALTH_MONITOR_RETRY_INTERVALS_MS = HEALTH_MONITOR_RETRY_INTERVALS_MS;
|
|
95437
95148
|
exports.HEALTH_MONITOR_TICK_MS = HEALTH_MONITOR_TICK_MS;
|
|
95438
|
-
exports.HEAP_RECLAIM_MIN_INTERVAL_MS = HEAP_RECLAIM_MIN_INTERVAL_MS;
|
|
95439
|
-
exports.HEAP_RECLAIM_TRIGGER_MB = HEAP_RECLAIM_TRIGGER_MB;
|
|
95440
|
-
exports.HEAP_WATCH_INTERVAL_MS = HEAP_WATCH_INTERVAL_MS;
|
|
95441
|
-
exports.HEAP_WATCH_WARN_RATIO = HEAP_WATCH_WARN_RATIO;
|
|
95149
|
+
exports.HEAP_RECLAIM_MIN_INTERVAL_MS = require_manifest_python_deps.HEAP_RECLAIM_MIN_INTERVAL_MS;
|
|
95150
|
+
exports.HEAP_RECLAIM_TRIGGER_MB = require_manifest_python_deps.HEAP_RECLAIM_TRIGGER_MB;
|
|
95151
|
+
exports.HEAP_WATCH_INTERVAL_MS = require_manifest_python_deps.HEAP_WATCH_INTERVAL_MS;
|
|
95152
|
+
exports.HEAP_WATCH_WARN_RATIO = require_manifest_python_deps.HEAP_WATCH_WARN_RATIO;
|
|
95442
95153
|
exports.HUB_CAP_FWD_ACTION = require_manifest_python_deps.HUB_CAP_FWD_ACTION;
|
|
95443
95154
|
exports.HUB_CAP_FWD_SERVICE = require_manifest_python_deps.HUB_CAP_FWD_SERVICE;
|
|
95444
95155
|
exports.HubForwarderAddon = require_builtins_hub_forwarder_index.HubForwarderAddon$1;
|
|
@@ -95477,6 +95188,7 @@ exports.PipelineValidator = PipelineValidator;
|
|
|
95477
95188
|
exports.PythonEnvManager = PythonEnvManager;
|
|
95478
95189
|
exports.QUARANTINE_DIRNAME = QUARANTINE_DIRNAME;
|
|
95479
95190
|
exports.RESTART_MARKER_FILE = RESTART_MARKER_FILE;
|
|
95191
|
+
exports.RUNNER_HEAP_WATCH_INTERVAL_MS = require_manifest_python_deps.RUNNER_HEAP_WATCH_INTERVAL_MS;
|
|
95480
95192
|
exports.RUNTIME_DEFAULTS = require_dist.RUNTIME_DEFAULTS;
|
|
95481
95193
|
exports.ReadinessRegistry = require_dist.ReadinessRegistry;
|
|
95482
95194
|
exports.ReadinessTimeoutError = require_dist.ReadinessTimeoutError;
|
|
@@ -95515,7 +95227,7 @@ Object.defineProperty(exports, "buildBinaryPath", {
|
|
|
95515
95227
|
}
|
|
95516
95228
|
});
|
|
95517
95229
|
exports.buildCapRouters = buildCapRouters;
|
|
95518
|
-
exports.buildHeapSample = buildHeapSample;
|
|
95230
|
+
exports.buildHeapSample = require_manifest_python_deps.buildHeapSample;
|
|
95519
95231
|
exports.buildLinkChain = require_manifest_python_deps.buildLinkChain;
|
|
95520
95232
|
exports.buildNativeCapProxy = require_manifest_python_deps.buildNativeCapProxy;
|
|
95521
95233
|
exports.buildNodeManifest = buildNodeManifest;
|
|
@@ -95560,7 +95272,7 @@ exports.createUdsEventBridge = require_manifest_python_deps.createUdsEventBridge
|
|
|
95560
95272
|
exports.createUdsEventBus = require_manifest_python_deps.createUdsEventBus;
|
|
95561
95273
|
exports.createUdsLogger = require_manifest_python_deps.createUdsLogger;
|
|
95562
95274
|
exports.createUdsLoggerWithControl = require_manifest_python_deps.createUdsLoggerWithControl;
|
|
95563
|
-
exports.createV8Reclaimer = createV8Reclaimer;
|
|
95275
|
+
exports.createV8Reclaimer = require_manifest_python_deps.createV8Reclaimer;
|
|
95564
95276
|
exports.deleteModelFromDisk = require_file_data_plane.deleteModelFromDisk;
|
|
95565
95277
|
exports.deriveAgentListenPort = deriveAgentListenPort;
|
|
95566
95278
|
exports.describeProviderKindDrift = describeProviderKindDrift;
|
|
@@ -95686,9 +95398,10 @@ exports.selectAddonResidue = selectAddonResidue;
|
|
|
95686
95398
|
exports.serializeTypedArrays = require_manifest_python_deps.serializeTypedArrays;
|
|
95687
95399
|
exports.setHubConnected = require_manifest_python_deps.setHubConnected;
|
|
95688
95400
|
exports.setNodeEventInterest = require_manifest_python_deps.setNodeEventInterest;
|
|
95689
|
-
exports.shouldReclaim = shouldReclaim;
|
|
95690
|
-
exports.startHeapWatch = startHeapWatch;
|
|
95691
|
-
exports.
|
|
95401
|
+
exports.shouldReclaim = require_manifest_python_deps.shouldReclaim;
|
|
95402
|
+
exports.startHeapWatch = require_manifest_python_deps.startHeapWatch;
|
|
95403
|
+
exports.startRunnerHeapWatch = require_manifest_python_deps.startRunnerHeapWatch;
|
|
95404
|
+
exports.strandedMb = require_manifest_python_deps.strandedMb;
|
|
95692
95405
|
exports.stripCamstackDeps = stripCamstackDeps;
|
|
95693
95406
|
exports.subscribePassthrough = require_manifest_python_deps.subscribePassthrough;
|
|
95694
95407
|
exports.udsChildLogToWorkerEntry = require_manifest_python_deps.udsChildLogToWorkerEntry;
|