Files
trading_bot_v3/lib/aggressive-cleanup.ts
mindesbunister efbec62b68 fix: Restore working cleanup system in development mode
- Change cleanup message from 'DISABLED' to 'Using on-demand cleanup (triggered after analysis)'
- Cleanup is now properly enabled and will run after each analysis cycle
- Prevents Chromium process accumulation that was causing memory leaks
- Maintains the critical cleanup functionality needed for 24/7 operation

This restores the working cleanup system that was accidentally reverted.
2025-07-24 11:19:41 +02:00

344 lines
11 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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 lastApiCallTime = Date.now()
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<void> {
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 {
// Multiple checks for active analysis sessions
let hasActiveSessions = false
// Check 1: Progress tracker
try {
const { progressTracker } = await import('./progress-tracker')
const activeSessions = progressTracker.getActiveSessions()
if (activeSessions.length > 0) {
console.log(`⚠️ Found ${activeSessions.length} active progress sessions: ${activeSessions.join(', ')}`)
hasActiveSessions = true
}
} catch (importError) {
console.log('⚠️ Could not check progress tracker, being conservative')
hasActiveSessions = true // Be conservative if we can't check
}
// Check 2: Recent browser activity (processes less than 2 minutes old)
const chromiumProcesses = await this.findChromiumProcesses()
if (chromiumProcesses.length > 0) {
const recentProcesses = await this.checkProcessAge(chromiumProcesses)
if (recentProcesses.length > 0) {
console.log(`⚠️ Found ${recentProcesses.length} recent browser processes (< 2 min old)`)
hasActiveSessions = true
}
}
// Check 3: In development, be extra conservative - only cleanup if no recent API calls
if (isDevelopment) {
const lastApiCall = this.getLastApiCallTime()
const timeSinceLastApi = Date.now() - lastApiCall
if (timeSinceLastApi < 30000) { // 30 seconds
console.log(`⚠️ Recent API activity detected (${Math.round(timeSinceLastApi/1000)}s ago), skipping cleanup`)
hasActiveSessions = true
}
}
if (hasActiveSessions) {
console.log(`⚠️ Skipping cleanup - active analysis detected`)
return
}
console.log('✅ No active analysis sessions detected, proceeding with cleanup')
// Find and kill orphaned chromium processes
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 5 seconds to shut down gracefully (increased from 3)
await new Promise(resolve => setTimeout(resolve, 5000))
// 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<string[]> {
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 []
}
}
private async checkProcessAge(pids: string[]): Promise<string[]> {
const recentProcesses: string[] = []
const twoMinutesAgo = Date.now() - (2 * 60 * 1000)
for (const pid of pids) {
try {
const { stdout } = await execAsync(`ps -o lstart= -p ${pid}`)
const startTime = new Date(stdout.trim()).getTime()
if (startTime > twoMinutesAgo) {
recentProcesses.push(pid)
}
} catch (error) {
// Process might not exist anymore, skip
}
}
return recentProcesses
}
private getLastApiCallTime(): number {
return this.lastApiCallTime
}
updateApiCallTime(): void {
this.lastApiCallTime = Date.now()
}
async forceCleanup(): Promise<void> {
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<void> {
const isDevelopment = process.env.NODE_ENV === 'development'
if (isDevelopment) {
console.log('🔧 Development mode: Checking if safe to cleanup...')
// In development, still check for completion flags before cleanup
const isAnalysisComplete = await this.checkAnalysisCompletion()
if (isAnalysisComplete) {
console.log('✅ Analysis complete, safe to cleanup Chromium processes')
await this.cleanupChromeProcessesOnly()
} else {
console.log('⏳ Analysis still running, skipping cleanup to prevent interference')
}
return
}
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()
}
private async checkAnalysisCompletion(): Promise<boolean> {
try {
// Check if analysis completion flag exists
const { analysisCompletionFlag } = await import('./analysis-completion-flag')
return analysisCompletionFlag.canCleanup()
} catch (error) {
console.log('⚠️ Could not check analysis completion, assuming it is complete')
return true // Assume complete if we can't check
}
}
private async cleanupChromeProcessesOnly(): Promise<void> {
console.log('🧹 Cleaning up Chromium processes only...')
try {
// First pass - graceful termination
const gracefulCommands = [
"pkill -TERM -f 'chromium.*--remote-debugging-port' || true",
"pkill -TERM -f 'chromium.*--user-data-dir' || true",
"pkill -TERM -f '/usr/lib/chromium/chromium' || true",
"pkill -TERM -f chrome || true"
]
for (const cmd of gracefulCommands) {
try {
await execAsync(cmd)
} catch (error) {
// Ignore errors - processes might not exist
}
}
// Wait 2 seconds for graceful shutdown
await new Promise(resolve => setTimeout(resolve, 2000))
// Second pass - force kill
const forceCommands = [
"pkill -9 -f 'chromium.*--remote-debugging-port' || true",
"pkill -9 -f 'chromium.*--user-data-dir' || true",
"pkill -9 -f '/usr/lib/chromium/chromium' || true",
"pkill -9 -f chrome || true",
"pkill -9 -f 'type=zygote' || true",
"pkill -9 -f 'type=gpu-process' || true",
"pkill -9 -f 'type=utility' || true",
"pkill -9 -f 'defunct' || true"
]
for (const cmd of forceCommands) {
try {
await execAsync(cmd)
} catch (error) {
// Ignore errors - processes might not exist
}
}
console.log('✅ Chromium processes cleanup completed')
} catch (error) {
console.error('❌ Error in Chromium cleanup:', error)
}
}
// Force cleanup after successful trade execution
async forceCleanupAfterTrade(): Promise<void> {
console.log('💰 Trade executed - forcing cleanup of Chromium processes')
// Wait longer to ensure analysis is completely done and no new analysis starts
await new Promise(resolve => setTimeout(resolve, 10000)) // 10 seconds
// Check if analysis is still running
const analysisComplete = await this.checkAnalysisCompletion()
if (analysisComplete) {
console.log('✅ Analysis confirmed complete, proceeding with cleanup')
await this.cleanupChromeProcessesOnly()
} else {
console.log('⏳ Analysis still active, skipping cleanup to prevent interference')
}
}
stop(): void {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval)
this.cleanupInterval = null
}
}
}
// Initialize the aggressive cleanup
const aggressiveCleanup = AggressiveCleanup.getInstance()
export default aggressiveCleanup