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:
@@ -74,9 +74,14 @@ RUN chmod +x node_modules/.bin/*
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Copy startup script
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# Set environment variables for Puppeteer
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||
ENV DOCKER_ENV=true
|
||||
|
||||
# Start the app (default to development mode)
|
||||
CMD ["npm", "run", "dev:docker"]
|
||||
# Start the app with cleanup handlers
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
|
||||
26
cleanup-chromium.sh
Executable file
26
cleanup-chromium.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Cleanup script to kill zombie Chromium processes
|
||||
# This should be run periodically or when the application shuts down
|
||||
|
||||
echo "🧹 Cleaning up zombie Chromium processes..."
|
||||
|
||||
# Kill all defunct chromium processes
|
||||
pkill -f "chromium.*defunct" 2>/dev/null
|
||||
|
||||
# Kill any remaining chromium processes in the container
|
||||
if [ -n "$DOCKER_ENV" ]; then
|
||||
echo "Running in Docker environment, cleaning up container processes..."
|
||||
# In Docker, we need to be more careful about process cleanup
|
||||
ps aux | grep '[c]hromium' | grep -v grep | awk '{print $2}' | xargs -r kill -TERM 2>/dev/null
|
||||
sleep 2
|
||||
ps aux | grep '[c]hromium' | grep -v grep | awk '{print $2}' | xargs -r kill -KILL 2>/dev/null
|
||||
else
|
||||
echo "Running in host environment, cleaning up host processes..."
|
||||
pkill -f "chromium" 2>/dev/null
|
||||
fi
|
||||
|
||||
# Clean up any temporary puppeteer profiles
|
||||
rm -rf /tmp/puppeteer_dev_chrome_profile-* 2>/dev/null
|
||||
|
||||
echo "✅ Cleanup completed"
|
||||
34
docker-entrypoint.sh
Executable file
34
docker-entrypoint.sh
Executable file
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Trading Bot Startup Script with Process Cleanup
|
||||
# This script initializes the process cleanup handlers and starts the Next.js app
|
||||
|
||||
echo "🚀 Starting Trading Bot with Process Cleanup..."
|
||||
|
||||
# Initialize process cleanup
|
||||
echo "🧹 Initializing process cleanup handlers..."
|
||||
|
||||
# Create a signal handler to cleanup on container stop
|
||||
cleanup() {
|
||||
echo "🛑 Received shutdown signal, cleaning up..."
|
||||
|
||||
# Kill any remaining chromium processes
|
||||
pkill -f "chromium" 2>/dev/null || true
|
||||
|
||||
# Clean up temporary files
|
||||
rm -rf /tmp/puppeteer_dev_chrome_profile-* 2>/dev/null || true
|
||||
|
||||
echo "✅ Cleanup completed"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Register signal handlers
|
||||
trap cleanup SIGINT SIGTERM SIGQUIT
|
||||
|
||||
# Start the Next.js application
|
||||
echo "🚀 Starting Next.js application..."
|
||||
if [ "$NODE_ENV" = "development" ]; then
|
||||
exec npm run dev:docker
|
||||
else
|
||||
exec npm start
|
||||
fi
|
||||
74
lib/process-cleanup.ts
Normal file
74
lib/process-cleanup.ts
Normal 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
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user