@camstack/system 1.2.217 → 1.2.219
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 +2 -2
- package/dist/addon-utils.mjs +2 -2
- 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 +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/gpu-probe.d.ts +18 -0
- package/dist/builtins/native-metrics/native-metrics-provider.d.ts +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.d.ts +24 -8
- package/dist/builtins/native-metrics/native-metrics.addon.js +1318 -1098
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1318 -1101
- package/dist/builtins/native-metrics/proc-process-table.d.ts +25 -0
- package/dist/builtins/platform-probe/index.js +3 -3
- package/dist/builtins/platform-probe/index.mjs +3 -3
- 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-pragmas.d.ts +23 -0
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +1 -0
- 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/sqlite-storage/wal-checkpoint-policy.d.ts +115 -0
- package/dist/builtins/sqlite-storage/wal-checkpoint-worker.d.ts +1 -0
- package/dist/builtins/sqlite-storage/wal-checkpointer.d.ts +70 -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-F__gp_VX.mjs → custom-action-registry-B6gOtUWA.mjs} +19 -0
- package/dist/{custom-action-registry-jY0NOZK8.js → custom-action-registry-DKWhaWL1.js} +19 -0
- package/dist/{dist-C85OOASj.js → dist-B17W9ngu.js} +118 -3
- package/dist/{dist-DtDKMSeG.mjs → dist-Cyz5_i7z.mjs} +113 -4
- package/dist/index.d.ts +9 -2
- package/dist/index.js +39 -36
- package/dist/index.mjs +23 -38
- 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 +39 -1
- package/dist/kernel/moleculer/mesh-queue-report.d.ts +51 -0
- package/dist/kernel/outbound-queue-watch.d.ts +94 -0
- package/dist/kernel/socket-plane-report.d.ts +75 -4
- package/dist/kernel/transport/cap-routing-hints.d.ts +21 -0
- package/dist/kernel/transport/child-cap-protocol.d.ts +43 -3
- package/dist/kernel/transport/frame-codec.d.ts +49 -0
- package/dist/kernel/transport/local-child-client.d.ts +25 -2
- package/dist/kernel/transport/local-child-registry.d.ts +36 -1
- package/dist/kernel/transport/local-transport.d.ts +45 -2
- package/dist/kernel/transport/socket-channel.d.ts +19 -1
- package/dist/{manifest-system-deps-726RBOX7.mjs → manifest-system-deps-C7dihwWT.mjs} +1604 -788
- package/dist/{manifest-system-deps-DvY1bY_n.js → manifest-system-deps-g_nK0xsF.js} +1607 -683
- package/dist/process/proc-stat.d.ts +75 -0
- package/dist/process/resource-monitor.d.ts +9 -1
- package/dist/{resource-monitor-CdnzxBLP.js → resource-monitor-LVE1BoLs.js} +42 -23
- package/dist/{resource-monitor-BWmQ5i-o.mjs → resource-monitor-Vp0bPV3U.mjs} +42 -22
- package/dist/{retired-settings-keys-Cx-lYIbU.mjs → retired-settings-keys-BCYUrdsA.mjs} +1 -1
- package/dist/{retired-settings-keys-Dfj9LA-i.js → retired-settings-keys-CjvB7Etx.js} +1 -1
- package/dist/wal-checkpoint-policy-CAcg63o-.mjs +92 -0
- package/dist/wal-checkpoint-policy-CCJZngds.js +127 -0
- package/dist/wal-checkpoint-worker.js +138 -0
- package/dist/wal-checkpoint-worker.mjs +137 -0
- package/package.json +1 -1
- package/dist/{event-loop-stall-monitor-DM9OAzy8.js → event-loop-stall-monitor-CAsHdMwP.js} +1 -1
- package/dist/{event-loop-stall-monitor-B3CFd9tM.mjs → event-loop-stall-monitor-GPrVvx14.mjs} +1 -1
- package/dist/{tls-DOTmtLCW.mjs → tls-2xbgg78V.mjs} +2 -2
- package/dist/{tls-BxQlomxd.js → tls-BFs4PzoW.js} +2 -2
|
@@ -6,15 +6,16 @@ let _camstack_types_node = require("@camstack/types/node");
|
|
|
6
6
|
let node_fs_promises = require("node:fs/promises");
|
|
7
7
|
let node_path = require("node:path");
|
|
8
8
|
node_path = require_chunk.__toESM(node_path);
|
|
9
|
-
let node_child_process = require("node:child_process");
|
|
10
|
-
let node_util = require("node:util");
|
|
11
9
|
let node_fs = require("node:fs");
|
|
12
10
|
node_fs = require_chunk.__toESM(node_fs);
|
|
11
|
+
let node_child_process = require("node:child_process");
|
|
13
12
|
let node_os = require("node:os");
|
|
14
13
|
node_os = require_chunk.__toESM(node_os);
|
|
14
|
+
let node_util = require("node:util");
|
|
15
15
|
let node_http = require("node:http");
|
|
16
16
|
let node_v8 = require("node:v8");
|
|
17
17
|
node_v8 = require_chunk.__toESM(node_v8);
|
|
18
|
+
let node_inspector_promises = require("node:inspector/promises");
|
|
18
19
|
let node_perf_hooks = require("node:perf_hooks");
|
|
19
20
|
let node_vm = require("node:vm");
|
|
20
21
|
node_vm = require_chunk.__toESM(node_vm);
|
|
@@ -310,12 +311,15 @@ function createSocketPlaneReader(sources) {
|
|
|
310
311
|
peers.push({
|
|
311
312
|
peerId: child.childId,
|
|
312
313
|
tx: traffic.tx,
|
|
313
|
-
rx: traffic.rx
|
|
314
|
+
rx: traffic.rx,
|
|
315
|
+
queuedBytes: registry.getChildQueuedBytes(child.childId)
|
|
314
316
|
});
|
|
315
317
|
}
|
|
318
|
+
const forward = registry.readForwardCensus?.();
|
|
316
319
|
return {
|
|
317
320
|
peers,
|
|
318
|
-
peersConnected: listed.length
|
|
321
|
+
peersConnected: listed.length,
|
|
322
|
+
...forward === void 0 ? {} : { forward }
|
|
319
323
|
};
|
|
320
324
|
} catch {
|
|
321
325
|
return;
|
|
@@ -333,8 +337,41 @@ function createSocketPlaneReader(sources) {
|
|
|
333
337
|
* stops being read.
|
|
334
338
|
*/
|
|
335
339
|
var SOCKET_PLANE_TOP_N = 5;
|
|
340
|
+
/** How many queue-holding peers get named. Three: a queue is an exception,
|
|
341
|
+
* and the line already carries five names for traffic. */
|
|
342
|
+
var SOCKET_QUEUE_TOP_N = 3;
|
|
343
|
+
/** The queue reading over the current counters — a level, so no baseline. */
|
|
344
|
+
function readSocketQueues(current) {
|
|
345
|
+
let totalBytes = 0;
|
|
346
|
+
let unmeasured = 0;
|
|
347
|
+
const holding = [];
|
|
348
|
+
for (const peer of current.peers) {
|
|
349
|
+
if (peer.queuedBytes === null) {
|
|
350
|
+
unmeasured += 1;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
totalBytes += peer.queuedBytes;
|
|
354
|
+
if (peer.queuedBytes > 0) holding.push({
|
|
355
|
+
peerId: peer.peerId,
|
|
356
|
+
bytes: peer.queuedBytes
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
totalBytes,
|
|
361
|
+
unmeasured,
|
|
362
|
+
top: holding.toSorted((a, b) => b.bytes - a.bytes || a.peerId.localeCompare(b.peerId)).slice(0, 3)
|
|
363
|
+
};
|
|
364
|
+
}
|
|
336
365
|
/** A process that has never read the plane. */
|
|
337
366
|
var EMPTY_SOCKET_PLANE_BASELINE = { peers: /* @__PURE__ */ new Map() };
|
|
367
|
+
/** The census over the window; a counter that went backwards reads as quiet. */
|
|
368
|
+
function diffForward(current, before) {
|
|
369
|
+
if (current === void 0) return void 0;
|
|
370
|
+
return {
|
|
371
|
+
raw: Math.max(0, current.raw - (before?.raw ?? 0)),
|
|
372
|
+
decoded: Math.max(0, current.decoded - (before?.decoded ?? 0))
|
|
373
|
+
};
|
|
374
|
+
}
|
|
338
375
|
function diffDirection(current, before) {
|
|
339
376
|
const previous = before ?? EMPTY_SOCKET_DIRECTION;
|
|
340
377
|
return {
|
|
@@ -360,6 +397,14 @@ function addDirection(into, add) {
|
|
|
360
397
|
function peerMessages(delta) {
|
|
361
398
|
return socketDirectionMessages(delta.tx) + socketDirectionMessages(delta.rx);
|
|
362
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Total bytes a peer moved in the window, both directions — the OTHER
|
|
402
|
+
* ranking, and the one that names a peer the message ranking structurally
|
|
403
|
+
* cannot see (D454).
|
|
404
|
+
*/
|
|
405
|
+
function peerBytes(delta) {
|
|
406
|
+
return socketDirectionBytes(delta.tx) + socketDirectionBytes(delta.rx);
|
|
407
|
+
}
|
|
363
408
|
/** Subtract the previous cumulative reading from the current one. */
|
|
364
409
|
function diffSocketPlane(baseline, current, topN = 5) {
|
|
365
410
|
const deltas = [];
|
|
@@ -376,12 +421,16 @@ function diffSocketPlane(baseline, current, topN = 5) {
|
|
|
376
421
|
rx = addDirection(rx, delta.rx);
|
|
377
422
|
if (peerMessages(delta) > 0) deltas.push(delta);
|
|
378
423
|
}
|
|
424
|
+
const forward = diffForward(current.forward, baseline.forward);
|
|
379
425
|
return {
|
|
380
426
|
peersConnected: current.peersConnected,
|
|
381
427
|
peersMeasured: current.peers.length,
|
|
382
428
|
tx,
|
|
383
429
|
rx,
|
|
384
|
-
top: deltas.toSorted((a, b) => peerMessages(b) - peerMessages(a) || a.peerId.localeCompare(b.peerId)).slice(0, topN)
|
|
430
|
+
top: deltas.toSorted((a, b) => peerMessages(b) - peerMessages(a) || a.peerId.localeCompare(b.peerId)).slice(0, topN),
|
|
431
|
+
topBytes: deltas.toSorted((a, b) => peerBytes(b) - peerBytes(a) || a.peerId.localeCompare(b.peerId)).slice(0, topN),
|
|
432
|
+
queued: readSocketQueues(current),
|
|
433
|
+
...forward === void 0 ? {} : { forward }
|
|
385
434
|
};
|
|
386
435
|
}
|
|
387
436
|
function createSocketPlaneMeter(reader, topN = 5) {
|
|
@@ -390,24 +439,43 @@ function createSocketPlaneMeter(reader, topN = 5) {
|
|
|
390
439
|
const current = reader();
|
|
391
440
|
if (current === void 0) return void 0;
|
|
392
441
|
const window = diffSocketPlane(baseline, current, topN);
|
|
393
|
-
baseline = {
|
|
442
|
+
baseline = {
|
|
443
|
+
peers: new Map(current.peers.map((p) => [p.peerId, p])),
|
|
444
|
+
...current.forward === void 0 ? {} : { forward: current.forward }
|
|
445
|
+
};
|
|
394
446
|
return window;
|
|
395
447
|
} };
|
|
396
448
|
}
|
|
397
449
|
/** Bytes → whole kB. The plane moves 180–330 MB/min; raw bytes would put
|
|
398
450
|
* nine-digit numbers on a line that has to stay readable. */
|
|
399
|
-
function kb(bytes) {
|
|
451
|
+
function kb$1(bytes) {
|
|
400
452
|
return Math.round(bytes / 1024);
|
|
401
453
|
}
|
|
402
454
|
/** `req:<msgs>/<kB>,res:<msgs>/<kB>,evt:<msgs>/<kB>` — every kind, always,
|
|
403
455
|
* including at zero: a kind that went quiet is a reading, not a blank. */
|
|
404
456
|
function formatDirection(sample) {
|
|
405
|
-
return `req:${sample.reqMessages}/${kb(sample.reqBytes)}kB,res:${sample.resMessages}/${kb(sample.resBytes)}kB,evt:${sample.evtMessages}/${kb(sample.evtBytes)}kB`;
|
|
457
|
+
return `req:${sample.reqMessages}/${kb$1(sample.reqBytes)}kB,res:${sample.resMessages}/${kb$1(sample.resBytes)}kB,evt:${sample.evtMessages}/${kb$1(sample.evtBytes)}kB`;
|
|
406
458
|
}
|
|
407
|
-
/** `<
|
|
408
|
-
*
|
|
459
|
+
/** `<req>/<res>/<evt>` in kB — the byte triple, in the same order as the
|
|
460
|
+
* message triple next to it, so the two read as one table. */
|
|
461
|
+
function formatPeerBytes(sample) {
|
|
462
|
+
return `${kb$1(sample.reqBytes)}/${kb$1(sample.resBytes)}/${kb$1(sample.evtBytes)}`;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* `<peer>:tx=<req>/<res>/<evt>:rx=…:txkB=<req>/<res>/<evt>:rxkB=…` — the whole
|
|
466
|
+
* peer×kind cross for one peer, messages and bytes, which is the question in
|
|
467
|
+
* one token.
|
|
468
|
+
*
|
|
469
|
+
* The bytes were a per-direction TOTAL until D454, and the total could not
|
|
470
|
+
* answer the question `socket-traffic.ts` says this instrument exists to
|
|
471
|
+
* settle: whether a peer's bytes are RESPONSES (media, and D9/D18 re-opens) or
|
|
472
|
+
* fan-out (`evt`, which scales with runner count and nothing an operator
|
|
473
|
+
* asked for). On 2026-09-11 hub-main wrote 118 MB in one minute to
|
|
474
|
+
* `hub/pipeline-analytics` and the line could only say "118 MB" — separating
|
|
475
|
+
* `res` from `evt` took an hour of arithmetic against the plane-wide totals.
|
|
476
|
+
*/
|
|
409
477
|
function formatPeer(delta) {
|
|
410
|
-
return `${delta.peerId}:tx=${delta.tx.reqMessages}/${delta.tx.resMessages}/${delta.tx.evtMessages}:rx=${delta.rx.reqMessages}/${delta.rx.resMessages}/${delta.rx.evtMessages}:
|
|
478
|
+
return `${delta.peerId}:tx=${delta.tx.reqMessages}/${delta.tx.resMessages}/${delta.tx.evtMessages}:rx=${delta.rx.reqMessages}/${delta.rx.resMessages}/${delta.rx.evtMessages}:txkB=${formatPeerBytes(delta.tx)}:rxkB=${formatPeerBytes(delta.rx)}`;
|
|
411
479
|
}
|
|
412
480
|
/**
|
|
413
481
|
* The suffix appended to a `[mem]` line — leading space included, empty when
|
|
@@ -420,14 +488,234 @@ function formatPeer(delta) {
|
|
|
420
488
|
* is the only way to see an unmeasured channel, and the per-kind splits because
|
|
421
489
|
* "the plane went quiet for a minute" is a reading.
|
|
422
490
|
*
|
|
423
|
-
* `socketTop` is
|
|
424
|
-
*
|
|
425
|
-
*
|
|
491
|
+
* `socketTop` is a conditional field — a ranked list of nothing is noise — and
|
|
492
|
+
* it is a single comma-separated value so the field COUNT does not change from
|
|
493
|
+
* line to line.
|
|
494
|
+
*
|
|
495
|
+
* `socketTopBytes` is the other one, and its PRESENCE is the reading: it is
|
|
496
|
+
* printed only when ranking by bytes names a peer that ranking by messages
|
|
497
|
+
* does not. A line without it says the two rankings agree; a line with it says
|
|
498
|
+
* a peer is moving few, heavy frames and names it (D454).
|
|
426
499
|
*/
|
|
427
500
|
function formatSocketPlane(window) {
|
|
428
501
|
if (window === void 0) return "";
|
|
429
502
|
const top = window.top.map(formatPeer).join(",");
|
|
430
|
-
|
|
503
|
+
const named = new Set(window.top.map((p) => p.peerId));
|
|
504
|
+
const topBytes = window.topBytes.filter((p) => !named.has(p.peerId)).map(formatPeer).join(",");
|
|
505
|
+
const queuedTop = window.queued.top.map((p) => `${p.peerId}:${kb$1(p.bytes)}`).join(",");
|
|
506
|
+
return ` socketPeers=${window.peersMeasured}/${window.peersConnected} socketTx=${formatDirection(window.tx)} socketRx=${formatDirection(window.rx)}` + (top.length === 0 ? "" : ` socketTop=${top}`) + (topBytes.length === 0 ? "" : ` socketTopBytes=${topBytes}`) + ` socketQueued=${kb$1(window.queued.totalBytes)}kB` + (window.queued.unmeasured === 0 ? "" : ` socketQueuedUnmeasured=${window.queued.unmeasured}`) + (queuedTop.length === 0 ? "" : ` socketQueuedTop=${queuedTop}`) + (window.forward === void 0 ? "" : ` socketFwd=${window.forward.raw}/${window.forward.decoded}`);
|
|
507
|
+
}
|
|
508
|
+
//#endregion
|
|
509
|
+
//#region src/kernel/moleculer/mesh-queue-report.ts
|
|
510
|
+
function isRecord$1(value) {
|
|
511
|
+
return typeof value === "object" && value !== null;
|
|
512
|
+
}
|
|
513
|
+
/** The shape `TcpWriter` keeps its sockets in. Anything else is not a mesh. */
|
|
514
|
+
function writerSocketsOf(broker) {
|
|
515
|
+
if (!isRecord$1(broker)) return void 0;
|
|
516
|
+
const transit = broker["transit"];
|
|
517
|
+
if (!isRecord$1(transit)) return void 0;
|
|
518
|
+
const transporter = transit["tx"];
|
|
519
|
+
if (!isRecord$1(transporter)) return void 0;
|
|
520
|
+
const writer = transporter["writer"];
|
|
521
|
+
if (!isRecord$1(writer)) return void 0;
|
|
522
|
+
const sockets = writer["sockets"];
|
|
523
|
+
return sockets instanceof Map ? sockets : void 0;
|
|
524
|
+
}
|
|
525
|
+
function readSocket(nodeId, socket) {
|
|
526
|
+
if (typeof nodeId !== "string" || !isRecord$1(socket)) return void 0;
|
|
527
|
+
const queuedBytes = socket["writableLength"];
|
|
528
|
+
const bytesWritten = socket["bytesWritten"];
|
|
529
|
+
if (typeof queuedBytes !== "number" || typeof bytesWritten !== "number") return void 0;
|
|
530
|
+
return {
|
|
531
|
+
nodeId,
|
|
532
|
+
queuedBytes,
|
|
533
|
+
bytesWritten
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Build a reader over a lazily-resolved broker. Wrapped: a broker mid-teardown
|
|
538
|
+
* may throw, and a diagnostic that can crash the process it watches is worse
|
|
539
|
+
* than none.
|
|
540
|
+
*/
|
|
541
|
+
function createMeshQueueReader(getBroker) {
|
|
542
|
+
return () => {
|
|
543
|
+
try {
|
|
544
|
+
const sockets = writerSocketsOf(getBroker());
|
|
545
|
+
if (sockets === void 0) return void 0;
|
|
546
|
+
const peers = [];
|
|
547
|
+
for (const [nodeId, socket] of sockets) {
|
|
548
|
+
const peer = readSocket(nodeId, socket);
|
|
549
|
+
if (peer !== void 0) peers.push(peer);
|
|
550
|
+
}
|
|
551
|
+
return peers;
|
|
552
|
+
} catch {
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
/** At most this many nodes named. A cluster has a handful. */
|
|
558
|
+
var MESH_QUEUE_TOP_N = 3;
|
|
559
|
+
function kb(bytes) {
|
|
560
|
+
return Math.round(bytes / 1024);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* The suffix appended to a `[mem]` line — leading space included, empty when
|
|
564
|
+
* this process has no mesh writer. `meshQueued` is printed always, at zero
|
|
565
|
+
* included: a quiet mesh is a reading. `meshQueuedTop` names only nodes with
|
|
566
|
+
* a queue, largest first — a ranked list of zeros is noise.
|
|
567
|
+
*/
|
|
568
|
+
function formatMeshQueue(peers) {
|
|
569
|
+
if (peers === void 0) return "";
|
|
570
|
+
const total = peers.reduce((sum, p) => sum + p.queuedBytes, 0);
|
|
571
|
+
const top = peers.filter((p) => p.queuedBytes > 0).toSorted((a, b) => b.queuedBytes - a.queuedBytes || a.nodeId.localeCompare(b.nodeId)).slice(0, 3).map((p) => `${p.nodeId}:${kb(p.queuedBytes)}`).join(",");
|
|
572
|
+
return ` meshQueued=${kb(total)}kB` + (top.length === 0 ? "" : ` meshQueuedTop=${top}`);
|
|
573
|
+
}
|
|
574
|
+
//#endregion
|
|
575
|
+
//#region src/kernel/outbound-queue-watch.ts
|
|
576
|
+
/**
|
|
577
|
+
* outbound-queue-watch — an outbound socket queue that HOLDS is named, and its
|
|
578
|
+
* release is timestamped (D446).
|
|
579
|
+
*
|
|
580
|
+
* ## The blind spot this closes
|
|
581
|
+
*
|
|
582
|
+
* On 2026-09-10 hub-main's `arrayBuffers` climbed twice at a constant rate
|
|
583
|
+
* (20 MB/min to 1 264 MB; 63 MB/min for 77 min to 4 968 MB), LIVE across a
|
|
584
|
+
* forced compaction, with ~4 % of JS heap alongside, and each time released in
|
|
585
|
+
* one instant with no line from any owner. The investigation excluded "every
|
|
586
|
+
* socket write queue" by summing `writableLength` over
|
|
587
|
+
* `process._getActiveHandles()` — 84 MB, all of it the phone's WebSocket.
|
|
588
|
+
*
|
|
589
|
+
* `_getActiveHandles()` lists only handles that keep the loop alive. An
|
|
590
|
+
* UNREF'D socket is not one of them, and Moleculer's `TcpWriter.connect`
|
|
591
|
+
* unrefs every socket it opens to a peer node (`transporters/tcp/tcp-writer.js`,
|
|
592
|
+
* `socket.unref()`). So the one outbound plane that writes to another MACHINE
|
|
593
|
+
* — where a reader that stops draining is the ordinary failure — was never in
|
|
594
|
+
* the sum. The UDS children were in it, but only as a total, at one instant,
|
|
595
|
+
* by hand.
|
|
596
|
+
*
|
|
597
|
+
* ## What this is
|
|
598
|
+
*
|
|
599
|
+
* A pure gate over per-peer queue depths the heartbeat reads on every probe:
|
|
600
|
+
* the UDS children's `socket.writableLength` (`SocketChannel.queuedBytes`) and
|
|
601
|
+
* the mesh writer sockets' (`mesh-queue-report.ts`). It says three things and
|
|
602
|
+
* nothing else:
|
|
603
|
+
*
|
|
604
|
+
* - a peer whose queue has been at or above {@link OUTBOUND_QUEUE_THRESHOLD_BYTES}
|
|
605
|
+
* for {@link OUTBOUND_QUEUE_SUSTAIN_MS} is NAMED, with its current and peak
|
|
606
|
+
* depth and how long it has held — at most once per
|
|
607
|
+
* {@link OUTBOUND_QUEUE_COOLDOWN_MS};
|
|
608
|
+
* - a named peer whose queue then falls under half the threshold, or that
|
|
609
|
+
* vanishes from the readings (its socket died — which is how a queue is
|
|
610
|
+
* dropped in one instant), is reported as RELEASED with its peak and the
|
|
611
|
+
* hold, so the release the episodes never logged has a line and a peer;
|
|
612
|
+
* - a burst that drains inside the sustain window says nothing. Backpressure
|
|
613
|
+
* measures LIVENESS, not identity (D444): a peer momentarily behind is
|
|
614
|
+
* coming back.
|
|
615
|
+
*
|
|
616
|
+
* ## What it is NOT
|
|
617
|
+
*
|
|
618
|
+
* Not a bound. It terminates nothing: a UDS child whose channel is destroyed
|
|
619
|
+
* loses every in-flight call, and a mesh socket Moleculer reconnects on its
|
|
620
|
+
* own terms. The WS guard (`ws-slow-consumer-guard.ts`) bounds the one plane
|
|
621
|
+
* whose client is expendable. Here the line is the deliverable; what to do
|
|
622
|
+
* about a peer that holds is decided with the peer named, not before.
|
|
623
|
+
*/
|
|
624
|
+
/** 64 MiB. Below this a queue is a burst on a busy peer (the orchestrator's
|
|
625
|
+
* channel moves ~1.5 MB/s of audio responses); above it, and held, it is
|
|
626
|
+
* retention with a name. */
|
|
627
|
+
var OUTBOUND_QUEUE_THRESHOLD_BYTES = 64 * 1024 * 1024;
|
|
628
|
+
/** Matches the ArrayBuffer census sustain: a minute above the bound. */
|
|
629
|
+
var OUTBOUND_QUEUE_SUSTAIN_MS = 6e4;
|
|
630
|
+
/** A held queue is re-named every ten minutes, not every probe. */
|
|
631
|
+
var OUTBOUND_QUEUE_COOLDOWN_MS = 10 * 6e4;
|
|
632
|
+
/** A release is a fall under this fraction of the threshold. */
|
|
633
|
+
var RELEASE_RATIO = .5;
|
|
634
|
+
/**
|
|
635
|
+
* The pure decision — threshold, sustain, cooldown, release, gone — testable
|
|
636
|
+
* without a socket.
|
|
637
|
+
*/
|
|
638
|
+
function createOutboundQueueGate(options = {}) {
|
|
639
|
+
const thresholdBytes = options.thresholdBytes ?? 67108864;
|
|
640
|
+
const sustainMs = options.sustainMs ?? 6e4;
|
|
641
|
+
const cooldownMs = options.cooldownMs ?? 6e5;
|
|
642
|
+
const tracked = /* @__PURE__ */ new Map();
|
|
643
|
+
const judgeOne = (peerId, bytes, at) => {
|
|
644
|
+
const state = tracked.get(peerId);
|
|
645
|
+
if (bytes >= thresholdBytes) {
|
|
646
|
+
const next = state === void 0 ? {
|
|
647
|
+
aboveSince: at,
|
|
648
|
+
peakBytes: bytes,
|
|
649
|
+
lastNamedAt: null
|
|
650
|
+
} : {
|
|
651
|
+
...state,
|
|
652
|
+
peakBytes: Math.max(state.peakBytes, bytes)
|
|
653
|
+
};
|
|
654
|
+
const heldForMs = at - next.aboveSince;
|
|
655
|
+
const due = heldForMs >= sustainMs && (next.lastNamedAt === null || at - next.lastNamedAt >= cooldownMs);
|
|
656
|
+
tracked.set(peerId, due ? {
|
|
657
|
+
...next,
|
|
658
|
+
lastNamedAt: at
|
|
659
|
+
} : next);
|
|
660
|
+
return due ? {
|
|
661
|
+
kind: "sustained",
|
|
662
|
+
peerId,
|
|
663
|
+
bytes,
|
|
664
|
+
peakBytes: next.peakBytes,
|
|
665
|
+
heldForMs
|
|
666
|
+
} : void 0;
|
|
667
|
+
}
|
|
668
|
+
if (state === void 0) return void 0;
|
|
669
|
+
if (bytes >= thresholdBytes * RELEASE_RATIO) return void 0;
|
|
670
|
+
tracked.delete(peerId);
|
|
671
|
+
if (state.lastNamedAt === null) return void 0;
|
|
672
|
+
return {
|
|
673
|
+
kind: "released",
|
|
674
|
+
peerId,
|
|
675
|
+
peakBytes: state.peakBytes,
|
|
676
|
+
toBytes: bytes,
|
|
677
|
+
heldForMs: at - state.aboveSince,
|
|
678
|
+
gone: false
|
|
679
|
+
};
|
|
680
|
+
};
|
|
681
|
+
return { observe(readings, at) {
|
|
682
|
+
const verdicts = [];
|
|
683
|
+
const seen = /* @__PURE__ */ new Set();
|
|
684
|
+
for (const reading of readings) {
|
|
685
|
+
seen.add(reading.peerId);
|
|
686
|
+
const verdict = judgeOne(reading.peerId, reading.bytes, at);
|
|
687
|
+
if (verdict !== void 0) verdicts.push(verdict);
|
|
688
|
+
}
|
|
689
|
+
for (const [peerId, state] of tracked) {
|
|
690
|
+
if (seen.has(peerId)) continue;
|
|
691
|
+
tracked.delete(peerId);
|
|
692
|
+
if (state.lastNamedAt === null) continue;
|
|
693
|
+
verdicts.push({
|
|
694
|
+
kind: "released",
|
|
695
|
+
peerId,
|
|
696
|
+
peakBytes: state.peakBytes,
|
|
697
|
+
toBytes: 0,
|
|
698
|
+
heldForMs: at - state.aboveSince,
|
|
699
|
+
gone: true
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
return verdicts;
|
|
703
|
+
} };
|
|
704
|
+
}
|
|
705
|
+
var BYTES_PER_MB$1 = 1048576;
|
|
706
|
+
function mb$2(bytes) {
|
|
707
|
+
return Math.round(bytes / BYTES_PER_MB$1);
|
|
708
|
+
}
|
|
709
|
+
/** `60s` under two minutes, `77m` from there — the hold is read at a glance. */
|
|
710
|
+
function duration(ms) {
|
|
711
|
+
const seconds = Math.round(ms / 1e3);
|
|
712
|
+
return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)}m`;
|
|
713
|
+
}
|
|
714
|
+
/** The line for one verdict. Both carry the peer, the bytes and the hold;
|
|
715
|
+
* the sustained one also says why the older census could not see it. */
|
|
716
|
+
function formatOutboundQueueVerdict(label, verdict) {
|
|
717
|
+
if (verdict.kind === "sustained") return `[mem] ${label} outbound queue to ${verdict.peerId} has held ${mb$2(verdict.bytes)}MB (peak ${mb$2(verdict.peakBytes)}MB) for ${duration(verdict.heldForMs)} — the peer is not draining what this process writes to it. This is socket.writableLength on the channel itself; a sum over _getActiveHandles() does not list an unref'd socket and would miss it.`;
|
|
718
|
+
return `[mem] ${label} outbound queue to ${verdict.peerId} RELEASED — ${mb$2(verdict.peakBytes)}MB at peak, ${mb$2(verdict.toBytes)}MB now, held ${duration(verdict.heldForMs)}` + (verdict.gone ? " (peer gone: the socket closed and dropped its queue)" : " (drained)");
|
|
431
719
|
}
|
|
432
720
|
//#endregion
|
|
433
721
|
//#region src/kernel/heap-spaces.ts
|
|
@@ -514,7 +802,7 @@ var DECISIVE_HEAP_SPACES = ["old_space", "large_object_space"];
|
|
|
514
802
|
var HEAP_SPACE_REPORT_MIN_MB = 32;
|
|
515
803
|
var BYTES_PER_MB = 1048576;
|
|
516
804
|
/** Whole MB, matching the rest of the `[mem]` line. */
|
|
517
|
-
function mb(bytes) {
|
|
805
|
+
function mb$1(bytes) {
|
|
518
806
|
return Math.round(bytes / BYTES_PER_MB);
|
|
519
807
|
}
|
|
520
808
|
/** Tenths of an MB — the resolution the SIGUSR2 report has always printed. */
|
|
@@ -561,7 +849,7 @@ function heapSpaceField(v8Name) {
|
|
|
561
849
|
*/
|
|
562
850
|
function selectReportedSpaces(spaces, minMb = 32) {
|
|
563
851
|
const decisive = DECISIVE_HEAP_SPACES.map((name) => spaces.find((space) => space.name === name)).filter((space) => space !== void 0);
|
|
564
|
-
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));
|
|
852
|
+
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));
|
|
565
853
|
return [...decisive, ...rest];
|
|
566
854
|
}
|
|
567
855
|
/**
|
|
@@ -576,7 +864,7 @@ function selectReportedSpaces(spaces, minMb = 32) {
|
|
|
576
864
|
function formatHeapSpaces(spaces, minMb = 32) {
|
|
577
865
|
const selected = selectReportedSpaces(spaces, minMb);
|
|
578
866
|
if (selected.length === 0) return "";
|
|
579
|
-
return ` ${selected.map((s) => `${heapSpaceField(s.name)}=${mb(s.usedBytes)}MB`).join(" ")}`;
|
|
867
|
+
return ` ${selected.map((s) => `${heapSpaceField(s.name)}=${mb$1(s.usedBytes)}MB`).join(" ")}`;
|
|
580
868
|
}
|
|
581
869
|
var defaultDeps = {
|
|
582
870
|
readMemory: () => process.memoryUsage(),
|
|
@@ -606,6 +894,251 @@ function emitHeapDiagnosticReport(request, deps = defaultDeps) {
|
|
|
606
894
|
}
|
|
607
895
|
}
|
|
608
896
|
//#endregion
|
|
897
|
+
//#region src/kernel/array-buffer-census.ts
|
|
898
|
+
/**
|
|
899
|
+
* array-buffer-census — WHAT SHAPE the live ArrayBuffers have, taken by the
|
|
900
|
+
* heartbeat itself when they grow, because nobody can be awake for it.
|
|
901
|
+
*
|
|
902
|
+
* ## The episodes this exists for
|
|
903
|
+
*
|
|
904
|
+
* hub-main, 2026-09-10 (CEST): `arrayBuffers` climbed linearly and released in
|
|
905
|
+
* one instant, twice, with not one line from the process at either boundary —
|
|
906
|
+
* 17:26→18:31 at ~20 MB/min to 1 264 MB, released in one heartbeat; 22:20→23:36
|
|
907
|
+
* at 60–85 MB/min to **4 968 MB**, released between two probes at 23:38. Both
|
|
908
|
+
* were LIVE: a forced compacting GC at 23:36:40 returned 4 968→4 954 MB. The
|
|
909
|
+
* measurement that names their shape had been prepared (D443) and was never
|
|
910
|
+
* run, because it needed a human to run it during the episode, and the
|
|
911
|
+
* episode does not wait. Two hours after the second release the process died
|
|
912
|
+
* on `Ineffective mark-compacts near heap limit`.
|
|
913
|
+
*
|
|
914
|
+
* ## What this is, and what it is NOT
|
|
915
|
+
*
|
|
916
|
+
* `Runtime.queryObjects(ArrayBuffer.prototype)` over an IN-PROCESS inspector
|
|
917
|
+
* session, followed by `Runtime.callFunctionOn` computing a byteLength
|
|
918
|
+
* histogram over the result array, returned BY VALUE. It walks the heap in
|
|
919
|
+
* memory and returns a few hundred bytes of JSON. Nothing is serialised,
|
|
920
|
+
* nothing is written, no file exists afterwards.
|
|
921
|
+
*
|
|
922
|
+
* It is NOT a heap snapshot. `scripts/check-no-heap-dump-command.ts` forbids
|
|
923
|
+
* `writeHeapSnapshot` because a multi-GB serialisation of the heap took the
|
|
924
|
+
* whole host down twice; this module contains no such call and must never
|
|
925
|
+
* grow one. The two look alike only in that both need a GC first.
|
|
926
|
+
*
|
|
927
|
+
* ## What it costs, and why the line says so
|
|
928
|
+
*
|
|
929
|
+
* `queryObjects` forces ONE full garbage collection before it walks — the same
|
|
930
|
+
* stop-the-world the reclaim pass already pays every 10 minutes on this
|
|
931
|
+
* process (~1 s at a 2.5 GB heap). The cost is printed on the line it
|
|
932
|
+
* produces, because a pause with no owner in the log is exactly the kind of
|
|
933
|
+
* stall D181 warns about on the process that mediates every cross-runner call.
|
|
934
|
+
* The gate ({@link createArrayBufferCensusGate}) bounds it: a threshold, held
|
|
935
|
+
* for a sustain window so a burst that dies in the next scavenge never pays,
|
|
936
|
+
* and a generous cooldown so an hour-long episode buys a handful of readings,
|
|
937
|
+
* not one per probe.
|
|
938
|
+
*
|
|
939
|
+
* ## Why the byteLength histogram, and not "who holds them"
|
|
940
|
+
*
|
|
941
|
+
* `queryObjects` returns the objects, not their retainers; a retainer walk is
|
|
942
|
+
* a heap snapshot by another name. But the SIZE distribution already
|
|
943
|
+
* discriminates the candidates that were argued about for a night: 65 536-byte
|
|
944
|
+
* buffers are socket chunks, 8 192-byte ones are Buffer-pool slabs, a few
|
|
945
|
+
* hundred equal multi-megabyte ones are frames or JPEGs, and the count says
|
|
946
|
+
* whether it is thousands of small or dozens of huge. The exact top sizes are
|
|
947
|
+
* printed as well as power-of-two buckets, because "1 462 272 B × 880" names
|
|
948
|
+
* a producer where "≤2 MB: 1.2 GB" does not.
|
|
949
|
+
*/
|
|
950
|
+
/**
|
|
951
|
+
* Take the census once `arrayBuffers` has been at or above this for
|
|
952
|
+
* {@link ARRAY_BUFFER_CENSUS_SUSTAIN_MS}.
|
|
953
|
+
*
|
|
954
|
+
* 512 MB. The two 2026-09-10 episodes crossed it 25 and 8 minutes in; the
|
|
955
|
+
* routine reading in six hours of 2-second samples around them was 18–100 MB
|
|
956
|
+
* with single-sample spikes to 479 MB, and the process's floor after every
|
|
957
|
+
* compaction is 12–18 MB. A dead-buffer sawtooth between major GCs (measured
|
|
958
|
+
* up to 3.65 GB on 2026-08-08 when the heap had 4 GB of headroom) cannot reach
|
|
959
|
+
* it while GC runs every few seconds — and if one did, the census would SAY
|
|
960
|
+
* so: it reports the live total after its own GC next to the raw reading.
|
|
961
|
+
*/
|
|
962
|
+
var ARRAY_BUFFER_CENSUS_THRESHOLD_MB = 512;
|
|
963
|
+
/**
|
|
964
|
+
* How long the reading must stay above the threshold before a census is taken.
|
|
965
|
+
*
|
|
966
|
+
* 60 s = 30 probes at the 2 s cadence. A burst that a scavenge or the next
|
|
967
|
+
* major GC returns never pays a forced GC; an episode climbing at the slowest
|
|
968
|
+
* rate measured (20 MB/min) is still 20 MB higher by the time it is measured,
|
|
969
|
+
* which is nothing against the threshold.
|
|
970
|
+
*/
|
|
971
|
+
var ARRAY_BUFFER_CENSUS_SUSTAIN_MS = 6e4;
|
|
972
|
+
/**
|
|
973
|
+
* Minimum spacing between two censuses.
|
|
974
|
+
*
|
|
975
|
+
* 30 minutes: the second episode ran 76 minutes, so it would have produced
|
|
976
|
+
* three readings — enough to see the shape change (or not) as it grew — at a
|
|
977
|
+
* cost the router already pays six times an hour for the reclaim pass.
|
|
978
|
+
*/
|
|
979
|
+
var ARRAY_BUFFER_CENSUS_COOLDOWN_MS = 30 * 6e4;
|
|
980
|
+
/**
|
|
981
|
+
* A drop of at least this share of the threshold between two consecutive
|
|
982
|
+
* probes, from a reading that had been SUSTAINED above the threshold, is a
|
|
983
|
+
* RELEASE — the boundary both episodes crossed with no line, and the one line
|
|
984
|
+
* that lets the release be correlated with whatever else the process logged
|
|
985
|
+
* that second. Sustained, not merely above: a one-probe spike that the next
|
|
986
|
+
* scavenge sweeps (a 1.3 GB burst in 11 s was measured on 2026-08-29) is not
|
|
987
|
+
* an episode ending, and a WARN for every one of those would bury the line
|
|
988
|
+
* this exists to produce.
|
|
989
|
+
*/
|
|
990
|
+
var ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO = .5;
|
|
991
|
+
/**
|
|
992
|
+
* The pure decision: threshold, sustain, cooldown, release — testable without
|
|
993
|
+
* allocating a gigabyte or forcing a GC.
|
|
994
|
+
*/
|
|
995
|
+
function createArrayBufferCensusGate(options = {}) {
|
|
996
|
+
const thresholdMb = options.thresholdMb ?? 512;
|
|
997
|
+
const sustainMs = options.sustainMs ?? 6e4;
|
|
998
|
+
const cooldownMs = options.cooldownMs ?? 18e5;
|
|
999
|
+
let aboveSince = null;
|
|
1000
|
+
let lastTakenAt = Number.NEGATIVE_INFINITY;
|
|
1001
|
+
let previousMb = null;
|
|
1002
|
+
let previousAt = null;
|
|
1003
|
+
return { observe(arrayBuffersMb, at) {
|
|
1004
|
+
const previous = previousMb;
|
|
1005
|
+
const previousHeldForMs = previous !== null && previousAt !== null && aboveSince !== null ? previousAt - aboveSince : null;
|
|
1006
|
+
previousMb = arrayBuffersMb;
|
|
1007
|
+
previousAt = at;
|
|
1008
|
+
if (previous !== null && previousHeldForMs !== null && previousHeldForMs >= sustainMs && previous - arrayBuffersMb >= thresholdMb * .5) {
|
|
1009
|
+
aboveSince = null;
|
|
1010
|
+
return {
|
|
1011
|
+
kind: "released",
|
|
1012
|
+
fromMb: previous,
|
|
1013
|
+
toMb: arrayBuffersMb
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
if (arrayBuffersMb < thresholdMb) {
|
|
1017
|
+
aboveSince = null;
|
|
1018
|
+
return { kind: "hold" };
|
|
1019
|
+
}
|
|
1020
|
+
aboveSince ??= at;
|
|
1021
|
+
const heldForMs = at - aboveSince;
|
|
1022
|
+
if (heldForMs < sustainMs) return { kind: "hold" };
|
|
1023
|
+
if (at - lastTakenAt < cooldownMs) return { kind: "hold" };
|
|
1024
|
+
lastTakenAt = at;
|
|
1025
|
+
return {
|
|
1026
|
+
kind: "take",
|
|
1027
|
+
reason: `arrayBuffers=${arrayBuffersMb}MB has been >=${thresholdMb}MB for ${heldForMs}ms`
|
|
1028
|
+
};
|
|
1029
|
+
} };
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* The function evaluated INSIDE the inspector against the `queryObjects`
|
|
1033
|
+
* result array (`this`). Kept as source text because that is what the
|
|
1034
|
+
* protocol takes; kept small because it runs with the world stopped.
|
|
1035
|
+
*/
|
|
1036
|
+
var HISTOGRAM_FN = `function () {
|
|
1037
|
+
var byBucket = new Map(), bySize = new Map()
|
|
1038
|
+
var count = 0, bytes = 0, detached = 0
|
|
1039
|
+
for (var i = 0; i < this.length; i++) {
|
|
1040
|
+
var b = this[i].byteLength
|
|
1041
|
+
count++
|
|
1042
|
+
bytes += b
|
|
1043
|
+
if (b === 0) { detached++; continue }
|
|
1044
|
+
var bucket = 1
|
|
1045
|
+
while (bucket < b) bucket *= 2
|
|
1046
|
+
var e = byBucket.get(bucket); if (!e) { e = [0, 0]; byBucket.set(bucket, e) }
|
|
1047
|
+
e[0]++; e[1] += b
|
|
1048
|
+
var s = bySize.get(b); if (!s) { s = [0, 0]; bySize.set(b, s) }
|
|
1049
|
+
s[0]++; s[1] += b
|
|
1050
|
+
}
|
|
1051
|
+
var buckets = []
|
|
1052
|
+
byBucket.forEach(function (v, k) { buckets.push({ upToBytes: k, count: v[0], bytes: v[1] }) })
|
|
1053
|
+
buckets.sort(function (a, b) { return b.bytes - a.bytes })
|
|
1054
|
+
var topSizes = []
|
|
1055
|
+
bySize.forEach(function (v, k) { topSizes.push({ byteLength: k, count: v[0], bytes: v[1] }) })
|
|
1056
|
+
topSizes.sort(function (a, b) { return b.bytes - a.bytes })
|
|
1057
|
+
return { count: count, bytes: bytes, detached: detached,
|
|
1058
|
+
buckets: buckets.slice(0, 6), topSizes: topSizes.slice(0, 5) }
|
|
1059
|
+
}`;
|
|
1060
|
+
function isFiniteNumber(value) {
|
|
1061
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1062
|
+
}
|
|
1063
|
+
function isBucket(value) {
|
|
1064
|
+
return typeof value === "object" && value !== null && isFiniteNumber(Reflect.get(value, "upToBytes")) && isFiniteNumber(Reflect.get(value, "count")) && isFiniteNumber(Reflect.get(value, "bytes"));
|
|
1065
|
+
}
|
|
1066
|
+
function isExactSize(value) {
|
|
1067
|
+
return typeof value === "object" && value !== null && isFiniteNumber(Reflect.get(value, "byteLength")) && isFiniteNumber(Reflect.get(value, "count")) && isFiniteNumber(Reflect.get(value, "bytes"));
|
|
1068
|
+
}
|
|
1069
|
+
/** The inspector answers `unknown`; the report is trusted only once it has this shape. */
|
|
1070
|
+
function isArrayBufferCensusReport(value) {
|
|
1071
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1072
|
+
const buckets = Reflect.get(value, "buckets");
|
|
1073
|
+
const topSizes = Reflect.get(value, "topSizes");
|
|
1074
|
+
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);
|
|
1075
|
+
}
|
|
1076
|
+
function readObjectId(result, path) {
|
|
1077
|
+
let cursor = result;
|
|
1078
|
+
for (const key of path) {
|
|
1079
|
+
if (typeof cursor !== "object" || cursor === null) throw new Error(`inspector reply has no ${path.join(".")}`);
|
|
1080
|
+
cursor = Reflect.get(cursor, key);
|
|
1081
|
+
}
|
|
1082
|
+
if (typeof cursor !== "string") throw new Error(`inspector reply has no ${path.join(".")}`);
|
|
1083
|
+
return cursor;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* The real census: an in-process inspector session, opened for the call and
|
|
1087
|
+
* closed after it, so no session is left listening between censuses.
|
|
1088
|
+
*
|
|
1089
|
+
* Every remote object it creates is released before the session closes; a
|
|
1090
|
+
* failure anywhere still disconnects. The session is in-process — nothing
|
|
1091
|
+
* listens on a port, nothing outside this process can reach it.
|
|
1092
|
+
*/
|
|
1093
|
+
function createInspectorArrayBufferCensus() {
|
|
1094
|
+
return async () => {
|
|
1095
|
+
const session = new node_inspector_promises.Session();
|
|
1096
|
+
session.connect();
|
|
1097
|
+
try {
|
|
1098
|
+
const prototypeObjectId = readObjectId(await session.post("Runtime.evaluate", { expression: "ArrayBuffer.prototype" }), ["result", "objectId"]);
|
|
1099
|
+
try {
|
|
1100
|
+
const objectId = readObjectId(await session.post("Runtime.queryObjects", { prototypeObjectId }), ["objects", "objectId"]);
|
|
1101
|
+
try {
|
|
1102
|
+
const evaluated = await session.post("Runtime.callFunctionOn", {
|
|
1103
|
+
objectId,
|
|
1104
|
+
functionDeclaration: HISTOGRAM_FN,
|
|
1105
|
+
returnByValue: true
|
|
1106
|
+
});
|
|
1107
|
+
const value = typeof evaluated === "object" && evaluated !== null ? Reflect.get(Reflect.get(evaluated, "result") ?? {}, "value") : void 0;
|
|
1108
|
+
if (!isArrayBufferCensusReport(value)) throw new Error("inspector returned a histogram of an unexpected shape");
|
|
1109
|
+
return value;
|
|
1110
|
+
} finally {
|
|
1111
|
+
await session.post("Runtime.releaseObject", { objectId });
|
|
1112
|
+
}
|
|
1113
|
+
} finally {
|
|
1114
|
+
await session.post("Runtime.releaseObject", { objectId: prototypeObjectId });
|
|
1115
|
+
}
|
|
1116
|
+
} finally {
|
|
1117
|
+
session.disconnect();
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
var MB$1 = 1048576;
|
|
1122
|
+
function mb(bytes) {
|
|
1123
|
+
return Math.round(bytes / MB$1);
|
|
1124
|
+
}
|
|
1125
|
+
function formatBucket(bucket) {
|
|
1126
|
+
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`;
|
|
1127
|
+
}
|
|
1128
|
+
function formatExact(size) {
|
|
1129
|
+
return `${size.byteLength}Bx${size.count}=${mb(size.bytes)}MB`;
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* The census line. `live` is what survived the census's own GC; `raw` is the
|
|
1133
|
+
* reading that triggered it — when they disagree by a lot, the growth was dead
|
|
1134
|
+
* buffers awaiting a sweep, not a retention, and the line says which.
|
|
1135
|
+
*/
|
|
1136
|
+
function formatArrayBufferCensus(label, report, context) {
|
|
1137
|
+
const live = mb(report.bytes);
|
|
1138
|
+
const verdict = live < context.rawMb * .5 ? "mostly DEAD buffers awaiting a sweep, not a retention" : "LIVE — something holds them";
|
|
1139
|
+
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}`;
|
|
1140
|
+
}
|
|
1141
|
+
//#endregion
|
|
609
1142
|
//#region src/kernel/heap-watch.ts
|
|
610
1143
|
/**
|
|
611
1144
|
* heap-watch — hub-main's own memory, on the record, and returned to the OS.
|
|
@@ -1170,6 +1703,27 @@ function format(label, s, loop, budgetMb, suffix) {
|
|
|
1170
1703
|
return `${line} loopP50=${loop.p50Ms}ms loopP99=${loop.p99Ms}ms loopMax=${loop.maxMs}ms${suffix}`;
|
|
1171
1704
|
}
|
|
1172
1705
|
/**
|
|
1706
|
+
* Every outbound queue the heartbeat can reach, as the gate wants them: the
|
|
1707
|
+
* UDS children (only those whose channel keeps a reading — an unmeasured
|
|
1708
|
+
* peer is not a zero) and the mesh writer sockets, each prefixed with its
|
|
1709
|
+
* plane.
|
|
1710
|
+
*/
|
|
1711
|
+
function outboundQueueReadings(uds, mesh) {
|
|
1712
|
+
const readings = [];
|
|
1713
|
+
for (const peer of uds?.peers ?? []) {
|
|
1714
|
+
if (peer.queuedBytes === null) continue;
|
|
1715
|
+
readings.push({
|
|
1716
|
+
peerId: `uds:${peer.peerId}`,
|
|
1717
|
+
bytes: peer.queuedBytes
|
|
1718
|
+
});
|
|
1719
|
+
}
|
|
1720
|
+
for (const node of mesh ?? []) readings.push({
|
|
1721
|
+
peerId: `mesh:${node.nodeId}`,
|
|
1722
|
+
bytes: node.queuedBytes
|
|
1723
|
+
});
|
|
1724
|
+
return readings;
|
|
1725
|
+
}
|
|
1726
|
+
/**
|
|
1173
1727
|
* Start the heartbeat. Returns a stop function.
|
|
1174
1728
|
*
|
|
1175
1729
|
* `unref`ed so it can never hold the process open, and every read is wrapped:
|
|
@@ -1283,7 +1837,7 @@ function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, i
|
|
|
1283
1837
|
const due = at - lastLoggedAt >= intervalMs;
|
|
1284
1838
|
if (mode === "escalated" || due) {
|
|
1285
1839
|
lastLoggedAt = at;
|
|
1286
|
-
const line = format(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}` + formatSocketPlane(socketPlane?.read()));
|
|
1840
|
+
const line = format(label, sample, loopDelay?.read(), budgetMb, `${formatHeapSpaces(readSpaces())}${formatEventPlane(eventPlane?.read())}` + formatSocketPlane(socketPlane?.read()) + formatMeshQueue(meshQueue?.()));
|
|
1287
1841
|
if (sample.nearLimit) sink.warn(`${line} — APPROACHING HEAP LIMIT`);
|
|
1288
1842
|
else if (mode === "escalated") sink.warn(`${line} — heap elevated, sampling every ${probeIntervalMs}ms`);
|
|
1289
1843
|
else sink.info(line);
|
|
@@ -1291,8 +1845,44 @@ function startHeapWatch(label = HUB_MAIN_HEAP_WATCH_LABEL, sink = consoleSink, i
|
|
|
1291
1845
|
if (previous === "escalated" && mode === "steady") sink.info(`[mem] ${label} heap back to routine — logging every ${intervalMs}ms`);
|
|
1292
1846
|
checkRssBudget(sample, at);
|
|
1293
1847
|
maybeReclaim(sample);
|
|
1848
|
+
judgeArrayBuffers(sample, at);
|
|
1849
|
+
judgeOutboundQueues(at);
|
|
1294
1850
|
} catch {}
|
|
1295
1851
|
};
|
|
1852
|
+
const meshQueue = probes?.meshQueue;
|
|
1853
|
+
const queueReader = probes?.socketPlane;
|
|
1854
|
+
const queueGate = queueReader === void 0 && meshQueue === void 0 ? void 0 : createOutboundQueueGate(probes?.outboundQueue);
|
|
1855
|
+
const judgeOutboundQueues = (at) => {
|
|
1856
|
+
if (queueGate === void 0) return;
|
|
1857
|
+
const readings = outboundQueueReadings(queueReader?.(), meshQueue?.());
|
|
1858
|
+
for (const verdict of queueGate.observe(readings, at)) sink.warn(formatOutboundQueueVerdict(label, verdict));
|
|
1859
|
+
};
|
|
1860
|
+
const censusProbe = probes?.arrayBufferCensus;
|
|
1861
|
+
const censusGate = censusProbe === void 0 ? void 0 : createArrayBufferCensusGate(censusProbe);
|
|
1862
|
+
let censusInFlight = false;
|
|
1863
|
+
const judgeArrayBuffers = (sample, at) => {
|
|
1864
|
+
if (censusProbe === void 0 || censusGate === void 0) return;
|
|
1865
|
+
const verdict = censusGate.observe(sample.arrayBuffersMb, at);
|
|
1866
|
+
if (verdict.kind === "released") {
|
|
1867
|
+
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.`);
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
if (verdict.kind !== "take" || censusInFlight) return;
|
|
1871
|
+
censusInFlight = true;
|
|
1872
|
+
const startedAt = now();
|
|
1873
|
+
const rawMb = sample.arrayBuffersMb;
|
|
1874
|
+
censusProbe.census().then((report) => {
|
|
1875
|
+
sink.warn(formatArrayBufferCensus(label, report, {
|
|
1876
|
+
rawMb,
|
|
1877
|
+
tookMs: now() - startedAt,
|
|
1878
|
+
reason: verdict.reason
|
|
1879
|
+
}));
|
|
1880
|
+
}).catch((error) => {
|
|
1881
|
+
sink.warn(`[mem] ${label} arrayBuffer census FAILED after ${now() - startedAt}ms (trigger: ${verdict.reason}) — ${error instanceof Error ? error.message : String(error)}`);
|
|
1882
|
+
}).finally(() => {
|
|
1883
|
+
censusInFlight = false;
|
|
1884
|
+
});
|
|
1885
|
+
};
|
|
1296
1886
|
const timer = setInterval(tick, probeIntervalMs);
|
|
1297
1887
|
timer.unref?.();
|
|
1298
1888
|
tick();
|
|
@@ -5063,6 +5653,74 @@ function encodeFrame(value) {
|
|
|
5063
5653
|
Buffer.from(body.buffer, body.byteOffset, body.byteLength).copy(out, 4);
|
|
5064
5654
|
return out;
|
|
5065
5655
|
}
|
|
5656
|
+
/** msgpack `fixstr` of {@link PAYLOAD_KEY}: `0xa0 | length`, then the bytes. */
|
|
5657
|
+
var PAYLOAD_KEY_BYTES = Buffer.from([162, ...Buffer.from("pl")]);
|
|
5658
|
+
/** msgpack `bin 32` marker: one byte, then a 4-byte big-endian length. */
|
|
5659
|
+
var BIN32_MARKER = 198;
|
|
5660
|
+
/** msgpack `fixmap` range: `0x80 | count`, count in `0..15`. */
|
|
5661
|
+
var FIXMAP_BASE = 128;
|
|
5662
|
+
var FIXMAP_MAX_COUNT = 15;
|
|
5663
|
+
/**
|
|
5664
|
+
* Encode the HEAD of a frame that carries an opaque payload: the length
|
|
5665
|
+
* prefix, the msgpack envelope with one extra key ({@link PAYLOAD_KEY}), and
|
|
5666
|
+
* the `bin 32` header for the payload — everything but the payload bytes
|
|
5667
|
+
* themselves, which the caller writes straight after it.
|
|
5668
|
+
*
|
|
5669
|
+
* ## Why the payload is the frame's TAIL and not a field
|
|
5670
|
+
*
|
|
5671
|
+
* hub-main routes every sibling↔sibling call. Until this existed it decoded
|
|
5672
|
+
* the whole request (args included), built a new message, and re-encoded it
|
|
5673
|
+
* — walking a 13.5 KB PCM chunk or a 2 000-row settings answer twice on the
|
|
5674
|
+
* one thread that serves every tRPC request, and allocating an `Encoder`
|
|
5675
|
+
* buffer that doubles from 2 KiB up to the body's size on every write
|
|
5676
|
+
* (D441: ~27 % of hub-main's busy time in msgpack decode, ~6 % in encode,
|
|
5677
|
+
* and the churn that fed a 29.5 % GC share).
|
|
5678
|
+
*
|
|
5679
|
+
* With the payload appended as the LAST key of the map, the receiver's
|
|
5680
|
+
* ordinary msgpack decode yields it as a zero-copy VIEW of the frame
|
|
5681
|
+
* (`@msgpack/msgpack` decodes `bin` as `subarray`), the router reads only the
|
|
5682
|
+
* envelope, and forwarding is: encode a ~60-byte head, write it, write the
|
|
5683
|
+
* view. The wire stays plain msgpack — a peer that never heard of payloads
|
|
5684
|
+
* decodes `{ …frame, pl: <bytes> }` and can be told, at register, not to be
|
|
5685
|
+
* sent one.
|
|
5686
|
+
*
|
|
5687
|
+
* ## The one hand-written msgpack in this repository
|
|
5688
|
+
*
|
|
5689
|
+
* `@msgpack/msgpack` has no "encode a map, then append a key" API, so the
|
|
5690
|
+
* envelope is encoded normally and its `fixmap` count byte is bumped by one
|
|
5691
|
+
* before the key and the `bin 32` header are appended. That patch is only
|
|
5692
|
+
* valid for a `fixmap` (≤ 15 keys); a `Frame` has at most four, and anything
|
|
5693
|
+
* else is refused rather than silently corrupted.
|
|
5694
|
+
*/
|
|
5695
|
+
function encodeFrameHead(frame, payload) {
|
|
5696
|
+
const envelope = encode(frame, ENCODE_OPTIONS);
|
|
5697
|
+
const first = envelope[0];
|
|
5698
|
+
if (envelope.byteLength === 0 || first === void 0 || first < FIXMAP_BASE || first > 142) throw new Error(`encodeFrameHead: envelope must be a msgpack fixmap with at most ${FIXMAP_MAX_COUNT - 1} keys (got leading byte 0x${(first ?? 0).toString(16)})`);
|
|
5699
|
+
const bodyLength = envelope.byteLength + PAYLOAD_KEY_BYTES.byteLength + 5 + payload.byteLength;
|
|
5700
|
+
const head = Buffer.allocUnsafe(HEADER_BYTES + envelope.byteLength + PAYLOAD_KEY_BYTES.byteLength + 5);
|
|
5701
|
+
head.writeUInt32BE(bodyLength, 0);
|
|
5702
|
+
Buffer.from(envelope.buffer, envelope.byteOffset, envelope.byteLength).copy(head, HEADER_BYTES);
|
|
5703
|
+
head[HEADER_BYTES] = first + 1;
|
|
5704
|
+
let at = HEADER_BYTES + envelope.byteLength;
|
|
5705
|
+
PAYLOAD_KEY_BYTES.copy(head, at);
|
|
5706
|
+
at += PAYLOAD_KEY_BYTES.byteLength;
|
|
5707
|
+
head[at] = BIN32_MARKER;
|
|
5708
|
+
head.writeUInt32BE(payload.byteLength, at + 1);
|
|
5709
|
+
return head;
|
|
5710
|
+
}
|
|
5711
|
+
/**
|
|
5712
|
+
* Serialise a value into the bytes a payload carries. The SAME options as the
|
|
5713
|
+
* envelope codec, so `undefined` properties are omitted rather than turned
|
|
5714
|
+
* into `null` — a `z.void()` cap output must read back as `undefined` on the
|
|
5715
|
+
* far side exactly as it did when it travelled inline.
|
|
5716
|
+
*/
|
|
5717
|
+
function packPayload(value) {
|
|
5718
|
+
return encode(value, ENCODE_OPTIONS);
|
|
5719
|
+
}
|
|
5720
|
+
/** Inverse of {@link packPayload}. A `bin` inside decodes as a view of `bytes`. */
|
|
5721
|
+
function unpackPayload(bytes) {
|
|
5722
|
+
return decode(bytes);
|
|
5723
|
+
}
|
|
5066
5724
|
/** Bytes in the big-endian length prefix that opens every frame. */
|
|
5067
5725
|
var HEADER_BYTES = 4;
|
|
5068
5726
|
/**
|
|
@@ -5221,6 +5879,25 @@ function localEndpointPath(nodeId) {
|
|
|
5221
5879
|
if (process.platform === "win32") return `\\\\.\\pipe\\camstack-${safe}`;
|
|
5222
5880
|
return (0, node_path.join)((0, node_os.tmpdir)(), `camstack-${safe}.sock`);
|
|
5223
5881
|
}
|
|
5882
|
+
//#endregion
|
|
5883
|
+
//#region src/kernel/transport/local-transport.ts
|
|
5884
|
+
/**
|
|
5885
|
+
* An answer that carries an opaque payload beside its body.
|
|
5886
|
+
*
|
|
5887
|
+
* A CLASS rather than a shape so the channel can tell it from a provider's
|
|
5888
|
+
* ordinary `{ body, payload }` return value by identity, never by duck-typing:
|
|
5889
|
+
* a cap method is free to return an object with those two keys and it must
|
|
5890
|
+
* travel inline like any other. `request()` resolves with one of these only
|
|
5891
|
+
* when the peer's `res` frame carried a payload.
|
|
5892
|
+
*/
|
|
5893
|
+
var PayloadReply = class {
|
|
5894
|
+
body;
|
|
5895
|
+
payload;
|
|
5896
|
+
constructor(body, payload) {
|
|
5897
|
+
this.body = body;
|
|
5898
|
+
this.payload = payload;
|
|
5899
|
+
}
|
|
5900
|
+
};
|
|
5224
5901
|
var SocketChannel = class {
|
|
5225
5902
|
socket;
|
|
5226
5903
|
nextId = 1;
|
|
@@ -5277,7 +5954,7 @@ var SocketChannel = class {
|
|
|
5277
5954
|
k: "req",
|
|
5278
5955
|
id,
|
|
5279
5956
|
body
|
|
5280
|
-
});
|
|
5957
|
+
}, opts?.payload);
|
|
5281
5958
|
});
|
|
5282
5959
|
}
|
|
5283
5960
|
emit(body) {
|
|
@@ -5317,11 +5994,38 @@ var SocketChannel = class {
|
|
|
5317
5994
|
rx: sampleSocketDirection(this.rxCounters)
|
|
5318
5995
|
};
|
|
5319
5996
|
}
|
|
5320
|
-
|
|
5997
|
+
/**
|
|
5998
|
+
* Bytes handed to the socket that the kernel has not taken yet — the
|
|
5999
|
+
* `Writable` buffer plus the write in flight. Read LIVE, never cached: this
|
|
6000
|
+
* is the number that says whether the peer is draining what we write, and
|
|
6001
|
+
* it is read on every heartbeat probe (D446). An unref'd socket keeps it
|
|
6002
|
+
* exactly like a ref'd one, which is why it is read here and not summed
|
|
6003
|
+
* over `_getActiveHandles()`.
|
|
6004
|
+
*/
|
|
6005
|
+
queuedBytes() {
|
|
6006
|
+
return this.socket.writableLength;
|
|
6007
|
+
}
|
|
6008
|
+
/**
|
|
6009
|
+
* Write one frame. With a payload the head and the tail are two `write`
|
|
6010
|
+
* calls under `cork()` — the kernel sees one `writev`, and the payload bytes
|
|
6011
|
+
* are never copied into the head. A forwarded payload is a view into the
|
|
6012
|
+
* frame it arrived on, and that frame's buffer is never mutated, so handing
|
|
6013
|
+
* the view to the socket is safe.
|
|
6014
|
+
*/
|
|
6015
|
+
send(frame, payload) {
|
|
5321
6016
|
if (this.closed) return;
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
6017
|
+
if (payload === void 0) {
|
|
6018
|
+
const encoded = encodeFrame(frame);
|
|
6019
|
+
recordSocketFrame(this.txCounters, frame.k, encoded.byteLength);
|
|
6020
|
+
this.socket.write(encoded);
|
|
6021
|
+
return;
|
|
6022
|
+
}
|
|
6023
|
+
const head = encodeFrameHead(frame, payload);
|
|
6024
|
+
recordSocketFrame(this.txCounters, frame.k, head.byteLength + payload.byteLength);
|
|
6025
|
+
this.socket.cork();
|
|
6026
|
+
this.socket.write(head);
|
|
6027
|
+
this.socket.write(payload);
|
|
6028
|
+
this.socket.uncork();
|
|
5325
6029
|
}
|
|
5326
6030
|
onData(chunk) {
|
|
5327
6031
|
const frames = this.decoder.push(chunk);
|
|
@@ -5341,8 +6045,14 @@ var SocketChannel = class {
|
|
|
5341
6045
|
recordSocketFrame(this.rxCounters, frame.k, wireBytes);
|
|
5342
6046
|
if (frame.k === "req") {
|
|
5343
6047
|
try {
|
|
5344
|
-
const result = await this.requestHandler(frame.body);
|
|
5345
|
-
this.send({
|
|
6048
|
+
const result = await this.requestHandler(frame.body, frame.pl);
|
|
6049
|
+
if (result instanceof PayloadReply) this.send({
|
|
6050
|
+
k: "res",
|
|
6051
|
+
id: frame.id,
|
|
6052
|
+
ok: true,
|
|
6053
|
+
body: result.body
|
|
6054
|
+
}, result.payload);
|
|
6055
|
+
else this.send({
|
|
5346
6056
|
k: "res",
|
|
5347
6057
|
id: frame.id,
|
|
5348
6058
|
ok: true,
|
|
@@ -5363,8 +6073,9 @@ var SocketChannel = class {
|
|
|
5363
6073
|
if (!p) return;
|
|
5364
6074
|
this.pending.delete(frame.id);
|
|
5365
6075
|
if (p.timer !== null) clearTimeout(p.timer);
|
|
5366
|
-
if (frame.ok) p.
|
|
5367
|
-
else p.
|
|
6076
|
+
if (!frame.ok) p.reject(new Error(frame.err));
|
|
6077
|
+
else if (frame.pl !== void 0) p.resolve(new PayloadReply(frame.body, frame.pl));
|
|
6078
|
+
else p.resolve(frame.body);
|
|
5368
6079
|
return;
|
|
5369
6080
|
}
|
|
5370
6081
|
this.eventHandler(frame.body);
|
|
@@ -5915,527 +6626,99 @@ function deliverShared(state, event) {
|
|
|
5915
6626
|
}
|
|
5916
6627
|
}
|
|
5917
6628
|
//#endregion
|
|
5918
|
-
//#region src/kernel/
|
|
5919
|
-
/**
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
}
|
|
5928
|
-
/**
|
|
5929
|
-
* Call a Moleculer action; on a service-discovery error, wait for the
|
|
5930
|
-
* named service to be discovered and retry the call exactly once.
|
|
5931
|
-
*/
|
|
5932
|
-
async function callWithServiceDiscovery(broker, serviceName, action, params, opts, discoveryTimeoutMs = DEFAULT_DISCOVERY_TIMEOUT_MS$1) {
|
|
5933
|
-
try {
|
|
5934
|
-
return await broker.call(action, params, opts);
|
|
5935
|
-
} catch (err) {
|
|
5936
|
-
if (!isDiscoveryError(err)) throw err;
|
|
5937
|
-
await broker.waitForServices([serviceName], discoveryTimeoutMs);
|
|
5938
|
-
return await broker.call(action, params, opts);
|
|
5939
|
-
}
|
|
6629
|
+
//#region src/kernel/transport/cap-routing-hints.ts
|
|
6630
|
+
/** Lift {@link CapRoutingHints} from a call's args. Never throws. */
|
|
6631
|
+
function liftRoutingHints(args) {
|
|
6632
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) return {};
|
|
6633
|
+
const nodeId = Reflect.get(args, "nodeId");
|
|
6634
|
+
const addonId = Reflect.get(args, "addonId");
|
|
6635
|
+
return {
|
|
6636
|
+
...typeof nodeId === "string" && nodeId.length > 0 ? { nodeId } : {},
|
|
6637
|
+
...typeof addonId === "string" && addonId.length > 0 ? { addonId } : {}
|
|
6638
|
+
};
|
|
5940
6639
|
}
|
|
5941
6640
|
//#endregion
|
|
5942
|
-
//#region src/kernel/transport/
|
|
5943
|
-
function
|
|
5944
|
-
const
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
const rejectedClause = rejectedStr.length > 0 ? ` (rejected: ${rejectedStr})` : "";
|
|
5948
|
-
return `${call} not routable${target}: ${detail.reason}${rejectedClause}`;
|
|
6641
|
+
//#region src/kernel/transport/local-child-registry.ts
|
|
6642
|
+
function readFanoutMode() {
|
|
6643
|
+
const raw = process.env.CAMSTACK_UDS_EVENT_FANOUT;
|
|
6644
|
+
if (raw === "shadow" || raw === "broadcast") return raw;
|
|
6645
|
+
return "filter";
|
|
5949
6646
|
}
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
6647
|
+
/**
|
|
6648
|
+
* Sentinel prefix used in the no-route error thrown when `cap-call-out` has no
|
|
6649
|
+
* local sibling and no `onUnownedCall` fallback. `ipcParentLink` detects this
|
|
6650
|
+
* prefix to distinguish routing failures (safe to retry via broker) from real
|
|
6651
|
+
* provider errors (must NOT retry to avoid double-executing side effects).
|
|
6652
|
+
*/
|
|
6653
|
+
var UDS_NO_ROUTE_PREFIX = "UDS_NO_ROUTE";
|
|
6654
|
+
/**
|
|
6655
|
+
* Parent-side authority for local addon-runners reachable over a
|
|
6656
|
+
* `LocalTransportServer` (UDS). Children register their cap manifest on
|
|
6657
|
+
* connect; the registry routes `(capName, deviceId?)` cap calls to the
|
|
6658
|
+
* owning child over the channel and drops a child's caps on disconnect.
|
|
6659
|
+
*/
|
|
6660
|
+
var LocalChildRegistry = class {
|
|
6661
|
+
children = /* @__PURE__ */ new Map();
|
|
6662
|
+
registeredHandler = () => {};
|
|
6663
|
+
goneHandler = () => {};
|
|
6664
|
+
/** Source of {@link RegisteredChild.incarnation}; one bump per accepted connection. */
|
|
6665
|
+
nextIncarnation = 1;
|
|
6666
|
+
eventHandler = null;
|
|
6667
|
+
logHandler = null;
|
|
6668
|
+
readinessHandler = null;
|
|
6669
|
+
server;
|
|
6670
|
+
onUnownedCall;
|
|
6671
|
+
logger;
|
|
6672
|
+
getActiveSingletonAddonId;
|
|
6673
|
+
resolveChildIdForAddon;
|
|
6674
|
+
/** This node's own Moleculer nodeId (see {@link LocalChildRegistryOptions.ownNodeId}). */
|
|
6675
|
+
ownNodeId;
|
|
6676
|
+
/** See {@link LocalChildRegistryOptions.isAggregatedCollectionMethod}. */
|
|
6677
|
+
isAggregatedCollectionMethod;
|
|
6678
|
+
isAddonPinnedCall;
|
|
6679
|
+
/** See {@link ForwardCensus}. Mutable counters on the hot path, by design. */
|
|
6680
|
+
forwardedRaw = 0;
|
|
6681
|
+
forwardedDecoded = 0;
|
|
6682
|
+
/** See {@link LocalChildRegistryOptions.capTimeoutMs}. */
|
|
6683
|
+
capTimeoutMs;
|
|
6684
|
+
/** See {@link LocalChildRegistryOptions.capUsageObserver}. */
|
|
6685
|
+
capUsageObserver;
|
|
6686
|
+
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
6687
|
+
egressRoutedCaps = /* @__PURE__ */ new Set();
|
|
6688
|
+
/** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
|
|
6689
|
+
fanoutMode = readFanoutMode();
|
|
6690
|
+
/** Per-child event fan-out counters (sent / suppressed). */
|
|
6691
|
+
childEventStats = /* @__PURE__ */ new Map();
|
|
6692
|
+
/** Last pattern-set string logged per child, to dedup the INFO line. */
|
|
6693
|
+
loggedPatternSet = /* @__PURE__ */ new Map();
|
|
5954
6694
|
/**
|
|
5955
|
-
*
|
|
5956
|
-
*
|
|
5957
|
-
*
|
|
6695
|
+
* Accepts either a plain positional `server` argument (backward-compatible)
|
|
6696
|
+
* or a full `LocalChildRegistryOptions` object.
|
|
6697
|
+
*
|
|
6698
|
+
* Positional overloads (existing call sites are unchanged):
|
|
6699
|
+
* new LocalChildRegistry(server)
|
|
6700
|
+
* new LocalChildRegistry(server, onUnownedCall)
|
|
6701
|
+
*
|
|
6702
|
+
* Options object (new call sites that pass a logger):
|
|
6703
|
+
* new LocalChildRegistry({ server, onUnownedCall, logger })
|
|
5958
6704
|
*/
|
|
5959
|
-
constructor(
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
*
|
|
5977
|
-
* Singleton path (no nodeId):
|
|
5978
|
-
* 1. hub-in-process (hub provides in-process)
|
|
5979
|
-
* 2. hub-local-uds (a hub-local UDS child provides it)
|
|
5980
|
-
* 3. remote-moleculer (any online, non-hub node that knows it)
|
|
5981
|
-
* 4. → CapRouteError{reason:'no-provider', rejected: all considered routes}
|
|
5982
|
-
*
|
|
5983
|
-
* PURE: no side effects, no async, no broker/registry imports.
|
|
5984
|
-
*/
|
|
5985
|
-
function classifyCapRoute(capName, opts, snapshot) {
|
|
5986
|
-
const rejected = [];
|
|
5987
|
-
if (opts.nodeId !== void 0) return classifyExplicitNode(capName, opts.nodeId, opts.deviceId, snapshot, rejected);
|
|
5988
|
-
return classifySingleton(capName, opts.deviceId, snapshot, rejected);
|
|
5989
|
-
}
|
|
5990
|
-
function classifyExplicitNode(capName, nodeId, deviceId, snap, rejected) {
|
|
5991
|
-
if (nodeId === snap.hubNodeId) {
|
|
5992
|
-
const residentProvides = snap.hubResidentProvides;
|
|
5993
|
-
const providesOnHub = residentProvides ?? snap.hubInProcessProvides;
|
|
5994
|
-
const getRef = residentProvides !== void 0 ? snap.getHubResidentProviderRef : snap.getInProcessProviderRef;
|
|
5995
|
-
if (providesOnHub(capName)) {
|
|
5996
|
-
const ref = getRef?.(capName) ?? null;
|
|
5997
|
-
if (ref !== null) return {
|
|
5998
|
-
kind: "hub-in-process",
|
|
5999
|
-
capName,
|
|
6000
|
-
ref
|
|
6001
|
-
};
|
|
6002
|
-
rejected.push({
|
|
6003
|
-
kind: "hub-in-process",
|
|
6004
|
-
why: "provider ref not available in snapshot"
|
|
6005
|
-
});
|
|
6006
|
-
throw new CapRouteError(capName, void 0, {
|
|
6007
|
-
reason: "no-provider",
|
|
6008
|
-
nodeId,
|
|
6009
|
-
rejected
|
|
6010
|
-
});
|
|
6011
|
-
}
|
|
6012
|
-
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
6013
|
-
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
6014
|
-
if (childId !== null) return {
|
|
6015
|
-
kind: "hub-local-uds",
|
|
6016
|
-
capName,
|
|
6017
|
-
childId
|
|
6018
|
-
};
|
|
6019
|
-
rejected.push({
|
|
6020
|
-
kind: "hub-local-uds",
|
|
6021
|
-
why: "child id not resolvable from snapshot"
|
|
6022
|
-
});
|
|
6023
|
-
throw new CapRouteError(capName, void 0, {
|
|
6024
|
-
reason: "no-provider",
|
|
6025
|
-
nodeId,
|
|
6026
|
-
rejected
|
|
6027
|
-
});
|
|
6028
|
-
}
|
|
6029
|
-
rejected.push({
|
|
6030
|
-
kind: "hub-in-process",
|
|
6031
|
-
why: "hub does not provide this cap in-process"
|
|
6032
|
-
});
|
|
6033
|
-
rejected.push({
|
|
6034
|
-
kind: "hub-local-uds",
|
|
6035
|
-
why: "no hub-local child provides this cap"
|
|
6036
|
-
});
|
|
6037
|
-
throw new CapRouteError(capName, void 0, {
|
|
6038
|
-
reason: "no-provider",
|
|
6039
|
-
nodeId,
|
|
6040
|
-
rejected
|
|
6041
|
-
});
|
|
6042
|
-
}
|
|
6043
|
-
if (!snap.nodeOnline(nodeId)) {
|
|
6044
|
-
rejected.push({
|
|
6045
|
-
kind: "remote-moleculer",
|
|
6046
|
-
why: `node ${nodeId} is offline`
|
|
6047
|
-
});
|
|
6048
|
-
throw new CapRouteError(capName, void 0, {
|
|
6049
|
-
reason: "node-offline",
|
|
6050
|
-
nodeId,
|
|
6051
|
-
rejected
|
|
6052
|
-
});
|
|
6053
|
-
}
|
|
6054
|
-
if (snap.nodeIsAgent(nodeId)) {
|
|
6055
|
-
if (snap.nodeKnowsCap(nodeId, capName)) return {
|
|
6056
|
-
kind: "agent-child-forward",
|
|
6057
|
-
capName,
|
|
6058
|
-
agentNodeId: nodeId,
|
|
6059
|
-
childId: snap.getAgentChildId?.(nodeId, capName) ?? void 0
|
|
6060
|
-
};
|
|
6061
|
-
rejected.push({
|
|
6062
|
-
kind: "agent-child-forward",
|
|
6063
|
-
why: `agent ${nodeId} does not know cap ${capName}`
|
|
6064
|
-
});
|
|
6065
|
-
throw new CapRouteError(capName, void 0, {
|
|
6066
|
-
reason: "no-provider",
|
|
6067
|
-
nodeId,
|
|
6068
|
-
rejected
|
|
6069
|
-
});
|
|
6070
|
-
}
|
|
6071
|
-
return {
|
|
6072
|
-
kind: "remote-moleculer",
|
|
6073
|
-
capName,
|
|
6074
|
-
nodeId
|
|
6075
|
-
};
|
|
6076
|
-
}
|
|
6077
|
-
function classifySingleton(capName, deviceId, snap, rejected) {
|
|
6078
|
-
if (snap.hubInProcessProvides(capName)) {
|
|
6079
|
-
const ref = snap.getInProcessProviderRef?.(capName) ?? null;
|
|
6080
|
-
if (ref !== null) return {
|
|
6081
|
-
kind: "hub-in-process",
|
|
6082
|
-
capName,
|
|
6083
|
-
ref
|
|
6084
|
-
};
|
|
6085
|
-
rejected.push({
|
|
6086
|
-
kind: "hub-in-process",
|
|
6087
|
-
why: "provider ref not available in snapshot"
|
|
6088
|
-
});
|
|
6089
|
-
} else rejected.push({
|
|
6090
|
-
kind: "hub-in-process",
|
|
6091
|
-
why: "hub does not provide this cap in-process"
|
|
6092
|
-
});
|
|
6093
|
-
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
6094
|
-
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
6095
|
-
if (childId !== null) return {
|
|
6096
|
-
kind: "hub-local-uds",
|
|
6097
|
-
capName,
|
|
6098
|
-
childId
|
|
6099
|
-
};
|
|
6100
|
-
rejected.push({
|
|
6101
|
-
kind: "hub-local-uds",
|
|
6102
|
-
why: "child id not resolvable from snapshot"
|
|
6103
|
-
});
|
|
6104
|
-
} else rejected.push({
|
|
6105
|
-
kind: "hub-local-uds",
|
|
6106
|
-
why: "no hub-local child provides this cap"
|
|
6107
|
-
});
|
|
6108
|
-
const knownNodes = snap.listKnownNodeIds?.() ?? [];
|
|
6109
|
-
for (const nodeId of knownNodes) if (nodeId !== snap.hubNodeId && snap.nodeOnline(nodeId) && snap.nodeKnowsCap(nodeId, capName)) return {
|
|
6110
|
-
kind: "remote-moleculer",
|
|
6111
|
-
capName,
|
|
6112
|
-
nodeId
|
|
6113
|
-
};
|
|
6114
|
-
rejected.push({
|
|
6115
|
-
kind: "remote-moleculer",
|
|
6116
|
-
why: "no online remote node knows this cap"
|
|
6117
|
-
});
|
|
6118
|
-
throw new CapRouteError(capName, void 0, {
|
|
6119
|
-
reason: "no-provider",
|
|
6120
|
-
rejected
|
|
6121
|
-
});
|
|
6122
|
-
}
|
|
6123
|
-
//#endregion
|
|
6124
|
-
//#region src/kernel/transport/cap-route-resolver.ts
|
|
6125
|
-
/** The Moleculer service name that Task 6's agent registers. */
|
|
6126
|
-
var AGENT_CAP_FWD_SERVICE = "$agent-cap-fwd";
|
|
6127
|
-
/** The Moleculer action (service.action) for agent cap forwarding. */
|
|
6128
|
-
var AGENT_CAP_FWD_ACTION = `${AGENT_CAP_FWD_SERVICE}.forward`;
|
|
6129
|
-
/** Default timeout for remote Moleculer cap calls (ms). */
|
|
6130
|
-
var REMOTE_CALL_TIMEOUT_MS = 6e4;
|
|
6131
|
-
function extractDeviceId(args) {
|
|
6132
|
-
if (args === null || typeof args !== "object") return void 0;
|
|
6133
|
-
const raw = Reflect.get(args, "deviceId");
|
|
6134
|
-
return typeof raw === "number" ? raw : void 0;
|
|
6135
|
-
}
|
|
6136
|
-
/**
|
|
6137
|
-
* Extract an inline `nodeId` string from a cap call's args — the per-call node
|
|
6138
|
-
* pin a forked addon expresses by carrying `nodeId` in the input (the SAME way
|
|
6139
|
-
* device-scoped caps carry `deviceId`, and the way the generated cap-router on
|
|
6140
|
-
* the hub already honours an inline pin). Mirrors {@link extractDeviceId}.
|
|
6141
|
-
*
|
|
6142
|
-
* This is the routing source for hub→remote-node EXECUTION pinning (e.g. the
|
|
6143
|
-
* benchmark addon running a synthetic/decoder workload ON a chosen agent). The
|
|
6144
|
-
* out-of-band `nodePin(op.context)` path does NOT survive the forked addon →
|
|
6145
|
-
* hub UDS link chain, so `onUnownedCall` reads the inline pin instead. Provider
|
|
6146
|
-
* methods that don't declare `nodeId` simply ignore the extra field (they
|
|
6147
|
-
* destructure the fields they need); methods that DO declare it (e.g.
|
|
6148
|
-
* `getEngineProvisioning({nodeId})`) still receive it unchanged.
|
|
6149
|
-
*/
|
|
6150
|
-
function extractNodeId(args) {
|
|
6151
|
-
if (args === null || typeof args !== "object") return void 0;
|
|
6152
|
-
const raw = Reflect.get(args, "nodeId");
|
|
6153
|
-
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
6154
|
-
}
|
|
6155
|
-
/**
|
|
6156
|
-
* Merge `callerAddonId` into `args` as an extra field — the SAME "provider
|
|
6157
|
-
* destructures what it needs and ignores the rest" convention
|
|
6158
|
-
* {@link extractNodeId} already relies on for the inline `nodeId` pin.
|
|
6159
|
-
* `undefined` (the overwhelmingly common case: no caller hint, or the census
|
|
6160
|
-
* consuming it is disarmed) returns `args` unchanged — no allocation. A
|
|
6161
|
-
* non-object `args` (or `null`/array) is returned unchanged too: there is
|
|
6162
|
-
* nowhere to attach an extra key without changing the payload's shape, and a
|
|
6163
|
-
* provider taking a bare value never reads `callerAddonId` off it anyway.
|
|
6164
|
-
*/
|
|
6165
|
-
function withCallerAddonId(args, callerAddonId) {
|
|
6166
|
-
if (callerAddonId === void 0) return args;
|
|
6167
|
-
if (args === null || typeof args !== "object" || Array.isArray(args)) return args;
|
|
6168
|
-
return {
|
|
6169
|
-
...args,
|
|
6170
|
-
callerAddonId
|
|
6171
|
-
};
|
|
6172
|
-
}
|
|
6173
|
-
var CapRouteResolver = class {
|
|
6174
|
-
hubNodeId;
|
|
6175
|
-
broker;
|
|
6176
|
-
hubLocalRegistry;
|
|
6177
|
-
nodeAuthority;
|
|
6178
|
-
inProcessProviders;
|
|
6179
|
-
hubResidentProviders;
|
|
6180
|
-
capTimeoutMs;
|
|
6181
|
-
snapshot;
|
|
6182
|
-
constructor(deps) {
|
|
6183
|
-
this.hubNodeId = deps.hubNodeId;
|
|
6184
|
-
this.broker = deps.broker;
|
|
6185
|
-
this.hubLocalRegistry = deps.hubLocalRegistry;
|
|
6186
|
-
this.nodeAuthority = deps.nodeAuthority;
|
|
6187
|
-
this.inProcessProviders = deps.inProcessProviders;
|
|
6188
|
-
this.hubResidentProviders = deps.hubResidentProviders;
|
|
6189
|
-
this.capTimeoutMs = deps.capTimeoutMs;
|
|
6190
|
-
this.snapshot = this.buildSnapshot();
|
|
6191
|
-
}
|
|
6192
|
-
/** Per-call RPC timeout: the cap method's declared override, else the default. */
|
|
6193
|
-
callTimeout(capName, method) {
|
|
6194
|
-
return this.capTimeoutMs?.(capName, method) ?? REMOTE_CALL_TIMEOUT_MS;
|
|
6195
|
-
}
|
|
6196
|
-
resolveCapRoute(capName, opts) {
|
|
6197
|
-
return classifyCapRoute(capName, opts, this.snapshot);
|
|
6198
|
-
}
|
|
6199
|
-
/**
|
|
6200
|
-
* Resolve the hub-local-uds route for a cap owned by a forked hub-local
|
|
6201
|
-
* child, IGNORING any in-hub provider registered for the same cap name.
|
|
6202
|
-
*
|
|
6203
|
-
* `resolveCapRoute` gives Priority 1 to `hub-in-process`: when an in-hub
|
|
6204
|
-
* provider (e.g. a wrapper) is registered for the cap, the route always
|
|
6205
|
-
* classifies as `hub-in-process` and the hub-local-uds NATIVE child is never
|
|
6206
|
-
* reached. That is correct for the generic dispatch path (the wrapper is the
|
|
6207
|
-
* active provider), but WRONG for the native-cap fallback
|
|
6208
|
-
* (`setNativeFallback`), whose contract is to reach the NATIVE provider in
|
|
6209
|
-
* the forked vendor child so a wrapper can delegate to it. A wrapper cap
|
|
6210
|
-
* with a forked native (today: `snapshot`) otherwise resolves to the wrapper
|
|
6211
|
-
* itself, the native is never invoked, and the wrapper silently falls
|
|
6212
|
-
* through to its secondary strategy.
|
|
6213
|
-
*
|
|
6214
|
-
* This method consults ONLY the hub-local-child authority
|
|
6215
|
-
* (`hubLocalChildProvides` + `getHubLocalChildId`, both deviceId-aware), so
|
|
6216
|
-
* it returns the native child route regardless of any in-process shadow.
|
|
6217
|
-
* Returns null when no hub-local child owns `(capName, deviceId)` — the
|
|
6218
|
-
* caller then falls through to its remote-resolution branch.
|
|
6219
|
-
*/
|
|
6220
|
-
resolveHubLocalUdsRoute(capName, deviceId) {
|
|
6221
|
-
if (!this.snapshot.hubLocalChildProvides(capName, deviceId)) return null;
|
|
6222
|
-
const childId = this.snapshot.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
6223
|
-
if (childId === null) return null;
|
|
6224
|
-
return {
|
|
6225
|
-
kind: "hub-local-uds",
|
|
6226
|
-
capName,
|
|
6227
|
-
childId
|
|
6228
|
-
};
|
|
6229
|
-
}
|
|
6230
|
-
/**
|
|
6231
|
-
* @param addonId Optional out-of-band PROVIDER-selection hint (Task 7.5a):
|
|
6232
|
-
* the addonId of the provider the CALLER already resolved (e.g.
|
|
6233
|
-
* `buildCapCallFn`'s `deps.addonId` for a registered grouped-runner
|
|
6234
|
-
* provider). Only meaningful for `agent-child-forward` — a `remote-moleculer`
|
|
6235
|
-
* route already encodes the provider in its action name
|
|
6236
|
-
* (`${addonId}.${cap}.${method}`, via `NodeCapAuthority.getAddonId`), and a
|
|
6237
|
-
* `hub-local-uds` caller that already knows the addonId (`buildCapCallFn`)
|
|
6238
|
-
* talks to `LocalChildRegistry.callCapOnChild` directly rather than through
|
|
6239
|
-
* `dispatch`.
|
|
6240
|
-
* @param callerAddonId Optional out-of-band CALLER-identity hint — the
|
|
6241
|
-
* addon that ORIGINATED the call, distinct from `addonId` above (which
|
|
6242
|
-
* names the resolved PROVIDER, not the caller). Set by
|
|
6243
|
-
* `onUnownedCall` from `CapCallInput.callerAddonId`
|
|
6244
|
-
* (`LocalChildRegistry` stamped it off the registered `childId`). Only
|
|
6245
|
-
* the `hub-in-process` branch consumes it — merged into `args` so a
|
|
6246
|
-
* diagnostic tool deep in that provider (e.g. device-manager's
|
|
6247
|
-
* fleet-read census) can read it without every provider method changing
|
|
6248
|
-
* signature, the same "extra field the provider destructures or
|
|
6249
|
-
* ignores" convention `nodeId` already uses on this path.
|
|
6250
|
-
*/
|
|
6251
|
-
async dispatch(route, method, args, addonId, callerAddonId) {
|
|
6252
|
-
try {
|
|
6253
|
-
return await this.dispatchInner(route, method, args, addonId, callerAddonId);
|
|
6254
|
-
} catch (err) {
|
|
6255
|
-
if (err instanceof CapRouteError) throw err;
|
|
6256
|
-
const nodeId = this.routeNodeId(route);
|
|
6257
|
-
const cause = err instanceof Error ? err : new Error(String(err));
|
|
6258
|
-
throw new CapRouteError(route.capName, method, {
|
|
6259
|
-
reason: "transport-failed",
|
|
6260
|
-
nodeId,
|
|
6261
|
-
rejected: [{
|
|
6262
|
-
kind: route.kind,
|
|
6263
|
-
why: cause.message
|
|
6264
|
-
}]
|
|
6265
|
-
}, cause);
|
|
6266
|
-
}
|
|
6267
|
-
}
|
|
6268
|
-
/**
|
|
6269
|
-
* Inner dispatch: may throw CapRouteError (validation failures) or arbitrary
|
|
6270
|
-
* transport errors. The outer `dispatch` wraps non-CapRouteErrors.
|
|
6271
|
-
*/
|
|
6272
|
-
async dispatchInner(route, method, args, addonId, callerAddonId) {
|
|
6273
|
-
switch (route.kind) {
|
|
6274
|
-
case "hub-in-process": return route.ref.invoke(method, withCallerAddonId(args, callerAddonId));
|
|
6275
|
-
case "hub-local-uds": {
|
|
6276
|
-
const registry = this.hubLocalRegistry;
|
|
6277
|
-
if (registry === null) throw new CapRouteError(route.capName, method, {
|
|
6278
|
-
reason: "no-provider",
|
|
6279
|
-
rejected: [{
|
|
6280
|
-
kind: "hub-local-uds",
|
|
6281
|
-
why: "UDS registry not available"
|
|
6282
|
-
}]
|
|
6283
|
-
});
|
|
6284
|
-
const deviceId = extractDeviceId(args);
|
|
6285
|
-
const input = {
|
|
6286
|
-
capName: route.capName,
|
|
6287
|
-
method,
|
|
6288
|
-
args,
|
|
6289
|
-
...deviceId !== void 0 ? { deviceId } : {}
|
|
6290
|
-
};
|
|
6291
|
-
return registry.callCapOnChild(route.childId, input);
|
|
6292
|
-
}
|
|
6293
|
-
case "remote-moleculer": {
|
|
6294
|
-
const addonId = this.nodeAuthority.getAddonId(route.nodeId, route.capName);
|
|
6295
|
-
if (addonId === null) throw new CapRouteError(route.capName, method, {
|
|
6296
|
-
reason: "no-provider",
|
|
6297
|
-
nodeId: route.nodeId,
|
|
6298
|
-
rejected: [{
|
|
6299
|
-
kind: "remote-moleculer",
|
|
6300
|
-
why: `no addonId known for ${route.nodeId}/${route.capName}`
|
|
6301
|
-
}]
|
|
6302
|
-
});
|
|
6303
|
-
const deviceId = extractDeviceId(args);
|
|
6304
|
-
const isNative = this.nodeAuthority.isNativeCap(route.nodeId, route.capName, deviceId);
|
|
6305
|
-
const action = capActionName(addonId, route.capName, method, isNative);
|
|
6306
|
-
return callWithServiceDiscovery(this.broker, addonId, action, args, {
|
|
6307
|
-
nodeID: route.nodeId,
|
|
6308
|
-
timeout: this.callTimeout(route.capName, method)
|
|
6309
|
-
});
|
|
6310
|
-
}
|
|
6311
|
-
case "agent-child-forward": {
|
|
6312
|
-
const deviceId = extractDeviceId(args);
|
|
6313
|
-
const params = {
|
|
6314
|
-
capName: route.capName,
|
|
6315
|
-
method,
|
|
6316
|
-
args,
|
|
6317
|
-
...route.childId !== void 0 ? { childId: route.childId } : {},
|
|
6318
|
-
...deviceId !== void 0 ? { deviceId } : {},
|
|
6319
|
-
...addonId !== void 0 ? { addonId } : {}
|
|
6320
|
-
};
|
|
6321
|
-
return callWithServiceDiscovery(this.broker, AGENT_CAP_FWD_SERVICE, AGENT_CAP_FWD_ACTION, params, {
|
|
6322
|
-
nodeID: route.agentNodeId,
|
|
6323
|
-
timeout: this.callTimeout(route.capName, method)
|
|
6324
|
-
});
|
|
6325
|
-
}
|
|
6326
|
-
}
|
|
6327
|
-
}
|
|
6328
|
-
buildSnapshot() {
|
|
6329
|
-
const hubLocalRegistry = this.hubLocalRegistry;
|
|
6330
|
-
const nodeAuthority = this.nodeAuthority;
|
|
6331
|
-
const inProcessProviders = this.inProcessProviders;
|
|
6332
|
-
const hubResidentProviders = this.hubResidentProviders;
|
|
6333
|
-
return {
|
|
6334
|
-
hubNodeId: this.hubNodeId,
|
|
6335
|
-
hubInProcessProvides: (cap) => inProcessProviders(cap) !== null,
|
|
6336
|
-
...hubResidentProviders !== void 0 ? {
|
|
6337
|
-
hubResidentProvides: (cap) => hubResidentProviders(cap) !== null,
|
|
6338
|
-
getHubResidentProviderRef: (cap) => hubResidentProviders(cap)
|
|
6339
|
-
} : {},
|
|
6340
|
-
hubLocalChildProvides: (cap, deviceId) => hubLocalRegistry !== null && hubLocalRegistry.resolveChildId(cap, deviceId) !== null,
|
|
6341
|
-
nodeKnowsCap: (nodeId, cap) => nodeAuthority.nodeKnowsCap(nodeId, cap),
|
|
6342
|
-
nodeIsAgent: (nodeId) => nodeAuthority.nodeIsAgent(nodeId),
|
|
6343
|
-
nodeOnline: (nodeId) => nodeAuthority.nodeOnline(nodeId),
|
|
6344
|
-
listKnownNodeIds: () => nodeAuthority.listNodeIds(),
|
|
6345
|
-
getInProcessProviderRef: (cap) => inProcessProviders(cap),
|
|
6346
|
-
getHubLocalChildId: (cap, deviceId) => hubLocalRegistry !== null ? hubLocalRegistry.resolveChildId(cap, deviceId) : null,
|
|
6347
|
-
getAgentChildId: (agentNodeId, cap) => nodeAuthority.getAgentChildId(agentNodeId, cap)
|
|
6348
|
-
};
|
|
6349
|
-
}
|
|
6350
|
-
/** Extract a nodeId string from a route for error reporting, or undefined. */
|
|
6351
|
-
routeNodeId(route) {
|
|
6352
|
-
switch (route.kind) {
|
|
6353
|
-
case "hub-in-process": return this.hubNodeId;
|
|
6354
|
-
case "hub-local-uds": return `${this.hubNodeId}/${route.childId}`;
|
|
6355
|
-
case "remote-moleculer": return route.nodeId;
|
|
6356
|
-
case "agent-child-forward": return route.agentNodeId;
|
|
6357
|
-
}
|
|
6358
|
-
}
|
|
6359
|
-
};
|
|
6360
|
-
//#endregion
|
|
6361
|
-
//#region src/kernel/transport/local-child-registry.ts
|
|
6362
|
-
function readFanoutMode() {
|
|
6363
|
-
const raw = process.env.CAMSTACK_UDS_EVENT_FANOUT;
|
|
6364
|
-
if (raw === "shadow" || raw === "broadcast") return raw;
|
|
6365
|
-
return "filter";
|
|
6366
|
-
}
|
|
6367
|
-
/**
|
|
6368
|
-
* Sentinel prefix used in the no-route error thrown when `cap-call-out` has no
|
|
6369
|
-
* local sibling and no `onUnownedCall` fallback. `ipcParentLink` detects this
|
|
6370
|
-
* prefix to distinguish routing failures (safe to retry via broker) from real
|
|
6371
|
-
* provider errors (must NOT retry to avoid double-executing side effects).
|
|
6372
|
-
*/
|
|
6373
|
-
var UDS_NO_ROUTE_PREFIX = "UDS_NO_ROUTE";
|
|
6374
|
-
/**
|
|
6375
|
-
* Parent-side authority for local addon-runners reachable over a
|
|
6376
|
-
* `LocalTransportServer` (UDS). Children register their cap manifest on
|
|
6377
|
-
* connect; the registry routes `(capName, deviceId?)` cap calls to the
|
|
6378
|
-
* owning child over the channel and drops a child's caps on disconnect.
|
|
6379
|
-
*/
|
|
6380
|
-
var LocalChildRegistry = class {
|
|
6381
|
-
children = /* @__PURE__ */ new Map();
|
|
6382
|
-
registeredHandler = () => {};
|
|
6383
|
-
goneHandler = () => {};
|
|
6384
|
-
/** Source of {@link RegisteredChild.incarnation}; one bump per accepted connection. */
|
|
6385
|
-
nextIncarnation = 1;
|
|
6386
|
-
eventHandler = null;
|
|
6387
|
-
logHandler = null;
|
|
6388
|
-
readinessHandler = null;
|
|
6389
|
-
server;
|
|
6390
|
-
onUnownedCall;
|
|
6391
|
-
logger;
|
|
6392
|
-
getActiveSingletonAddonId;
|
|
6393
|
-
resolveChildIdForAddon;
|
|
6394
|
-
/** This node's own Moleculer nodeId (see {@link LocalChildRegistryOptions.ownNodeId}). */
|
|
6395
|
-
ownNodeId;
|
|
6396
|
-
/** See {@link LocalChildRegistryOptions.isAggregatedCollectionMethod}. */
|
|
6397
|
-
isAggregatedCollectionMethod;
|
|
6398
|
-
isAddonPinnedCall;
|
|
6399
|
-
/** See {@link LocalChildRegistryOptions.capTimeoutMs}. */
|
|
6400
|
-
capTimeoutMs;
|
|
6401
|
-
/** See {@link LocalChildRegistryOptions.capUsageObserver}. */
|
|
6402
|
-
capUsageObserver;
|
|
6403
|
-
/** Tracks capNames already logged as UDS-routed; one INFO line per capName per process. */
|
|
6404
|
-
egressRoutedCaps = /* @__PURE__ */ new Set();
|
|
6405
|
-
/** Active event fan-out mode, read once from `CAMSTACK_UDS_EVENT_FANOUT`. */
|
|
6406
|
-
fanoutMode = readFanoutMode();
|
|
6407
|
-
/** Per-child event fan-out counters (sent / suppressed). */
|
|
6408
|
-
childEventStats = /* @__PURE__ */ new Map();
|
|
6409
|
-
/** Last pattern-set string logged per child, to dedup the INFO line. */
|
|
6410
|
-
loggedPatternSet = /* @__PURE__ */ new Map();
|
|
6411
|
-
/**
|
|
6412
|
-
* Accepts either a plain positional `server` argument (backward-compatible)
|
|
6413
|
-
* or a full `LocalChildRegistryOptions` object.
|
|
6414
|
-
*
|
|
6415
|
-
* Positional overloads (existing call sites are unchanged):
|
|
6416
|
-
* new LocalChildRegistry(server)
|
|
6417
|
-
* new LocalChildRegistry(server, onUnownedCall)
|
|
6418
|
-
*
|
|
6419
|
-
* Options object (new call sites that pass a logger):
|
|
6420
|
-
* new LocalChildRegistry({ server, onUnownedCall, logger })
|
|
6421
|
-
*/
|
|
6422
|
-
constructor(serverOrOptions, onUnownedCallArg) {
|
|
6423
|
-
if (serverOrOptions && !("listen" in serverOrOptions)) {
|
|
6424
|
-
const opts = serverOrOptions;
|
|
6425
|
-
this.server = opts.server;
|
|
6426
|
-
this.onUnownedCall = opts.onUnownedCall;
|
|
6427
|
-
this.logger = opts.logger;
|
|
6428
|
-
this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
|
|
6429
|
-
this.resolveChildIdForAddon = opts.resolveChildIdForAddon;
|
|
6430
|
-
this.ownNodeId = opts.ownNodeId;
|
|
6431
|
-
this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
|
|
6432
|
-
this.isAddonPinnedCall = opts.isAddonPinnedCall;
|
|
6433
|
-
this.capTimeoutMs = opts.capTimeoutMs;
|
|
6434
|
-
this.capUsageObserver = opts.capUsageObserver;
|
|
6435
|
-
} else {
|
|
6436
|
-
this.server = serverOrOptions;
|
|
6437
|
-
this.onUnownedCall = onUnownedCallArg;
|
|
6438
|
-
}
|
|
6705
|
+
constructor(serverOrOptions, onUnownedCallArg) {
|
|
6706
|
+
if (serverOrOptions && !("listen" in serverOrOptions)) {
|
|
6707
|
+
const opts = serverOrOptions;
|
|
6708
|
+
this.server = opts.server;
|
|
6709
|
+
this.onUnownedCall = opts.onUnownedCall;
|
|
6710
|
+
this.logger = opts.logger;
|
|
6711
|
+
this.getActiveSingletonAddonId = opts.getActiveSingletonAddonId;
|
|
6712
|
+
this.resolveChildIdForAddon = opts.resolveChildIdForAddon;
|
|
6713
|
+
this.ownNodeId = opts.ownNodeId;
|
|
6714
|
+
this.isAggregatedCollectionMethod = opts.isAggregatedCollectionMethod;
|
|
6715
|
+
this.isAddonPinnedCall = opts.isAddonPinnedCall;
|
|
6716
|
+
this.capTimeoutMs = opts.capTimeoutMs;
|
|
6717
|
+
this.capUsageObserver = opts.capUsageObserver;
|
|
6718
|
+
} else {
|
|
6719
|
+
this.server = serverOrOptions;
|
|
6720
|
+
this.onUnownedCall = onUnownedCallArg;
|
|
6721
|
+
}
|
|
6439
6722
|
}
|
|
6440
6723
|
async start() {
|
|
6441
6724
|
this.logger?.info("UDS event fan-out mode", { mode: this.fanoutMode });
|
|
@@ -6471,6 +6754,24 @@ var LocalChildRegistry = class {
|
|
|
6471
6754
|
return entry.channel.readTraffic?.() ?? null;
|
|
6472
6755
|
}
|
|
6473
6756
|
/**
|
|
6757
|
+
* Bytes this process has written to a child that the child has not yet
|
|
6758
|
+
* taken, live, or `null` when the channel keeps no such reading. Read on
|
|
6759
|
+
* every heartbeat probe (D446) — a child that stops draining is the shape
|
|
6760
|
+
* of an outbound-queue episode, and the number lives on the channel.
|
|
6761
|
+
*/
|
|
6762
|
+
getChildQueuedBytes(childId) {
|
|
6763
|
+
const entry = this.children.get(childId);
|
|
6764
|
+
if (entry === void 0) return null;
|
|
6765
|
+
return entry.channel.queuedBytes?.() ?? null;
|
|
6766
|
+
}
|
|
6767
|
+
/** Cumulative {@link ForwardCensus} of this parent's `cap-call-out` routing. */
|
|
6768
|
+
readForwardCensus() {
|
|
6769
|
+
return {
|
|
6770
|
+
raw: this.forwardedRaw,
|
|
6771
|
+
decoded: this.forwardedDecoded
|
|
6772
|
+
};
|
|
6773
|
+
}
|
|
6774
|
+
/**
|
|
6474
6775
|
* The regime the counters above were produced under, read once from
|
|
6475
6776
|
* `CAMSTACK_UDS_EVENT_FANOUT` at construction.
|
|
6476
6777
|
*
|
|
@@ -6848,7 +7149,7 @@ var LocalChildRegistry = class {
|
|
|
6848
7149
|
return;
|
|
6849
7150
|
}
|
|
6850
7151
|
});
|
|
6851
|
-
channel.onRequest(async (body) => {
|
|
7152
|
+
channel.onRequest(async (body, payload) => {
|
|
6852
7153
|
const msg = body;
|
|
6853
7154
|
if (msg.kind === "register") {
|
|
6854
7155
|
if (childId !== null && childId !== msg.childId) throw new Error(`child attempted to change identity from "${childId}" to "${msg.childId}"`);
|
|
@@ -6865,31 +7166,38 @@ var LocalChildRegistry = class {
|
|
|
6865
7166
|
channel,
|
|
6866
7167
|
caps: msg.caps,
|
|
6867
7168
|
eventPatterns,
|
|
6868
|
-
incarnation
|
|
7169
|
+
incarnation,
|
|
7170
|
+
rawForward: msg.rawForward === true
|
|
6869
7171
|
});
|
|
6870
7172
|
this.logPatternSet(msg.childId, eventPatterns);
|
|
6871
7173
|
this.registeredHandler({
|
|
6872
7174
|
childId: msg.childId,
|
|
6873
7175
|
caps: msg.caps,
|
|
6874
|
-
incarnation
|
|
7176
|
+
incarnation,
|
|
7177
|
+
...msg.customActions !== void 0 ? { customActions: msg.customActions } : {}
|
|
6875
7178
|
});
|
|
6876
|
-
return {
|
|
7179
|
+
return {
|
|
7180
|
+
ok: true,
|
|
7181
|
+
rawForward: true
|
|
7182
|
+
};
|
|
6877
7183
|
}
|
|
6878
7184
|
if (msg.kind === "cap-call-out") {
|
|
6879
7185
|
const out = msg;
|
|
6880
|
-
const
|
|
7186
|
+
const hints = out.hints ?? liftRoutingHints(out.args);
|
|
7187
|
+
const materialise = () => payload !== void 0 ? unpackPayload(payload) : out.args;
|
|
7188
|
+
const buildInput = () => ({
|
|
6881
7189
|
capName: out.capName,
|
|
6882
7190
|
method: out.method,
|
|
6883
|
-
args:
|
|
7191
|
+
args: materialise(),
|
|
6884
7192
|
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {},
|
|
6885
7193
|
...out.nodeId !== void 0 ? { nodeId: out.nodeId } : {},
|
|
6886
7194
|
...out.native === true ? { native: true } : {},
|
|
6887
7195
|
...childId !== null ? { callerAddonId: childId } : {}
|
|
6888
|
-
};
|
|
6889
|
-
const pinnedNodeId = out.nodeId ??
|
|
7196
|
+
});
|
|
7197
|
+
const pinnedNodeId = out.nodeId ?? hints.nodeId;
|
|
6890
7198
|
const pinTargetsThisNode = pinnedNodeId !== void 0 && this.ownNodeId !== void 0 && pinnedNodeId === this.ownNodeId;
|
|
6891
7199
|
const aggregated = pinnedNodeId === void 0 && this.isAggregatedCollectionMethod?.(out.capName, out.method) === true;
|
|
6892
|
-
const addonPinned = pinnedNodeId === void 0 && this.isAddonPinnedCall?.(out.capName, out.method,
|
|
7200
|
+
const addonPinned = pinnedNodeId === void 0 && this.isAddonPinnedCall?.(out.capName, out.method, hints) === true;
|
|
6893
7201
|
const target = !(out.native === true) && !aggregated && !addonPinned && (pinnedNodeId === void 0 || pinTargetsThisNode) ? this.resolveChildId(out.capName, out.deviceId) : null;
|
|
6894
7202
|
this.recordCapUsage(childId, target, out.capName, out.method);
|
|
6895
7203
|
if (target !== null) {
|
|
@@ -6897,9 +7205,27 @@ var LocalChildRegistry = class {
|
|
|
6897
7205
|
this.egressRoutedCaps.add(out.capName);
|
|
6898
7206
|
this.logger?.info("routed child egress over UDS", { capName: out.capName });
|
|
6899
7207
|
}
|
|
6900
|
-
|
|
7208
|
+
const entry = this.children.get(target);
|
|
7209
|
+
if (payload !== void 0 && entry !== void 0 && entry.rawForward) {
|
|
7210
|
+
this.forwardedRaw += 1;
|
|
7211
|
+
const forward = {
|
|
7212
|
+
kind: "cap-call",
|
|
7213
|
+
capName: out.capName,
|
|
7214
|
+
method: out.method,
|
|
7215
|
+
args: void 0,
|
|
7216
|
+
...out.deviceId !== void 0 ? { deviceId: out.deviceId } : {}
|
|
7217
|
+
};
|
|
7218
|
+
const declared = this.capTimeoutMs?.(out.capName, out.method);
|
|
7219
|
+
return entry.channel.request(forward, {
|
|
7220
|
+
...typeof declared === "number" ? { timeoutMs: declared } : {},
|
|
7221
|
+
payload
|
|
7222
|
+
});
|
|
7223
|
+
}
|
|
7224
|
+
this.forwardedDecoded += 1;
|
|
7225
|
+
return this.callCap(buildInput());
|
|
6901
7226
|
}
|
|
6902
|
-
|
|
7227
|
+
this.forwardedDecoded += 1;
|
|
7228
|
+
if (this.onUnownedCall !== void 0) return this.onUnownedCall(buildInput());
|
|
6903
7229
|
throw new Error(`${UDS_NO_ROUTE_PREFIX}: cap-call-out has no local provider for "${out.capName}" and no fallback`);
|
|
6904
7230
|
}
|
|
6905
7231
|
if (msg.kind === "readiness-request") return {
|
|
@@ -6929,6 +7255,11 @@ var LocalChildRegistry = class {
|
|
|
6929
7255
|
};
|
|
6930
7256
|
//#endregion
|
|
6931
7257
|
//#region src/kernel/transport/local-child-client.ts
|
|
7258
|
+
/** Did the parent's `register` answer say it accepts payload-borne args? */
|
|
7259
|
+
function ackAcceptsRawForward(ack) {
|
|
7260
|
+
if (ack === null || typeof ack !== "object") return false;
|
|
7261
|
+
return ack.rawForward === true;
|
|
7262
|
+
}
|
|
6932
7263
|
/**
|
|
6933
7264
|
* Child side of the local UDS transport. Connects to its parent (hub or
|
|
6934
7265
|
* agent), registers its cap manifest, and serves parent→child cap calls by
|
|
@@ -6973,8 +7304,24 @@ var LocalChildClient = class {
|
|
|
6973
7304
|
* subscriptions do), the register carries the real union — even if empty.
|
|
6974
7305
|
*/
|
|
6975
7306
|
hasDeclaredEventPatterns = false;
|
|
7307
|
+
/**
|
|
7308
|
+
* The custom-action catalogs of every hosted addon, once the runner's init
|
|
7309
|
+
* loop has produced them (`updateCaps(caps, catalogs)` at post-init). `null`
|
|
7310
|
+
* until then, and OMITTED from the register frame while null: the parent
|
|
7311
|
+
* reads absence as "not described yet", and a pre-init register must never
|
|
7312
|
+
* look like "this child declares no actions". Kept on the client so every
|
|
7313
|
+
* later re-register (native-cap change, reconnect) re-carries it unchanged.
|
|
7314
|
+
*/
|
|
7315
|
+
latestCustomActions = null;
|
|
6976
7316
|
/** Events and logs queued while the channel is not yet open. */
|
|
6977
7317
|
pendingEmits = [];
|
|
7318
|
+
/**
|
|
7319
|
+
* Whether the parent acknowledged raw-forward at `register`. Until it has,
|
|
7320
|
+
* `callOut` sends args inline exactly as every child always did — a legacy
|
|
7321
|
+
* parent would decode a payload-borne call to `args: undefined` and run the
|
|
7322
|
+
* provider on nothing, silently. Re-read on every re-register.
|
|
7323
|
+
*/
|
|
7324
|
+
parentRawForward = false;
|
|
6978
7325
|
/** Handler for parent→child events. Registered via `onEvent`. */
|
|
6979
7326
|
eventHandler = null;
|
|
6980
7327
|
/**
|
|
@@ -7071,153 +7418,622 @@ var LocalChildClient = class {
|
|
|
7071
7418
|
onSetLogLevel(handler) {
|
|
7072
7419
|
this.setLogLevelHandler = handler;
|
|
7073
7420
|
}
|
|
7074
|
-
/**
|
|
7075
|
-
* True once `start()` has successfully connected and registered. Used by
|
|
7076
|
-
* callers (event-bus/logger bridges) to know the channel is live.
|
|
7077
|
-
*/
|
|
7078
|
-
get isConnected() {
|
|
7079
|
-
return this.channel !== null;
|
|
7421
|
+
/**
|
|
7422
|
+
* True once `start()` has successfully connected and registered. Used by
|
|
7423
|
+
* callers (event-bus/logger bridges) to know the channel is live.
|
|
7424
|
+
*/
|
|
7425
|
+
get isConnected() {
|
|
7426
|
+
return this.channel !== null;
|
|
7427
|
+
}
|
|
7428
|
+
async start() {
|
|
7429
|
+
if (this.client !== null) throw new Error("LocalChildClient: already started — call close() first");
|
|
7430
|
+
const client = createLocalTransport().createClient(this.options.nodeId);
|
|
7431
|
+
const channel = await client.connect();
|
|
7432
|
+
channel.onRequest(async (body, payload) => {
|
|
7433
|
+
const msg = body;
|
|
7434
|
+
if (msg.kind === "cap-call") {
|
|
7435
|
+
const args = payload !== void 0 ? unpackPayload(payload) : msg.args;
|
|
7436
|
+
const result = await this.options.dispatch({
|
|
7437
|
+
capName: msg.capName,
|
|
7438
|
+
method: msg.method,
|
|
7439
|
+
args,
|
|
7440
|
+
...msg.deviceId !== void 0 ? { deviceId: msg.deviceId } : {},
|
|
7441
|
+
...msg.addonId !== void 0 ? { addonId: msg.addonId } : {}
|
|
7442
|
+
});
|
|
7443
|
+
if (payload !== void 0 && result !== void 0) return new PayloadReply(void 0, packPayload(result));
|
|
7444
|
+
return result;
|
|
7445
|
+
}
|
|
7446
|
+
if (msg.kind === "addon-call") {
|
|
7447
|
+
if (this.addonCallHandler === null) throw new Error(`LocalChildClient: addon-call for "${msg.addonId}" arrived but no onAddonCall handler is registered`);
|
|
7448
|
+
return this.addonCallHandler({
|
|
7449
|
+
addonId: msg.addonId,
|
|
7450
|
+
target: msg.target,
|
|
7451
|
+
...msg.action !== void 0 ? { action: msg.action } : {},
|
|
7452
|
+
...msg.method !== void 0 ? { method: msg.method } : {},
|
|
7453
|
+
...msg.args !== void 0 ? { args: msg.args } : {},
|
|
7454
|
+
...msg.caller !== void 0 ? { caller: msg.caller } : {}
|
|
7455
|
+
});
|
|
7456
|
+
}
|
|
7457
|
+
throw new Error(`unknown parent request kind: ${msg.kind}`);
|
|
7458
|
+
});
|
|
7459
|
+
channel.onEvent((body) => {
|
|
7460
|
+
const msg = body;
|
|
7461
|
+
if (msg.kind === "event") {
|
|
7462
|
+
this.eventHandler?.(msg.event);
|
|
7463
|
+
return;
|
|
7464
|
+
}
|
|
7465
|
+
if (msg.kind === "set-log-level") this.setLogLevelHandler?.(msg.level);
|
|
7466
|
+
});
|
|
7467
|
+
const register = this.registerFrame(this.latestCaps);
|
|
7468
|
+
try {
|
|
7469
|
+
this.parentRawForward = ackAcceptsRawForward(await channel.request(register));
|
|
7470
|
+
} catch (err) {
|
|
7471
|
+
await client.close();
|
|
7472
|
+
throw err;
|
|
7473
|
+
}
|
|
7474
|
+
this.client = client;
|
|
7475
|
+
this.channel = channel;
|
|
7476
|
+
this.flushPending(channel);
|
|
7477
|
+
for (const cb of this.connectedHandlers) cb();
|
|
7478
|
+
}
|
|
7479
|
+
/** Flush buffered pre-start emits over the now-open channel. */
|
|
7480
|
+
flushPending(channel) {
|
|
7481
|
+
for (const item of this.pendingEmits) channel.emit(item.msg);
|
|
7482
|
+
this.pendingEmits.length = 0;
|
|
7483
|
+
}
|
|
7484
|
+
/**
|
|
7485
|
+
* Re-send the cap manifest to the parent, atomically replacing the child's
|
|
7486
|
+
* registered descriptor set. Call this after device-restore so newly
|
|
7487
|
+
* registered native (device-scoped) caps become routable over UDS.
|
|
7488
|
+
*
|
|
7489
|
+
* Safe to call before `start()`: the new set is buffered and `start()` sends
|
|
7490
|
+
* it (device-restore can race ahead of the UDS connect). After `start()`,
|
|
7491
|
+
* the set is sent immediately.
|
|
7492
|
+
*/
|
|
7493
|
+
async updateCaps(caps, customActions) {
|
|
7494
|
+
this.latestCaps = caps;
|
|
7495
|
+
if (customActions !== void 0) this.latestCustomActions = customActions;
|
|
7496
|
+
if (this.channel === null) return;
|
|
7497
|
+
this.parentRawForward = ackAcceptsRawForward(await this.channel.request(this.registerFrame(caps)));
|
|
7498
|
+
}
|
|
7499
|
+
/**
|
|
7500
|
+
* The register frame: caps, plus everything a re-register must atomically
|
|
7501
|
+
* re-carry so no separate re-sync can be missed — the subscription union
|
|
7502
|
+
* (omitted if nothing ever declared, which keeps the fail-open shape) and the
|
|
7503
|
+
* custom-action catalogs (omitted until the init loop produced them).
|
|
7504
|
+
*/
|
|
7505
|
+
registerFrame(caps) {
|
|
7506
|
+
return {
|
|
7507
|
+
kind: "register",
|
|
7508
|
+
childId: this.options.childId,
|
|
7509
|
+
caps,
|
|
7510
|
+
...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {},
|
|
7511
|
+
...this.latestCustomActions !== null ? { customActions: this.latestCustomActions } : {},
|
|
7512
|
+
rawForward: true
|
|
7513
|
+
};
|
|
7514
|
+
}
|
|
7515
|
+
/**
|
|
7516
|
+
* Fire-and-forget: send a system event to the parent for forwarding to the
|
|
7517
|
+
* hub event bus. Safe to call before `start()` — events are buffered and
|
|
7518
|
+
* flushed on connect.
|
|
7519
|
+
*/
|
|
7520
|
+
emitEvent(event) {
|
|
7521
|
+
const msg = {
|
|
7522
|
+
kind: "event",
|
|
7523
|
+
event
|
|
7524
|
+
};
|
|
7525
|
+
if (this.channel !== null) this.channel.emit(msg);
|
|
7526
|
+
else this.pendingEmits.push({
|
|
7527
|
+
kind: "event",
|
|
7528
|
+
msg
|
|
7529
|
+
});
|
|
7530
|
+
}
|
|
7531
|
+
/**
|
|
7532
|
+
* Fire-and-forget: send a structured log entry to the parent. Safe to call
|
|
7533
|
+
* before `start()` — log entries are buffered and flushed on connect.
|
|
7534
|
+
*/
|
|
7535
|
+
sendLog(entry) {
|
|
7536
|
+
const msg = {
|
|
7537
|
+
kind: "log",
|
|
7538
|
+
...entry
|
|
7539
|
+
};
|
|
7540
|
+
if (this.channel !== null) this.channel.emit(msg);
|
|
7541
|
+
else this.pendingEmits.push({
|
|
7542
|
+
kind: "log",
|
|
7543
|
+
msg
|
|
7544
|
+
});
|
|
7545
|
+
}
|
|
7546
|
+
/**
|
|
7547
|
+
* Request the current readiness snapshot from the parent. Returns the
|
|
7548
|
+
* authoritative set of `IReadinessRegistryRecord` entries the hub holds.
|
|
7549
|
+
* Requires `start()` to have been called; throws with a clear message if not.
|
|
7550
|
+
*/
|
|
7551
|
+
async requestReadinessSnapshot() {
|
|
7552
|
+
if (this.channel === null) throw new Error("LocalChildClient: requestReadinessSnapshot called before start()");
|
|
7553
|
+
return (await this.channel.request({ kind: "readiness-request" })).records;
|
|
7554
|
+
}
|
|
7555
|
+
/**
|
|
7556
|
+
* Ask the parent to execute a cap call this child does NOT own. The parent
|
|
7557
|
+
* routes to the owning local sibling over UDS, or — if no sibling owns it —
|
|
7558
|
+
* to its `onUnownedCall` fallback (the cluster CapabilityRegistry in
|
|
7559
|
+
* production). Throws if called before `start()`.
|
|
7560
|
+
*/
|
|
7561
|
+
async callOut(input) {
|
|
7562
|
+
if (this.channel === null) throw new Error("LocalChildClient: callOut before start");
|
|
7563
|
+
const routing = {
|
|
7564
|
+
...input.deviceId !== void 0 ? { deviceId: input.deviceId } : {},
|
|
7565
|
+
...input.nodeId !== void 0 ? { nodeId: input.nodeId } : {},
|
|
7566
|
+
...input.native === true ? { native: true } : {}
|
|
7567
|
+
};
|
|
7568
|
+
if (this.parentRawForward && input.args !== void 0) {
|
|
7569
|
+
const hints = liftRoutingHints(input.args);
|
|
7570
|
+
const msg = {
|
|
7571
|
+
kind: "cap-call-out",
|
|
7572
|
+
capName: input.capName,
|
|
7573
|
+
method: input.method,
|
|
7574
|
+
...Object.keys(hints).length > 0 ? { hints } : {},
|
|
7575
|
+
...routing
|
|
7576
|
+
};
|
|
7577
|
+
const reply = await this.channel.request(msg, { payload: packPayload(input.args) });
|
|
7578
|
+
return reply instanceof PayloadReply ? unpackPayload(reply.payload) : reply;
|
|
7579
|
+
}
|
|
7580
|
+
const msg = {
|
|
7581
|
+
kind: "cap-call-out",
|
|
7582
|
+
capName: input.capName,
|
|
7583
|
+
method: input.method,
|
|
7584
|
+
args: input.args,
|
|
7585
|
+
...routing
|
|
7586
|
+
};
|
|
7587
|
+
return this.channel.request(msg);
|
|
7588
|
+
}
|
|
7589
|
+
/** Disconnect from the parent. Safe to call before `start()` (no-op) and idempotent. */
|
|
7590
|
+
async close() {
|
|
7591
|
+
await this.client?.close();
|
|
7592
|
+
this.client = null;
|
|
7593
|
+
this.channel = null;
|
|
7594
|
+
}
|
|
7595
|
+
};
|
|
7596
|
+
//#endregion
|
|
7597
|
+
//#region src/kernel/transport/cap-route.ts
|
|
7598
|
+
function buildMessage(capName, method, detail) {
|
|
7599
|
+
const call = method !== void 0 ? `${capName}.${method}` : capName;
|
|
7600
|
+
const target = detail.nodeId !== void 0 ? ` to node ${detail.nodeId}` : "";
|
|
7601
|
+
const rejectedStr = detail.rejected.map((r) => `${r.kind}=${r.why}`).join("; ");
|
|
7602
|
+
const rejectedClause = rejectedStr.length > 0 ? ` (rejected: ${rejectedStr})` : "";
|
|
7603
|
+
return `${call} not routable${target}: ${detail.reason}${rejectedClause}`;
|
|
7604
|
+
}
|
|
7605
|
+
var CapRouteError = class extends Error {
|
|
7606
|
+
reason;
|
|
7607
|
+
nodeId;
|
|
7608
|
+
rejected;
|
|
7609
|
+
/**
|
|
7610
|
+
* @param cause Optional original error that triggered this routing failure.
|
|
7611
|
+
* Stored as `Error.cause` (TC39 standard option, Node 16.9+).
|
|
7612
|
+
* Dispatchers wrapping transport errors MUST pass the original.
|
|
7613
|
+
*/
|
|
7614
|
+
constructor(capName, method, detail, cause) {
|
|
7615
|
+
super(buildMessage(capName, method, detail), cause !== void 0 ? { cause } : void 0);
|
|
7616
|
+
this.name = "CapRouteError";
|
|
7617
|
+
this.reason = detail.reason;
|
|
7618
|
+
this.nodeId = detail.nodeId;
|
|
7619
|
+
this.rejected = detail.rejected;
|
|
7620
|
+
}
|
|
7621
|
+
};
|
|
7622
|
+
/**
|
|
7623
|
+
* Classifies a (capName, opts) pair into a typed CapRoute dispatch descriptor.
|
|
7624
|
+
*
|
|
7625
|
+
* Precedence (explicit nodeId path):
|
|
7626
|
+
* 1. hub-in-process — hub node + hubInProcessProvides
|
|
7627
|
+
* 2. hub-local-uds — hub node + hubLocalChildProvides
|
|
7628
|
+
* 3. node-offline → CapRouteError{reason:'node-offline'}
|
|
7629
|
+
* 4. agent-child-forward — node is an agent AND nodeKnowsCap
|
|
7630
|
+
* 5. remote-moleculer — any other online non-agent node
|
|
7631
|
+
*
|
|
7632
|
+
* Singleton path (no nodeId):
|
|
7633
|
+
* 1. hub-in-process (hub provides in-process)
|
|
7634
|
+
* 2. hub-local-uds (a hub-local UDS child provides it)
|
|
7635
|
+
* 3. remote-moleculer (any online, non-hub node that knows it)
|
|
7636
|
+
* 4. → CapRouteError{reason:'no-provider', rejected: all considered routes}
|
|
7637
|
+
*
|
|
7638
|
+
* PURE: no side effects, no async, no broker/registry imports.
|
|
7639
|
+
*/
|
|
7640
|
+
function classifyCapRoute(capName, opts, snapshot) {
|
|
7641
|
+
const rejected = [];
|
|
7642
|
+
if (opts.nodeId !== void 0) return classifyExplicitNode(capName, opts.nodeId, opts.deviceId, snapshot, rejected);
|
|
7643
|
+
return classifySingleton(capName, opts.deviceId, snapshot, rejected);
|
|
7644
|
+
}
|
|
7645
|
+
function classifyExplicitNode(capName, nodeId, deviceId, snap, rejected) {
|
|
7646
|
+
if (nodeId === snap.hubNodeId) {
|
|
7647
|
+
const residentProvides = snap.hubResidentProvides;
|
|
7648
|
+
const providesOnHub = residentProvides ?? snap.hubInProcessProvides;
|
|
7649
|
+
const getRef = residentProvides !== void 0 ? snap.getHubResidentProviderRef : snap.getInProcessProviderRef;
|
|
7650
|
+
if (providesOnHub(capName)) {
|
|
7651
|
+
const ref = getRef?.(capName) ?? null;
|
|
7652
|
+
if (ref !== null) return {
|
|
7653
|
+
kind: "hub-in-process",
|
|
7654
|
+
capName,
|
|
7655
|
+
ref
|
|
7656
|
+
};
|
|
7657
|
+
rejected.push({
|
|
7658
|
+
kind: "hub-in-process",
|
|
7659
|
+
why: "provider ref not available in snapshot"
|
|
7660
|
+
});
|
|
7661
|
+
throw new CapRouteError(capName, void 0, {
|
|
7662
|
+
reason: "no-provider",
|
|
7663
|
+
nodeId,
|
|
7664
|
+
rejected
|
|
7665
|
+
});
|
|
7666
|
+
}
|
|
7667
|
+
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
7668
|
+
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
7669
|
+
if (childId !== null) return {
|
|
7670
|
+
kind: "hub-local-uds",
|
|
7671
|
+
capName,
|
|
7672
|
+
childId
|
|
7673
|
+
};
|
|
7674
|
+
rejected.push({
|
|
7675
|
+
kind: "hub-local-uds",
|
|
7676
|
+
why: "child id not resolvable from snapshot"
|
|
7677
|
+
});
|
|
7678
|
+
throw new CapRouteError(capName, void 0, {
|
|
7679
|
+
reason: "no-provider",
|
|
7680
|
+
nodeId,
|
|
7681
|
+
rejected
|
|
7682
|
+
});
|
|
7683
|
+
}
|
|
7684
|
+
rejected.push({
|
|
7685
|
+
kind: "hub-in-process",
|
|
7686
|
+
why: "hub does not provide this cap in-process"
|
|
7687
|
+
});
|
|
7688
|
+
rejected.push({
|
|
7689
|
+
kind: "hub-local-uds",
|
|
7690
|
+
why: "no hub-local child provides this cap"
|
|
7691
|
+
});
|
|
7692
|
+
throw new CapRouteError(capName, void 0, {
|
|
7693
|
+
reason: "no-provider",
|
|
7694
|
+
nodeId,
|
|
7695
|
+
rejected
|
|
7696
|
+
});
|
|
7697
|
+
}
|
|
7698
|
+
if (!snap.nodeOnline(nodeId)) {
|
|
7699
|
+
rejected.push({
|
|
7700
|
+
kind: "remote-moleculer",
|
|
7701
|
+
why: `node ${nodeId} is offline`
|
|
7702
|
+
});
|
|
7703
|
+
throw new CapRouteError(capName, void 0, {
|
|
7704
|
+
reason: "node-offline",
|
|
7705
|
+
nodeId,
|
|
7706
|
+
rejected
|
|
7707
|
+
});
|
|
7080
7708
|
}
|
|
7081
|
-
|
|
7082
|
-
if (
|
|
7083
|
-
|
|
7084
|
-
|
|
7085
|
-
|
|
7086
|
-
|
|
7087
|
-
|
|
7088
|
-
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
...msg.deviceId !== void 0 ? { deviceId: msg.deviceId } : {},
|
|
7092
|
-
...msg.addonId !== void 0 ? { addonId: msg.addonId } : {}
|
|
7093
|
-
});
|
|
7094
|
-
if (msg.kind === "addon-call") {
|
|
7095
|
-
if (this.addonCallHandler === null) throw new Error(`LocalChildClient: addon-call for "${msg.addonId}" arrived but no onAddonCall handler is registered`);
|
|
7096
|
-
return this.addonCallHandler({
|
|
7097
|
-
addonId: msg.addonId,
|
|
7098
|
-
target: msg.target,
|
|
7099
|
-
...msg.action !== void 0 ? { action: msg.action } : {},
|
|
7100
|
-
...msg.method !== void 0 ? { method: msg.method } : {},
|
|
7101
|
-
...msg.args !== void 0 ? { args: msg.args } : {},
|
|
7102
|
-
...msg.caller !== void 0 ? { caller: msg.caller } : {}
|
|
7103
|
-
});
|
|
7104
|
-
}
|
|
7105
|
-
throw new Error(`unknown parent request kind: ${msg.kind}`);
|
|
7709
|
+
if (snap.nodeIsAgent(nodeId)) {
|
|
7710
|
+
if (snap.nodeKnowsCap(nodeId, capName)) return {
|
|
7711
|
+
kind: "agent-child-forward",
|
|
7712
|
+
capName,
|
|
7713
|
+
agentNodeId: nodeId,
|
|
7714
|
+
childId: snap.getAgentChildId?.(nodeId, capName) ?? void 0
|
|
7715
|
+
};
|
|
7716
|
+
rejected.push({
|
|
7717
|
+
kind: "agent-child-forward",
|
|
7718
|
+
why: `agent ${nodeId} does not know cap ${capName}`
|
|
7106
7719
|
});
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7110
|
-
|
|
7111
|
-
return;
|
|
7112
|
-
}
|
|
7113
|
-
if (msg.kind === "set-log-level") this.setLogLevelHandler?.(msg.level);
|
|
7720
|
+
throw new CapRouteError(capName, void 0, {
|
|
7721
|
+
reason: "no-provider",
|
|
7722
|
+
nodeId,
|
|
7723
|
+
rejected
|
|
7114
7724
|
});
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7725
|
+
}
|
|
7726
|
+
return {
|
|
7727
|
+
kind: "remote-moleculer",
|
|
7728
|
+
capName,
|
|
7729
|
+
nodeId
|
|
7730
|
+
};
|
|
7731
|
+
}
|
|
7732
|
+
function classifySingleton(capName, deviceId, snap, rejected) {
|
|
7733
|
+
if (snap.hubInProcessProvides(capName)) {
|
|
7734
|
+
const ref = snap.getInProcessProviderRef?.(capName) ?? null;
|
|
7735
|
+
if (ref !== null) return {
|
|
7736
|
+
kind: "hub-in-process",
|
|
7737
|
+
capName,
|
|
7738
|
+
ref
|
|
7120
7739
|
};
|
|
7121
|
-
|
|
7122
|
-
|
|
7123
|
-
|
|
7124
|
-
|
|
7125
|
-
|
|
7126
|
-
|
|
7127
|
-
this
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7740
|
+
rejected.push({
|
|
7741
|
+
kind: "hub-in-process",
|
|
7742
|
+
why: "provider ref not available in snapshot"
|
|
7743
|
+
});
|
|
7744
|
+
} else rejected.push({
|
|
7745
|
+
kind: "hub-in-process",
|
|
7746
|
+
why: "hub does not provide this cap in-process"
|
|
7747
|
+
});
|
|
7748
|
+
if (snap.hubLocalChildProvides(capName, deviceId)) {
|
|
7749
|
+
const childId = snap.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
7750
|
+
if (childId !== null) return {
|
|
7751
|
+
kind: "hub-local-uds",
|
|
7752
|
+
capName,
|
|
7753
|
+
childId
|
|
7754
|
+
};
|
|
7755
|
+
rejected.push({
|
|
7756
|
+
kind: "hub-local-uds",
|
|
7757
|
+
why: "child id not resolvable from snapshot"
|
|
7758
|
+
});
|
|
7759
|
+
} else rejected.push({
|
|
7760
|
+
kind: "hub-local-uds",
|
|
7761
|
+
why: "no hub-local child provides this cap"
|
|
7762
|
+
});
|
|
7763
|
+
const knownNodes = snap.listKnownNodeIds?.() ?? [];
|
|
7764
|
+
for (const nodeId of knownNodes) if (nodeId !== snap.hubNodeId && snap.nodeOnline(nodeId) && snap.nodeKnowsCap(nodeId, capName)) return {
|
|
7765
|
+
kind: "remote-moleculer",
|
|
7766
|
+
capName,
|
|
7767
|
+
nodeId
|
|
7768
|
+
};
|
|
7769
|
+
rejected.push({
|
|
7770
|
+
kind: "remote-moleculer",
|
|
7771
|
+
why: "no online remote node knows this cap"
|
|
7772
|
+
});
|
|
7773
|
+
throw new CapRouteError(capName, void 0, {
|
|
7774
|
+
reason: "no-provider",
|
|
7775
|
+
rejected
|
|
7776
|
+
});
|
|
7777
|
+
}
|
|
7778
|
+
//#endregion
|
|
7779
|
+
//#region src/kernel/moleculer/resilient-cap-call.ts
|
|
7780
|
+
/** Moleculer error `type` values meaning "the service is not (yet) routable". */
|
|
7781
|
+
var DISCOVERY_ERROR_TYPES = new Set(["SERVICE_NOT_FOUND", "SERVICE_NOT_AVAILABLE"]);
|
|
7782
|
+
/** Default ceiling for the discovery wait — fail-fast past this. */
|
|
7783
|
+
var DEFAULT_DISCOVERY_TIMEOUT_MS$1 = 3e4;
|
|
7784
|
+
function isDiscoveryError(err) {
|
|
7785
|
+
if (typeof err !== "object" || err === null) return false;
|
|
7786
|
+
const type = err.type;
|
|
7787
|
+
return typeof type === "string" && DISCOVERY_ERROR_TYPES.has(type);
|
|
7788
|
+
}
|
|
7789
|
+
/**
|
|
7790
|
+
* Call a Moleculer action; on a service-discovery error, wait for the
|
|
7791
|
+
* named service to be discovered and retry the call exactly once.
|
|
7792
|
+
*/
|
|
7793
|
+
async function callWithServiceDiscovery(broker, serviceName, action, params, opts, discoveryTimeoutMs = DEFAULT_DISCOVERY_TIMEOUT_MS$1) {
|
|
7794
|
+
try {
|
|
7795
|
+
return await broker.call(action, params, opts);
|
|
7796
|
+
} catch (err) {
|
|
7797
|
+
if (!isDiscoveryError(err)) throw err;
|
|
7798
|
+
await broker.waitForServices([serviceName], discoveryTimeoutMs);
|
|
7799
|
+
return await broker.call(action, params, opts);
|
|
7131
7800
|
}
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7801
|
+
}
|
|
7802
|
+
//#endregion
|
|
7803
|
+
//#region src/kernel/transport/cap-route-resolver.ts
|
|
7804
|
+
/** The Moleculer service name that Task 6's agent registers. */
|
|
7805
|
+
var AGENT_CAP_FWD_SERVICE = "$agent-cap-fwd";
|
|
7806
|
+
/** The Moleculer action (service.action) for agent cap forwarding. */
|
|
7807
|
+
var AGENT_CAP_FWD_ACTION = `${AGENT_CAP_FWD_SERVICE}.forward`;
|
|
7808
|
+
/** Default timeout for remote Moleculer cap calls (ms). */
|
|
7809
|
+
var REMOTE_CALL_TIMEOUT_MS = 6e4;
|
|
7810
|
+
function extractDeviceId(args) {
|
|
7811
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
7812
|
+
const raw = Reflect.get(args, "deviceId");
|
|
7813
|
+
return typeof raw === "number" ? raw : void 0;
|
|
7814
|
+
}
|
|
7815
|
+
/**
|
|
7816
|
+
* Extract an inline `nodeId` string from a cap call's args — the per-call node
|
|
7817
|
+
* pin a forked addon expresses by carrying `nodeId` in the input (the SAME way
|
|
7818
|
+
* device-scoped caps carry `deviceId`, and the way the generated cap-router on
|
|
7819
|
+
* the hub already honours an inline pin). Mirrors {@link extractDeviceId}.
|
|
7820
|
+
*
|
|
7821
|
+
* This is the routing source for hub→remote-node EXECUTION pinning (e.g. the
|
|
7822
|
+
* benchmark addon running a synthetic/decoder workload ON a chosen agent). The
|
|
7823
|
+
* out-of-band `nodePin(op.context)` path does NOT survive the forked addon →
|
|
7824
|
+
* hub UDS link chain, so `onUnownedCall` reads the inline pin instead. Provider
|
|
7825
|
+
* methods that don't declare `nodeId` simply ignore the extra field (they
|
|
7826
|
+
* destructure the fields they need); methods that DO declare it (e.g.
|
|
7827
|
+
* `getEngineProvisioning({nodeId})`) still receive it unchanged.
|
|
7828
|
+
*/
|
|
7829
|
+
function extractNodeId(args) {
|
|
7830
|
+
if (args === null || typeof args !== "object") return void 0;
|
|
7831
|
+
const raw = Reflect.get(args, "nodeId");
|
|
7832
|
+
return typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
7833
|
+
}
|
|
7834
|
+
/**
|
|
7835
|
+
* Merge `callerAddonId` into `args` as an extra field — the SAME "provider
|
|
7836
|
+
* destructures what it needs and ignores the rest" convention
|
|
7837
|
+
* {@link extractNodeId} already relies on for the inline `nodeId` pin.
|
|
7838
|
+
* `undefined` (the overwhelmingly common case: no caller hint, or the census
|
|
7839
|
+
* consuming it is disarmed) returns `args` unchanged — no allocation. A
|
|
7840
|
+
* non-object `args` (or `null`/array) is returned unchanged too: there is
|
|
7841
|
+
* nowhere to attach an extra key without changing the payload's shape, and a
|
|
7842
|
+
* provider taking a bare value never reads `callerAddonId` off it anyway.
|
|
7843
|
+
*/
|
|
7844
|
+
function withCallerAddonId(args, callerAddonId) {
|
|
7845
|
+
if (callerAddonId === void 0) return args;
|
|
7846
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) return args;
|
|
7847
|
+
return {
|
|
7848
|
+
...args,
|
|
7849
|
+
callerAddonId
|
|
7850
|
+
};
|
|
7851
|
+
}
|
|
7852
|
+
var CapRouteResolver = class {
|
|
7853
|
+
hubNodeId;
|
|
7854
|
+
broker;
|
|
7855
|
+
hubLocalRegistry;
|
|
7856
|
+
nodeAuthority;
|
|
7857
|
+
inProcessProviders;
|
|
7858
|
+
hubResidentProviders;
|
|
7859
|
+
capTimeoutMs;
|
|
7860
|
+
snapshot;
|
|
7861
|
+
constructor(deps) {
|
|
7862
|
+
this.hubNodeId = deps.hubNodeId;
|
|
7863
|
+
this.broker = deps.broker;
|
|
7864
|
+
this.hubLocalRegistry = deps.hubLocalRegistry;
|
|
7865
|
+
this.nodeAuthority = deps.nodeAuthority;
|
|
7866
|
+
this.inProcessProviders = deps.inProcessProviders;
|
|
7867
|
+
this.hubResidentProviders = deps.hubResidentProviders;
|
|
7868
|
+
this.capTimeoutMs = deps.capTimeoutMs;
|
|
7869
|
+
this.snapshot = this.buildSnapshot();
|
|
7136
7870
|
}
|
|
7137
|
-
/**
|
|
7138
|
-
|
|
7139
|
-
|
|
7140
|
-
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
* it (device-restore can race ahead of the UDS connect). After `start()`,
|
|
7144
|
-
* the set is sent immediately.
|
|
7145
|
-
*/
|
|
7146
|
-
async updateCaps(caps) {
|
|
7147
|
-
this.latestCaps = caps;
|
|
7148
|
-
if (this.channel === null) return;
|
|
7149
|
-
const register = {
|
|
7150
|
-
kind: "register",
|
|
7151
|
-
childId: this.options.childId,
|
|
7152
|
-
caps,
|
|
7153
|
-
...this.hasDeclaredEventPatterns ? { eventPatterns: this.latestEventPatterns } : {}
|
|
7154
|
-
};
|
|
7155
|
-
await this.channel.request(register);
|
|
7871
|
+
/** Per-call RPC timeout: the cap method's declared override, else the default. */
|
|
7872
|
+
callTimeout(capName, method) {
|
|
7873
|
+
return this.capTimeoutMs?.(capName, method) ?? REMOTE_CALL_TIMEOUT_MS;
|
|
7874
|
+
}
|
|
7875
|
+
resolveCapRoute(capName, opts) {
|
|
7876
|
+
return classifyCapRoute(capName, opts, this.snapshot);
|
|
7156
7877
|
}
|
|
7157
7878
|
/**
|
|
7158
|
-
*
|
|
7159
|
-
*
|
|
7160
|
-
*
|
|
7879
|
+
* Resolve the hub-local-uds route for a cap owned by a forked hub-local
|
|
7880
|
+
* child, IGNORING any in-hub provider registered for the same cap name.
|
|
7881
|
+
*
|
|
7882
|
+
* `resolveCapRoute` gives Priority 1 to `hub-in-process`: when an in-hub
|
|
7883
|
+
* provider (e.g. a wrapper) is registered for the cap, the route always
|
|
7884
|
+
* classifies as `hub-in-process` and the hub-local-uds NATIVE child is never
|
|
7885
|
+
* reached. That is correct for the generic dispatch path (the wrapper is the
|
|
7886
|
+
* active provider), but WRONG for the native-cap fallback
|
|
7887
|
+
* (`setNativeFallback`), whose contract is to reach the NATIVE provider in
|
|
7888
|
+
* the forked vendor child so a wrapper can delegate to it. A wrapper cap
|
|
7889
|
+
* with a forked native (today: `snapshot`) otherwise resolves to the wrapper
|
|
7890
|
+
* itself, the native is never invoked, and the wrapper silently falls
|
|
7891
|
+
* through to its secondary strategy.
|
|
7892
|
+
*
|
|
7893
|
+
* This method consults ONLY the hub-local-child authority
|
|
7894
|
+
* (`hubLocalChildProvides` + `getHubLocalChildId`, both deviceId-aware), so
|
|
7895
|
+
* it returns the native child route regardless of any in-process shadow.
|
|
7896
|
+
* Returns null when no hub-local child owns `(capName, deviceId)` — the
|
|
7897
|
+
* caller then falls through to its remote-resolution branch.
|
|
7161
7898
|
*/
|
|
7162
|
-
|
|
7163
|
-
|
|
7164
|
-
|
|
7165
|
-
|
|
7899
|
+
resolveHubLocalUdsRoute(capName, deviceId) {
|
|
7900
|
+
if (!this.snapshot.hubLocalChildProvides(capName, deviceId)) return null;
|
|
7901
|
+
const childId = this.snapshot.getHubLocalChildId?.(capName, deviceId) ?? null;
|
|
7902
|
+
if (childId === null) return null;
|
|
7903
|
+
return {
|
|
7904
|
+
kind: "hub-local-uds",
|
|
7905
|
+
capName,
|
|
7906
|
+
childId
|
|
7166
7907
|
};
|
|
7167
|
-
if (this.channel !== null) this.channel.emit(msg);
|
|
7168
|
-
else this.pendingEmits.push({
|
|
7169
|
-
kind: "event",
|
|
7170
|
-
msg
|
|
7171
|
-
});
|
|
7172
7908
|
}
|
|
7173
7909
|
/**
|
|
7174
|
-
*
|
|
7175
|
-
*
|
|
7910
|
+
* @param addonId Optional out-of-band PROVIDER-selection hint (Task 7.5a):
|
|
7911
|
+
* the addonId of the provider the CALLER already resolved (e.g.
|
|
7912
|
+
* `buildCapCallFn`'s `deps.addonId` for a registered grouped-runner
|
|
7913
|
+
* provider). Only meaningful for `agent-child-forward` — a `remote-moleculer`
|
|
7914
|
+
* route already encodes the provider in its action name
|
|
7915
|
+
* (`${addonId}.${cap}.${method}`, via `NodeCapAuthority.getAddonId`), and a
|
|
7916
|
+
* `hub-local-uds` caller that already knows the addonId (`buildCapCallFn`)
|
|
7917
|
+
* talks to `LocalChildRegistry.callCapOnChild` directly rather than through
|
|
7918
|
+
* `dispatch`.
|
|
7919
|
+
* @param callerAddonId Optional out-of-band CALLER-identity hint — the
|
|
7920
|
+
* addon that ORIGINATED the call, distinct from `addonId` above (which
|
|
7921
|
+
* names the resolved PROVIDER, not the caller). Set by
|
|
7922
|
+
* `onUnownedCall` from `CapCallInput.callerAddonId`
|
|
7923
|
+
* (`LocalChildRegistry` stamped it off the registered `childId`). Only
|
|
7924
|
+
* the `hub-in-process` branch consumes it — merged into `args` so a
|
|
7925
|
+
* diagnostic tool deep in that provider (e.g. device-manager's
|
|
7926
|
+
* fleet-read census) can read it without every provider method changing
|
|
7927
|
+
* signature, the same "extra field the provider destructures or
|
|
7928
|
+
* ignores" convention `nodeId` already uses on this path.
|
|
7176
7929
|
*/
|
|
7177
|
-
|
|
7178
|
-
|
|
7179
|
-
|
|
7180
|
-
|
|
7181
|
-
|
|
7182
|
-
|
|
7183
|
-
|
|
7184
|
-
|
|
7185
|
-
|
|
7186
|
-
|
|
7930
|
+
async dispatch(route, method, args, addonId, callerAddonId) {
|
|
7931
|
+
try {
|
|
7932
|
+
return await this.dispatchInner(route, method, args, addonId, callerAddonId);
|
|
7933
|
+
} catch (err) {
|
|
7934
|
+
if (err instanceof CapRouteError) throw err;
|
|
7935
|
+
const nodeId = this.routeNodeId(route);
|
|
7936
|
+
const cause = err instanceof Error ? err : new Error(String(err));
|
|
7937
|
+
throw new CapRouteError(route.capName, method, {
|
|
7938
|
+
reason: "transport-failed",
|
|
7939
|
+
nodeId,
|
|
7940
|
+
rejected: [{
|
|
7941
|
+
kind: route.kind,
|
|
7942
|
+
why: cause.message
|
|
7943
|
+
}]
|
|
7944
|
+
}, cause);
|
|
7945
|
+
}
|
|
7187
7946
|
}
|
|
7188
7947
|
/**
|
|
7189
|
-
*
|
|
7190
|
-
*
|
|
7191
|
-
* Requires `start()` to have been called; throws with a clear message if not.
|
|
7948
|
+
* Inner dispatch: may throw CapRouteError (validation failures) or arbitrary
|
|
7949
|
+
* transport errors. The outer `dispatch` wraps non-CapRouteErrors.
|
|
7192
7950
|
*/
|
|
7193
|
-
async
|
|
7194
|
-
|
|
7195
|
-
|
|
7951
|
+
async dispatchInner(route, method, args, addonId, callerAddonId) {
|
|
7952
|
+
switch (route.kind) {
|
|
7953
|
+
case "hub-in-process": return route.ref.invoke(method, withCallerAddonId(args, callerAddonId));
|
|
7954
|
+
case "hub-local-uds": {
|
|
7955
|
+
const registry = this.hubLocalRegistry;
|
|
7956
|
+
if (registry === null) throw new CapRouteError(route.capName, method, {
|
|
7957
|
+
reason: "no-provider",
|
|
7958
|
+
rejected: [{
|
|
7959
|
+
kind: "hub-local-uds",
|
|
7960
|
+
why: "UDS registry not available"
|
|
7961
|
+
}]
|
|
7962
|
+
});
|
|
7963
|
+
const deviceId = extractDeviceId(args);
|
|
7964
|
+
const input = {
|
|
7965
|
+
capName: route.capName,
|
|
7966
|
+
method,
|
|
7967
|
+
args,
|
|
7968
|
+
...deviceId !== void 0 ? { deviceId } : {}
|
|
7969
|
+
};
|
|
7970
|
+
return registry.callCapOnChild(route.childId, input);
|
|
7971
|
+
}
|
|
7972
|
+
case "remote-moleculer": {
|
|
7973
|
+
const addonId = this.nodeAuthority.getAddonId(route.nodeId, route.capName);
|
|
7974
|
+
if (addonId === null) throw new CapRouteError(route.capName, method, {
|
|
7975
|
+
reason: "no-provider",
|
|
7976
|
+
nodeId: route.nodeId,
|
|
7977
|
+
rejected: [{
|
|
7978
|
+
kind: "remote-moleculer",
|
|
7979
|
+
why: `no addonId known for ${route.nodeId}/${route.capName}`
|
|
7980
|
+
}]
|
|
7981
|
+
});
|
|
7982
|
+
const deviceId = extractDeviceId(args);
|
|
7983
|
+
const isNative = this.nodeAuthority.isNativeCap(route.nodeId, route.capName, deviceId);
|
|
7984
|
+
const action = capActionName(addonId, route.capName, method, isNative);
|
|
7985
|
+
return callWithServiceDiscovery(this.broker, addonId, action, args, {
|
|
7986
|
+
nodeID: route.nodeId,
|
|
7987
|
+
timeout: this.callTimeout(route.capName, method)
|
|
7988
|
+
});
|
|
7989
|
+
}
|
|
7990
|
+
case "agent-child-forward": {
|
|
7991
|
+
const deviceId = extractDeviceId(args);
|
|
7992
|
+
const params = {
|
|
7993
|
+
capName: route.capName,
|
|
7994
|
+
method,
|
|
7995
|
+
args,
|
|
7996
|
+
...route.childId !== void 0 ? { childId: route.childId } : {},
|
|
7997
|
+
...deviceId !== void 0 ? { deviceId } : {},
|
|
7998
|
+
...addonId !== void 0 ? { addonId } : {}
|
|
7999
|
+
};
|
|
8000
|
+
return callWithServiceDiscovery(this.broker, AGENT_CAP_FWD_SERVICE, AGENT_CAP_FWD_ACTION, params, {
|
|
8001
|
+
nodeID: route.agentNodeId,
|
|
8002
|
+
timeout: this.callTimeout(route.capName, method)
|
|
8003
|
+
});
|
|
8004
|
+
}
|
|
8005
|
+
}
|
|
7196
8006
|
}
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7203
|
-
|
|
7204
|
-
|
|
7205
|
-
|
|
7206
|
-
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
|
|
7210
|
-
|
|
7211
|
-
|
|
7212
|
-
|
|
8007
|
+
buildSnapshot() {
|
|
8008
|
+
const hubLocalRegistry = this.hubLocalRegistry;
|
|
8009
|
+
const nodeAuthority = this.nodeAuthority;
|
|
8010
|
+
const inProcessProviders = this.inProcessProviders;
|
|
8011
|
+
const hubResidentProviders = this.hubResidentProviders;
|
|
8012
|
+
return {
|
|
8013
|
+
hubNodeId: this.hubNodeId,
|
|
8014
|
+
hubInProcessProvides: (cap) => inProcessProviders(cap) !== null,
|
|
8015
|
+
...hubResidentProviders !== void 0 ? {
|
|
8016
|
+
hubResidentProvides: (cap) => hubResidentProviders(cap) !== null,
|
|
8017
|
+
getHubResidentProviderRef: (cap) => hubResidentProviders(cap)
|
|
8018
|
+
} : {},
|
|
8019
|
+
hubLocalChildProvides: (cap, deviceId) => hubLocalRegistry !== null && hubLocalRegistry.resolveChildId(cap, deviceId) !== null,
|
|
8020
|
+
nodeKnowsCap: (nodeId, cap) => nodeAuthority.nodeKnowsCap(nodeId, cap),
|
|
8021
|
+
nodeIsAgent: (nodeId) => nodeAuthority.nodeIsAgent(nodeId),
|
|
8022
|
+
nodeOnline: (nodeId) => nodeAuthority.nodeOnline(nodeId),
|
|
8023
|
+
listKnownNodeIds: () => nodeAuthority.listNodeIds(),
|
|
8024
|
+
getInProcessProviderRef: (cap) => inProcessProviders(cap),
|
|
8025
|
+
getHubLocalChildId: (cap, deviceId) => hubLocalRegistry !== null ? hubLocalRegistry.resolveChildId(cap, deviceId) : null,
|
|
8026
|
+
getAgentChildId: (agentNodeId, cap) => nodeAuthority.getAgentChildId(agentNodeId, cap)
|
|
7213
8027
|
};
|
|
7214
|
-
return this.channel.request(msg);
|
|
7215
8028
|
}
|
|
7216
|
-
/**
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
8029
|
+
/** Extract a nodeId string from a route for error reporting, or undefined. */
|
|
8030
|
+
routeNodeId(route) {
|
|
8031
|
+
switch (route.kind) {
|
|
8032
|
+
case "hub-in-process": return this.hubNodeId;
|
|
8033
|
+
case "hub-local-uds": return `${this.hubNodeId}/${route.childId}`;
|
|
8034
|
+
case "remote-moleculer": return route.nodeId;
|
|
8035
|
+
case "agent-child-forward": return route.agentNodeId;
|
|
8036
|
+
}
|
|
7221
8037
|
}
|
|
7222
8038
|
};
|
|
7223
8039
|
//#endregion
|
|
@@ -9901,6 +10717,30 @@ Object.defineProperty(exports, "AGENT_CAP_FWD_SERVICE", {
|
|
|
9901
10717
|
return AGENT_CAP_FWD_SERVICE;
|
|
9902
10718
|
}
|
|
9903
10719
|
});
|
|
10720
|
+
Object.defineProperty(exports, "ARRAY_BUFFER_CENSUS_COOLDOWN_MS", {
|
|
10721
|
+
enumerable: true,
|
|
10722
|
+
get: function() {
|
|
10723
|
+
return ARRAY_BUFFER_CENSUS_COOLDOWN_MS;
|
|
10724
|
+
}
|
|
10725
|
+
});
|
|
10726
|
+
Object.defineProperty(exports, "ARRAY_BUFFER_CENSUS_SUSTAIN_MS", {
|
|
10727
|
+
enumerable: true,
|
|
10728
|
+
get: function() {
|
|
10729
|
+
return ARRAY_BUFFER_CENSUS_SUSTAIN_MS;
|
|
10730
|
+
}
|
|
10731
|
+
});
|
|
10732
|
+
Object.defineProperty(exports, "ARRAY_BUFFER_CENSUS_THRESHOLD_MB", {
|
|
10733
|
+
enumerable: true,
|
|
10734
|
+
get: function() {
|
|
10735
|
+
return ARRAY_BUFFER_CENSUS_THRESHOLD_MB;
|
|
10736
|
+
}
|
|
10737
|
+
});
|
|
10738
|
+
Object.defineProperty(exports, "ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO", {
|
|
10739
|
+
enumerable: true,
|
|
10740
|
+
get: function() {
|
|
10741
|
+
return ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO;
|
|
10742
|
+
}
|
|
10743
|
+
});
|
|
9904
10744
|
Object.defineProperty(exports, "AddonDepsManager", {
|
|
9905
10745
|
enumerable: true,
|
|
9906
10746
|
get: function() {
|
|
@@ -10045,12 +10885,36 @@ Object.defineProperty(exports, "LocalChildRegistry", {
|
|
|
10045
10885
|
return LocalChildRegistry;
|
|
10046
10886
|
}
|
|
10047
10887
|
});
|
|
10888
|
+
Object.defineProperty(exports, "MESH_QUEUE_TOP_N", {
|
|
10889
|
+
enumerable: true,
|
|
10890
|
+
get: function() {
|
|
10891
|
+
return MESH_QUEUE_TOP_N;
|
|
10892
|
+
}
|
|
10893
|
+
});
|
|
10048
10894
|
Object.defineProperty(exports, "NATIVE_PROVIDER_SERVICE_INFIX", {
|
|
10049
10895
|
enumerable: true,
|
|
10050
10896
|
get: function() {
|
|
10051
10897
|
return NATIVE_PROVIDER_SERVICE_INFIX;
|
|
10052
10898
|
}
|
|
10053
10899
|
});
|
|
10900
|
+
Object.defineProperty(exports, "OUTBOUND_QUEUE_COOLDOWN_MS", {
|
|
10901
|
+
enumerable: true,
|
|
10902
|
+
get: function() {
|
|
10903
|
+
return OUTBOUND_QUEUE_COOLDOWN_MS;
|
|
10904
|
+
}
|
|
10905
|
+
});
|
|
10906
|
+
Object.defineProperty(exports, "OUTBOUND_QUEUE_SUSTAIN_MS", {
|
|
10907
|
+
enumerable: true,
|
|
10908
|
+
get: function() {
|
|
10909
|
+
return OUTBOUND_QUEUE_SUSTAIN_MS;
|
|
10910
|
+
}
|
|
10911
|
+
});
|
|
10912
|
+
Object.defineProperty(exports, "OUTBOUND_QUEUE_THRESHOLD_BYTES", {
|
|
10913
|
+
enumerable: true,
|
|
10914
|
+
get: function() {
|
|
10915
|
+
return OUTBOUND_QUEUE_THRESHOLD_BYTES;
|
|
10916
|
+
}
|
|
10917
|
+
});
|
|
10054
10918
|
Object.defineProperty(exports, "PARENT_ROUTED_PROVIDER_ADDON_ID", {
|
|
10055
10919
|
enumerable: true,
|
|
10056
10920
|
get: function() {
|
|
@@ -10087,6 +10951,12 @@ Object.defineProperty(exports, "SOCKET_PLANE_TOP_N", {
|
|
|
10087
10951
|
return SOCKET_PLANE_TOP_N;
|
|
10088
10952
|
}
|
|
10089
10953
|
});
|
|
10954
|
+
Object.defineProperty(exports, "SOCKET_QUEUE_TOP_N", {
|
|
10955
|
+
enumerable: true,
|
|
10956
|
+
get: function() {
|
|
10957
|
+
return SOCKET_QUEUE_TOP_N;
|
|
10958
|
+
}
|
|
10959
|
+
});
|
|
10090
10960
|
Object.defineProperty(exports, "SocketChannel", {
|
|
10091
10961
|
enumerable: true,
|
|
10092
10962
|
get: function() {
|
|
@@ -10231,6 +11101,12 @@ Object.defineProperty(exports, "createAddonService", {
|
|
|
10231
11101
|
return createAddonService;
|
|
10232
11102
|
}
|
|
10233
11103
|
});
|
|
11104
|
+
Object.defineProperty(exports, "createArrayBufferCensusGate", {
|
|
11105
|
+
enumerable: true,
|
|
11106
|
+
get: function() {
|
|
11107
|
+
return createArrayBufferCensusGate;
|
|
11108
|
+
}
|
|
11109
|
+
});
|
|
10234
11110
|
Object.defineProperty(exports, "createBrokerDeviceManagerApi", {
|
|
10235
11111
|
enumerable: true,
|
|
10236
11112
|
get: function() {
|
|
@@ -10255,6 +11131,12 @@ Object.defineProperty(exports, "createEventPlaneReader", {
|
|
|
10255
11131
|
return createEventPlaneReader;
|
|
10256
11132
|
}
|
|
10257
11133
|
});
|
|
11134
|
+
Object.defineProperty(exports, "createInspectorArrayBufferCensus", {
|
|
11135
|
+
enumerable: true,
|
|
11136
|
+
get: function() {
|
|
11137
|
+
return createInspectorArrayBufferCensus;
|
|
11138
|
+
}
|
|
11139
|
+
});
|
|
10258
11140
|
Object.defineProperty(exports, "createKernelHwAccel", {
|
|
10259
11141
|
enumerable: true,
|
|
10260
11142
|
get: function() {
|
|
@@ -10273,6 +11155,18 @@ Object.defineProperty(exports, "createLoopDelayMeter", {
|
|
|
10273
11155
|
return createLoopDelayMeter;
|
|
10274
11156
|
}
|
|
10275
11157
|
});
|
|
11158
|
+
Object.defineProperty(exports, "createMeshQueueReader", {
|
|
11159
|
+
enumerable: true,
|
|
11160
|
+
get: function() {
|
|
11161
|
+
return createMeshQueueReader;
|
|
11162
|
+
}
|
|
11163
|
+
});
|
|
11164
|
+
Object.defineProperty(exports, "createOutboundQueueGate", {
|
|
11165
|
+
enumerable: true,
|
|
11166
|
+
get: function() {
|
|
11167
|
+
return createOutboundQueueGate;
|
|
11168
|
+
}
|
|
11169
|
+
});
|
|
10276
11170
|
Object.defineProperty(exports, "createParentUnownedCallHandler", {
|
|
10277
11171
|
enumerable: true,
|
|
10278
11172
|
get: function() {
|
|
@@ -10369,6 +11263,12 @@ Object.defineProperty(exports, "encodeFrame", {
|
|
|
10369
11263
|
return encodeFrame;
|
|
10370
11264
|
}
|
|
10371
11265
|
});
|
|
11266
|
+
Object.defineProperty(exports, "formatArrayBufferCensus", {
|
|
11267
|
+
enumerable: true,
|
|
11268
|
+
get: function() {
|
|
11269
|
+
return formatArrayBufferCensus;
|
|
11270
|
+
}
|
|
11271
|
+
});
|
|
10372
11272
|
Object.defineProperty(exports, "formatEventPlane", {
|
|
10373
11273
|
enumerable: true,
|
|
10374
11274
|
get: function() {
|
|
@@ -10381,6 +11281,18 @@ Object.defineProperty(exports, "formatHeapSpaces", {
|
|
|
10381
11281
|
return formatHeapSpaces;
|
|
10382
11282
|
}
|
|
10383
11283
|
});
|
|
11284
|
+
Object.defineProperty(exports, "formatMeshQueue", {
|
|
11285
|
+
enumerable: true,
|
|
11286
|
+
get: function() {
|
|
11287
|
+
return formatMeshQueue;
|
|
11288
|
+
}
|
|
11289
|
+
});
|
|
11290
|
+
Object.defineProperty(exports, "formatOutboundQueueVerdict", {
|
|
11291
|
+
enumerable: true,
|
|
11292
|
+
get: function() {
|
|
11293
|
+
return formatOutboundQueueVerdict;
|
|
11294
|
+
}
|
|
11295
|
+
});
|
|
10384
11296
|
Object.defineProperty(exports, "formatSocketPlane", {
|
|
10385
11297
|
enumerable: true,
|
|
10386
11298
|
get: function() {
|
|
@@ -10471,6 +11383,12 @@ Object.defineProperty(exports, "ipcParentLink", {
|
|
|
10471
11383
|
return ipcParentLink;
|
|
10472
11384
|
}
|
|
10473
11385
|
});
|
|
11386
|
+
Object.defineProperty(exports, "isArrayBufferCensusReport", {
|
|
11387
|
+
enumerable: true,
|
|
11388
|
+
get: function() {
|
|
11389
|
+
return isArrayBufferCensusReport;
|
|
11390
|
+
}
|
|
11391
|
+
});
|
|
10474
11392
|
Object.defineProperty(exports, "localEndpointPath", {
|
|
10475
11393
|
enumerable: true,
|
|
10476
11394
|
get: function() {
|
|
@@ -10519,6 +11437,12 @@ Object.defineProperty(exports, "readMoleculerFanoutMode", {
|
|
|
10519
11437
|
return readMoleculerFanoutMode;
|
|
10520
11438
|
}
|
|
10521
11439
|
});
|
|
11440
|
+
Object.defineProperty(exports, "readSocketQueues", {
|
|
11441
|
+
enumerable: true,
|
|
11442
|
+
get: function() {
|
|
11443
|
+
return readSocketQueues;
|
|
11444
|
+
}
|
|
11445
|
+
});
|
|
10522
11446
|
Object.defineProperty(exports, "reclaimIntervalMs", {
|
|
10523
11447
|
enumerable: true,
|
|
10524
11448
|
get: function() {
|