feat: add pidfile management for daemon process tracking
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import os from 'node:os';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { writePidfile, readPidfile, cleanPidfile, isProcessAlive } from '../pidfile.js';
|
||||||
|
|
||||||
|
describe('pidfile management', () => {
|
||||||
|
let testDir: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testDir = path.join(os.tmpdir(), `visionl-pidfile-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
||||||
|
// clean up before in case previous run left files
|
||||||
|
try { fs.rmSync(testDir, { recursive: true, force: true }); } catch { /* ignore */ }
|
||||||
|
fs.mkdirSync(testDir, { recursive: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('writePidfile writes pid and port files, readPidfile reads them back', () => {
|
||||||
|
writePidfile(12345, 9527, testDir);
|
||||||
|
|
||||||
|
const result = readPidfile(testDir);
|
||||||
|
expect(result).toEqual({ pid: 12345, port: 9527 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('readPidfile returns null when pidfile does not exist', () => {
|
||||||
|
const result = readPidfile(testDir);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('readPidfile returns null when pidfile has invalid content', () => {
|
||||||
|
writePidfile(12345, 9527, testDir);
|
||||||
|
// overwrite with invalid data
|
||||||
|
fs.writeFileSync(path.join(testDir, 'daemon.pid'), 'not-a-number');
|
||||||
|
|
||||||
|
const result = readPidfile(testDir);
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cleanPidfile removes both pid and port files', () => {
|
||||||
|
writePidfile(12345, 9527, testDir);
|
||||||
|
|
||||||
|
cleanPidfile(testDir);
|
||||||
|
|
||||||
|
expect(fs.existsSync(path.join(testDir, 'daemon.pid'))).toBe(false);
|
||||||
|
expect(fs.existsSync(path.join(testDir, 'daemon.port'))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cleanPidfile is safe when files do not exist', () => {
|
||||||
|
// should not throw
|
||||||
|
expect(() => cleanPidfile(testDir)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isProcessAlive returns true for the current process', () => {
|
||||||
|
expect(isProcessAlive(process.pid)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('isProcessAlive returns false for a non-existent PID', () => {
|
||||||
|
expect(isProcessAlive(99999999)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import os from 'node:os';
|
||||||
|
|
||||||
|
const DEFAULT_DIR = path.join(os.homedir(), '.visionl');
|
||||||
|
|
||||||
|
function ensureDir(dir: string): void {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writePidfile(pid: number, port: number, dir: string = DEFAULT_DIR): void {
|
||||||
|
ensureDir(dir);
|
||||||
|
fs.writeFileSync(path.join(dir, 'daemon.pid'), String(pid));
|
||||||
|
fs.writeFileSync(path.join(dir, 'daemon.port'), String(port));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readPidfile(dir: string = DEFAULT_DIR): { pid: number; port: number } | null {
|
||||||
|
const pidPath = path.join(dir, 'daemon.pid');
|
||||||
|
const portPath = path.join(dir, 'daemon.port');
|
||||||
|
try {
|
||||||
|
const pid = parseInt(fs.readFileSync(pidPath, 'utf-8'), 10);
|
||||||
|
const port = parseInt(fs.readFileSync(portPath, 'utf-8'), 10);
|
||||||
|
if (isNaN(pid) || isNaN(port)) return null;
|
||||||
|
return { pid, port };
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function cleanPidfile(dir: string = DEFAULT_DIR): void {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(path.join(dir, 'daemon.pid'));
|
||||||
|
fs.unlinkSync(path.join(dir, 'daemon.port'));
|
||||||
|
} catch {
|
||||||
|
// Ignore if files don't exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isProcessAlive(pid: number): boolean {
|
||||||
|
try {
|
||||||
|
process.kill(pid, 0);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user