Files
mini-windrose-manager/dist/ProcessSupervisor.js
T

79 lines
2.8 KiB
JavaScript

import { spawn } from 'node:child_process';
import { mkdirSync, createWriteStream } from 'node:fs';
import { logsDir } from './paths.js';
export class ProcessSupervisor {
processes = new Map();
info = new Map();
// Crash event subscribers
crashHandlers = [];
onCrash(handler) {
this.crashHandlers.push(handler);
}
getInfo(id) {
return this.info.get(id);
}
async start(id, exePath, args = []) {
if (this.processes.has(id)) {
throw new Error(`Server ${id} already running`);
}
mkdirSync(logsDir(id), { recursive: true });
const stdoutLog = createWriteStream(`${logsDir(id)}/stdout.log`, { flags: 'a' });
const stderrLog = createWriteStream(`${logsDir(id)}/stderr.log`, { flags: 'a' });
const child = spawn('wine64', [exePath, ...args], {
env: process.env,
});
this.processes.set(id, child);
this.info.set(id, { id, pid: child.pid ?? null, status: 'starting' });
child.stdout.on('data', (chunk) => stdoutLog.write(chunk));
child.stderr.on('data', (chunk) => stderrLog.write(chunk));
child.on('spawn', () => {
const current = this.info.get(id);
if (current) {
current.status = 'running';
current.pid = child.pid ?? null;
this.info.set(id, current);
}
});
child.on('exit', (code) => {
this.processes.delete(id);
const current = this.info.get(id);
if (current) {
current.status = 'stopped';
current.lastExitCode = code ?? null;
this.info.set(id, current);
}
// Crash detection: non-zero exit code
if (code !== 0) {
this.crashHandlers.forEach((fn) => fn(id, code ?? -1));
}
});
child.on('error', (err) => {
console.error(`Server ${id} process error:`, err);
const current = this.info.get(id);
if (current) {
current.status = 'error';
this.info.set(id, current);
}
this.processes.delete(id);
// Treat process errors as crashes with exitCode = -1
this.crashHandlers.forEach((fn) => fn(id, -1));
});
}
async stop(id) {
const child = this.processes.get(id);
if (!child)
return;
child.kill('SIGTERM');
this.processes.delete(id);
const current = this.info.get(id);
if (current) {
current.status = 'stopped';
this.info.set(id, current);
}
}
async restart(id, exePath, args = []) {
await this.stop(id);
await this.start(id, exePath, args);
}
}