@camstack/system 1.2.311 → 1.2.312
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 +6 -8
- package/dist/addon-runner.mjs +6 -8
- package/dist/builtins/native-metrics/native-metrics.addon.js +9 -101
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +3 -91
- package/dist/{child-cap-dispatch-BCJjUtXV.mjs → child-cap-dispatch-CHTiHbsv.mjs} +294 -10
- package/dist/{child-cap-dispatch-Be8e2cBP.js → child-cap-dispatch-Dyn_7ezH.js} +347 -9
- package/dist/index.js +87 -17
- package/dist/index.mjs +88 -19
- package/dist/kernel/index.d.ts +1 -0
- package/dist/kernel/moleculer/process-service.d.ts +9 -0
- package/dist/kernel/moleculer/runner-process-group.d.ts +92 -0
- package/dist/proc-stat-CbC55Kqi.mjs +92 -0
- package/dist/proc-stat-DL-WUOhl.js +127 -0
- package/dist/{resource-monitor-Vp0bPV3U.mjs → resource-monitor-B1v6E07R.mjs} +1 -1
- package/dist/{resource-monitor-LVE1BoLs.js → resource-monitor-DzNn_IZK.js} +5 -5
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import "./builtins/liveness-monitor/index.mjs";
|
|
|
16
16
|
import { LocalAuthAddon, a as require_safe_buffer, i as require_ms, n as ScopedTokenManager, o as ApiKeyManager, r as AuthManager, t as UserManager } from "./builtins/local-auth/local-auth.addon.mjs";
|
|
17
17
|
import "./builtins/local-auth/index.mjs";
|
|
18
18
|
import { LokiDestination, LokiLoggingAddon } from "./builtins/loki-logging/index.mjs";
|
|
19
|
-
import NativeMetricsAddon, {
|
|
19
|
+
import NativeMetricsAddon, { t as NativeMetricsProvider } from "./builtins/native-metrics/native-metrics.addon.mjs";
|
|
20
20
|
import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
|
|
21
21
|
import { SqliteSettingsAddon, o as SqliteSettingsBackend, s as prefixRange } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
|
|
22
22
|
import { ConfigStore, DeviceStore } from "./builtins/sqlite-storage/index.mjs";
|
|
@@ -28,8 +28,8 @@ import "./builtins/system-config/index.mjs";
|
|
|
28
28
|
import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
|
|
29
29
|
import { _ as ensureModel, a as createFileDataPlaneHandler, c as parseRangeHeader, d as ModelDownloadService, g as downloadModel, h as downloadFile, i as startEventLoopStallMonitor, l as parseTokenizedUrl, m as deleteModelFromDisk, n as STALL_TICK_MS, o as contentTypeFor, p as collectModelFiles, r as attributeStall, s as createAuthenticatedFileServer, t as STALL_REPORT_MS, u as resolveFilePath, v as fetchJson, x as isModelDownloaded, y as getModelFilePath } from "./event-loop-stall-monitor-EXjzIxDz.mjs";
|
|
30
30
|
import { C as registerLanHttpHandler, S as readLanHttpState, _ as DEFAULT_LAN_HTTP_PORT, a as writeTlsMode, b as bindPendingLanHttp, c as loadTlsCert, d as collectCertIdentity, f as CA_VALIDITY_DAYS, g as evaluateExistingCert, h as SERVER_AUTH_OID, i as writeExtraSans, l as reissueTlsLeaf, m as MAX_LEAF_VALIDITY_DAYS, n as readTlsAccessStatus, o as validateUploadedTls, p as LEAF_RENEWAL_WINDOW_DAYS, r as readTlsMode, s as ensureTlsCert, t as readExtraSans, u as CA_COMMON_NAME, v as allFamiliesListenHost, x as closeLanHttp, y as applyLanHttp } from "./tls-IM-v7JV4.mjs";
|
|
31
|
-
import { $ as
|
|
32
|
-
import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-
|
|
31
|
+
import { $ as CapUsageRegistry, $n as capActionLabel, $t as buildHeapSample, A as resolveHwAccel, An as createOutboundQueueGate, At as CapabilityUnavailableError, B as udsChildLogToWorkerEntry, Bn as diffSocketPlane, Bt as HEAP_RECLAIM_MIN_INTERVAL_MS, C as getBrokerEventBus, Cn as formatHeapSpaces, Ct as capBareAction, D as subscribePassthrough, Dn as OUTBOUND_QUEUE_COOLDOWN_MS, Dt as serializeTypedArrays, E as setNodeEventInterest, En as selectReportedSpaces, Et as deserializeTypedArrays, F as ipcParentLink, Fn as EMPTY_SOCKET_PLANE_BASELINE, Ft as resolveNpmInvocation, G as CapRouteResolver, Gn as recordSocketFrame, Gt as HUB_MAIN_HEAP_WATCH_LABEL, H as createUdsLoggerWithControl, Hn as readSocketQueues, Ht as HEAP_RECLAIM_TRIGGER_MB, I as localProviderLink, In as SOCKET_PLANE_TOP_N, It as runNpm, J as classifyCapRoute, Jn as socketDirectionMessages, Jt as RSS_BUDGET_REANNOUNCE_MIN_MS, K as callWithServiceDiscovery, Kn as sampleSocketDirection, Kt as HUB_MAIN_RSS_BUDGET_MB, L as createParentUnownedCallHandler, Ln as SOCKET_QUEUE_TOP_N, Lt as createAddonPeerBytesFacility, M as brokerTransportLink, Mn as MESH_QUEUE_TOP_N, Mt as installManifestNativeDeps, N as buildLinkChain, Nn as createMeshQueueReader, Nt as resolveAddonClass, O as AddonDepsManager, On as OUTBOUND_QUEUE_SUSTAIN_MS, Ot as DeviceRegistry, P as deviceCapResolverLink, Pn as formatMeshQueue, Pt as buildNpmChildEnv, Q as readMoleculerFanoutMode, Qn as MAX_CAP_ACTIONS_PER_PEER, Qt as ZERO_LOOP_DELAY, R as createUdsEventBridge, Rn as createSocketPlaneMeter, Rt as hostNodeIdOf, S as clusterEventTopic, Sn as emitHeapDiagnosticReport, St as capActionSuffix, T as registerEventBusService, Tn as readHeapSpaces, Tt as parseCapAction, U as AGENT_CAP_FWD_ACTION, Un as EMPTY_SOCKET_DIRECTION, Ut as HEAP_WATCH_INTERVAL_MS, V as createUdsLogger, Vn as formatSocketPlane, Vt as HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, W as AGENT_CAP_FWD_SERVICE, Wn as createSocketDirectionCounters, Wt as HEAP_WATCH_WARN_RATIO, X as LocalChildRegistry, Xn as CAP_ACTION_UNLABELLED, Xt as RUNNER_HEAP_WATCH_INTERVAL_MS, Y as LocalChildClient, Yn as CAP_ACTION_OVERFLOW_LABEL, Yt as RSS_BUDGET_RELEASE_RATIO, Z as UDS_NO_ROUTE_PREFIX, Zn as EMPTY_CAP_ACTION_READING, Zt as RUNNER_RSS_BUDGET_ENV, _ as getWorkerDeviceRegistry, _n as createInspectorArrayBufferCensus, a as reapProcessGroup, an as nextRssBudgetState, ar as diffEventPlane, at as UdsLocalTransportServer, b as setHubConnected, bn as DECISIVE_HEAP_SPACES, bt as NATIVE_PROVIDER_SERVICE_INFIX, c as runnerOwnsProcessGroup, cn as shouldReclaim, ct as localEndpointPath, d as summarizeMembers, dn as strandedMb, dt as buildNativeCapProxy, en as createDeferredHeapWatchSink, er as createCapActionCensus, et as PARENT_ROUTED_PROVIDER_ADDON_ID, f as installManifestSystemDeps, fn as ARRAY_BUFFER_CENSUS_COOLDOWN_MS, ft as buildUdsNativeCapProxy, g as createUdsAddonContext, gn as createArrayBufferCensusGate, gt as mountNativeCapService, h as createAddonContext, hn as ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, in as hubMainRssBudget, ir as createEventPlaneReader, it as UdsLocalTransportClient, j as brokerCallForCap, jn as formatOutboundQueueVerdict, jt as copyBundledNativeModules, k as createKernelHwAccel, kn as OUTBOUND_QUEUE_THRESHOLD_BYTES, kt as CapabilityHandle, l as signalAllRunnerGroups, ln as startHeapWatch, lt as FrameDecoder, m as adaptBrokerToCluster, mn as ARRAY_BUFFER_CENSUS_THRESHOLD_MB, nn as createV8Reclaimer, nr as EVENT_PLANE_TOP_N, nt as getCapUsageRegistry, o as registerRunnerGroup, on as parseRssBudgetMb, or as formatEventPlane, ot as SocketChannel, p as installManifestPythonDeps, pn as ARRAY_BUFFER_CENSUS_SUSTAIN_MS, pt as createBrokerDeviceManagerApi, q as CapRouteError, qn as socketDirectionBytes, qt as HUB_RSS_BUDGET_ENV, r as defaultProcessGroupOps, rn as describeRss, rr as createEventPlaneMeter, rt as createLocalTransport, s as releaseRunnerGroup, sn as reclaimIntervalMs, st as PeerAnsweredError, t as CAP_METHOD_NOT_IMPLEMENTED, tn as createLoopDelayMeter, tr as diffCapActionCensus, tt as __resetCapUsageRegistryForTests, un as startRunnerHeapWatch, ut as encodeFrame, v as getOrInitReadinessRegistry, vn as formatArrayBufferCensus, vt as createAddonService, w as getMoleculerEventStats, wn as heapSpaceField, wt as capServiceName, x as EVENT_TOPIC_PREFIX, xn as HEAP_SPACE_REPORT_MIN_MB, xt as capActionName, y as getOrInitReadinessRegistryForClient, yn as isArrayBufferCensusReport, yt as validateProviderRegistrations, z as createUdsEventBus, zn as createSocketPlaneReader, zt as createAddonDataPlaneFacility } from "./child-cap-dispatch-CHTiHbsv.mjs";
|
|
32
|
+
import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-B1v6E07R.mjs";
|
|
33
33
|
import { t as CustomActionRegistry } from "./custom-action-registry-B4Cf4O_X.mjs";
|
|
34
34
|
import { z } from "zod";
|
|
35
35
|
import { createHash, randomUUID, timingSafeEqual } from "node:crypto";
|
|
@@ -95008,7 +95008,10 @@ var CRASH_WINDOW_MS = 6e4;
|
|
|
95008
95008
|
var MAX_CRASHES_IN_WINDOW = 5;
|
|
95009
95009
|
function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parentUdsPath) {
|
|
95010
95010
|
const spawnFn = deps?.spawnFn ?? spawn;
|
|
95011
|
+
const groupOps = deps?.processGroups ?? defaultProcessGroupOps;
|
|
95012
|
+
const runnersOwnGroups = runnerOwnsProcessGroup(deps?.platform ?? process.platform);
|
|
95011
95013
|
const processes = /* @__PURE__ */ new Map();
|
|
95014
|
+
const pendingGroupReaps = /* @__PURE__ */ new Map();
|
|
95012
95015
|
const runnerLocks = /* @__PURE__ */ new Map();
|
|
95013
95016
|
function withRunnerLock(runnerId, fn) {
|
|
95014
95017
|
const result = (runnerLocks.get(runnerId) ?? Promise.resolve()).then(fn, fn);
|
|
@@ -95048,6 +95051,49 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
95048
95051
|
}, data));
|
|
95049
95052
|
}
|
|
95050
95053
|
/**
|
|
95054
|
+
* One line per reap that found something, or that failed. An empty group is the
|
|
95055
|
+
* normal case after a clean stop and stays quiet at debug. The kernel cannot say
|
|
95056
|
+
* which device a child served — the runner id is the finest attribution it has.
|
|
95057
|
+
*/
|
|
95058
|
+
function logGroupReap(runnerId, result) {
|
|
95059
|
+
const found = result.membersAtExit;
|
|
95060
|
+
const killed = result.killed ?? [];
|
|
95061
|
+
const line = `[${runnerId}] runner group reaped {pgid=${result.pgid}, members=${found === null ? "unreadable" : found.length}${found !== null && found.length > 0 ? ` (${summarizeMembers(found)})` : ""}, sigkilled=${killed.length}${killed.length > 0 ? ` (${summarizeMembers(killed)})` : ""}${result.error !== null ? `, error=${result.error}` : ""}}`;
|
|
95062
|
+
const logger = capturedBroker?.logger;
|
|
95063
|
+
if (result.error !== null || killed.length > 0) if (logger) logger.warn(line);
|
|
95064
|
+
else console.error(line);
|
|
95065
|
+
else if ((found?.length ?? 0) > 0 || found === null) logger?.info(line);
|
|
95066
|
+
else logger?.debug(line);
|
|
95067
|
+
}
|
|
95068
|
+
/**
|
|
95069
|
+
* Reap a runner's process group (D668): SIGTERM, a bounded grace, SIGKILL for what
|
|
95070
|
+
* remains. Memoized on the entry so the `exit` handler and a stop/restart that timed
|
|
95071
|
+
* out waiting for that exit share one reap, and registered in `pendingGroupReaps` so
|
|
95072
|
+
* the next spawn under the same id waits for it. Never rejects.
|
|
95073
|
+
*/
|
|
95074
|
+
function reapRunnerGroup(entry) {
|
|
95075
|
+
if (entry.groupReap !== null) return entry.groupReap;
|
|
95076
|
+
if (!entry.ownsGroup) {
|
|
95077
|
+
entry.groupReap = Promise.resolve();
|
|
95078
|
+
return entry.groupReap;
|
|
95079
|
+
}
|
|
95080
|
+
const reap = reapProcessGroup(entry.pid, groupOps).then((result) => logGroupReap(entry.name, result)).catch((err) => {
|
|
95081
|
+
capturedBroker?.logger.error(`[${entry.name}] runner group reap threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
95082
|
+
}).finally(() => {
|
|
95083
|
+
releaseRunnerGroup(entry.pid);
|
|
95084
|
+
if (pendingGroupReaps.get(entry.name) === tracked) pendingGroupReaps.delete(entry.name);
|
|
95085
|
+
});
|
|
95086
|
+
const previous = pendingGroupReaps.get(entry.name);
|
|
95087
|
+
const tracked = previous ? Promise.all([previous, reap]).then(() => void 0) : reap;
|
|
95088
|
+
pendingGroupReaps.set(entry.name, tracked);
|
|
95089
|
+
entry.groupReap = reap;
|
|
95090
|
+
return reap;
|
|
95091
|
+
}
|
|
95092
|
+
/** Resolves once every reap started for `runnerId` has finished. */
|
|
95093
|
+
async function awaitPredecessorReap(runnerId) {
|
|
95094
|
+
await pendingGroupReaps.get(runnerId);
|
|
95095
|
+
}
|
|
95096
|
+
/**
|
|
95051
95097
|
* Spawn ONE runner subprocess (`addon-runner.js`) that hosts the
|
|
95052
95098
|
* `addons` set sharing a single Moleculer broker. The normal case is
|
|
95053
95099
|
* a size-1 `addons` array — one addon, one process; the media group
|
|
@@ -95096,8 +95142,9 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
95096
95142
|
"pipe",
|
|
95097
95143
|
"pipe"
|
|
95098
95144
|
],
|
|
95099
|
-
detached:
|
|
95145
|
+
detached: runnersOwnGroups
|
|
95100
95146
|
});
|
|
95147
|
+
const ownsGroup = runnersOwnGroups && typeof child.pid === "number" && child.pid > 1;
|
|
95101
95148
|
const entry = {
|
|
95102
95149
|
pid: child.pid,
|
|
95103
95150
|
nodeId,
|
|
@@ -95108,16 +95155,23 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
95108
95155
|
startedAt: Date.now(),
|
|
95109
95156
|
restartCount: 0,
|
|
95110
95157
|
state: "starting",
|
|
95111
|
-
child
|
|
95158
|
+
child,
|
|
95159
|
+
ownsGroup,
|
|
95160
|
+
groupReap: null
|
|
95112
95161
|
};
|
|
95162
|
+
if (ownsGroup) registerRunnerGroup(entry.pid, {
|
|
95163
|
+
runnerId,
|
|
95164
|
+
kill: groupOps.kill
|
|
95165
|
+
});
|
|
95113
95166
|
child.stdout?.on("data", (data) => {
|
|
95114
|
-
if (data.toString().includes(`READY:${nodeId}`)) entry.state = "running";
|
|
95167
|
+
if (data.toString().includes(`READY:${nodeId}`) && entry.state === "starting") entry.state = "running";
|
|
95115
95168
|
});
|
|
95116
95169
|
child.stderr?.on("data", (data) => {
|
|
95117
95170
|
process.stderr.write(`[${runnerId}] ${data.toString()}`);
|
|
95118
95171
|
});
|
|
95119
95172
|
child.on("exit", (code, signal) => {
|
|
95120
95173
|
console.error(`[process-service EXIT] runner=${runnerId} code=${String(code)} signal=${String(signal)} state=${entry.state}`);
|
|
95174
|
+
const groupReap = reapRunnerGroup(entry);
|
|
95121
95175
|
if (entry.state === "stopped") return;
|
|
95122
95176
|
entry.state = "crashed";
|
|
95123
95177
|
capturedBroker?.logger.error(`[${runnerId}] Process exited (code=${code}, signal=${signal})`);
|
|
@@ -95156,6 +95210,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
95156
95210
|
if (capturedBroker) forceMoleculerDisconnect(capturedBroker, entry.nodeId);
|
|
95157
95211
|
setTimeout(() => {
|
|
95158
95212
|
withRunnerLock(runnerId, async () => {
|
|
95213
|
+
await groupReap;
|
|
95159
95214
|
if (processes.has(runnerId)) return;
|
|
95160
95215
|
const restarted = spawnRunner(runnerId, addons, env);
|
|
95161
95216
|
restarted.restartCount = entry.restartCount + 1;
|
|
@@ -95168,10 +95223,12 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
95168
95223
|
return {
|
|
95169
95224
|
name: "$process",
|
|
95170
95225
|
actions: {
|
|
95171
|
-
spawnRunner: { handler(ctx) {
|
|
95226
|
+
spawnRunner: { async handler(ctx) {
|
|
95172
95227
|
const { runnerId, addons, env } = ctx.params;
|
|
95173
95228
|
if (processes.has(runnerId)) throw new Error(`Runner "${runnerId}" already running`);
|
|
95174
95229
|
if (!Array.isArray(addons) || addons.length === 0) throw new Error(`spawnRunner requires non-empty addons array`);
|
|
95230
|
+
await awaitPredecessorReap(runnerId);
|
|
95231
|
+
if (processes.has(runnerId)) throw new Error(`Runner "${runnerId}" already running`);
|
|
95175
95232
|
invalidateHeapProfiles(addons);
|
|
95176
95233
|
const entry = spawnRunner(runnerId, addons, env);
|
|
95177
95234
|
return {
|
|
@@ -95208,6 +95265,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
95208
95265
|
});
|
|
95209
95266
|
});
|
|
95210
95267
|
}
|
|
95268
|
+
await reapRunnerGroup(target);
|
|
95211
95269
|
processes.delete(name);
|
|
95212
95270
|
crashSupervisor.reset(name);
|
|
95213
95271
|
forceMoleculerDisconnect(ctx.broker, deadNodeId);
|
|
@@ -95244,6 +95302,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
95244
95302
|
});
|
|
95245
95303
|
});
|
|
95246
95304
|
}
|
|
95305
|
+
await reapRunnerGroup(target);
|
|
95247
95306
|
processes.delete(name);
|
|
95248
95307
|
crashSupervisor.reset(name);
|
|
95249
95308
|
forceMoleculerDisconnect(ctx.broker, deadNodeId);
|
|
@@ -95303,6 +95362,7 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
95303
95362
|
});
|
|
95304
95363
|
});
|
|
95305
95364
|
}
|
|
95365
|
+
await reapRunnerGroup(entry);
|
|
95306
95366
|
processes.delete(name);
|
|
95307
95367
|
crashSupervisor.reset(name);
|
|
95308
95368
|
invalidateHeapProfiles(runnerAddons);
|
|
@@ -95353,19 +95413,28 @@ function createProcessService(parentNodeId, dataDir, deps, parentTcpPort, parent
|
|
|
95353
95413
|
started() {
|
|
95354
95414
|
capturedBroker = this.broker;
|
|
95355
95415
|
},
|
|
95356
|
-
stopped() {
|
|
95357
|
-
const
|
|
95358
|
-
|
|
95416
|
+
async stopped() {
|
|
95417
|
+
const entries = [...processes.values()];
|
|
95418
|
+
await Promise.all(entries.map(async (entry) => {
|
|
95359
95419
|
entry.state = "stopped";
|
|
95360
|
-
entry.child.
|
|
95361
|
-
|
|
95362
|
-
|
|
95363
|
-
|
|
95364
|
-
|
|
95365
|
-
|
|
95366
|
-
|
|
95420
|
+
if (!(entry.child.exitCode !== null || entry.child.signalCode !== null)) {
|
|
95421
|
+
entry.child.kill("SIGTERM");
|
|
95422
|
+
await new Promise((resolve) => {
|
|
95423
|
+
const timer = setTimeout(() => {
|
|
95424
|
+
if (entry.child.exitCode === null && entry.child.signalCode === null) {
|
|
95425
|
+
capturedBroker?.logger.warn(`[${entry.name}] Did not exit on SIGTERM — sending SIGKILL`);
|
|
95426
|
+
entry.child.kill("SIGKILL");
|
|
95427
|
+
}
|
|
95428
|
+
resolve();
|
|
95429
|
+
}, 3e3);
|
|
95430
|
+
entry.child.once("exit", () => {
|
|
95431
|
+
clearTimeout(timer);
|
|
95432
|
+
resolve();
|
|
95433
|
+
});
|
|
95434
|
+
});
|
|
95367
95435
|
}
|
|
95368
|
-
|
|
95436
|
+
await reapRunnerGroup(entry);
|
|
95437
|
+
}));
|
|
95369
95438
|
processes.clear();
|
|
95370
95439
|
capturedBroker = null;
|
|
95371
95440
|
}
|
|
@@ -95849,4 +95918,4 @@ var LifecycleJobEngine = class {
|
|
|
95849
95918
|
}
|
|
95850
95919
|
};
|
|
95851
95920
|
//#endregion
|
|
95852
|
-
export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, ARRAY_BUFFER_CENSUS_COOLDOWN_MS, ARRAY_BUFFER_CENSUS_SUSTAIN_MS, ARRAY_BUFFER_CENSUS_THRESHOLD_MB, ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CAP_ACTION_OVERFLOW_LABEL, CAP_ACTION_UNLABELLED, CAP_METHOD_NOT_IMPLEMENTED, CA_COMMON_NAME, CA_VALIDITY_DAYS, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ComposerAddon, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CoreBlocksAddon, CustomActionRegistry, DECISIVE_HEAP_SPACES, DEFAULT_DATA_PATH, DEFAULT_LAN_HTTP_PORT, DEFAULT_LOG_LEVEL, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EMPTY_CAP_ACTION_READING, EMPTY_SOCKET_DIRECTION, EMPTY_SOCKET_PLANE_BASELINE, EVENT_PLANE_TOP_N, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HEAP_RECLAIM_MIN_INTERVAL_MS, HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, HEAP_RECLAIM_TRIGGER_MB, HEAP_SPACE_REPORT_MIN_MB, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HUB_MAIN_HEAP_WATCH_LABEL, HUB_MAIN_RSS_BUDGET_MB, HUB_RSS_BUDGET_ENV, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LEAF_RENEWAL_WINDOW_DAYS, LifecycleJobEngine, LifecycleStateMachine, LivenessMonitorAddon, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, LoggingGate, LokiDestination, LokiLoggingAddon, MAX_CAP_ACTIONS_PER_PEER, MAX_LEAF_VALIDITY_DAYS, MESH_QUEUE_TOP_N, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NATIVE_SCAN_DEPTH, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, OUTBOUND_QUEUE_COOLDOWN_MS, OUTBOUND_QUEUE_SUSTAIN_MS, OUTBOUND_QUEUE_THRESHOLD_BYTES, PARENT_ROUTED_PROVIDER_ADDON_ID, PYTHON_VERSION, PeerAnsweredError, PipelineRunner, PipelineValidator, PythonEnvManager, QUARANTINE_DIRNAME, RESTART_MARKER_FILE, RSS_BUDGET_REANNOUNCE_MIN_MS, RSS_BUDGET_RELEASE_RATIO, RUNNER_HEAP_WATCH_INTERVAL_MS, RUNNER_RSS_BUDGET_ENV, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, SERVER_AUTH_OID, SOCKET_PLANE_TOP_N, SOCKET_QUEUE_TOP_N, STALL_REPORT_MS, STALL_TICK_MS, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, TRADITIONAL_NATIVE_PACKAGES, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, ZERO_LOOP_DELAY, __resetCapUsageRegistryForTests, __resetLoggingGateForTests, adaptBrokerToCluster, addonSettingsCapability, allFamiliesListenHost, applyLanHttp, attributeStall, bindPendingLanHttp, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildHeapSample, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildNpmChildEnv, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, canServeDataPlane, capActionLabel, capActionName, capActionSuffix, capBareAction, capServiceName, classifyAddonDir, classifyCapRoute, clearPendingRestart, closeLanHttp, clusterEventTopic, clusterSecretMatches, collectCertIdentity, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonPeerBytesFacility, createAddonService, createArrayBufferCensusGate, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCapActionCensus, createCoreCapService, createDeferredHeapWatchSink, createDoorSettingsView, createEventPlaneMeter, createEventPlaneReader, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createInspectorArrayBufferCensus, createKernelHwAccel, createLocalTransport, createLoopDelayMeter, createMeshQueueReader, createOutboundQueueGate, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createSocketDirectionCounters, createSocketPlaneMeter, createSocketPlaneReader, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, createV8Reclaimer, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, describeRss, detectWorkspacePackagesDir, deviceCapResolverLink, diffCapActionCensus, diffEventPlane, diffSocketPlane, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, emitHeapDiagnosticReport, encodeFrame, ensureAddonNativePrebuilds, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensureNativePrebuilds, ensurePython, ensureTlsCert, evaluateExistingCert, expandCapMethods, fetchJson, findInPath, formatArrayBufferCensus, formatEventPlane, formatHeapSpaces, formatLogLine, formatMeshQueue, formatOutboundQueueVerdict, formatSocketPlane, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getLoggingGate, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hasDotNode, hashClusterSecret, heapSpaceField, hostNodeIdOf, hubMainRssBudget, installManifestNativeDeps, installManifestPythonDeps, installManifestSystemDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcParentLink, isAddonDeploySource, isArrayBufferCensusReport, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, isolatedBuiltinPhase, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, nextRssBudgetState, parseCapAction, parseRangeHeader, parseRssBudgetMb, parseTokenizedUrl, partitionIsolatedBuiltinIds, proxyToUpstream, quarantineAddonResidue, readExtraSans, readHeapSpaces, readLanHttpState, readMoleculerFanoutMode, readPendingRestart, readSocketQueues, readTlsAccessStatus, readTlsMode, readinessKey, reclaimIntervalMs, recordSocketFrame, registerEventBusService, registerLanHttpHandler, reissueTlsLeaf, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runHubAddonBoot, runNpm, sampleSocketDirection, scheduleSelfRestart, scopeKey, scopesAllowAddon, scopesAllowDeviceCap, selectAddonResidue, selectReportedSpaces, serializeTypedArrays, setHubConnected, setNodeEventInterest, setWrapperActive, shouldReclaim, socketDirectionBytes, socketDirectionMessages, startEventLoopStallMonitor, startHeapWatch, startRunnerHeapWatch, strandedMb, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, validateUploadedTls, waitUntilReady, writeExtraSans, writePendingRestart, writeTlsMode };
|
|
95921
|
+
export { AGENT_CAP_FWD_ACTION, AGENT_CAP_FWD_SERVICE, AGENT_READINESS_SERVICE_NAME, ALL_CAPABILITY_DEFINITIONS, ARRAY_BUFFER_CENSUS_COOLDOWN_MS, ARRAY_BUFFER_CENSUS_SUSTAIN_MS, ARRAY_BUFFER_CENSUS_THRESHOLD_MB, ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, AddonApiFactory, AddonDepsManager, AddonEngineManager, AddonHealthMonitor, AddonInstaller, AddonLoader, AddonManifest, AddonRouteRegistry, AlertCenterAddon, ApiKeyManager, AuthManager, CAP_ACTION_OVERFLOW_LABEL, CAP_ACTION_UNLABELLED, CAP_METHOD_NOT_IMPLEMENTED, CA_COMMON_NAME, CA_VALIDITY_DAYS, CLUSTER_SECRET_MISMATCH_TYPE, CLUSTER_SECRET_REJECTED_EXIT_CODE, CORE_CAP_SERVICE_NAME, CapRouteError, CapRouteResolver, CapUsageRegistry, CapabilityHandle, CapabilityRegistry, CapabilityUnavailableError, ComposerAddon, ConfigManager, ConfigStore, ConsoleDestination, ConsoleLoggingAddon, CoreBlocksAddon, CustomActionRegistry, DECISIVE_HEAP_SPACES, DEFAULT_DATA_PATH, DEFAULT_LAN_HTTP_PORT, DEFAULT_LOG_LEVEL, DEVICE_SETTINGS_CONTRIBUTION_METHODS, DEVICE_STATUS_METHOD, DataPlaneRegistry, DeviceManagerAddon, DeviceRegistry, DeviceStore, EMPTY_CAP_ACTION_READING, EMPTY_SOCKET_DIRECTION, EMPTY_SOCKET_PLANE_BASELINE, EVENT_PLANE_TOP_N, EVENT_TOPIC_PREFIX, EngineManagerResolver, EventBus, FeatureManager, FilesystemStorageAddon, FilesystemStorageProvider, FrameDecoder, FsStorageBackend, HEALTH_MONITOR_GRACE_PERIOD_MS, HEALTH_MONITOR_RETRY_INTERVALS_MS, HEALTH_MONITOR_TICK_MS, HEAP_RECLAIM_MIN_INTERVAL_MS, HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, HEAP_RECLAIM_TRIGGER_MB, HEAP_SPACE_REPORT_MIN_MB, HEAP_WATCH_INTERVAL_MS, HEAP_WATCH_WARN_RATIO, HUB_CAP_FWD_ACTION, HUB_CAP_FWD_SERVICE, HUB_MAIN_HEAP_WATCH_LABEL, HUB_MAIN_RSS_BUDGET_MB, HUB_RSS_BUDGET_ENV, HubForwarderAddon, HubForwarderDestination, HubLogForwarder, HubNodeRegistry, INFRA_CAPABILITIES, IntegrationRegistry, JobJournal, LEAF_RENEWAL_WINDOW_DAYS, LifecycleJobEngine, LifecycleStateMachine, LivenessMonitorAddon, LocalAuthAddon, LocalChildClient, LocalChildRegistry, LogManager, LogRingBuffer, LoggingGate, LokiDestination, LokiLoggingAddon, MAX_CAP_ACTIONS_PER_PEER, MAX_LEAF_VALIDITY_DAYS, MESH_QUEUE_TOP_N, METHOD_ACCESS_MAP, ModelDownloadService, NATIVE_PROVIDER_SERVICE_INFIX, NATIVE_SCAN_DEPTH, NativeMetricsAddon, NativeMetricsProvider, NetworkQualityTracker, NotificationService, OUTBOUND_QUEUE_COOLDOWN_MS, OUTBOUND_QUEUE_SUSTAIN_MS, OUTBOUND_QUEUE_THRESHOLD_BYTES, PARENT_ROUTED_PROVIDER_ADDON_ID, PYTHON_VERSION, PeerAnsweredError, PipelineRunner, PipelineValidator, PythonEnvManager, QUARANTINE_DIRNAME, RESTART_MARKER_FILE, RSS_BUDGET_REANNOUNCE_MIN_MS, RSS_BUDGET_RELEASE_RATIO, RUNNER_HEAP_WATCH_INTERVAL_MS, RUNNER_RSS_BUDGET_ENV, RUNTIME_DEFAULTS, ReadinessRegistry, ReadinessTimeoutError, ReplEngine, RingBuffer, SERVER_AUTH_OID, SOCKET_PLANE_TOP_N, SOCKET_QUEUE_TOP_N, STALL_REPORT_MS, STALL_TICK_MS, ScopedLogger, ScopedTokenManager, SocketChannel, SqliteSettingsAddon, SqliteSettingsBackend, StagingArea, StorageLocationManager, StorageManager, StorageOrchestratorAddon, StorageOrchestratorService, SystemConfigAddon, SystemEventBus, TRADITIONAL_NATIVE_PACKAGES, ToastService, UDS_NO_ROUTE_PREFIX, UdsLocalTransportClient, UdsLocalTransportServer, UserManager, WinstonDestination, WinstonLoggingAddon, ZERO_LOOP_DELAY, __resetCapUsageRegistryForTests, __resetLoggingGateForTests, adaptBrokerToCluster, addonSettingsCapability, allFamiliesListenHost, applyLanHttp, attributeStall, bindPendingLanHttp, bootstrapSchema, brokerCallForCap, brokerTransportLink, buildBinaryPath, buildCapRouters, buildHeapSample, buildLinkChain, buildNativeCapProxy, buildNodeManifest, buildNpmChildEnv, buildStorageLocationRegistry, buildUdsNativeCapProxy, builderMountedCapNames, callRegisterNodeWithRetry, callWithServiceDiscovery, canServeDataPlane, capActionLabel, capActionName, capActionSuffix, capBareAction, capServiceName, classifyAddonDir, classifyCapRoute, clearPendingRestart, closeLanHttp, clusterEventTopic, clusterSecretMatches, collectCertIdentity, collectModelFiles, contentTypeFor, copyDirRecursive, copyExtraFileDirs, createAddonContext, createAddonDataPlaneFacility, createAddonPeerBytesFacility, createAddonService, createArrayBufferCensusGate, createAuthenticatedFileServer, createBroker, createBrokerDeviceManagerApi, createCapActionCensus, createCoreCapService, createDeferredHeapWatchSink, createDoorSettingsView, createEventPlaneMeter, createEventPlaneReader, createFileDataPlaneHandler, createHubCapForwardService, createHubService, createInspectorArrayBufferCensus, createKernelHwAccel, createLocalTransport, createLoopDelayMeter, createMeshQueueReader, createOutboundQueueGate, createParentUnownedCallHandler, createProcessService, createReadinessService, createReadinessServiceForRegistry, createScopedProcessManager, createSocketDirectionCounters, createSocketPlaneMeter, createSocketPlaneReader, createStreamProbeBrokerService, createUdsAddonContext, createUdsEventBridge, createUdsEventBus, createUdsLogger, createUdsLoggerWithControl, createV8Reclaimer, deleteModelFromDisk, deriveAgentListenPort, describeProviderKindDrift, describeRss, detectWorkspacePackagesDir, deviceCapResolverLink, diffCapActionCensus, diffEventPlane, diffSocketPlane, downloadBinary, downloadFile, downloadModel, emitDownForOwnedCaps, emitHeapDiagnosticReport, encodeFrame, ensureAddonNativePrebuilds, ensureBinary, ensureDir, ensureFfmpeg, ensureLibraryBuilt, ensureModel, ensureNativePrebuilds, ensurePython, ensureTlsCert, evaluateExistingCert, expandCapMethods, fetchJson, findInPath, formatArrayBufferCensus, formatEventPlane, formatHeapSpaces, formatLogLine, formatMeshQueue, formatOutboundQueueVerdict, formatSocketPlane, getBrokerEventBus, getCapUsageRegistry, getFfmpegDownloadUrl, getLoggingGate, getModelFilePath, getMoleculerEventStats, getOrInitReadinessRegistry, getOrInitReadinessRegistryForClient, getPidStats, getPlatformInfo, getPythonDownloadUrl, getRestartMarkerPath, getSinglePidStats, getWorkerDeviceRegistry, hasDotNode, hashClusterSecret, heapSpaceField, hostNodeIdOf, hubMainRssBudget, installManifestNativeDeps, installManifestPythonDeps, installManifestSystemDeps, installPackageFromNpm, installPythonPackages, installPythonRequirements, ipcParentLink, isAddonDeploySource, isArrayBufferCensusReport, isArrayOutputSchema, isClusterSecretMismatchError, isCollectionArrayMethod, isInfraCapability, isModelDownloaded, isSourceNewer, isolatedBuiltinPhase, loadTlsCert, localEndpointPath, localProviderLink, mountNativeCapService, nextRssBudgetState, parseCapAction, parseRangeHeader, parseRssBudgetMb, parseTokenizedUrl, partitionIsolatedBuiltinIds, proxyToUpstream, quarantineAddonResidue, readExtraSans, readHeapSpaces, readLanHttpState, readMoleculerFanoutMode, readPendingRestart, readSocketQueues, readTlsAccessStatus, readTlsMode, readinessKey, reclaimIntervalMs, recordSocketFrame, registerEventBusService, registerLanHttpHandler, reissueTlsLeaf, resolveFilePath, resolveHwAccel, resolveNpmInvocation, runHubAddonBoot, runNpm, sampleSocketDirection, scheduleSelfRestart, scopeKey, scopesAllowAddon, scopesAllowDeviceCap, selectAddonResidue, selectReportedSpaces, serializeTypedArrays, setHubConnected, setNodeEventInterest, setWrapperActive, shouldReclaim, signalAllRunnerGroups, socketDirectionBytes, socketDirectionMessages, startEventLoopStallMonitor, startHeapWatch, startRunnerHeapWatch, strandedMb, stripCamstackDeps, subscribePassthrough, udsChildLogToWorkerEntry, validateProviderRegistrations, validateUploadedTls, waitUntilReady, writeExtraSans, writePendingRestart, writeTlsMode };
|
package/dist/kernel/index.d.ts
CHANGED
|
@@ -73,6 +73,7 @@ export { localProviderLink, brokerTransportLink, deviceCapResolverLink, ipcParen
|
|
|
73
73
|
export type { LocalProviderResolver, BrokerCapCallOpts } from './moleculer/trpc-links.js';
|
|
74
74
|
export { createProcessService } from './moleculer/process-service.js';
|
|
75
75
|
export type { ProcessInfo } from './moleculer/process-service.js';
|
|
76
|
+
export { signalAllRunnerGroups } from './moleculer/runner-process-group.js';
|
|
76
77
|
export { CapUsageRegistry, getCapUsageRegistry, PARENT_ROUTED_PROVIDER_ADDON_ID, __resetCapUsageRegistryForTests, } from './moleculer/cap-usage-registry.js';
|
|
77
78
|
export type { CapUsageCallRecord, CapUsageEdge, CapUsageRegistryOptions, CapUsageStats, } from './moleculer/cap-usage-registry.js';
|
|
78
79
|
export { createScopedProcessManager } from './moleculer/process-context.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ChildProcess, SpawnOptions } from 'node:child_process';
|
|
2
2
|
import { ServiceSchema } from 'moleculer';
|
|
3
|
+
import { ProcessGroupOps } from './runner-process-group.js';
|
|
3
4
|
/** One addon hosted inside a runner subprocess. */
|
|
4
5
|
interface RunnerAddonSpec {
|
|
5
6
|
readonly addonId: string;
|
|
@@ -137,6 +138,10 @@ interface SpawnedProcess {
|
|
|
137
138
|
*/
|
|
138
139
|
state: 'starting' | 'running' | 'stopping' | 'stopped' | 'crashed' | 'failed';
|
|
139
140
|
child: ChildProcess;
|
|
141
|
+
/** The runner leads its own process group (POSIX, D668); its pgid is its `pid`. */
|
|
142
|
+
readonly ownsGroup: boolean;
|
|
143
|
+
/** The reap of this runner's group, once started. Memoized: every exit path shares it. */
|
|
144
|
+
groupReap: Promise<void> | null;
|
|
140
145
|
}
|
|
141
146
|
interface ProcessInfo {
|
|
142
147
|
readonly pid: number;
|
|
@@ -174,6 +179,10 @@ interface ProcessInfo {
|
|
|
174
179
|
type SpawnFn = (command: string, args: readonly string[], opts: SpawnOptions) => ChildProcess;
|
|
175
180
|
interface ProcessServiceDeps {
|
|
176
181
|
readonly spawnFn?: SpawnFn;
|
|
182
|
+
/** Group signalling + member listing. Tests inject a fake: a fake pid must never be signalled. */
|
|
183
|
+
readonly processGroups?: ProcessGroupOps;
|
|
184
|
+
/** Platform deciding whether a runner leads a process group. Defaults to `process.platform`. */
|
|
185
|
+
readonly platform?: NodeJS.Platform;
|
|
177
186
|
}
|
|
178
187
|
declare function createProcessService(parentNodeId: string, dataDir: string, deps?: ProcessServiceDeps, parentTcpPort?: number, parentUdsPath?: string): ServiceSchema;
|
|
179
188
|
export type { ProcessInfo, SpawnedProcess };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/** SIGTERM → SIGKILL grace for a runner's group, in ms. ffmpeg finalises a segment well inside it. */
|
|
2
|
+
export declare const RUNNER_GROUP_REAP_GRACE_MS = 2000;
|
|
3
|
+
/** Whether a runner is spawned as a process-group leader on this platform. */
|
|
4
|
+
export declare function runnerOwnsProcessGroup(platform?: NodeJS.Platform): boolean;
|
|
5
|
+
/** One live (non-zombie) member of a process group. */
|
|
6
|
+
export interface GroupMember {
|
|
7
|
+
readonly pid: number;
|
|
8
|
+
/** Executable name (`ffmpeg`, `llama-server`, …). */
|
|
9
|
+
readonly command: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The OS surface a group reap needs. Injectable so a unit test never signals a real pid:
|
|
13
|
+
* the fake-spawn suites hand out pids like 70001, and a `kill(-70001)` on a developer's
|
|
14
|
+
* machine is not a test.
|
|
15
|
+
*/
|
|
16
|
+
export interface ProcessGroupOps {
|
|
17
|
+
/** `process.kill`. A negative pid addresses a group; signal 0 probes. Throws ESRCH when empty. */
|
|
18
|
+
readonly kill: (pid: number, signal: NodeJS.Signals | 0) => void;
|
|
19
|
+
/** Live members of `pgid`, or `null` when the table could not be read. */
|
|
20
|
+
readonly listMembers: (pgid: number) => Promise<readonly GroupMember[] | null>;
|
|
21
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
22
|
+
readonly graceMs: number;
|
|
23
|
+
}
|
|
24
|
+
/** What one reap did. `membersAtExit === null` means the table was unreadable, not empty. */
|
|
25
|
+
export interface GroupReapResult {
|
|
26
|
+
readonly pgid: number;
|
|
27
|
+
readonly membersAtExit: readonly GroupMember[] | null;
|
|
28
|
+
/** Members still present after the grace, which were sent SIGKILL. */
|
|
29
|
+
readonly killed: readonly GroupMember[] | null;
|
|
30
|
+
/** The group was already empty when the reap began — the normal case after a clean stop. */
|
|
31
|
+
readonly alreadyEmpty: boolean;
|
|
32
|
+
/** A signal the OS refused for a reason other than ESRCH (EPERM). */
|
|
33
|
+
readonly error: string | null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* SIGTERM the whole group, wait up to `graceMs` for it to empty, SIGKILL what is left.
|
|
37
|
+
*
|
|
38
|
+
* pid reuse: POSIX never hands out a pid while a process group with that id exists, so
|
|
39
|
+
* the SIGTERM always lands on our group. The SIGKILL is sent only when the group is
|
|
40
|
+
* still populated at the end of the grace, which is the same group; the residual window
|
|
41
|
+
* (group empties, the pid wraps around AND is reused as a new group leader, all inside
|
|
42
|
+
* the grace) is not reachable in practice.
|
|
43
|
+
*/
|
|
44
|
+
export declare function reapProcessGroup(pgid: number, ops: ProcessGroupOps): Promise<GroupReapResult>;
|
|
45
|
+
/** `ffmpeg×28 llama-server×1` — what a reap line says it removed. */
|
|
46
|
+
export declare function summarizeMembers(members: readonly GroupMember[]): string;
|
|
47
|
+
/** Parse `ps -A -o pid=,pgid=,stat=,comm=` for one group, zombies excluded. */
|
|
48
|
+
export declare function parsePsGroupMembers(stdout: string, pgid: number): readonly GroupMember[];
|
|
49
|
+
/** Live members of `pgid`: `/proc` on Linux, `ps` elsewhere; `null` when neither answers. */
|
|
50
|
+
export declare function listProcessGroupMembers(pgid: number, platform?: NodeJS.Platform): Promise<readonly GroupMember[] | null>;
|
|
51
|
+
export declare const defaultProcessGroupOps: ProcessGroupOps;
|
|
52
|
+
interface OwnedGroup {
|
|
53
|
+
readonly runnerId: string;
|
|
54
|
+
readonly kill: ProcessGroupOps['kill'];
|
|
55
|
+
}
|
|
56
|
+
export declare function registerRunnerGroup(pgid: number, group: OwnedGroup): void;
|
|
57
|
+
export declare function releaseRunnerGroup(pgid: number): void;
|
|
58
|
+
/**
|
|
59
|
+
* Signal every runner group this process owns, synchronously. Hub-main's SIGTERM/SIGINT
|
|
60
|
+
* handler calls it: before D668 the runners were in hub-main's own group and its
|
|
61
|
+
* `kill -- -<pid>` reached them and their children; now each is its own group.
|
|
62
|
+
* Returns how many groups were signalled.
|
|
63
|
+
*/
|
|
64
|
+
export declare function signalAllRunnerGroups(signal: NodeJS.Signals): number;
|
|
65
|
+
/** Test seam: the groups currently owned, by pgid. */
|
|
66
|
+
export declare function ownedRunnerGroupIds(): readonly number[];
|
|
67
|
+
export interface RunnerParentDeathWatchdogOptions {
|
|
68
|
+
readonly initialPpid?: number;
|
|
69
|
+
readonly readPpid?: () => number;
|
|
70
|
+
readonly intervalMs?: number;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Arm the runner's parent-death watchdog before addon code starts loading.
|
|
74
|
+
*
|
|
75
|
+
* A parent can die while an addon module or `initialize()` is still pending, so
|
|
76
|
+
* installing this only after READY leaves exactly the boot window where a detached
|
|
77
|
+
* runner can become an orphan. Any PPID change means the direct parent died and the
|
|
78
|
+
* kernel reparented us; checking for a change also covers a non-PID-1 subreaper.
|
|
79
|
+
*/
|
|
80
|
+
export declare function startRunnerParentDeathWatchdog(onParentDeath: () => void, options?: RunnerParentDeathWatchdogOptions): NodeJS.Timeout | null;
|
|
81
|
+
/**
|
|
82
|
+
* Last resort for a detached runner whose direct parent disappeared without an
|
|
83
|
+
* `exit` hook (SIGKILL, native crash). The runner's own pid is also its pgid, so
|
|
84
|
+
* one SIGKILL removes the runner and every descendant in that group atomically.
|
|
85
|
+
*
|
|
86
|
+
* Calling `kill(-process.pid, ...)` from an ordinary, non-group-leading process
|
|
87
|
+
* returns ESRCH: while this pid exists, no unrelated process group can have the
|
|
88
|
+
* same id. That makes the failed call a safe compatibility fallback for an older
|
|
89
|
+
* parent that did not spawn the runner detached.
|
|
90
|
+
*/
|
|
91
|
+
export declare function killOwnRunnerGroupAfterParentLoss(platform?: NodeJS.Platform, kill?: ProcessGroupOps['kill']): boolean;
|
|
92
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* Parse one `/proc/<pid>/stat` line. `null` for a line that does not parse —
|
|
4
|
+
* a process that exited between the readdir and the read leaves a truncated
|
|
5
|
+
* or empty file, and that is normal, not an error.
|
|
6
|
+
*/
|
|
7
|
+
function parseProcStat(line) {
|
|
8
|
+
const close = line.lastIndexOf(")");
|
|
9
|
+
const open = line.indexOf("(");
|
|
10
|
+
if (close < 0 || open < 0 || close < open) return null;
|
|
11
|
+
const pid = Number(line.slice(0, open).trim());
|
|
12
|
+
const comm = line.slice(open + 1, close);
|
|
13
|
+
const rest = line.slice(close + 1).trim().split(/\s+/);
|
|
14
|
+
const state = rest[0];
|
|
15
|
+
const ppid = Number(rest[1]);
|
|
16
|
+
const pgid = Number(rest[2]);
|
|
17
|
+
const utime = Number(rest[11]);
|
|
18
|
+
const stime = Number(rest[12]);
|
|
19
|
+
const startTicks = Number(rest[19]);
|
|
20
|
+
const rssPages = Number(rest[21]);
|
|
21
|
+
if (state === void 0 || state === "") return null;
|
|
22
|
+
if (![
|
|
23
|
+
pid,
|
|
24
|
+
ppid,
|
|
25
|
+
pgid,
|
|
26
|
+
utime,
|
|
27
|
+
stime,
|
|
28
|
+
startTicks,
|
|
29
|
+
rssPages
|
|
30
|
+
].every(Number.isFinite)) return null;
|
|
31
|
+
return {
|
|
32
|
+
pid,
|
|
33
|
+
comm,
|
|
34
|
+
state,
|
|
35
|
+
ppid,
|
|
36
|
+
pgid,
|
|
37
|
+
ticks: utime + stime,
|
|
38
|
+
startTicks,
|
|
39
|
+
rssPages
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function statusKb(status, key) {
|
|
43
|
+
const match = status.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"));
|
|
44
|
+
return match ? parseInt(match[1], 10) * 1024 : void 0;
|
|
45
|
+
}
|
|
46
|
+
/** Read `VmRSS` / `RssAnon` / `RssFile` (kB in the file) out of `/proc/<pid>/status`. */
|
|
47
|
+
function parseProcStatusRss(status) {
|
|
48
|
+
return {
|
|
49
|
+
rssBytes: statusKb(status, "VmRSS"),
|
|
50
|
+
privateBytes: statusKb(status, "RssAnon"),
|
|
51
|
+
sharedBytes: statusKb(status, "RssFile")
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Seconds the process has been alive — `ps etime`, whole seconds. */
|
|
55
|
+
function elapsedSecSince(input) {
|
|
56
|
+
return Math.max(0, Math.floor(input.uptimeSec - input.startTicks / 100));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `ps pcpu`: CPU time over elapsed time, as a percentage with one decimal.
|
|
60
|
+
*
|
|
61
|
+
* This is deliberately the LIFETIME average `ps` prints, not an instantaneous
|
|
62
|
+
* rate: the columns fed from it have always shown that quantity, and the
|
|
63
|
+
* instantaneous split is a different number with a different owner
|
|
64
|
+
* (`cpuMainPercent` / `cpuGcPercent` from `thread-cpu-sampler.ts`). A process
|
|
65
|
+
* with no elapsed window yet reports 0, never a division by zero.
|
|
66
|
+
*/
|
|
67
|
+
function lifetimeCpuPercent(input) {
|
|
68
|
+
const elapsedSec = input.uptimeSec - input.startTicks / 100;
|
|
69
|
+
if (!(elapsedSec > 0)) return 0;
|
|
70
|
+
const cpuSec = input.ticks / 100;
|
|
71
|
+
return Math.round(cpuSec / elapsedSec * 1e3) / 10;
|
|
72
|
+
}
|
|
73
|
+
var PID_RE = /^\d+$/;
|
|
74
|
+
var nodeProcReader = {
|
|
75
|
+
listPids: async () => (await readdir("/proc")).filter((entry) => PID_RE.test(entry)).map((entry) => Number(entry)),
|
|
76
|
+
readStat: (pid) => readFile(`/proc/${pid}/stat`, "latin1"),
|
|
77
|
+
readStatus: (pid) => readFile(`/proc/${pid}/status`, "latin1"),
|
|
78
|
+
readCmdline: (pid) => readFile(`/proc/${pid}/cmdline`, "latin1"),
|
|
79
|
+
readUptimeSec: async () => {
|
|
80
|
+
const [first] = (await readFile("/proc/uptime", "latin1")).trim().split(/\s+/);
|
|
81
|
+
const seconds = Number(first);
|
|
82
|
+
if (!Number.isFinite(seconds)) throw new Error(`/proc/uptime unreadable: ${String(first)}`);
|
|
83
|
+
return seconds;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/** `ps command=`: argv joined by spaces, or `[comm]` when there is no argv. */
|
|
87
|
+
function commandFromCmdline(cmdline, comm) {
|
|
88
|
+
const joined = cmdline.split("\0").filter((part) => part.length > 0).join(" ");
|
|
89
|
+
return joined.length > 0 ? joined : `[${comm}]`;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
export { parseProcStat as a, nodeProcReader as i, elapsedSecSince as n, parseProcStatusRss as o, lifetimeCpuPercent as r, commandFromCmdline as t };
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
let node_fs_promises = require("node:fs/promises");
|
|
2
|
+
/**
|
|
3
|
+
* Parse one `/proc/<pid>/stat` line. `null` for a line that does not parse —
|
|
4
|
+
* a process that exited between the readdir and the read leaves a truncated
|
|
5
|
+
* or empty file, and that is normal, not an error.
|
|
6
|
+
*/
|
|
7
|
+
function parseProcStat(line) {
|
|
8
|
+
const close = line.lastIndexOf(")");
|
|
9
|
+
const open = line.indexOf("(");
|
|
10
|
+
if (close < 0 || open < 0 || close < open) return null;
|
|
11
|
+
const pid = Number(line.slice(0, open).trim());
|
|
12
|
+
const comm = line.slice(open + 1, close);
|
|
13
|
+
const rest = line.slice(close + 1).trim().split(/\s+/);
|
|
14
|
+
const state = rest[0];
|
|
15
|
+
const ppid = Number(rest[1]);
|
|
16
|
+
const pgid = Number(rest[2]);
|
|
17
|
+
const utime = Number(rest[11]);
|
|
18
|
+
const stime = Number(rest[12]);
|
|
19
|
+
const startTicks = Number(rest[19]);
|
|
20
|
+
const rssPages = Number(rest[21]);
|
|
21
|
+
if (state === void 0 || state === "") return null;
|
|
22
|
+
if (![
|
|
23
|
+
pid,
|
|
24
|
+
ppid,
|
|
25
|
+
pgid,
|
|
26
|
+
utime,
|
|
27
|
+
stime,
|
|
28
|
+
startTicks,
|
|
29
|
+
rssPages
|
|
30
|
+
].every(Number.isFinite)) return null;
|
|
31
|
+
return {
|
|
32
|
+
pid,
|
|
33
|
+
comm,
|
|
34
|
+
state,
|
|
35
|
+
ppid,
|
|
36
|
+
pgid,
|
|
37
|
+
ticks: utime + stime,
|
|
38
|
+
startTicks,
|
|
39
|
+
rssPages
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function statusKb(status, key) {
|
|
43
|
+
const match = status.match(new RegExp(`^${key}:\\s+(\\d+)`, "m"));
|
|
44
|
+
return match ? parseInt(match[1], 10) * 1024 : void 0;
|
|
45
|
+
}
|
|
46
|
+
/** Read `VmRSS` / `RssAnon` / `RssFile` (kB in the file) out of `/proc/<pid>/status`. */
|
|
47
|
+
function parseProcStatusRss(status) {
|
|
48
|
+
return {
|
|
49
|
+
rssBytes: statusKb(status, "VmRSS"),
|
|
50
|
+
privateBytes: statusKb(status, "RssAnon"),
|
|
51
|
+
sharedBytes: statusKb(status, "RssFile")
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** Seconds the process has been alive — `ps etime`, whole seconds. */
|
|
55
|
+
function elapsedSecSince(input) {
|
|
56
|
+
return Math.max(0, Math.floor(input.uptimeSec - input.startTicks / 100));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* `ps pcpu`: CPU time over elapsed time, as a percentage with one decimal.
|
|
60
|
+
*
|
|
61
|
+
* This is deliberately the LIFETIME average `ps` prints, not an instantaneous
|
|
62
|
+
* rate: the columns fed from it have always shown that quantity, and the
|
|
63
|
+
* instantaneous split is a different number with a different owner
|
|
64
|
+
* (`cpuMainPercent` / `cpuGcPercent` from `thread-cpu-sampler.ts`). A process
|
|
65
|
+
* with no elapsed window yet reports 0, never a division by zero.
|
|
66
|
+
*/
|
|
67
|
+
function lifetimeCpuPercent(input) {
|
|
68
|
+
const elapsedSec = input.uptimeSec - input.startTicks / 100;
|
|
69
|
+
if (!(elapsedSec > 0)) return 0;
|
|
70
|
+
const cpuSec = input.ticks / 100;
|
|
71
|
+
return Math.round(cpuSec / elapsedSec * 1e3) / 10;
|
|
72
|
+
}
|
|
73
|
+
var PID_RE = /^\d+$/;
|
|
74
|
+
var nodeProcReader = {
|
|
75
|
+
listPids: async () => (await (0, node_fs_promises.readdir)("/proc")).filter((entry) => PID_RE.test(entry)).map((entry) => Number(entry)),
|
|
76
|
+
readStat: (pid) => (0, node_fs_promises.readFile)(`/proc/${pid}/stat`, "latin1"),
|
|
77
|
+
readStatus: (pid) => (0, node_fs_promises.readFile)(`/proc/${pid}/status`, "latin1"),
|
|
78
|
+
readCmdline: (pid) => (0, node_fs_promises.readFile)(`/proc/${pid}/cmdline`, "latin1"),
|
|
79
|
+
readUptimeSec: async () => {
|
|
80
|
+
const [first] = (await (0, node_fs_promises.readFile)("/proc/uptime", "latin1")).trim().split(/\s+/);
|
|
81
|
+
const seconds = Number(first);
|
|
82
|
+
if (!Number.isFinite(seconds)) throw new Error(`/proc/uptime unreadable: ${String(first)}`);
|
|
83
|
+
return seconds;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/** `ps command=`: argv joined by spaces, or `[comm]` when there is no argv. */
|
|
87
|
+
function commandFromCmdline(cmdline, comm) {
|
|
88
|
+
const joined = cmdline.split("\0").filter((part) => part.length > 0).join(" ");
|
|
89
|
+
return joined.length > 0 ? joined : `[${comm}]`;
|
|
90
|
+
}
|
|
91
|
+
//#endregion
|
|
92
|
+
Object.defineProperty(exports, "commandFromCmdline", {
|
|
93
|
+
enumerable: true,
|
|
94
|
+
get: function() {
|
|
95
|
+
return commandFromCmdline;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
Object.defineProperty(exports, "elapsedSecSince", {
|
|
99
|
+
enumerable: true,
|
|
100
|
+
get: function() {
|
|
101
|
+
return elapsedSecSince;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
Object.defineProperty(exports, "lifetimeCpuPercent", {
|
|
105
|
+
enumerable: true,
|
|
106
|
+
get: function() {
|
|
107
|
+
return lifetimeCpuPercent;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
Object.defineProperty(exports, "nodeProcReader", {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
get: function() {
|
|
113
|
+
return nodeProcReader;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
Object.defineProperty(exports, "parseProcStat", {
|
|
117
|
+
enumerable: true,
|
|
118
|
+
get: function() {
|
|
119
|
+
return parseProcStat;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
Object.defineProperty(exports, "parseProcStatusRss", {
|
|
123
|
+
enumerable: true,
|
|
124
|
+
get: function() {
|
|
125
|
+
return parseProcStatusRss;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { r as __exportAll } from "./chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as parseProcStat, i as nodeProcReader, o as parseProcStatusRss, r as lifetimeCpuPercent } from "./proc-stat-CbC55Kqi.mjs";
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { platform } from "node:os";
|
|
5
5
|
//#region src/process/resource-monitor.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
2
|
-
const
|
|
2
|
+
const require_proc_stat = require("./proc-stat-DL-WUOhl.js");
|
|
3
3
|
let node_child_process = require("node:child_process");
|
|
4
4
|
let node_os = require("node:os");
|
|
5
5
|
//#region src/process/resource-monitor.ts
|
|
@@ -22,7 +22,7 @@ function execPsForPids(pids) {
|
|
|
22
22
|
}
|
|
23
23
|
var defaultDeps = {
|
|
24
24
|
platform: (0, node_os.platform)(),
|
|
25
|
-
reader:
|
|
25
|
+
reader: require_proc_stat.nodeProcReader,
|
|
26
26
|
spawnPs: execPsForPids
|
|
27
27
|
};
|
|
28
28
|
function parsePsOutput(stdout) {
|
|
@@ -52,12 +52,12 @@ async function readProcPidStats(pid, uptimeSec, reader) {
|
|
|
52
52
|
} catch {
|
|
53
53
|
return null;
|
|
54
54
|
}
|
|
55
|
-
const parsed =
|
|
55
|
+
const parsed = require_proc_stat.parseProcStat(stat);
|
|
56
56
|
if (parsed === null) return null;
|
|
57
|
-
const rss =
|
|
57
|
+
const rss = require_proc_stat.parseProcStatusRss(status);
|
|
58
58
|
if (rss.rssBytes === void 0) return null;
|
|
59
59
|
return {
|
|
60
|
-
cpu:
|
|
60
|
+
cpu: require_proc_stat.lifetimeCpuPercent({
|
|
61
61
|
ticks: parsed.ticks,
|
|
62
62
|
startTicks: parsed.startTicks,
|
|
63
63
|
uptimeSec
|