feat: implement on-demand cleanup triggered after analysis completion

- Replace time-based cleanup with on-demand cleanup in development mode
- Cleanup is triggered immediately after AI analysis completes
- Added runPostAnalysisCleanup() method to aggressive-cleanup service
- Cleanup triggers added to both single and batch analysis endpoints
- More efficient: cleanup happens only when needed, not on timer
- Prevents zombie processes without interfering with active analysis
- Production mode still uses periodic cleanup as backup (10 min intervals)
- Gentle cleanup in development: SIGTERM first, then SIGKILL if needed
This commit is contained in:
mindesbunister
2025-07-18 19:11:15 +02:00
parent 2bdf9e2b41
commit 74b0087f17
4 changed files with 84 additions and 12 deletions

View File

@@ -206,6 +206,17 @@ export async function POST(request) {
// Clean up session // Clean up session
setTimeout(() => progressTracker.deleteSession(sessionId), 2000) setTimeout(() => progressTracker.deleteSession(sessionId), 2000)
// Trigger post-analysis cleanup in development mode
if (process.env.NODE_ENV === 'development') {
try {
const { default: aggressiveCleanup } = await import('../../../lib/aggressive-cleanup')
// Run cleanup in background, don't block the response
aggressiveCleanup.runPostAnalysisCleanup().catch(console.error)
} catch (cleanupError) {
console.error('Error triggering post-batch-analysis cleanup:', cleanupError)
}
}
return NextResponse.json(result) return NextResponse.json(result)
} catch (error) { } catch (error) {

View File

@@ -113,6 +113,17 @@ export async function POST(request) {
message: `Successfully captured ${screenshots.length} screenshot(s)${analysis ? ' with AI analysis' : ''}` message: `Successfully captured ${screenshots.length} screenshot(s)${analysis ? ' with AI analysis' : ''}`
} }
// Trigger post-analysis cleanup in development mode
if (process.env.NODE_ENV === 'development') {
try {
const { default: aggressiveCleanup } = await import('../../../lib/aggressive-cleanup')
// Run cleanup in background, don't block the response
aggressiveCleanup.runPostAnalysisCleanup().catch(console.error)
} catch (cleanupError) {
console.error('Error triggering post-analysis cleanup:', cleanupError)
}
}
return NextResponse.json(result) return NextResponse.json(result)
} catch (error) { } catch (error) {
console.error('Enhanced screenshot API error:', error) console.error('Enhanced screenshot API error:', error)

View File

@@ -30,34 +30,38 @@ class AggressiveCleanup {
this.isInitialized = true this.isInitialized = true
console.log('🚀 Starting aggressive cleanup system') console.log('🚀 Starting aggressive cleanup system')
// In development, disable aggressive cleanup to avoid interfering with analysis // In development, use on-demand cleanup instead of periodic
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
console.log('🔒 Aggressive cleanup disabled in development mode') console.log('🔧 Development mode: Using on-demand cleanup (triggered after analysis)')
console.log('✅ On-demand cleanup system ready')
return return
} }
// Clean up every 5 minutes // Production: Clean up every 10 minutes (longer intervals)
this.cleanupInterval = setInterval(async () => { this.cleanupInterval = setInterval(async () => {
try { try {
await this.cleanupOrphanedProcesses() await this.cleanupOrphanedProcesses()
} catch (error) { } catch (error) {
console.error('Error in periodic cleanup:', error) console.error('Error in periodic cleanup:', error)
} }
}, 5 * 60 * 1000) // 5 minutes }, 10 * 60 * 1000) // 10 minutes
// Also run initial cleanup after 30 seconds // Also run initial cleanup after 60 seconds
setTimeout(() => { setTimeout(() => {
this.cleanupOrphanedProcesses().catch(console.error) this.cleanupOrphanedProcesses().catch(console.error)
}, 30000) }, 60000)
console.log('✅ Aggressive cleanup system started (5 min intervals)') console.log('✅ Periodic cleanup system started (10 min intervals)')
} }
async cleanupOrphanedProcesses(): Promise<void> { async cleanupOrphanedProcesses(): Promise<void> {
if (this.isRunning) return if (this.isRunning) return
this.isRunning = true this.isRunning = true
console.log('🧹 Running aggressive cleanup for orphaned processes...') const isDevelopment = process.env.NODE_ENV === 'development'
const cleanupType = isDevelopment ? 'gentle' : 'aggressive'
console.log(`🧹 Running ${cleanupType} cleanup for orphaned processes...`)
try { try {
// Check for active analysis sessions // Check for active analysis sessions
@@ -85,12 +89,35 @@ class AggressiveCleanup {
for (const pid of chromiumProcesses) { for (const pid of chromiumProcesses) {
try { try {
await execAsync(`kill -9 ${pid}`) if (isDevelopment) {
console.log(`✅ Killed process ${pid}`) // 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) { } catch (error) {
// Process might already be dead // 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 // Clean up temp directories
@@ -110,7 +137,7 @@ class AggressiveCleanup {
} }
} catch (error) { } catch (error) {
console.error('Error in aggressive cleanup:', error) console.error(`Error in ${cleanupType} cleanup:`, error)
} finally { } finally {
this.isRunning = false this.isRunning = false
} }
@@ -119,7 +146,7 @@ class AggressiveCleanup {
private async findChromiumProcesses(): Promise<string[]> { private async findChromiumProcesses(): Promise<string[]> {
try { try {
const { stdout } = await execAsync('ps aux | grep -E "(chromium|chrome)" | grep -v grep | awk \'{print $2}\'') const { stdout } = await execAsync('ps aux | grep -E "(chromium|chrome)" | grep -v grep | awk \'{print $2}\'')
return stdout.trim().split('\n').filter(pid => pid && pid !== '') return stdout.trim().split('\n').filter((pid: string) => pid && pid !== '')
} catch (error) { } catch (error) {
return [] return []
} }
@@ -146,6 +173,16 @@ class AggressiveCleanup {
} }
} }
// New method for on-demand cleanup after analysis
async runPostAnalysisCleanup(): Promise<void> {
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 { stop(): void {
if (this.cleanupInterval) { if (this.cleanupInterval) {
clearInterval(this.cleanupInterval) clearInterval(this.cleanupInterval)

View File

@@ -718,6 +718,19 @@ Analyze all provided screenshots comprehensively and return only the JSON respon
setTimeout(() => progressTracker.deleteSession(sessionId), 1000) setTimeout(() => progressTracker.deleteSession(sessionId), 1000)
} }
// Trigger post-analysis cleanup in development mode
if (process.env.NODE_ENV === 'development') {
try {
// Dynamic import to avoid circular dependencies
const aggressiveCleanupModule = await import('./aggressive-cleanup')
const aggressiveCleanup = aggressiveCleanupModule.default
// Run cleanup in background, don't block the response
aggressiveCleanup.runPostAnalysisCleanup().catch(console.error)
} catch (cleanupError) {
console.error('Error triggering post-analysis cleanup:', cleanupError)
}
}
return { return {
screenshots, screenshots,
analysis analysis