@sma1lboy/rove 0.9.72 → 0.9.73

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.
@@ -186,6 +186,7 @@ async function nodePtyDriver(spawn) {
186
186
  }
187
187
 
188
188
  // ../kobe-daemon/src/daemon/pty-server.ts
189
+ import { readFileSync as readFileSync4 } from "node:fs";
189
190
  import { mkdir as mkdir2, unlink as unlink2, writeFile } from "node:fs/promises";
190
191
  import { createServer } from "node:net";
191
192
  import { dirname as dirname3 } from "node:path";
@@ -1285,6 +1286,14 @@ class PtyHost {
1285
1286
 
1286
1287
  // ../kobe-daemon/src/daemon/pty-server.ts
1287
1288
  var DEFAULT_IDLE_EXIT_MS = 60000;
1289
+ var DEFAULT_ORPHAN_CHECK_MS = 30000;
1290
+ function resolveMaxLifetimeMs() {
1291
+ const raw = process.env.KOBE_PTY_MAX_LIFETIME_MS;
1292
+ if (raw === undefined)
1293
+ return null;
1294
+ const n = Number(raw);
1295
+ return Number.isFinite(n) && n > 0 ? n : null;
1296
+ }
1288
1297
  function resolveIdleExitMs() {
1289
1298
  const raw = process.env.KOBE_PTY_IDLE_EXIT_MS;
1290
1299
  if (raw === undefined)
@@ -1297,6 +1306,9 @@ async function startPtyHostServer(options = {}) {
1297
1306
  const pidPath = options.pidPath ?? defaultPtyHostPidPath();
1298
1307
  const freezeDir = options.freezeDir ?? defaultPtyFreezeDir();
1299
1308
  const idleExitMs = options.idleExitMs || resolveIdleExitMs();
1309
+ const orphanCheckMs = options.orphanCheckMs || DEFAULT_ORPHAN_CHECK_MS;
1310
+ const maxLifetimeMs = options.maxLifetimeMs ?? resolveMaxLifetimeMs();
1311
+ const bootedAtMs = Date.now();
1300
1312
  const log = options.log ?? (() => {});
1301
1313
  const clients = new Set;
1302
1314
  let stopping = false;
@@ -1318,6 +1330,31 @@ async function startPtyHostServer(options = {}) {
1318
1330
  stop();
1319
1331
  }, idleExitMs);
1320
1332
  };
1333
+ const orphaned = () => {
1334
+ if (maxLifetimeMs !== null && Date.now() - bootedAtMs >= maxLifetimeMs) {
1335
+ return `exceeded its ${maxLifetimeMs}ms lifetime ceiling`;
1336
+ }
1337
+ let owner;
1338
+ try {
1339
+ owner = readFileSync4(pidPath, "utf8").trim();
1340
+ } catch {
1341
+ return `pidfile ${pidPath} is gone`;
1342
+ }
1343
+ const pid = Number.parseInt(owner, 10);
1344
+ if (pid === process.pid)
1345
+ return null;
1346
+ return Number.isInteger(pid) ? `pidfile ${pidPath} now names pid ${pid}` : `pidfile ${pidPath} is unreadable`;
1347
+ };
1348
+ const orphanTimer = setInterval(() => {
1349
+ if (stopping)
1350
+ return;
1351
+ const reason = orphaned();
1352
+ if (!reason)
1353
+ return;
1354
+ log("orphan", `${reason} — exiting rather than outliving my owner`);
1355
+ stop();
1356
+ }, orphanCheckMs);
1357
+ orphanTimer.unref?.();
1321
1358
  const ptys = new PtyHost({
1322
1359
  onSessionStart: cancelIdle,
1323
1360
  onSessionEnd: () => {
@@ -1371,6 +1408,7 @@ async function startPtyHostServer(options = {}) {
1371
1408
  return;
1372
1409
  stopping = true;
1373
1410
  cancelIdle();
1411
+ clearInterval(orphanTimer);
1374
1412
  await ptys.shutdown();
1375
1413
  if (wipeFreezeOnStop)
1376
1414
  clearFrozenSessions(freezeDir);