@camstack/system 1.2.216 → 1.2.218
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 +9 -6
- package/dist/addon-runner.mjs +9 -6
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -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 +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- 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.d.ts +0 -7
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -8
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -8
- 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 +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- 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/{custom-action-registry-jY0NOZK8.js → custom-action-registry-BDwM5qAK.js} +19 -0
- package/dist/{custom-action-registry-F__gp_VX.mjs → custom-action-registry-CsONUlhC.mjs} +19 -0
- package/dist/{dist-DYWqXngV.js → dist-BOlMCOeT.js} +157 -5
- package/dist/{dist-MtqqwJVI.mjs → dist-DigmGsQS.mjs} +152 -6
- package/dist/index.d.ts +3 -1
- package/dist/index.js +11 -3
- package/dist/index.mjs +4 -4
- package/dist/kernel/array-buffer-census.d.ts +122 -0
- package/dist/kernel/custom-action-registry.d.ts +10 -1
- package/dist/kernel/heap-watch.d.ts +15 -0
- package/dist/kernel/transport/child-cap-protocol.d.ts +15 -1
- package/dist/kernel/transport/local-child-client.d.ts +18 -2
- package/dist/{manifest-system-deps-DvY1bY_n.js → manifest-system-deps-Bo92lg5u.js} +350 -14
- package/dist/{manifest-system-deps-726RBOX7.mjs → manifest-system-deps-D--wyMD9.mjs} +303 -15
- package/dist/{retired-settings-keys-BBrgMHDy.mjs → retired-settings-keys-vK_U-CG2.mjs} +1 -1
- package/dist/{retired-settings-keys-CXLIB19R.js → retired-settings-keys-xEAlYnv7.js} +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ import * as os from "node:os";
|
|
|
13
13
|
import { tmpdir } from "node:os";
|
|
14
14
|
import { createServer } from "node:http";
|
|
15
15
|
import * as v8 from "node:v8";
|
|
16
|
+
import { Session } from "node:inspector/promises";
|
|
16
17
|
import { monitorEventLoopDelay } from "node:perf_hooks";
|
|
17
18
|
import * as vm from "node:vm";
|
|
18
19
|
import { BOOT_RECOVERY_BACKOFF_MS, DATAPLANE_SECRET_HEADER, DEVICE_CHILDREN_BATCH_MAX, DeviceType, DisposerChain, EventCategory, ReadinessRegistry, asJsonObject, asString, createDeviceProxy, createEventBusSliceSource, deviceOpsCapability, emitReadiness, errMsg, expandCapMethods, readNodePin, scopeKey, sleep, withCapInputDefaults } from "@camstack/types/addon";
|
|
@@ -511,7 +512,7 @@ var DECISIVE_HEAP_SPACES = ["old_space", "large_object_space"];
|
|
|
511
512
|
var HEAP_SPACE_REPORT_MIN_MB = 32;
|
|
512
513
|
var BYTES_PER_MB = 1048576;
|
|
513
514
|
/** Whole MB, matching the rest of the `[mem]` line. */
|
|
514
|
-
function mb(bytes) {
|
|
515
|
+
function mb$1(bytes) {
|
|
515
516
|
return Math.round(bytes / BYTES_PER_MB);
|
|
516
517
|
}
|
|
517
518
|
/** Tenths of an MB — the resolution the SIGUSR2 report has always printed. */
|
|
@@ -558,7 +559,7 @@ function heapSpaceField(v8Name) {
|
|
|
558
559
|
*/
|
|
559
560
|
function selectReportedSpaces(spaces, minMb = 32) {
|
|
560
561
|
const decisive = DECISIVE_HEAP_SPACES.map((name) => spaces.find((space) => space.name === name)).filter((space) => space !== void 0);
|
|
561
|
-
const rest = spaces.filter((space) => !DECISIVE_HEAP_SPACES.includes(space.name)).filter((space) => mb(space.usedBytes) >= minMb).toSorted((a, b) => b.usedBytes - a.usedBytes || a.name.localeCompare(b.name));
|
|
562
|
+
const rest = spaces.filter((space) => !DECISIVE_HEAP_SPACES.includes(space.name)).filter((space) => mb$1(space.usedBytes) >= minMb).toSorted((a, b) => b.usedBytes - a.usedBytes || a.name.localeCompare(b.name));
|
|
562
563
|
return [...decisive, ...rest];
|
|
563
564
|
}
|
|
564
565
|
/**
|
|
@@ -573,7 +574,7 @@ function selectReportedSpaces(spaces, minMb = 32) {
|
|
|
573
574
|
function formatHeapSpaces(spaces, minMb = 32) {
|
|
574
575
|
const selected = selectReportedSpaces(spaces, minMb);
|
|
575
576
|
if (selected.length === 0) return "";
|
|
576
|
-
return ` ${selected.map((s) => `${heapSpaceField(s.name)}=${mb(s.usedBytes)}MB`).join(" ")}`;
|
|
577
|
+
return ` ${selected.map((s) => `${heapSpaceField(s.name)}=${mb$1(s.usedBytes)}MB`).join(" ")}`;
|
|
577
578
|
}
|
|
578
579
|
var defaultDeps = {
|
|
579
580
|
readMemory: () => process.memoryUsage(),
|
|
@@ -603,6 +604,251 @@ function emitHeapDiagnosticReport(request, deps = defaultDeps) {
|
|
|
603
604
|
}
|
|
604
605
|
}
|
|
605
606
|
//#endregion
|
|
607
|
+
//#region src/kernel/array-buffer-census.ts
|
|
608
|
+
/**
|
|
609
|
+
* array-buffer-census — WHAT SHAPE the live ArrayBuffers have, taken by the
|
|
610
|
+
* heartbeat itself when they grow, because nobody can be awake for it.
|
|
611
|
+
*
|
|
612
|
+
* ## The episodes this exists for
|
|
613
|
+
*
|
|
614
|
+
* hub-main, 2026-09-10 (CEST): `arrayBuffers` climbed linearly and released in
|
|
615
|
+
* one instant, twice, with not one line from the process at either boundary —
|
|
616
|
+
* 17:26→18:31 at ~20 MB/min to 1 264 MB, released in one heartbeat; 22:20→23:36
|
|
617
|
+
* at 60–85 MB/min to **4 968 MB**, released between two probes at 23:38. Both
|
|
618
|
+
* were LIVE: a forced compacting GC at 23:36:40 returned 4 968→4 954 MB. The
|
|
619
|
+
* measurement that names their shape had been prepared (D443) and was never
|
|
620
|
+
* run, because it needed a human to run it during the episode, and the
|
|
621
|
+
* episode does not wait. Two hours after the second release the process died
|
|
622
|
+
* on `Ineffective mark-compacts near heap limit`.
|
|
623
|
+
*
|
|
624
|
+
* ## What this is, and what it is NOT
|
|
625
|
+
*
|
|
626
|
+
* `Runtime.queryObjects(ArrayBuffer.prototype)` over an IN-PROCESS inspector
|
|
627
|
+
* session, followed by `Runtime.callFunctionOn` computing a byteLength
|
|
628
|
+
* histogram over the result array, returned BY VALUE. It walks the heap in
|
|
629
|
+
* memory and returns a few hundred bytes of JSON. Nothing is serialised,
|
|
630
|
+
* nothing is written, no file exists afterwards.
|
|
631
|
+
*
|
|
632
|
+
* It is NOT a heap snapshot. `scripts/check-no-heap-dump-command.ts` forbids
|
|
633
|
+
* `writeHeapSnapshot` because a multi-GB serialisation of the heap took the
|
|
634
|
+
* whole host down twice; this module contains no such call and must never
|
|
635
|
+
* grow one. The two look alike only in that both need a GC first.
|
|
636
|
+
*
|
|
637
|
+
* ## What it costs, and why the line says so
|
|
638
|
+
*
|
|
639
|
+
* `queryObjects` forces ONE full garbage collection before it walks — the same
|
|
640
|
+
* stop-the-world the reclaim pass already pays every 10 minutes on this
|
|
641
|
+
* process (~1 s at a 2.5 GB heap). The cost is printed on the line it
|
|
642
|
+
* produces, because a pause with no owner in the log is exactly the kind of
|
|
643
|
+
* stall D181 warns about on the process that mediates every cross-runner call.
|
|
644
|
+
* The gate ({@link createArrayBufferCensusGate}) bounds it: a threshold, held
|
|
645
|
+
* for a sustain window so a burst that dies in the next scavenge never pays,
|
|
646
|
+
* and a generous cooldown so an hour-long episode buys a handful of readings,
|
|
647
|
+
* not one per probe.
|
|
648
|
+
*
|
|
649
|
+
* ## Why the byteLength histogram, and not "who holds them"
|
|
650
|
+
*
|
|
651
|
+
* `queryObjects` returns the objects, not their retainers; a retainer walk is
|
|
652
|
+
* a heap snapshot by another name. But the SIZE distribution already
|
|
653
|
+
* discriminates the candidates that were argued about for a night: 65 536-byte
|
|
654
|
+
* buffers are socket chunks, 8 192-byte ones are Buffer-pool slabs, a few
|
|
655
|
+
* hundred equal multi-megabyte ones are frames or JPEGs, and the count says
|
|
656
|
+
* whether it is thousands of small or dozens of huge. The exact top sizes are
|
|
657
|
+
* printed as well as power-of-two buckets, because "1 462 272 B × 880" names
|
|
658
|
+
* a producer where "≤2 MB: 1.2 GB" does not.
|
|
659
|
+
*/
|
|
660
|
+
/**
|
|
661
|
+
* Take the census once `arrayBuffers` has been at or above this for
|
|
662
|
+
* {@link ARRAY_BUFFER_CENSUS_SUSTAIN_MS}.
|
|
663
|
+
*
|
|
664
|
+
* 512 MB. The two 2026-09-10 episodes crossed it 25 and 8 minutes in; the
|
|
665
|
+
* routine reading in six hours of 2-second samples around them was 18–100 MB
|
|
666
|
+
* with single-sample spikes to 479 MB, and the process's floor after every
|
|
667
|
+
* compaction is 12–18 MB. A dead-buffer sawtooth between major GCs (measured
|
|
668
|
+
* up to 3.65 GB on 2026-08-08 when the heap had 4 GB of headroom) cannot reach
|
|
669
|
+
* it while GC runs every few seconds — and if one did, the census would SAY
|
|
670
|
+
* so: it reports the live total after its own GC next to the raw reading.
|
|
671
|
+
*/
|
|
672
|
+
var ARRAY_BUFFER_CENSUS_THRESHOLD_MB = 512;
|
|
673
|
+
/**
|
|
674
|
+
* How long the reading must stay above the threshold before a census is taken.
|
|
675
|
+
*
|
|
676
|
+
* 60 s = 30 probes at the 2 s cadence. A burst that a scavenge or the next
|
|
677
|
+
* major GC returns never pays a forced GC; an episode climbing at the slowest
|
|
678
|
+
* rate measured (20 MB/min) is still 20 MB higher by the time it is measured,
|
|
679
|
+
* which is nothing against the threshold.
|
|
680
|
+
*/
|
|
681
|
+
var ARRAY_BUFFER_CENSUS_SUSTAIN_MS = 6e4;
|
|
682
|
+
/**
|
|
683
|
+
* Minimum spacing between two censuses.
|
|
684
|
+
*
|
|
685
|
+
* 30 minutes: the second episode ran 76 minutes, so it would have produced
|
|
686
|
+
* three readings — enough to see the shape change (or not) as it grew — at a
|
|
687
|
+
* cost the router already pays six times an hour for the reclaim pass.
|
|
688
|
+
*/
|
|
689
|
+
var ARRAY_BUFFER_CENSUS_COOLDOWN_MS = 30 * 6e4;
|
|
690
|
+
/**
|
|
691
|
+
* A drop of at least this share of the threshold between two consecutive
|
|
692
|
+
* probes, from a reading that had been SUSTAINED above the threshold, is a
|
|
693
|
+
* RELEASE — the boundary both episodes crossed with no line, and the one line
|
|
694
|
+
* that lets the release be correlated with whatever else the process logged
|
|
695
|
+
* that second. Sustained, not merely above: a one-probe spike that the next
|
|
696
|
+
* scavenge sweeps (a 1.3 GB burst in 11 s was measured on 2026-08-29) is not
|
|
697
|
+
* an episode ending, and a WARN for every one of those would bury the line
|
|
698
|
+
* this exists to produce.
|
|
699
|
+
*/
|
|
700
|
+
var ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO = .5;
|
|
701
|
+
/**
|
|
702
|
+
* The pure decision: threshold, sustain, cooldown, release — testable without
|
|
703
|
+
* allocating a gigabyte or forcing a GC.
|
|
704
|
+
*/
|
|
705
|
+
function createArrayBufferCensusGate(options = {}) {
|
|
706
|
+
const thresholdMb = options.thresholdMb ?? 512;
|
|
707
|
+
const sustainMs = options.sustainMs ?? 6e4;
|
|
708
|
+
const cooldownMs = options.cooldownMs ?? 18e5;
|
|
709
|
+
let aboveSince = null;
|
|
710
|
+
let lastTakenAt = Number.NEGATIVE_INFINITY;
|
|
711
|
+
let previousMb = null;
|
|
712
|
+
let previousAt = null;
|
|
713
|
+
return { observe(arrayBuffersMb, at) {
|
|
714
|
+
const previous = previousMb;
|
|
715
|
+
const previousHeldForMs = previous !== null && previousAt !== null && aboveSince !== null ? previousAt - aboveSince : null;
|
|
716
|
+
previousMb = arrayBuffersMb;
|
|
717
|
+
previousAt = at;
|
|
718
|
+
if (previous !== null && previousHeldForMs !== null && previousHeldForMs >= sustainMs && previous - arrayBuffersMb >= thresholdMb * .5) {
|
|
719
|
+
aboveSince = null;
|
|
720
|
+
return {
|
|
721
|
+
kind: "released",
|
|
722
|
+
fromMb: previous,
|
|
723
|
+
toMb: arrayBuffersMb
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
if (arrayBuffersMb < thresholdMb) {
|
|
727
|
+
aboveSince = null;
|
|
728
|
+
return { kind: "hold" };
|
|
729
|
+
}
|
|
730
|
+
aboveSince ??= at;
|
|
731
|
+
const heldForMs = at - aboveSince;
|
|
732
|
+
if (heldForMs < sustainMs) return { kind: "hold" };
|
|
733
|
+
if (at - lastTakenAt < cooldownMs) return { kind: "hold" };
|
|
734
|
+
lastTakenAt = at;
|
|
735
|
+
return {
|
|
736
|
+
kind: "take",
|
|
737
|
+
reason: `arrayBuffers=${arrayBuffersMb}MB has been >=${thresholdMb}MB for ${heldForMs}ms`
|
|
738
|
+
};
|
|
739
|
+
} };
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* The function evaluated INSIDE the inspector against the `queryObjects`
|
|
743
|
+
* result array (`this`). Kept as source text because that is what the
|
|
744
|
+
* protocol takes; kept small because it runs with the world stopped.
|
|
745
|
+
*/
|
|
746
|
+
var HISTOGRAM_FN = `function () {
|
|
747
|
+
var byBucket = new Map(), bySize = new Map()
|
|
748
|
+
var count = 0, bytes = 0, detached = 0
|
|
749
|
+
for (var i = 0; i < this.length; i++) {
|
|
750
|
+
var b = this[i].byteLength
|
|
751
|
+
count++
|
|
752
|
+
bytes += b
|
|
753
|
+
if (b === 0) { detached++; continue }
|
|
754
|
+
var bucket = 1
|
|
755
|
+
while (bucket < b) bucket *= 2
|
|
756
|
+
var e = byBucket.get(bucket); if (!e) { e = [0, 0]; byBucket.set(bucket, e) }
|
|
757
|
+
e[0]++; e[1] += b
|
|
758
|
+
var s = bySize.get(b); if (!s) { s = [0, 0]; bySize.set(b, s) }
|
|
759
|
+
s[0]++; s[1] += b
|
|
760
|
+
}
|
|
761
|
+
var buckets = []
|
|
762
|
+
byBucket.forEach(function (v, k) { buckets.push({ upToBytes: k, count: v[0], bytes: v[1] }) })
|
|
763
|
+
buckets.sort(function (a, b) { return b.bytes - a.bytes })
|
|
764
|
+
var topSizes = []
|
|
765
|
+
bySize.forEach(function (v, k) { topSizes.push({ byteLength: k, count: v[0], bytes: v[1] }) })
|
|
766
|
+
topSizes.sort(function (a, b) { return b.bytes - a.bytes })
|
|
767
|
+
return { count: count, bytes: bytes, detached: detached,
|
|
768
|
+
buckets: buckets.slice(0, 6), topSizes: topSizes.slice(0, 5) }
|
|
769
|
+
}`;
|
|
770
|
+
function isFiniteNumber(value) {
|
|
771
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
772
|
+
}
|
|
773
|
+
function isBucket(value) {
|
|
774
|
+
return typeof value === "object" && value !== null && isFiniteNumber(Reflect.get(value, "upToBytes")) && isFiniteNumber(Reflect.get(value, "count")) && isFiniteNumber(Reflect.get(value, "bytes"));
|
|
775
|
+
}
|
|
776
|
+
function isExactSize(value) {
|
|
777
|
+
return typeof value === "object" && value !== null && isFiniteNumber(Reflect.get(value, "byteLength")) && isFiniteNumber(Reflect.get(value, "count")) && isFiniteNumber(Reflect.get(value, "bytes"));
|
|
778
|
+
}
|
|
779
|
+
/** The inspector answers `unknown`; the report is trusted only once it has this shape. */
|
|
780
|
+
function isArrayBufferCensusReport(value) {
|
|
781
|
+
if (typeof value !== "object" || value === null) return false;
|
|
782
|
+
const buckets = Reflect.get(value, "buckets");
|
|
783
|
+
const topSizes = Reflect.get(value, "topSizes");
|
|
784
|
+
return isFiniteNumber(Reflect.get(value, "count")) && isFiniteNumber(Reflect.get(value, "bytes")) && isFiniteNumber(Reflect.get(value, "detached")) && Array.isArray(buckets) && buckets.every(isBucket) && Array.isArray(topSizes) && topSizes.every(isExactSize);
|
|
785
|
+
}
|
|
786
|
+
function readObjectId(result, path) {
|
|
787
|
+
let cursor = result;
|
|
788
|
+
for (const key of path) {
|
|
789
|
+
if (typeof cursor !== "object" || cursor === null) throw new Error(`inspector reply has no ${path.join(".")}`);
|
|
790
|
+
cursor = Reflect.get(cursor, key);
|
|
791
|
+
}
|
|
792
|
+
if (typeof cursor !== "string") throw new Error(`inspector reply has no ${path.join(".")}`);
|
|
793
|
+
return cursor;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* The real census: an in-process inspector session, opened for the call and
|
|
797
|
+
* closed after it, so no session is left listening between censuses.
|
|
798
|
+
*
|
|
799
|
+
* Every remote object it creates is released before the session closes; a
|
|
800
|
+
* failure anywhere still disconnects. The session is in-process — nothing
|
|
801
|
+
* listens on a port, nothing outside this process can reach it.
|
|
802
|
+
*/
|
|
803
|
+
function createInspectorArrayBufferCensus() {
|
|
804
|
+
return async () => {
|
|
805
|
+
const session = new Session();
|
|
806
|
+
session.connect();
|
|
807
|
+
try {
|
|
808
|
+
const prototypeObjectId = readObjectId(await session.post("Runtime.evaluate", { expression: "ArrayBuffer.prototype" }), ["result", "objectId"]);
|
|
809
|
+
try {
|
|
810
|
+
const objectId = readObjectId(await session.post("Runtime.queryObjects", { prototypeObjectId }), ["objects", "objectId"]);
|
|
811
|
+
try {
|
|
812
|
+
const evaluated = await session.post("Runtime.callFunctionOn", {
|
|
813
|
+
objectId,
|
|
814
|
+
functionDeclaration: HISTOGRAM_FN,
|
|
815
|
+
returnByValue: true
|
|
816
|
+
});
|
|
817
|
+
const value = typeof evaluated === "object" && evaluated !== null ? Reflect.get(Reflect.get(evaluated, "result") ?? {}, "value") : void 0;
|
|
818
|
+
if (!isArrayBufferCensusReport(value)) throw new Error("inspector returned a histogram of an unexpected shape");
|
|
819
|
+
return value;
|
|
820
|
+
} finally {
|
|
821
|
+
await session.post("Runtime.releaseObject", { objectId });
|
|
822
|
+
}
|
|
823
|
+
} finally {
|
|
824
|
+
await session.post("Runtime.releaseObject", { objectId: prototypeObjectId });
|
|
825
|
+
}
|
|
826
|
+
} finally {
|
|
827
|
+
session.disconnect();
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
var MB$1 = 1048576;
|
|
832
|
+
function mb(bytes) {
|
|
833
|
+
return Math.round(bytes / MB$1);
|
|
834
|
+
}
|
|
835
|
+
function formatBucket(bucket) {
|
|
836
|
+
return `<=${bucket.upToBytes >= MB$1 ? `${Math.round(bucket.upToBytes / MB$1)}MB` : bucket.upToBytes >= 1024 ? `${Math.round(bucket.upToBytes / 1024)}KB` : `${bucket.upToBytes}B`}:${bucket.count}x/${mb(bucket.bytes)}MB`;
|
|
837
|
+
}
|
|
838
|
+
function formatExact(size) {
|
|
839
|
+
return `${size.byteLength}Bx${size.count}=${mb(size.bytes)}MB`;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* The census line. `live` is what survived the census's own GC; `raw` is the
|
|
843
|
+
* reading that triggered it — when they disagree by a lot, the growth was dead
|
|
844
|
+
* buffers awaiting a sweep, not a retention, and the line says which.
|
|
845
|
+
*/
|
|
846
|
+
function formatArrayBufferCensus(label, report, context) {
|
|
847
|
+
const live = mb(report.bytes);
|
|
848
|
+
const verdict = live < context.rawMb * .5 ? "mostly DEAD buffers awaiting a sweep, not a retention" : "LIVE — something holds them";
|
|
849
|
+
return `[mem] ${label} arrayBuffer census: live=${live}MB in ${report.count} ArrayBuffers (raw arrayBuffers=${context.rawMb}MB at the trigger → ${verdict}) top sizes: ${report.topSizes.map(formatExact).join(", ") || "(none)"}; buckets: ${report.buckets.map(formatBucket).join(", ") || "(none)"}; detached=${report.detached} took=${context.tookMs}ms — cost: ONE forced full GC (the same pause as a reclaim pass), walked in memory, nothing serialised, nothing written; trigger: ${context.reason}`;
|
|
850
|
+
}
|
|
851
|
+
//#endregion
|
|
606
852
|
//#region src/kernel/heap-watch.ts
|
|
607
853
|
/**
|
|
608
854
|
* heap-watch — hub-main's own memory, on the record, and returned to the OS.
|
|
@@ -1288,8 +1534,35 @@ function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, i
|
|
|
1288
1534
|
if (previous === "escalated" && mode === "steady") sink.info(`[mem] ${label} heap back to routine — logging every ${intervalMs}ms`);
|
|
1289
1535
|
checkRssBudget(sample, at);
|
|
1290
1536
|
maybeReclaim(sample);
|
|
1537
|
+
judgeArrayBuffers(sample, at);
|
|
1291
1538
|
} catch {}
|
|
1292
1539
|
};
|
|
1540
|
+
const censusProbe = probes?.arrayBufferCensus;
|
|
1541
|
+
const censusGate = censusProbe === void 0 ? void 0 : createArrayBufferCensusGate(censusProbe);
|
|
1542
|
+
let censusInFlight = false;
|
|
1543
|
+
const judgeArrayBuffers = (sample, at) => {
|
|
1544
|
+
if (censusProbe === void 0 || censusGate === void 0) return;
|
|
1545
|
+
const verdict = censusGate.observe(sample.arrayBuffersMb, at);
|
|
1546
|
+
if (verdict.kind === "released") {
|
|
1547
|
+
sink.warn(`[mem] ${label} arrayBuffers RELEASED ${verdict.fromMb}MB→${verdict.toMb}MB between two probes ${probeIntervalMs}ms apart — no owner logged this release; whatever held them let go in one instant (a socket that died, a queue that drained, a map that was cleared). Correlate with every line of this process in that window.`);
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
if (verdict.kind !== "take" || censusInFlight) return;
|
|
1551
|
+
censusInFlight = true;
|
|
1552
|
+
const startedAt = now();
|
|
1553
|
+
const rawMb = sample.arrayBuffersMb;
|
|
1554
|
+
censusProbe.census().then((report) => {
|
|
1555
|
+
sink.warn(formatArrayBufferCensus(label, report, {
|
|
1556
|
+
rawMb,
|
|
1557
|
+
tookMs: now() - startedAt,
|
|
1558
|
+
reason: verdict.reason
|
|
1559
|
+
}));
|
|
1560
|
+
}).catch((error) => {
|
|
1561
|
+
sink.warn(`[mem] ${label} arrayBuffer census FAILED after ${now() - startedAt}ms (trigger: ${verdict.reason}) — ${error instanceof Error ? error.message : String(error)}`);
|
|
1562
|
+
}).finally(() => {
|
|
1563
|
+
censusInFlight = false;
|
|
1564
|
+
});
|
|
1565
|
+
};
|
|
1293
1566
|
const timer = setInterval(tick, probeIntervalMs);
|
|
1294
1567
|
timer.unref?.();
|
|
1295
1568
|
tick();
|
|
@@ -6868,7 +7141,8 @@ var LocalChildRegistry = class {
|
|
|
6868
7141
|
this.registeredHandler({
|
|
6869
7142
|
childId: msg.childId,
|
|
6870
7143
|
caps: msg.caps,
|
|
6871
|
-
incarnation
|
|
7144
|
+
incarnation,
|
|
7145
|
+
...msg.customActions !== void 0 ? { customActions: msg.customActions } : {}
|
|
6872
7146
|
});
|
|
6873
7147
|
return { ok: true };
|
|
6874
7148
|
}
|
|
@@ -6970,6 +7244,15 @@ var LocalChildClient = class {
|
|
|
6970
7244
|
* subscriptions do), the register carries the real union — even if empty.
|
|
6971
7245
|
*/
|
|
6972
7246
|
hasDeclaredEventPatterns = false;
|
|
7247
|
+
/**
|
|
7248
|
+
* The custom-action catalogs of every hosted addon, once the runner's init
|
|
7249
|
+
* loop has produced them (`updateCaps(caps, catalogs)` at post-init). `null`
|
|
7250
|
+
* until then, and OMITTED from the register frame while null: the parent
|
|
7251
|
+
* reads absence as "not described yet", and a pre-init register must never
|
|
7252
|
+
* look like "this child declares no actions". Kept on the client so every
|
|
7253
|
+
* later re-register (native-cap change, reconnect) re-carries it unchanged.
|
|
7254
|
+
*/
|
|
7255
|
+
latestCustomActions = null;
|
|
6973
7256
|
/** Events and logs queued while the channel is not yet open. */
|
|
6974
7257
|
pendingEmits = [];
|
|
6975
7258
|
/** Handler for parent→child events. Registered via `onEvent`. */
|
|
@@ -7109,12 +7392,7 @@ var LocalChildClient = class {
|
|
|
7109
7392
|
}
|
|
7110
7393
|
if (msg.kind === "set-log-level") this.setLogLevelHandler?.(msg.level);
|
|
7111
7394
|
});
|
|
7112
|
-
const register =
|
|
7113
|
-
kind: "register",
|
|
7114
|
-
childId: this.options.childId,
|
|
7115
|
-
caps: this.latestCaps,
|
|
7116
|
-
...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {}
|
|
7117
|
-
};
|
|
7395
|
+
const register = this.registerFrame(this.latestCaps);
|
|
7118
7396
|
try {
|
|
7119
7397
|
await channel.request(register);
|
|
7120
7398
|
} catch (err) {
|
|
@@ -7140,16 +7418,26 @@ var LocalChildClient = class {
|
|
|
7140
7418
|
* it (device-restore can race ahead of the UDS connect). After `start()`,
|
|
7141
7419
|
* the set is sent immediately.
|
|
7142
7420
|
*/
|
|
7143
|
-
async updateCaps(caps) {
|
|
7421
|
+
async updateCaps(caps, customActions) {
|
|
7144
7422
|
this.latestCaps = caps;
|
|
7423
|
+
if (customActions !== void 0) this.latestCustomActions = customActions;
|
|
7145
7424
|
if (this.channel === null) return;
|
|
7146
|
-
|
|
7425
|
+
await this.channel.request(this.registerFrame(caps));
|
|
7426
|
+
}
|
|
7427
|
+
/**
|
|
7428
|
+
* The register frame: caps, plus everything a re-register must atomically
|
|
7429
|
+
* re-carry so no separate re-sync can be missed — the subscription union
|
|
7430
|
+
* (omitted if nothing ever declared, which keeps the fail-open shape) and the
|
|
7431
|
+
* custom-action catalogs (omitted until the init loop produced them).
|
|
7432
|
+
*/
|
|
7433
|
+
registerFrame(caps) {
|
|
7434
|
+
return {
|
|
7147
7435
|
kind: "register",
|
|
7148
7436
|
childId: this.options.childId,
|
|
7149
7437
|
caps,
|
|
7150
|
-
...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {}
|
|
7438
|
+
...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {},
|
|
7439
|
+
...this.latestCustomActions !== null ? { customActions: this.latestCustomActions } : {}
|
|
7151
7440
|
};
|
|
7152
|
-
await this.channel.request(register);
|
|
7153
7441
|
}
|
|
7154
7442
|
/**
|
|
7155
7443
|
* Fire-and-forget: send a system event to the parent for forwarding to the
|
|
@@ -9886,4 +10174,4 @@ async function defaultRun(command, args) {
|
|
|
9886
10174
|
await execFileAsync(command, [...args], { timeout: INSTALL_TIMEOUT_MS });
|
|
9887
10175
|
}
|
|
9888
10176
|
//#endregion
|
|
9889
|
-
export { buildUdsNativeCapProxy as $,
|
|
10177
|
+
export { buildUdsNativeCapProxy as $, ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO as $t, createUdsLoggerWithControl as A, HUB_MAIN_RSS_BUDGET_MB as At, readMoleculerFanoutMode as B, createV8Reclaimer as Bt, ipcParentLink as C, EVENT_PLANE_TOP_N as Cn, createAddonDataPlaneFacility as Ct, createUdsEventBus as D, formatEventPlane as Dn, HEAP_WATCH_INTERVAL_MS as Dt, createUdsEventBridge as E, diffEventPlane as En, HEAP_RECLAIM_TRIGGER_MB as Et, AGENT_CAP_FWD_SERVICE as F, RUNNER_RSS_BUDGET_ENV as Ft, createLocalTransport as G, reclaimIntervalMs as Gt, PARENT_ROUTED_PROVIDER_ADDON_ID as H, hubMainRssBudget as Ht, CapRouteResolver as I, ZERO_LOOP_DELAY as It, SocketChannel as J, startRunnerHeapWatch as Jt, UdsLocalTransportClient as K, shouldReclaim as Kt, CapRouteError as L, buildHeapSample as Lt, LocalChildRegistry as M, RSS_BUDGET_REANNOUNCE_MIN_MS as Mt, UDS_NO_ROUTE_PREFIX as N, RSS_BUDGET_RELEASE_RATIO as Nt, udsChildLogToWorkerEntry as O, HEAP_WATCH_WARN_RATIO as Ot, AGENT_CAP_FWD_ACTION as P, RUNNER_HEAP_WATCH_INTERVAL_MS as Pt, buildNativeCapProxy as Q, ARRAY_BUFFER_CENSUS_THRESHOLD_MB as Qt, classifyCapRoute as R, createDeferredHeapWatchSink as Rt, buildLinkChain as S, socketDirectionMessages as Sn, runNpm as St, createParentUnownedCallHandler as T, createEventPlaneReader as Tn, HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS as Tt, __resetCapUsageRegistryForTests as U, nextRssBudgetState as Ut, CapUsageRegistry as V, describeRss as Vt, getCapUsageRegistry as W, parseRssBudgetMb as Wt, FrameDecoder as X, ARRAY_BUFFER_CENSUS_COOLDOWN_MS as Xt, localEndpointPath as Y, strandedMb as Yt, encodeFrame as Z, ARRAY_BUFFER_CENSUS_SUSTAIN_MS as Zt, AddonDepsManager as _, EMPTY_SOCKET_DIRECTION as _n, CapabilityUnavailableError as _t, createUdsAddonContext as a, HEAP_SPACE_REPORT_MIN_MB as an, createAddonService as at, brokerCallForCap as b, sampleSocketDirection as bn, resolveAddonClass as bt, getOrInitReadinessRegistryForClient as c, heapSpaceField as cn, capActionName as ct, clusterEventTopic as d, EMPTY_SOCKET_PLANE_BASELINE as dn, capServiceName as dt, createArrayBufferCensusGate as en, createBrokerDeviceManagerApi as et, getBrokerEventBus as f, SOCKET_PLANE_TOP_N as fn, parseCapAction as ft, subscribePassthrough as g, formatSocketPlane as gn, CapabilityHandle as gt, setNodeEventInterest as h, diffSocketPlane as hn, DeviceRegistry as ht, createAddonContext as i, DECISIVE_HEAP_SPACES as in, setWorkerNativeCapsChangeListener as it, LocalChildClient as j, HUB_RSS_BUDGET_ENV as jt, createUdsLogger as k, HUB_MAIN_HEAP_WATCH_LABEL as kt, setHubConnected as l, readHeapSpaces as ln, capActionSuffix as lt, registerEventBusService as m, createSocketPlaneReader as mn, serializeTypedArrays as mt, installManifestPythonDeps as n, formatArrayBufferCensus as nn, getWorkerNativeCapSnapshot as nt, getWorkerDeviceRegistry as o, emitHeapDiagnosticReport as on, validateProviderRegistrations as ot, getMoleculerEventStats as p, createSocketPlaneMeter as pn, deserializeTypedArrays as pt, UdsLocalTransportServer as q, startHeapWatch as qt, adaptBrokerToCluster as r, isArrayBufferCensusReport as rn, mountNativeCapService as rt, getOrInitReadinessRegistry as s, formatHeapSpaces as sn, NATIVE_PROVIDER_SERVICE_INFIX as st, installManifestSystemDeps as t, createInspectorArrayBufferCensus as tn, getWorkerNativeCapProvider as tt, EVENT_TOPIC_PREFIX as u, selectReportedSpaces as un, capBareAction as ut, createKernelHwAccel as v, createSocketDirectionCounters as vn, copyBundledNativeModules as vt, localProviderLink as w, createEventPlaneMeter as wn, HEAP_RECLAIM_MIN_INTERVAL_MS as wt, brokerTransportLink as x, socketDirectionBytes as xn, resolveNpmInvocation as xt, resolveHwAccel as y, recordSocketFrame as yn, installManifestNativeDeps as yt, callWithServiceDiscovery as z, createLoopDelayMeter as zt };
|