From 153aacba03b095c9fa5e95c5c3acee000f26a87c Mon Sep 17 00:00:00 2001 From: Smittix Date: Sat, 28 Feb 2026 17:59:46 +0000 Subject: [PATCH] fix: defer heavy init so gunicorn worker serves requests immediately MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Blueprint registration and database init run synchronously (essential for routing). Process cleanup, database cleanup scheduling, and TLE satellite updates are deferred to a background thread with a 1-second delay so the gevent worker can start serving HTTP requests right away. Previously all init ran synchronously during module import, blocking the single gevent worker for minutes while TLE data was fetched from CelesTrak. Also removes duplicate TLE update — init_tle_auto_refresh() already schedules its own background fetch. Co-Authored-By: Claude Opus 4.6 --- app.py | 108 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/app.py b/app.py index 30638a8..e6f5b05 100644 --- a/app.py +++ b/app.py @@ -932,11 +932,15 @@ _app_initialized = False def _init_app() -> None: - """Initialize blueprints, database, cleanup, and websockets. + """Initialize blueprints, database, and websockets. Safe to call multiple times — subsequent calls are no-ops. Called automatically at module level for gunicorn, and also from main() for the Flask dev server path. + + Heavy/network operations (TLE updates, process cleanup) are + deferred to a background thread so the worker can serve + requests immediately. """ global _app_initialized if _app_initialized: @@ -945,81 +949,75 @@ def _init_app() -> None: import os - # Clean up any stale processes from previous runs - cleanup_stale_processes() - cleanup_stale_dump1090() - # Initialize database for settings storage from utils.database import init_db init_db() - # Register database cleanup functions - from utils.database import ( - cleanup_old_signal_history, - cleanup_old_timeline_entries, - cleanup_old_dsc_alerts, - cleanup_old_payloads - ) - cleanup_manager.register_db_cleanup(cleanup_old_signal_history, interval_multiplier=1440) - cleanup_manager.register_db_cleanup(cleanup_old_timeline_entries, interval_multiplier=1440) - cleanup_manager.register_db_cleanup(cleanup_old_dsc_alerts, interval_multiplier=1440) - cleanup_manager.register_db_cleanup(cleanup_old_payloads, interval_multiplier=1440) - - # Start automatic cleanup of stale data entries - cleanup_manager.start() - - # Register blueprints + # Register blueprints (essential — without these, all routes 404) from routes import register_blueprints register_blueprints(app) - # Initialize TLE auto-refresh (must be after blueprint registration) - try: - from routes.satellite import init_tle_auto_refresh - if not os.environ.get('TESTING'): - init_tle_auto_refresh() - except Exception as e: - logger.warning(f"Failed to initialize TLE auto-refresh: {e}") - - # Update TLE data in background thread (non-blocking) - def update_tle_background(): - try: - from routes.satellite import refresh_tle_data - print("Updating satellite TLE data from CelesTrak...") - updated = refresh_tle_data() - if updated: - print(f"TLE data updated for: {', '.join(updated)}") - else: - print("TLE update: No satellites updated (may be offline)") - except Exception as e: - print(f"TLE update failed (will use cached data): {e}") - - import threading - tle_thread = threading.Thread(target=update_tle_background, daemon=True) - tle_thread.start() - # Initialize WebSocket for audio streaming try: from routes.audio_websocket import init_audio_websocket init_audio_websocket(app) - print("WebSocket audio streaming enabled") - except ImportError as e: - print(f"WebSocket audio disabled (install flask-sock): {e}") + except ImportError: + pass # Initialize KiwiSDR WebSocket audio proxy try: from routes.websdr import init_websdr_audio init_websdr_audio(app) - print("KiwiSDR audio proxy enabled") - except ImportError as e: - print(f"KiwiSDR audio proxy disabled: {e}") + except ImportError: + pass # Initialize WebSocket for waterfall streaming try: from routes.waterfall_websocket import init_waterfall_websocket init_waterfall_websocket(app) - print("WebSocket waterfall streaming enabled") - except ImportError as e: - print(f"WebSocket waterfall disabled: {e}") + except ImportError: + pass + + # Defer heavy/network operations so the worker can serve requests immediately + import threading + + def _deferred_init(): + """Run heavy initialization after a short delay.""" + import time + time.sleep(1) # Let the worker start serving first + + # Clean up stale processes from previous runs + try: + cleanup_stale_processes() + cleanup_stale_dump1090() + except Exception as e: + logger.warning(f"Stale process cleanup failed: {e}") + + # Register and start database cleanup + try: + from utils.database import ( + cleanup_old_signal_history, + cleanup_old_timeline_entries, + cleanup_old_dsc_alerts, + cleanup_old_payloads + ) + cleanup_manager.register_db_cleanup(cleanup_old_signal_history, interval_multiplier=1440) + cleanup_manager.register_db_cleanup(cleanup_old_timeline_entries, interval_multiplier=1440) + cleanup_manager.register_db_cleanup(cleanup_old_dsc_alerts, interval_multiplier=1440) + cleanup_manager.register_db_cleanup(cleanup_old_payloads, interval_multiplier=1440) + cleanup_manager.start() + except Exception as e: + logger.warning(f"Cleanup manager init failed: {e}") + + # Initialize TLE auto-refresh (must be after blueprint registration) + try: + from routes.satellite import init_tle_auto_refresh + if not os.environ.get('TESTING'): + init_tle_auto_refresh() + except Exception as e: + logger.warning(f"Failed to initialize TLE auto-refresh: {e}") + + threading.Thread(target=_deferred_init, daemon=True).start() # Auto-initialize when imported (e.g. by gunicorn)