UNPKG

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
/** * 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