ai-debug-local-mcp
Version:
๐ฏ ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh
221 lines โข 8 kB
JavaScript
/**
* Zombie Process Prevention and Cleanup
*
* Prevents accumulation of zombie AI-Debug server processes that cause
* resource conflicts and tool disconnections.
*/
import { spawn } from 'child_process';
export class ZombieProcessKiller {
static AI_DEBUG_PATTERNS = [
'ai-debug-mcp',
'dist/server.js',
'src/server.ts'
];
static GRACE_PERIOD_MS = 5000; // 5 seconds
static MAX_CLEANUP_ATTEMPTS = 3;
/**
* Find zombie AI-Debug processes (processes that are no longer responsive)
*/
static async findZombieProcesses() {
return new Promise((resolve, reject) => {
const ps = spawn('ps', ['aux']);
let output = '';
ps.stdout.on('data', (data) => {
output += data.toString();
});
ps.on('close', (code) => {
if (code !== 0) {
reject(new Error(`ps command failed with code ${code}`));
return;
}
const processes = this.parseProcessList(output);
const zombies = processes.filter(proc => this.isAIDebugProcess(proc) && this.isZombieProcess(proc));
resolve(zombies);
});
ps.on('error', reject);
});
}
/**
* Clean up zombie processes with grace period and escalation
*/
static async cleanupZombieProcesses() {
console.log('๐ Scanning for zombie AI-Debug processes...');
const zombies = await this.findZombieProcesses();
if (zombies.length === 0) {
console.log('โ
No zombie processes found');
return { cleaned: 0, failed: 0 };
}
console.log(`๐งน Found ${zombies.length} zombie AI-Debug processes:`, zombies.map(z => `PID ${z.pid} (${z.status})`));
let cleaned = 0;
let failed = 0;
for (const zombie of zombies) {
try {
const success = await this.killProcessGracefully(zombie.pid);
if (success) {
cleaned++;
console.log(`โ
Cleaned up zombie process PID ${zombie.pid}`);
}
else {
failed++;
console.warn(`โ Failed to clean up zombie process PID ${zombie.pid}`);
}
}
catch (error) {
failed++;
console.error(`โ Error cleaning up PID ${zombie.pid}:`, error);
}
}
console.log(`๐งน Zombie cleanup complete: ${cleaned} cleaned, ${failed} failed`);
return { cleaned, failed };
}
/**
* Kill a process gracefully with SIGTERM, then SIGKILL if needed
*/
static async killProcessGracefully(pid) {
try {
// First try SIGTERM (graceful)
process.kill(pid, 'SIGTERM');
// Wait for grace period
await this.sleep(this.GRACE_PERIOD_MS);
// Check if process still exists
if (this.isProcessAlive(pid)) {
console.log(`โ ๏ธ Process ${pid} didn't respond to SIGTERM, using SIGKILL`);
process.kill(pid, 'SIGKILL');
// Wait a bit more
await this.sleep(1000);
// Final check
if (this.isProcessAlive(pid)) {
console.error(`โ Process ${pid} survived SIGKILL`);
return false;
}
}
return true;
}
catch (error) {
if (error.code === 'ESRCH') {
// Process already dead
return true;
}
if (error.code === 'EPERM') {
console.warn(`โ ๏ธ Permission denied killing PID ${pid}`);
return false;
}
throw error;
}
}
/**
* Check if a process is still alive
*/
static isProcessAlive(pid) {
try {
process.kill(pid, 0); // Signal 0 doesn't kill, just checks existence
return true;
}
catch (error) {
return error.code !== 'ESRCH';
}
}
/**
* Parse ps aux output into process objects
*/
static parseProcessList(output) {
const lines = output.split('\\n').slice(1); // Skip header
const processes = [];
for (const line of lines) {
if (!line.trim())
continue;
const parts = line.trim().split(/\\s+/);
if (parts.length < 11)
continue;
const pid = parseInt(parts[1]);
const status = parts[7]; // STAT column
const cmd = parts.slice(10).join(' '); // CMD column
if (!isNaN(pid)) {
processes.push({ pid, cmd, status });
}
}
return processes;
}
/**
* Check if a process is an AI-Debug process
*/
static isAIDebugProcess(proc) {
return this.AI_DEBUG_PATTERNS.some(pattern => proc.cmd.includes(pattern));
}
/**
* Check if a process is a zombie based on status
*/
static isZombieProcess(proc) {
// Check for zombie status indicators
// UE = uninterruptible sleep (often indicates hung process)
// Z = zombie
// T = stopped
return proc.status.includes('UE') ||
proc.status.includes('Z') ||
proc.status.includes('T');
}
/**
* Setup periodic zombie cleanup
*/
static setupPeriodicCleanup(intervalMs = 5 * 60 * 1000) {
console.log(`๐ Setting up periodic zombie cleanup every ${intervalMs / 1000}s`);
return setInterval(async () => {
try {
await this.cleanupZombieProcesses();
}
catch (error) {
console.error('โ Periodic zombie cleanup failed:', error);
}
}, intervalMs);
}
/**
* Prevent current process from becoming zombie
*/
static preventCurrentProcessZombie() {
// Track if we're shutting down to prevent multiple exits
let isShuttingDown = false;
// Ensure clean shutdown on various signals
const cleanup = async (signal) => {
if (isShuttingDown)
return;
isShuttingDown = true;
console.log(`๐งน Preventing zombie state during shutdown (${signal})...`);
// Close stdio streams to prevent hanging
try {
process.stdin.destroy();
process.stdout.end();
process.stderr.end();
}
catch (error) {
// Ignore errors during stream cleanup
}
// Force exit after cleanup timeout
setTimeout(() => {
console.log('โก Force exit to prevent zombie state');
process.exit(0);
}, 1000); // Reduced timeout for faster cleanup
};
process.on('SIGINT', () => cleanup('SIGINT'));
process.on('SIGTERM', () => cleanup('SIGTERM'));
process.on('SIGQUIT', () => cleanup('SIGQUIT'));
process.on('SIGHUP', () => cleanup('SIGHUP'));
// Handle broken pipe errors
process.on('SIGPIPE', () => {
console.log('๐จ Broken pipe detected, exiting to prevent zombie');
process.exit(0);
});
// Prevent hanging on uncaught exceptions
process.on('uncaughtException', (error) => {
console.error('โ Uncaught exception, forcing exit to prevent zombie:', error);
process.exit(1);
});
process.on('unhandledRejection', (reason) => {
console.error('โ Unhandled rejection, forcing exit to prevent zombie:', reason);
process.exit(1);
});
}
static sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
//# sourceMappingURL=zombie-process-killer.js.map