[object Object]

← back to Ticket System

serialize ticket-board PM2 fleet reads

b7a70b2b0808714f73187960bb6d761c77dc5fa4 · 2026-08-30 19:20:15 -0700 · Steve Abrams

Files touched

Diff

commit b7a70b2b0808714f73187960bb6d761c77dc5fa4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Aug 30 19:20:15 2026 -0700

    serialize ticket-board PM2 fleet reads
---
 server.js | 54 ++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 40 insertions(+), 14 deletions(-)

diff --git a/server.js b/server.js
index 3976983d..f00b4ff3 100644
--- a/server.js
+++ b/server.js
@@ -35,6 +35,25 @@ function pm2DaemonReachable() {
   } catch (_) { return false; }
 }
 let runCache = { ts: 0, data: { pm2: [], sessions: 0, at: null } };
+const PM2_CLI_LOCK_TS = path.join(PM2_HOME_TS, 'cli.lock.d');
+async function acquirePm2CliLock() {
+  const deadline = Date.now() + 10000;
+  const staleMs = 8000;
+  while (Date.now() < deadline) {
+    try {
+      await fs.promises.mkdir(PM2_CLI_LOCK_TS);
+      return async () => { try { await fs.promises.rmdir(PM2_CLI_LOCK_TS); } catch {} };
+    } catch (e) {
+      if (e.code !== 'EEXIST') return null;
+      try {
+        const st = await fs.promises.stat(PM2_CLI_LOCK_TS);
+        if (Date.now() - st.mtimeMs > staleMs) { await fs.promises.rmdir(PM2_CLI_LOCK_TS); continue; }
+      } catch {}
+      await new Promise(resolve => setTimeout(resolve, 80));
+    }
+  }
+  return null;
+}
 function getRunning(cb) {
   if (Date.now() - runCache.ts < 5000) return cb(runCache.data);
   if (!pm2DaemonReachable()) {
@@ -42,20 +61,27 @@ function getRunning(cb) {
     runCache = { ts: Date.now(), data: { pm2: runCache.data.pm2 || [], sessions: runCache.data.sessions || 0, at: new Date().toISOString() } };
     return cb(runCache.data);
   }
-  exec('pm2 jlist', { maxBuffer: 16 * 1024 * 1024, timeout: 8000 }, (e, out) => {
-    let pm2 = [];
-    if (!e) { try {
-      pm2 = JSON.parse(out).filter(p => p.pm2_env && p.pm2_env.status === 'online')
-        .map(p => ({ name: p.name, cpu: (p.monit && p.monit.cpu) || 0,
-          mem: Math.round(((p.monit && p.monit.memory) || 0) / 1048576),
-          up: p.pm2_env.pm_uptime || 0, restarts: p.pm2_env.restart_time || 0 }))
-        .sort((a, b) => a.name < b.name ? -1 : 1);
-    } catch (_) {} }
-    // count live `claude` CLI sessions (exclude skills-dir helpers), best-effort
-    exec("ps -Ao command | grep '[c]laude' | grep -v 'skills/' | wc -l", { timeout: 4000 }, (e2, out2) => {
-      const sessions = e2 ? 0 : (parseInt(String(out2).trim(), 10) || 0);
-      runCache = { ts: Date.now(), data: { pm2, sessions, at: new Date().toISOString() } };
-      cb(runCache.data);
+  acquirePm2CliLock().then((release) => {
+    if (!release) {
+      runCache = { ts: Date.now(), data: { pm2: runCache.data.pm2 || [], sessions: runCache.data.sessions || 0, at: new Date().toISOString() } };
+      return cb(runCache.data);
+    }
+    exec('pm2 jlist', { maxBuffer: 16 * 1024 * 1024, timeout: 7000, killSignal: 'SIGKILL' }, async (e, out) => {
+      await release();
+      let pm2 = [];
+      if (!e) { try {
+        pm2 = JSON.parse(out).filter(p => p.pm2_env && p.pm2_env.status === 'online')
+          .map(p => ({ name: p.name, cpu: (p.monit && p.monit.cpu) || 0,
+            mem: Math.round(((p.monit && p.monit.memory) || 0) / 1048576),
+            up: p.pm2_env.pm_uptime || 0, restarts: p.pm2_env.restart_time || 0 }))
+          .sort((a, b) => a.name < b.name ? -1 : 1);
+      } catch (_) {} }
+      // count live `claude` CLI sessions (exclude skills-dir helpers), best-effort
+      exec("ps -Ao command | grep '[c]laude' | grep -v 'skills/' | wc -l", { timeout: 4000 }, (e2, out2) => {
+        const sessions = e2 ? 0 : (parseInt(String(out2).trim(), 10) || 0);
+        runCache = { ts: Date.now(), data: { pm2, sessions, at: new Date().toISOString() } };
+        cb(runCache.data);
+      });
     });
   });
 }

← 20fa65cc stage TK-10970 Kamatera fracture-fix memo (gated remote plan  ·  back to Ticket System  ·  use owner-safe PM2 CLI wrapper 5ef46940 →