← 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 →