From 1b0d92d6ad0dc8d6941c665ce294265f0cbf0edd Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Fri, 18 Jul 2025 12:15:59 +0200 Subject: [PATCH] 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 --- Dockerfile | 9 ++++- cleanup-chromium.sh | 26 ++++++++++++ docker-entrypoint.sh | 34 ++++++++++++++++ lib/process-cleanup.ts | 74 +++++++++++++++++++++++++++++++++++ lib/tradingview-automation.ts | 32 ++++++++++++++- 5 files changed, 172 insertions(+), 3 deletions(-) create mode 100755 cleanup-chromium.sh create mode 100755 docker-entrypoint.sh create mode 100644 lib/process-cleanup.ts diff --git a/Dockerfile b/Dockerfile index a6edfef..9818ec3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/cleanup-chromium.sh b/cleanup-chromium.sh new file mode 100755 index 0000000..c2c4791 --- /dev/null +++ b/cleanup-chromium.sh @@ -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" diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..4355e59 --- /dev/null +++ b/docker-entrypoint.sh @@ -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 diff --git a/lib/process-cleanup.ts b/lib/process-cleanup.ts new file mode 100644 index 0000000..4f1a0e7 --- /dev/null +++ b/lib/process-cleanup.ts @@ -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 { + 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 { + 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 { + await this.handleShutdown('MANUAL') + } +} + +// Initialize the cleanup handler +const processCleanup = ProcessCleanup.getInstance() + +export default processCleanup diff --git a/lib/tradingview-automation.ts b/lib/tradingview-automation.ts index 0417bc9..a0bccfb 100644 --- a/lib/tradingview-automation.ts +++ b/lib/tradingview-automation.ts @@ -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') }