27 lines
2.3 KiB
JavaScript
27 lines
2.3 KiB
JavaScript
'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 };
|