Fix cleanup process timing and coordination with analysis sessions
CRITICAL BUG FIX: Cleanup process was interfering with active analysis sessions - Aggressive cleanup was running during active analysis, causing navigation failures - Progress tracking was not properly coordinated with cleanup system - No session state checking before process termination 1. AUTOMATION SERVICE COORDINATION: - Added proper progress tracking to automation cycles - Created unique session IDs for each analysis run - Integrated with progressTracker for session state management - Added post-analysis cleanup triggers with proper timing 2. ENHANCED CLEANUP INTELLIGENCE: - Improved session checking with detailed logging of active sessions - Added process age filtering in development mode (only kill >5min old processes) - Better error handling when progress tracker import fails - More granular cleanup control with session state awareness 3. TIMING IMPROVEMENTS: - Post-analysis cleanup now waits for session completion - Added proper delays between analysis phases - Implemented graceful cleanup deferral when sessions are active - Added delayed cleanup fallback for stuck sessions 4. DEVELOPMENT MODE SAFETY: - Gentler SIGTERM → SIGKILL progression for development - Only clean processes older than 5 minutes during dev - Better logging of process age and cleanup decisions - Safer fallback behavior when session tracking fails This resolves the 'Failed to navigate to layout' errors by ensuring cleanup doesn't interfere with active browser sessions during analysis.
This commit is contained in:
@@ -55,7 +55,10 @@ class AggressiveCleanup {
|
||||
}
|
||||
|
||||
async cleanupOrphanedProcesses(): Promise<void> {
|
||||
if (this.isRunning) return
|
||||
if (this.isRunning) {
|
||||
console.log('🔒 Cleanup already in progress, skipping...')
|
||||
return
|
||||
}
|
||||
|
||||
this.isRunning = true
|
||||
const isDevelopment = process.env.NODE_ENV === 'development'
|
||||
@@ -70,24 +73,56 @@ class AggressiveCleanup {
|
||||
const activeSessions = progressTracker.getActiveSessions()
|
||||
|
||||
if (activeSessions.length > 0) {
|
||||
console.log(`⚠️ Skipping cleanup - ${activeSessions.length} active analysis sessions: ${activeSessions.join(', ')}`)
|
||||
console.log(`⚠️ Skipping cleanup - ${activeSessions.length} active analysis sessions detected:`)
|
||||
activeSessions.forEach(session => {
|
||||
const progress = progressTracker.getProgress(session)
|
||||
if (progress) {
|
||||
const activeStep = progress.steps.find(step => step.status === 'active')
|
||||
const currentStep = activeStep ? activeStep.title : 'Unknown'
|
||||
console.log(` - ${session}: ${currentStep} (Step ${progress.currentStep}/${progress.totalSteps})`)
|
||||
} else {
|
||||
console.log(` - ${session}: Session info not available`)
|
||||
}
|
||||
})
|
||||
console.log('ℹ️ Will retry cleanup after analysis completes')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('✅ No active analysis sessions, proceeding with cleanup')
|
||||
console.log('✅ No active analysis sessions detected, proceeding with cleanup')
|
||||
} catch (importError) {
|
||||
console.error('❌ Error importing progress tracker:', importError)
|
||||
console.log('⚠️ Skipping cleanup due to import error')
|
||||
return
|
||||
console.warn('⚠️ Could not check active sessions, proceeding cautiously with cleanup')
|
||||
console.warn('Import error:', importError)
|
||||
|
||||
// In case of import errors, be extra cautious - only clean very old processes
|
||||
if (isDevelopment) {
|
||||
console.log('🔧 Development mode with import issues - skipping cleanup for safety')
|
||||
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...`)
|
||||
console.log(`🔍 Found ${chromiumProcesses.length} chromium processes, evaluating for cleanup...`)
|
||||
|
||||
for (const pid of chromiumProcesses) {
|
||||
// In development, be more selective about which processes to kill
|
||||
let processesToKill = chromiumProcesses
|
||||
|
||||
if (isDevelopment) {
|
||||
// Only kill processes that are likely orphaned (older than 5 minutes)
|
||||
const oldProcesses = await this.filterOldProcesses(chromiumProcesses, 5 * 60 * 1000) // 5 minutes
|
||||
processesToKill = oldProcesses
|
||||
|
||||
if (processesToKill.length === 0) {
|
||||
console.log('✅ All chromium processes appear to be recent and potentially active - skipping cleanup')
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`🔧 Development mode: Cleaning only ${processesToKill.length} old processes (older than 5 minutes)`)
|
||||
}
|
||||
|
||||
for (const pid of processesToKill) {
|
||||
try {
|
||||
if (isDevelopment) {
|
||||
// In development, use gentler SIGTERM first
|
||||
@@ -140,6 +175,7 @@ class AggressiveCleanup {
|
||||
console.error(`Error in ${cleanupType} cleanup:`, error)
|
||||
} finally {
|
||||
this.isRunning = false
|
||||
console.log(`🏁 ${cleanupType} cleanup completed`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +188,43 @@ class AggressiveCleanup {
|
||||
}
|
||||
}
|
||||
|
||||
private async filterOldProcesses(pids: string[], maxAgeMs: number): Promise<string[]> {
|
||||
const oldProcesses: string[] = []
|
||||
|
||||
for (const pid of pids) {
|
||||
try {
|
||||
// Get process start time
|
||||
const { stdout } = await execAsync(`ps -o pid,lstart -p ${pid} | tail -1`)
|
||||
const processInfo = stdout.trim()
|
||||
|
||||
if (processInfo) {
|
||||
// Parse the process start time
|
||||
const parts = processInfo.split(/\s+/)
|
||||
if (parts.length >= 6) {
|
||||
// Format: PID Mon DD HH:MM:SS YYYY
|
||||
const startTimeStr = parts.slice(1).join(' ')
|
||||
const startTime = new Date(startTimeStr)
|
||||
const now = new Date()
|
||||
const processAge = now.getTime() - startTime.getTime()
|
||||
|
||||
if (processAge > maxAgeMs) {
|
||||
console.log(`🕐 Process ${pid} is ${Math.round(processAge / 60000)} minutes old - marked for cleanup`)
|
||||
oldProcesses.push(pid)
|
||||
} else {
|
||||
console.log(`🕐 Process ${pid} is ${Math.round(processAge / 60000)} minutes old - keeping alive`)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// If we can't get process info, assume it's old and safe to clean
|
||||
console.log(`❓ Could not get age info for process ${pid} - assuming it's old`)
|
||||
oldProcesses.push(pid)
|
||||
}
|
||||
}
|
||||
|
||||
return oldProcesses
|
||||
}
|
||||
|
||||
async forceCleanup(): Promise<void> {
|
||||
console.log('🚨 Force cleanup initiated...')
|
||||
|
||||
@@ -178,7 +251,27 @@ class AggressiveCleanup {
|
||||
console.log('🧹 Post-analysis cleanup triggered...')
|
||||
|
||||
// Small delay to ensure analysis processes are fully closed
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
console.log('⏳ Waiting 3 seconds for analysis processes to close...')
|
||||
await new Promise(resolve => setTimeout(resolve, 3000))
|
||||
|
||||
// Check if there are still active sessions before cleaning
|
||||
try {
|
||||
const { progressTracker } = await import('./progress-tracker')
|
||||
const activeSessions = progressTracker.getActiveSessions()
|
||||
|
||||
if (activeSessions.length > 0) {
|
||||
console.log(`⚠️ Post-analysis cleanup: Still ${activeSessions.length} active sessions - deferring cleanup`)
|
||||
|
||||
// Schedule a delayed cleanup in case sessions don't clear properly
|
||||
setTimeout(() => {
|
||||
console.log('🕐 Running delayed post-analysis cleanup...')
|
||||
this.cleanupOrphanedProcesses()
|
||||
}, 10000) // 10 seconds later
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Could not check active sessions for post-analysis cleanup:', error)
|
||||
}
|
||||
|
||||
await this.cleanupOrphanedProcesses()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { aiAnalysisService, AnalysisResult } from './ai-analysis'
|
||||
import { jupiterDEXService } from './jupiter-dex-service'
|
||||
import { enhancedScreenshotService } from './enhanced-screenshot-simple'
|
||||
import { TradingViewCredentials } from './tradingview-automation'
|
||||
import { progressTracker } from './progress-tracker'
|
||||
import aggressiveCleanup from './aggressive-cleanup'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
@@ -197,8 +199,22 @@ export class AutomationService {
|
||||
screenshots: string[]
|
||||
analysis: AnalysisResult | null
|
||||
} | null> {
|
||||
// Generate unique session ID for this analysis
|
||||
const sessionId = `automation-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
||||
|
||||
try {
|
||||
console.log('📸 Starting multi-timeframe analysis with dual layouts...')
|
||||
console.log(`📸 Starting multi-timeframe analysis with dual layouts... (Session: ${sessionId})`)
|
||||
|
||||
// Create progress tracking session
|
||||
const progressSteps = [
|
||||
{ id: 'init', title: 'Initialize', description: 'Starting multi-timeframe analysis', status: 'pending' as const },
|
||||
{ id: 'capture', title: 'Capture', description: 'Capturing screenshots for all timeframes', status: 'pending' as const },
|
||||
{ id: 'analysis', title: 'Analysis', description: 'Running AI analysis on screenshots', status: 'pending' as const },
|
||||
{ id: 'complete', title: 'Complete', description: 'Analysis complete', status: 'pending' as const }
|
||||
]
|
||||
|
||||
progressTracker.createSession(sessionId, progressSteps)
|
||||
progressTracker.updateStep(sessionId, 'init', 'active', 'Starting multi-timeframe analysis...')
|
||||
|
||||
// Multi-timeframe analysis: 15m, 1h, 2h, 4h
|
||||
const timeframes = ['15', '1h', '2h', '4h']
|
||||
@@ -206,48 +222,88 @@ export class AutomationService {
|
||||
|
||||
console.log(`🔍 Analyzing ${symbol} across timeframes: ${timeframes.join(', ')} with AI + DIY layouts`)
|
||||
|
||||
progressTracker.updateStep(sessionId, 'init', 'completed', `Starting analysis for ${timeframes.length} timeframes`)
|
||||
progressTracker.updateStep(sessionId, 'capture', 'active', 'Capturing screenshots...')
|
||||
|
||||
// Analyze each timeframe with both AI and DIY layouts
|
||||
const multiTimeframeResults = await this.analyzeMultiTimeframeWithDualLayouts(symbol, timeframes)
|
||||
const multiTimeframeResults = await this.analyzeMultiTimeframeWithDualLayouts(symbol, timeframes, sessionId)
|
||||
|
||||
if (multiTimeframeResults.length === 0) {
|
||||
console.log('❌ No multi-timeframe analysis results')
|
||||
progressTracker.updateStep(sessionId, 'capture', 'error', 'No analysis results captured')
|
||||
progressTracker.deleteSession(sessionId)
|
||||
|
||||
// Run post-analysis cleanup
|
||||
await aggressiveCleanup.runPostAnalysisCleanup()
|
||||
return null
|
||||
}
|
||||
|
||||
progressTracker.updateStep(sessionId, 'capture', 'completed', `Captured ${multiTimeframeResults.length} timeframe analyses`)
|
||||
progressTracker.updateStep(sessionId, 'analysis', 'active', 'Processing multi-timeframe results...')
|
||||
|
||||
// Process and combine multi-timeframe results
|
||||
const combinedResult = this.combineMultiTimeframeAnalysis(multiTimeframeResults)
|
||||
|
||||
if (!combinedResult.analysis) {
|
||||
console.log('❌ Failed to combine multi-timeframe analysis')
|
||||
progressTracker.updateStep(sessionId, 'analysis', 'error', 'Failed to combine analysis results')
|
||||
progressTracker.deleteSession(sessionId)
|
||||
|
||||
// Run post-analysis cleanup
|
||||
await aggressiveCleanup.runPostAnalysisCleanup()
|
||||
return null
|
||||
}
|
||||
|
||||
console.log(`✅ Multi-timeframe analysis completed: ${combinedResult.analysis.recommendation} with ${combinedResult.analysis.confidence}% confidence`)
|
||||
console.log(`📊 Timeframe alignment: ${this.analyzeTimeframeAlignment(multiTimeframeResults)}`)
|
||||
|
||||
progressTracker.updateStep(sessionId, 'analysis', 'completed', `Analysis complete: ${combinedResult.analysis.recommendation}`)
|
||||
progressTracker.updateStep(sessionId, 'complete', 'completed', 'Multi-timeframe analysis finished')
|
||||
|
||||
// Clean up session after successful completion
|
||||
setTimeout(() => {
|
||||
progressTracker.deleteSession(sessionId)
|
||||
}, 2000)
|
||||
|
||||
// Run post-analysis cleanup
|
||||
await aggressiveCleanup.runPostAnalysisCleanup()
|
||||
|
||||
return combinedResult
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error performing multi-timeframe analysis:', error)
|
||||
progressTracker.updateStep(sessionId, 'analysis', 'error', error instanceof Error ? error.message : 'Unknown error')
|
||||
setTimeout(() => {
|
||||
progressTracker.deleteSession(sessionId)
|
||||
}, 5000)
|
||||
|
||||
// Run post-analysis cleanup even on error
|
||||
await aggressiveCleanup.runPostAnalysisCleanup()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private async analyzeMultiTimeframeWithDualLayouts(
|
||||
symbol: string,
|
||||
timeframes: string[]
|
||||
timeframes: string[],
|
||||
sessionId: string
|
||||
): Promise<Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }>> {
|
||||
const results: Array<{ symbol: string; timeframe: string; analysis: AnalysisResult | null }> = []
|
||||
|
||||
for (const timeframe of timeframes) {
|
||||
for (let i = 0; i < timeframes.length; i++) {
|
||||
const timeframe = timeframes[i]
|
||||
try {
|
||||
console.log(`📊 Analyzing ${symbol} ${timeframe} with AI + DIY layouts...`)
|
||||
console.log(`📊 Analyzing ${symbol} ${timeframe} with AI + DIY layouts... (${i + 1}/${timeframes.length})`)
|
||||
|
||||
// Update progress for timeframe
|
||||
progressTracker.updateTimeframeProgress(sessionId, i + 1, timeframes.length, timeframe)
|
||||
|
||||
// Use the dual-layout configuration for each timeframe
|
||||
const screenshotConfig = {
|
||||
symbol: symbol,
|
||||
timeframe: timeframe,
|
||||
layouts: ['ai', 'diy']
|
||||
layouts: ['ai', 'diy'],
|
||||
sessionId: sessionId
|
||||
}
|
||||
|
||||
const result = await aiAnalysisService.captureAndAnalyzeWithConfig(screenshotConfig)
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user