fix: defer heavy init so gunicorn worker serves requests immediately

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 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-28 17:59:46 +00:00
parent bcbadac995
commit 153aacba03

108
app.py
View File

@@ -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)