feat: implement robust browser process cleanup system

- Add cleanup-chromium.sh script for manual zombie process cleanup
- Add docker-entrypoint.sh with signal handlers for graceful shutdown
- Add lib/process-cleanup.ts for automatic cleanup on app termination
- Enhanced forceCleanup() method in tradingview-automation.ts:
  - Individual page closing before browser termination
  - Force kill remaining processes with SIGKILL
  - Reset operation locks after cleanup
- Improved browser launch args to prevent zombie processes:
  - Better crash reporter handling
  - Enhanced background process management
  - Removed problematic --single-process flag
- Updated Dockerfile to use new entrypoint with cleanup handlers
- Set DOCKER_ENV environment variable for container detection
- Add proper signal handling (SIGINT, SIGTERM, SIGQUIT)
- Automatic cleanup of temporary Puppeteer profiles

Resolves zombie Chromium process accumulation issue
This commit is contained in:
mindesbunister
2025-07-18 12:15:59 +02:00
parent 1a7bdb4109
commit 1b0d92d6ad
5 changed files with 172 additions and 3 deletions

74
lib/process-cleanup.ts Normal file
View File

@@ -0,0 +1,74 @@
// Process cleanup utility for graceful shutdown
import { enhancedScreenshotService } from './enhanced-screenshot'
import { tradingViewAutomation } from './tradingview-automation'
class ProcessCleanup {
private static instance: ProcessCleanup
private isShuttingDown = false
private constructor() {
// Register cleanup handlers
process.on('SIGINT', this.handleShutdown.bind(this))
process.on('SIGTERM', this.handleShutdown.bind(this))
process.on('SIGQUIT', this.handleShutdown.bind(this))
process.on('uncaughtException', this.handleError.bind(this))
process.on('unhandledRejection', this.handleError.bind(this))
}
static getInstance(): ProcessCleanup {
if (!ProcessCleanup.instance) {
ProcessCleanup.instance = new ProcessCleanup()
}
return ProcessCleanup.instance
}
private async handleShutdown(signal: string): Promise<void> {
if (this.isShuttingDown) {
console.log('Already shutting down, forcing exit...')
process.exit(1)
}
this.isShuttingDown = true
console.log(`\n🛑 Received ${signal}, initiating graceful shutdown...`)
try {
// Clean up screenshot service
console.log('🧹 Cleaning up screenshot service...')
await enhancedScreenshotService.cleanup()
// Clean up trading view automation
console.log('🧹 Cleaning up TradingView automation...')
await tradingViewAutomation.forceCleanup()
console.log('✅ Graceful shutdown completed')
process.exit(0)
} catch (error) {
console.error('❌ Error during shutdown:', error)
process.exit(1)
}
}
private async handleError(error: Error): Promise<void> {
console.error('❌ Unhandled error:', error)
// Attempt cleanup
try {
await enhancedScreenshotService.cleanup()
await tradingViewAutomation.forceCleanup()
} catch (cleanupError) {
console.error('❌ Error during error cleanup:', cleanupError)
}
process.exit(1)
}
// Manual cleanup method
async cleanup(): Promise<void> {
await this.handleShutdown('MANUAL')
}
}
// Initialize the cleanup handler
const processCleanup = ProcessCleanup.getInstance()
export default processCleanup

View File

@@ -124,7 +124,19 @@ export class TradingViewAutomation {
'--disable-default-apps',
'--disable-sync',
'--window-size=1920,1080',
'--user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
'--user-agent=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
// Additional flags to prevent zombie processes
'--disable-background-networking',
'--disable-background-timer-throttling',
'--disable-backgrounding-occluded-windows',
'--disable-breakpad',
'--disable-client-side-phishing-detection',
'--disable-component-extensions-with-background-pages',
'--disable-crash-reporter',
'--disable-hang-monitor',
'--disable-prompt-on-repost',
'--no-crash-upload'
// Removed --single-process as it causes issues with Puppeteer
]
})
@@ -148,7 +160,24 @@ export class TradingViewAutomation {
console.log('🧹 Force cleanup: Closing browser and resetting state...')
try {
if (this.browser) {
// First, close all pages
const pages = await this.browser.pages()
for (const page of pages) {
try {
await page.close()
} catch (e) {
console.log('WARNING: Error closing page:', e)
}
}
// Then close the browser
await this.browser.close()
// Force kill any remaining processes
const browserProcess = this.browser.process()
if (browserProcess && !browserProcess.killed) {
browserProcess.kill('SIGKILL')
}
}
} catch (e) {
console.log('WARNING: Error during browser cleanup:', e)
@@ -157,6 +186,7 @@ export class TradingViewAutomation {
this.browser = null
this.page = null
this.isAuthenticated = false
this.operationLock = false
console.log('✅ Cleanup completed')
}