[object Object]

← back to AbramsEgo

serialize AbramsEgo PM2 fleet reads

8156792d515cf190f6ee0ccdd9cf992e0da2b6f2 · 2026-08-30 19:20:07 -0700 · Steve

Files touched

Diff

commit 8156792d515cf190f6ee0ccdd9cf992e0da2b6f2
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Aug 30 19:20:07 2026 -0700

    serialize AbramsEgo PM2 fleet reads
---
 server.js | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/server.js b/server.js
index bacf197c..2771885b 100644
--- a/server.js
+++ b/server.js
@@ -97,6 +97,30 @@ function sh(cmd, args, timeoutMs = 8000) {
     });
   });
 }
+
+// TK-10970: serialize local PM2 CLI access with the same lock used by the
+// fleet keepalive and microsite crawler. Concurrent `pm2 jlist` calls can fork
+// a socket-less God daemon during a transient rpc.sock access race.
+const PM2_CLI_LOCK = path.join(process.env.PM2_HOME || path.join(HOME, '.pm2'), 'cli.lock.d');
+async function withPm2CliLock(fn) {
+  const deadline = Date.now() + 10000;
+  const staleMs = 8000;
+  let held = false;
+  while (Date.now() < deadline) {
+    try { await fs.promises.mkdir(PM2_CLI_LOCK); held = true; break; }
+    catch (e) {
+      if (e.code !== 'EEXIST') break;
+      try {
+        const st = await fs.promises.stat(PM2_CLI_LOCK);
+        if (Date.now() - st.mtimeMs > staleMs) { await fs.promises.rmdir(PM2_CLI_LOCK); continue; }
+      } catch {}
+      await new Promise(resolve => setTimeout(resolve, 80));
+    }
+  }
+  if (!held) return { ok: false, out: '', err: 'pm2 CLI lock unavailable' };
+  try { return await fn(); }
+  finally { try { await fs.promises.rmdir(PM2_CLI_LOCK); } catch {} }
+}
 async function fetchJSON(url, timeoutMs = 5000) {
   const ctrl = new AbortController();
   const t = setTimeout(() => ctrl.abort(), timeoutMs);
@@ -213,7 +237,7 @@ async function collectLocalFleet() {
   const inCooldown = Date.now() - jlistFailedAt < JLIST_COOLDOWN_MS;
   const r = inCooldown
     ? { ok: false, out: '', err: 'jlist cooldown after recent failure' }
-    : await sh(pm2Bin, ['jlist'], 20000);
+    : await withPm2CliLock(() => sh(pm2Bin, ['jlist'], 7000));
   let list = null;
   if (r.ok) { try { list = JSON.parse(r.out); } catch (e) { /* fall through to cache */ } }
   if (!inCooldown && !list) jlistFailedAt = Date.now();

← 474d93ee auto-data-snapshot: 2026-08-30T19:03:41 (1 data files) — dat  ·  back to AbramsEgo  ·  auto-data-snapshot: 2026-08-30T19:40:35 (1 data files) — dat d8ef96bc →