// Aggressive process cleanup utility import { exec } from 'child_process' import { promisify } from 'util' const execAsync = promisify(exec) class AggressiveCleanup { private static instance: AggressiveCleanup private cleanupInterval: NodeJS.Timeout | null = null private isRunning = false private isInitialized = false private constructor() { // Don't auto-start - let startup.ts control it } static getInstance(): AggressiveCleanup { if (!AggressiveCleanup.instance) { AggressiveCleanup.instance = new AggressiveCleanup() } return AggressiveCleanup.instance } startPeriodicCleanup() { if (this.isInitialized) { console.log('๐Ÿ”„ Aggressive cleanup already initialized') return } this.isInitialized = true console.log('๐Ÿš€ Starting aggressive cleanup system') // In development, use on-demand cleanup instead of periodic if (process.env.NODE_ENV === 'development') { console.log('๐Ÿ”ง Development mode: Using on-demand cleanup (triggered after analysis)') console.log('โœ… On-demand cleanup system ready') return } // Production: Clean up every 10 minutes (longer intervals) this.cleanupInterval = setInterval(async () => { try { await this.cleanupOrphanedProcesses() } catch (error) { console.error('Error in periodic cleanup:', error) } }, 10 * 60 * 1000) // 10 minutes // Also run initial cleanup after 60 seconds setTimeout(() => { this.cleanupOrphanedProcesses().catch(console.error) }, 60000) console.log('โœ… Periodic cleanup system started (10 min intervals)') } async cleanupOrphanedProcesses(): Promise { if (this.isRunning) return this.isRunning = true const isDevelopment = process.env.NODE_ENV === 'development' const cleanupType = isDevelopment ? 'gentle' : 'aggressive' console.log(`๐Ÿงน Running ${cleanupType} cleanup for orphaned processes...`) try { // Check for active analysis sessions try { const { progressTracker } = await import('./progress-tracker') const activeSessions = progressTracker.getActiveSessions() if (activeSessions.length > 0) { console.log(`โš ๏ธ Skipping cleanup - ${activeSessions.length} active analysis sessions: ${activeSessions.join(', ')}`) return } console.log('โœ… No active analysis sessions, proceeding with cleanup') } catch (importError) { console.error('โŒ Error importing progress tracker:', importError) console.log('โš ๏ธ Skipping cleanup due to import error') return } // Find and kill orphaned chromium processes const chromiumProcesses = await this.findChromiumProcesses() if (chromiumProcesses.length > 0) { console.log(`Found ${chromiumProcesses.length} chromium processes, cleaning up...`) for (const pid of chromiumProcesses) { try { if (isDevelopment) { // In development, use gentler SIGTERM first console.log(`๐Ÿ”ง Dev mode: Gentle shutdown of process ${pid}`) await execAsync(`kill -TERM ${pid}`) // Give process 3 seconds to shut down gracefully await new Promise(resolve => setTimeout(resolve, 3000)) // Check if process is still running try { await execAsync(`kill -0 ${pid}`) // Process still running, force kill console.log(`โš ๏ธ Process ${pid} didn't shut down gracefully, force killing`) await execAsync(`kill -9 ${pid}`) } catch { // Process already dead, that's good console.log(`โœ… Process ${pid} shut down gracefully`) } } else { // Production: immediate force kill await execAsync(`kill -9 ${pid}`) console.log(`โœ… Killed process ${pid}`) } } catch (error) { // Process might already be dead console.log(`โ„น๏ธ Process ${pid} may already be terminated`) } } } else { console.log('โœ… No orphaned chromium processes found') } // Clean up temp directories try { await execAsync('rm -rf /tmp/puppeteer_dev_chrome_profile-* 2>/dev/null || true') console.log('โœ… Cleaned up temp directories') } catch (error) { // Ignore errors } // Clean up shared memory try { await execAsync('rm -rf /dev/shm/.org.chromium.* 2>/dev/null || true') console.log('โœ… Cleaned up shared memory') } catch (error) { // Ignore errors } } catch (error) { console.error(`Error in ${cleanupType} cleanup:`, error) } finally { this.isRunning = false } } private async findChromiumProcesses(): Promise { try { const { stdout } = await execAsync('ps aux | grep -E "(chromium|chrome)" | grep -v grep | awk \'{print $2}\'') return stdout.trim().split('\n').filter((pid: string) => pid && pid !== '') } catch (error) { return [] } } async forceCleanup(): Promise { console.log('๐Ÿšจ Force cleanup initiated...') // Stop periodic cleanup if (this.cleanupInterval) { clearInterval(this.cleanupInterval) } // Run aggressive cleanup await this.cleanupOrphanedProcesses() // Kill all chromium processes try { await execAsync('pkill -9 -f "chromium" 2>/dev/null || true') await execAsync('pkill -9 -f "chrome" 2>/dev/null || true') console.log('โœ… Force killed all browser processes') } catch (error) { console.error('Error in force cleanup:', error) } } // New method for on-demand cleanup after analysis async runPostAnalysisCleanup(): Promise { console.log('๐Ÿงน Post-analysis cleanup triggered...') // Small delay to ensure analysis processes are fully closed await new Promise(resolve => setTimeout(resolve, 2000)) await this.cleanupOrphanedProcesses() } stop(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval) this.cleanupInterval = null } } } // Initialize the aggressive cleanup const aggressiveCleanup = AggressiveCleanup.getInstance() export default aggressiveCleanup