kibi-runtime 2.13.2 → 2.13.3

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.
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // ../cli/src/engine-daemon.ts
6
- import { writeFileSync as writeFileSync3 } from "node:fs";
6
+ import { writeFileSync as writeFileSync4 } from "node:fs";
7
7
  import path9 from "node:path";
8
8
  import { pathToFileURL } from "node:url";
9
9
 
@@ -24,7 +24,7 @@ import {
24
24
  rmSync as rmSync2,
25
25
  statSync as statSync4,
26
26
  unlinkSync,
27
- writeFileSync as writeFileSync2
27
+ writeFileSync as writeFileSync3
28
28
  } from "node:fs";
29
29
  import * as net from "node:net";
30
30
  import * as os from "node:os";
@@ -102,7 +102,7 @@ function manifestVersion(manifestPath, expectedName) {
102
102
  }
103
103
  function cliVersion() {
104
104
  if (true)
105
- return "2.17.0";
105
+ return "2.18.0";
106
106
  return manifestVersion(path.join(moduleDirectory, "..", "package.json"), "kibi-cli") ?? "unknown";
107
107
  }
108
108
  function coreVersion() {
@@ -1576,7 +1576,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
1576
1576
  child.kill(signal);
1577
1577
  } catch {}
1578
1578
  }
1579
- async terminateProcessTree(child) {
1579
+ async terminateProcessTree(child, graceMs = 0) {
1580
1580
  let closed = false;
1581
1581
  const exited = new Promise((resolve) => {
1582
1582
  const markExited = () => {
@@ -1586,6 +1586,19 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
1586
1586
  child.once("close", markExited);
1587
1587
  child.once("error", markExited);
1588
1588
  });
1589
+ if (graceMs > 0 && child.exitCode === null && child.signalCode === null) {
1590
+ let graceTimer;
1591
+ await Promise.race([
1592
+ exited,
1593
+ new Promise((resolve) => {
1594
+ graceTimer = setTimeout(resolve, graceMs);
1595
+ })
1596
+ ]);
1597
+ if (graceTimer)
1598
+ clearTimeout(graceTimer);
1599
+ if (closed)
1600
+ return;
1601
+ }
1589
1602
  if (child.exitCode === null)
1590
1603
  this.signalProcessTree(child, "SIGTERM");
1591
1604
  await Promise.race([
@@ -1707,7 +1720,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
1707
1720
  }
1708
1721
  return new PrologProcessTerminatedError(this.terminationReason ?? "process exited");
1709
1722
  }
1710
- async terminate() {
1723
+ async terminate(options = {}) {
1711
1724
  if (this.interactiveStarted) {
1712
1725
  this.terminationReason ??= "terminated";
1713
1726
  }
@@ -1727,7 +1740,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
1727
1740
  this.terminationPromise = (async () => {
1728
1741
  if (current) {
1729
1742
  current.stdin?.end();
1730
- await this.terminateProcessTree(current);
1743
+ await this.terminateProcessTree(current, options.graceMs ?? 0);
1731
1744
  }
1732
1745
  await Promise.all(oneShots.map((child) => this.terminateProcessTree(child)));
1733
1746
  })();
@@ -1741,7 +1754,7 @@ writeln('${INTERACTIVE_QUERY_FRAME_END}').
1741
1754
  }
1742
1755
 
1743
1756
  // ../cli/src/prolog/store-lock.ts
1744
- import { existsSync as existsSync4, rmSync } from "node:fs";
1757
+ import { existsSync as existsSync4, rmSync, writeFileSync } from "node:fs";
1745
1758
  import { readFileSync as readFileSync3 } from "node:fs";
1746
1759
  import { dirname, join } from "node:path";
1747
1760
  var cachedBootId;
@@ -1797,6 +1810,14 @@ function breakStoreLock(rdfDirectory) {
1797
1810
  }
1798
1811
  }
1799
1812
  }
1813
+ var STORE_LOCK_TAKEOVER_FILE = ".kibi-lock-takeover.json";
1814
+ var STORE_LOCK_TAKEOVER_VISIBLE_MS = 24 * 60 * 60 * 1000;
1815
+ function recordStoreLockTakeover(storePath, brokenPid, reason, at = new Date) {
1816
+ try {
1817
+ writeFileSync(join(storePath, STORE_LOCK_TAKEOVER_FILE), `${JSON.stringify({ brokenPid, reason, at: at.toISOString() })}
1818
+ `, { mode: 384 });
1819
+ } catch {}
1820
+ }
1800
1821
  function decideStoreLockTakeover(owner) {
1801
1822
  const state = classifyStoreLockHolder(owner);
1802
1823
  switch (state) {
@@ -1833,6 +1854,7 @@ Store lock holder: ${holderSummary}. Close that session, or run 'kibi engine sto
1833
1854
  };
1834
1855
  }
1835
1856
  const lockDirectory = storeLocked.lockDirectory !== "" ? storeLocked.lockDirectory : join(kbPath, "rdf");
1857
+ recordStoreLockTakeover(kbPath, storeLocked.owner?.pid ?? null, decision.reason);
1836
1858
  breakStoreLock(lockDirectory);
1837
1859
  const retried = await prolog.query(`kb_attach('${escapeAtom(kbPath)}')`);
1838
1860
  if (retried.success) {
@@ -1856,7 +1878,7 @@ import {
1856
1878
  readFileSync as readFileSync4,
1857
1879
  readdirSync,
1858
1880
  statSync as statSync3,
1859
- writeFileSync
1881
+ writeFileSync as writeFileSync2
1860
1882
  } from "node:fs";
1861
1883
  import path5 from "node:path";
1862
1884
  var BRANCH_STORE_MANIFEST_VERSION = 1;
@@ -1918,7 +1940,7 @@ function ensureBranchStoreManifest(workspaceRoot, branch) {
1918
1940
  if (entries.length > 0) {
1919
1941
  throw new Error(`Branch store at ${storePath} has compiled data but no valid identity manifest; refusing to adopt it.`);
1920
1942
  }
1921
- writeFileSync(manifestPath, `${JSON.stringify(expectedBranchStoreManifest(branch), null, 2)}
1943
+ writeFileSync2(manifestPath, `${JSON.stringify(expectedBranchStoreManifest(branch), null, 2)}
1922
1944
  `, { mode: 384 });
1923
1945
  }
1924
1946
  return storePath;
@@ -2005,6 +2027,8 @@ var READ_LIMITED_METHODS = new Set([
2005
2027
  ]);
2006
2028
  var ENGINE_PROTOCOL_VERSION = 1;
2007
2029
  var ENGINE_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
2030
+ var ENGINE_IDLE_CHECK_INTERVAL_MS = 30 * 1000;
2031
+ var ENGINE_PROLOG_EXIT_GRACE_MS = 1000;
2008
2032
  function enginePackageVersions() {
2009
2033
  return kibiPackageVersions();
2010
2034
  }
@@ -2174,7 +2198,7 @@ function runtimeDirectory() {
2174
2198
  try {
2175
2199
  mkdirSync2(candidate, { recursive: true, mode: 448 });
2176
2200
  const probe = path8.join(candidate, `.kibi-write-${process.pid}`);
2177
- writeFileSync2(probe, "", { mode: 384 });
2201
+ writeFileSync3(probe, "", { mode: 384 });
2178
2202
  unlinkSync(probe);
2179
2203
  return candidate;
2180
2204
  } catch {}
@@ -2214,7 +2238,7 @@ function runtimeDirectory() {
2214
2238
  if (Buffer.byteLength(path8.join(canonical, socketName), "utf8") > maximumBytes)
2215
2239
  continue;
2216
2240
  const probe = path8.join(canonical, `.kibi-write-${randomUUID()}`);
2217
- writeFileSync2(probe, "", { mode: 384, flag: "wx" });
2241
+ writeFileSync3(probe, "", { mode: 384, flag: "wx" });
2218
2242
  unlinkSync(probe);
2219
2243
  return canonical;
2220
2244
  } catch {}
@@ -2340,7 +2364,7 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
2340
2364
  recoverStaleJournalLock(branchPath);
2341
2365
  archiveLegacyFiles(branchPath);
2342
2366
  if (!existsSync6(sentinelPath)) {
2343
- writeFileSync2(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
2367
+ writeFileSync3(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
2344
2368
  `, { mode: 384 });
2345
2369
  }
2346
2370
  return;
@@ -2359,7 +2383,7 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
2359
2383
  recoverStaleJournalLock(branchPath);
2360
2384
  archiveLegacyFiles(branchPath);
2361
2385
  if (!existsSync6(sentinelPath)) {
2362
- writeFileSync2(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
2386
+ writeFileSync3(sentinelPath, `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
2363
2387
  `, { mode: 384 });
2364
2388
  }
2365
2389
  return;
@@ -2369,13 +2393,13 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
2369
2393
  const legacyAudit = path8.join(branchPath, "audit.log");
2370
2394
  if (!existsSync6(legacyRdf) && !existsSync6(legacyAudit)) {
2371
2395
  mkdirSync2(path8.join(branchPath, "rdf"), { recursive: true });
2372
- writeFileSync2(markerPath, `{"format":"kibi.rdf-journal.v1","schemaVersion":1}
2396
+ writeFileSync3(markerPath, `{"format":"kibi.rdf-journal.v1","schemaVersion":1}
2373
2397
  `, { mode: 384 });
2374
- writeFileSync2(path8.join(branchPath, "CURRENT"), `generation-1:0
2398
+ writeFileSync3(path8.join(branchPath, "CURRENT"), `generation-1:0
2375
2399
  `, {
2376
2400
  mode: 384
2377
2401
  });
2378
- writeFileSync2(path8.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
2402
+ writeFileSync3(path8.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
2379
2403
  `, { mode: 384 });
2380
2404
  return;
2381
2405
  }
@@ -2424,7 +2448,7 @@ async function ensureJournaledBranchStoreAsync(branchPath) {
2424
2448
  renameSync(source, target);
2425
2449
  }
2426
2450
  archiveLegacyFiles(branchPath);
2427
- writeFileSync2(path8.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
2451
+ writeFileSync3(path8.join(branchPath, "kb.rdf"), `KIBI_STORAGE_FORMAT=kibi.rdf-journal.v1
2428
2452
  `, { mode: 384 });
2429
2453
  fsyncJournaledBranchStore(branchPath);
2430
2454
  rmSync2(staging, { recursive: true, force: true });
@@ -2617,11 +2641,12 @@ async function runEngineDaemon(requestedOptions) {
2617
2641
  rmSync2(options.socketPath, { force: true });
2618
2642
  } catch {}
2619
2643
  }
2620
- writeFileSync2(enginePidPath(options.workspaceRoot, options.branch), `${process.pid}
2644
+ writeFileSync3(enginePidPath(options.workspaceRoot, options.branch), `${process.pid}
2621
2645
  `, { mode: 384 });
2622
2646
  const server = net.createServer();
2623
2647
  let activeClients = 0;
2624
- let idleTimer = null;
2648
+ let lastActivityAt = Date.now();
2649
+ let idleWatch = null;
2625
2650
  let queue = Promise.resolve();
2626
2651
  let idleCompactionQueued = false;
2627
2652
  const queryCache = new Map;
@@ -2698,12 +2723,15 @@ async function runEngineDaemon(requestedOptions) {
2698
2723
  idleCompactionQueued = false;
2699
2724
  });
2700
2725
  }
2701
- if (idleTimer)
2702
- clearTimeout(idleTimer);
2703
- idleTimer = setTimeout(() => {
2704
- if (activeClients === 0)
2726
+ lastActivityAt = Date.now();
2727
+ if (idleWatch)
2728
+ return;
2729
+ const idleMs = engineIdleTimeoutMs();
2730
+ idleWatch = setInterval(() => {
2731
+ if (!shuttingDown && activeClients === 0 && Date.now() - lastActivityAt >= idleMs)
2705
2732
  shutdown();
2706
- }, engineIdleTimeoutMs());
2733
+ }, Math.min(idleMs, ENGINE_IDLE_CHECK_INTERVAL_MS));
2734
+ idleWatch.unref();
2707
2735
  };
2708
2736
  const handle = async (request, cancelledRequests) => {
2709
2737
  if (request.protocolVersion !== ENGINE_PROTOCOL_VERSION) {
@@ -2958,20 +2986,22 @@ async function runEngineDaemon(requestedOptions) {
2958
2986
  shuttingDown = true;
2959
2987
  process.off("SIGTERM", requestSignalShutdown);
2960
2988
  process.off("SIGINT", requestSignalShutdown);
2961
- if (idleTimer)
2962
- clearTimeout(idleTimer);
2989
+ if (idleWatch)
2990
+ clearInterval(idleWatch);
2963
2991
  clearInterval(workspaceWatchdog);
2964
2992
  server.close();
2965
2993
  await queue.catch(() => {
2966
2994
  return;
2967
2995
  });
2968
2996
  const saved = session.needsRecycle() ? null : await prolog.query("kb_save").catch(() => null);
2997
+ let detached = false;
2969
2998
  if (saved?.success) {
2970
2999
  try {
2971
3000
  fsyncJournaledBranchStore(branchPath);
2972
3001
  } catch {}
3002
+ detached = await prolog.query("kb_detach").then((result) => result.success).catch(() => false);
2973
3003
  }
2974
- await prolog.terminate().catch(() => {
3004
+ await prolog.terminate({ graceMs: detached ? 0 : ENGINE_PROLOG_EXIT_GRACE_MS }).catch(() => {
2975
3005
  return;
2976
3006
  });
2977
3007
  for (const client of clients)
@@ -3008,6 +3038,7 @@ async function runEngineDaemon(requestedOptions) {
3008
3038
  const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
3009
3039
  buffer = parseFrames(Buffer.concat([buffer, bytes]), (value) => {
3010
3040
  const request = value;
3041
+ lastActivityAt = Date.now();
3011
3042
  if (shuttingDown) {
3012
3043
  writeSocketFrame(socket, {
3013
3044
  id: request.id,
@@ -3114,7 +3145,7 @@ async function runEngineDaemonCli(args = process.argv.slice(2)) {
3114
3145
  const socketPath = socketIndex >= 0 ? args[socketIndex + 1] : undefined;
3115
3146
  if (socketPath) {
3116
3147
  try {
3117
- writeFileSync3(`${socketPath}.error`, `${message}
3148
+ writeFileSync4(`${socketPath}.error`, `${message}
3118
3149
  `, { mode: 384 });
3119
3150
  } catch {}
3120
3151
  }