Files

27 lines
2.3 KiB
JavaScript
Raw Permalink Normal View History

'use strict';
// ============================================================================
// AI 자식 프로세스 PID 기록 (v1.6.31, 요구 12) — 서비스가 죽거나 재시작돼도 뒤에 남은 claude/codex 작업을 다음 기동 때 정리한다.
// 자식은 detached 프로세스 그룹으로 실행되므로(취소 때 훅·하위까지 한 번에 종료) 서비스가 비정상 종료되면 그대로 살아남을 수 있었다.
// 저장: data/run/jobs.json { pid: { kind, siteDir, at } }. 기동 때 sweep(): 아직 살아 있고 시작 시각이 일치하는 프로세스 그룹에 SIGTERM → 2초 뒤 SIGKILL.
// ============================================================================
const fs = require('fs'); const path = require('path');
const DATA_ROOT = process.env.DATA_ROOT || path.join(__dirname, '..', 'data');
const FILE = path.join(DATA_ROOT, 'run', 'jobs.json');
function load() { try { return JSON.parse(fs.readFileSync(FILE, 'utf8')) || {}; } catch (_) { return {}; } }
function save(d) { try { fs.mkdirSync(path.dirname(FILE), { recursive: true }); fs.writeFileSync(FILE, JSON.stringify(d)); } catch (_) {} }
function add(pid, info) { const d = load(); d[pid] = { ...info, at: Date.now(), host: process.pid }; save(d); }
function remove(pid) { const d = load(); if (d[pid]) { delete d[pid]; save(d); } }
function alive(pid) { try { process.kill(pid, 0); return true; } catch (e) { return e.code === 'EPERM'; } }
/** 기동 때: 이전 서비스 프로세스가 남긴 자식 중 살아 있는 것을 끝낸다. @returns {number} 정리한 수 */
function sweep() {
const d = load(); let n = 0;
for (const [pidStr, info] of Object.entries(d)) {
const pid = Number(pidStr); if (!pid || info.host === process.pid) continue;
if (alive(pid)) { let cmd = ''; try { cmd = fs.readFileSync(`/proc/${pid}/cmdline`, 'utf8'); } catch (_) {}
if (!cmd || /claude|codex/.test(cmd)) { try { process.kill(-pid, 'SIGTERM'); } catch (_) { try { process.kill(pid, 'SIGTERM'); } catch (_) {} } setTimeout(() => { if (alive(pid)) { try { process.kill(-pid, 'SIGKILL'); } catch (_) { try { process.kill(pid, 'SIGKILL'); } catch (_) {} } } }, 2000).unref(); n += 1; } }
delete d[pidStr];
}
save(d); return n;
}
module.exports = { add, remove, sweep, alive, FILE };