@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/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, { a as NativeMetricsProvider } from "./builtins/native-metrics/native-metrics.addon.mjs";
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 localEndpointPath, $t as shouldReclaim, A as createUdsEventBus, An as createSocketPlaneReader, At as createAddonDataPlaneFacility, B as LocalChildClient, Bn as CAP_ACTION_OVERFLOW_LABEL, Bt as RSS_BUDGET_RELEASE_RATIO, C as brokerTransportLink, Cn as MESH_QUEUE_TOP_N, Ct as installManifestNativeDeps, D as localProviderLink, Dn as SOCKET_PLANE_TOP_N, Dt as runNpm, E as ipcParentLink, En as EMPTY_SOCKET_PLANE_BASELINE, Et as resolveNpmInvocation, F as AGENT_CAP_FWD_SERVICE, Fn as createSocketDirectionCounters, Ft as HEAP_WATCH_WARN_RATIO, G as PARENT_ROUTED_PROVIDER_ADDON_ID, Gn as createCapActionCensus, Gt as createDeferredHeapWatchSink, H as UDS_NO_ROUTE_PREFIX, Hn as EMPTY_CAP_ACTION_READING, Ht as RUNNER_RSS_BUDGET_ENV, I as CapRouteResolver, In as recordSocketFrame, It as HUB_MAIN_HEAP_WATCH_LABEL, J as createLocalTransport, Jn as createEventPlaneMeter, Jt as describeRss, K as __resetCapUsageRegistryForTests, Kn as diffCapActionCensus, Kt as createLoopDelayMeter, L as callWithServiceDiscovery, Ln as sampleSocketDirection, Lt as HUB_MAIN_RSS_BUDGET_MB, M as createUdsLogger, Mn as formatSocketPlane, Mt as HEAP_RECLAIM_STEADY_STATE_INTERVAL_MS, N as createUdsLoggerWithControl, Nn as readSocketQueues, Nt as HEAP_RECLAIM_TRIGGER_MB, O as createParentUnownedCallHandler, On as SOCKET_QUEUE_TOP_N, Ot as createAddonPeerBytesFacility, P as AGENT_CAP_FWD_ACTION, Pn as EMPTY_SOCKET_DIRECTION, Pt as HEAP_WATCH_INTERVAL_MS, Q as PeerAnsweredError, Qt as reclaimIntervalMs, R as CapRouteError, Rn as socketDirectionBytes, Rt as HUB_RSS_BUDGET_ENV, S as brokerCallForCap, Sn as formatOutboundQueueVerdict, St as copyBundledNativeModules, T as deviceCapResolverLink, Tn as formatMeshQueue, Tt as buildNpmChildEnv, U as readMoleculerFanoutMode, Un as MAX_CAP_ACTIONS_PER_PEER, Ut as ZERO_LOOP_DELAY, V as LocalChildRegistry, Vn as CAP_ACTION_UNLABELLED, Vt as RUNNER_HEAP_WATCH_INTERVAL_MS, W as CapUsageRegistry, Wn as capActionLabel, Wt as buildHeapSample, X as UdsLocalTransportServer, Xn as diffEventPlane, Xt as nextRssBudgetState, Y as UdsLocalTransportClient, Yn as createEventPlaneReader, Yt as hubMainRssBudget, Z as SocketChannel, Zn as formatEventPlane, Zt as parseRssBudgetMb, _ as setNodeEventInterest, _n as selectReportedSpaces, _t as deserializeTypedArrays, a as adaptBrokerToCluster, an as ARRAY_BUFFER_CENSUS_THRESHOLD_MB, b as createKernelHwAccel, bn as OUTBOUND_QUEUE_THRESHOLD_BYTES, bt as CapabilityHandle, c as getWorkerDeviceRegistry, cn as createInspectorArrayBufferCensus, d as setHubConnected, dn as DECISIVE_HEAP_SPACES, dt as NATIVE_PROVIDER_SERVICE_INFIX, en as startHeapWatch, et as FrameDecoder, f as EVENT_TOPIC_PREFIX, fn as HEAP_SPACE_REPORT_MIN_MB, ft as capActionName, g as registerEventBusService, gn as readHeapSpaces, gt as parseCapAction, h as getMoleculerEventStats, hn as heapSpaceField, ht as capServiceName, i as installManifestPythonDeps, in as ARRAY_BUFFER_CENSUS_SUSTAIN_MS, it as createBrokerDeviceManagerApi, j as udsChildLogToWorkerEntry, jn as diffSocketPlane, jt as HEAP_RECLAIM_MIN_INTERVAL_MS, k as createUdsEventBridge, kn as createSocketPlaneMeter, kt as hostNodeIdOf, l as getOrInitReadinessRegistry, ln as formatArrayBufferCensus, lt as createAddonService, m as getBrokerEventBus, mn as formatHeapSpaces, mt as capBareAction, nn as strandedMb, nt as buildNativeCapProxy, o as createAddonContext, on as ARRAY_BUFFER_RELEASE_MIN_DROP_RATIO, p as clusterEventTopic, pn as emitHeapDiagnosticReport, pt as capActionSuffix, q as getCapUsageRegistry, qn as EVENT_PLANE_TOP_N, qt as createV8Reclaimer, r as installManifestSystemDeps, rn as ARRAY_BUFFER_CENSUS_COOLDOWN_MS, rt as buildUdsNativeCapProxy, s as createUdsAddonContext, sn as createArrayBufferCensusGate, st as mountNativeCapService, t as CAP_METHOD_NOT_IMPLEMENTED, tn as startRunnerHeapWatch, tt as encodeFrame, u as getOrInitReadinessRegistryForClient, un as isArrayBufferCensusReport, ut as validateProviderRegistrations, v as subscribePassthrough, vn as OUTBOUND_QUEUE_COOLDOWN_MS, vt as serializeTypedArrays, w as buildLinkChain, wn as createMeshQueueReader, wt as resolveAddonClass, x as resolveHwAccel, xn as createOutboundQueueGate, xt as CapabilityUnavailableError, y as AddonDepsManager, yn as OUTBOUND_QUEUE_SUSTAIN_MS, yt as DeviceRegistry, z as classifyCapRoute, zn as socketDirectionMessages, zt as RSS_BUDGET_REANNOUNCE_MIN_MS } from "./child-cap-dispatch-BCJjUtXV.mjs";
32
- import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-Vp0bPV3U.mjs";
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: false
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 survivors = [];
95358
- for (const entry of processes.values()) {
95416
+ async stopped() {
95417
+ const entries = [...processes.values()];
95418
+ await Promise.all(entries.map(async (entry) => {
95359
95419
  entry.state = "stopped";
95360
- entry.child.kill("SIGTERM");
95361
- survivors.push(entry);
95362
- }
95363
- setTimeout(() => {
95364
- for (const entry of survivors) if (entry.child.exitCode === null && entry.child.signalCode === null) {
95365
- capturedBroker?.logger.warn(`[${entry.name}] Did not exit on SIGTERM — sending SIGKILL`);
95366
- entry.child.kill("SIGKILL");
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
- }, 3e3).unref();
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 };
@@ -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 { i as parseProcStatusRss, n as nodeProcReader, r as parseProcStat, t as lifetimeCpuPercent } from "./builtins/native-metrics/native-metrics.addon.mjs";
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 require_builtins_native_metrics_native_metrics_addon = require("./builtins/native-metrics/native-metrics.addon.js");
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: require_builtins_native_metrics_native_metrics_addon.nodeProcReader,
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 = require_builtins_native_metrics_native_metrics_addon.parseProcStat(stat);
55
+ const parsed = require_proc_stat.parseProcStat(stat);
56
56
  if (parsed === null) return null;
57
- const rss = require_builtins_native_metrics_native_metrics_addon.parseProcStatusRss(status);
57
+ const rss = require_proc_stat.parseProcStatusRss(status);
58
58
  if (rss.rssBytes === void 0) return null;
59
59
  return {
60
- cpu: require_builtins_native_metrics_native_metrics_addon.lifetimeCpuPercent({
60
+ cpu: require_proc_stat.lifetimeCpuPercent({
61
61
  ticks: parsed.ticks,
62
62
  startTicks: parsed.startTicks,
63
63
  uptimeSec