442 lines
17 KiB
Python
442 lines
17 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Zentraler Shutdown-Manager für robuste Anwendungsbeendigung
|
|
Koordiniert alle Cleanup-Operationen mit Timeouts und Fehlerbehandlung
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import signal
|
|
import threading
|
|
import time
|
|
import atexit
|
|
from typing import List, Callable, Dict, Any, Optional
|
|
from datetime import datetime
|
|
from contextlib import contextmanager
|
|
import logging
|
|
|
|
# Logging-Setup mit Fallback
|
|
try:
|
|
from utils.logging_config import get_logger
|
|
shutdown_logger = get_logger("shutdown_manager")
|
|
except ImportError:
|
|
logging.basicConfig(level=logging.INFO)
|
|
shutdown_logger = logging.getLogger("shutdown_manager")
|
|
|
|
|
|
class ShutdownManager:
|
|
"""
|
|
Zentraler Manager für ordnungsgemäße Anwendungsbeendigung.
|
|
Koordiniert alle Cleanup-Operationen mit Timeouts und Prioritäten.
|
|
"""
|
|
|
|
def __init__(self, timeout: int = 30):
|
|
self.timeout = timeout
|
|
self.is_shutting_down = False
|
|
self.shutdown_started = None
|
|
self.cleanup_functions: List[Dict[str, Any]] = []
|
|
self.components: Dict[str, Any] = {}
|
|
self._lock = threading.Lock()
|
|
self._signal_handlers_registered = False
|
|
|
|
# Shutdown-Ereignis für Thread-Koordination
|
|
self.shutdown_event = threading.Event()
|
|
|
|
# Registriere grundlegende Signal-Handler
|
|
self._register_signal_handlers()
|
|
|
|
shutdown_logger.info("🔧 Shutdown-Manager initialisiert")
|
|
|
|
def _register_signal_handlers(self):
|
|
"""Registriert plattformspezifische Signal-Handler"""
|
|
if self._signal_handlers_registered:
|
|
return
|
|
|
|
try:
|
|
if os.name == 'nt': # Windows
|
|
signal.signal(signal.SIGINT, self._signal_handler)
|
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
if hasattr(signal, 'SIGBREAK'):
|
|
signal.signal(signal.SIGBREAK, self._signal_handler)
|
|
else: # Unix/Linux
|
|
signal.signal(signal.SIGINT, self._signal_handler)
|
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
signal.signal(signal.SIGHUP, self._signal_handler)
|
|
|
|
self._signal_handlers_registered = True
|
|
shutdown_logger.debug("✅ Signal-Handler registriert")
|
|
|
|
except Exception as e:
|
|
shutdown_logger.warning(f"⚠️ Signal-Handler konnten nicht registriert werden: {e}")
|
|
|
|
def _signal_handler(self, signum, frame):
|
|
"""Zentraler Signal-Handler für ordnungsgemäße Beendigung"""
|
|
if not self.is_shutting_down:
|
|
shutdown_logger.warning(f"🛑 Signal {signum} empfangen - starte koordiniertes Shutdown")
|
|
self.shutdown()
|
|
|
|
def register_component(self, name: str, component: Any, stop_method: str = "stop"):
|
|
"""
|
|
Registriert eine Komponente für ordnungsgemäße Beendigung
|
|
|
|
Args:
|
|
name: Name der Komponente
|
|
component: Komponenten-Objekt
|
|
stop_method: Name der Stopp-Methode (Standard: "stop")
|
|
"""
|
|
with self._lock:
|
|
self.components[name] = {
|
|
'component': component,
|
|
'stop_method': stop_method,
|
|
'priority': 1 # Standard-Priorität
|
|
}
|
|
shutdown_logger.debug(f"📝 Komponente '{name}' registriert")
|
|
|
|
def register_cleanup_function(self,
|
|
func: Callable,
|
|
name: str,
|
|
priority: int = 1,
|
|
timeout: int = 10,
|
|
args: tuple = (),
|
|
kwargs: dict = None):
|
|
"""
|
|
Registriert eine Cleanup-Funktion
|
|
|
|
Args:
|
|
func: Cleanup-Funktion
|
|
name: Beschreibender Name
|
|
priority: Priorität (1=hoch, 3=niedrig)
|
|
timeout: Timeout in Sekunden
|
|
args: Funktions-Argumente
|
|
kwargs: Funktions-Keyword-Argumente
|
|
"""
|
|
if kwargs is None:
|
|
kwargs = {}
|
|
|
|
cleanup_item = {
|
|
'function': func,
|
|
'name': name,
|
|
'priority': priority,
|
|
'timeout': timeout,
|
|
'args': args,
|
|
'kwargs': kwargs
|
|
}
|
|
|
|
with self._lock:
|
|
self.cleanup_functions.append(cleanup_item)
|
|
# Nach Priorität sortieren (1 = höchste Priorität zuerst)
|
|
self.cleanup_functions.sort(key=lambda x: x['priority'])
|
|
|
|
shutdown_logger.debug(f"📝 Cleanup-Funktion '{name}' registriert (Priorität: {priority})")
|
|
|
|
def register_queue_manager(self, queue_manager_module):
|
|
"""Registriert den Queue Manager für ordnungsgemäße Beendigung"""
|
|
try:
|
|
stop_func = getattr(queue_manager_module, 'stop_queue_manager', None)
|
|
if stop_func:
|
|
self.register_cleanup_function(
|
|
func=stop_func,
|
|
name="Queue Manager",
|
|
priority=1, # Hohe Priorität
|
|
timeout=15
|
|
)
|
|
shutdown_logger.debug("✅ Queue Manager registriert")
|
|
else:
|
|
shutdown_logger.warning("⚠️ Queue Manager stop_queue_manager Funktion nicht gefunden")
|
|
except Exception as e:
|
|
shutdown_logger.error(f"❌ Fehler beim Registrieren des Queue Managers: {e}")
|
|
|
|
def register_scheduler(self, scheduler, scheduler_enabled: bool = True):
|
|
"""Registriert den Job-Scheduler für ordnungsgemäße Beendigung"""
|
|
if not scheduler_enabled or not scheduler:
|
|
shutdown_logger.debug("Scheduler nicht registriert (deaktiviert oder nicht vorhanden)")
|
|
return
|
|
|
|
def stop_scheduler():
|
|
try:
|
|
if hasattr(scheduler, 'shutdown'):
|
|
shutdown_logger.info("🔄 Beende Scheduler mit shutdown()...")
|
|
scheduler.shutdown(wait=True)
|
|
elif hasattr(scheduler, 'stop'):
|
|
shutdown_logger.info("🔄 Beende Scheduler mit stop()...")
|
|
scheduler.stop()
|
|
else:
|
|
shutdown_logger.warning("⚠️ Scheduler hat keine bekannte Stop-Methode")
|
|
|
|
shutdown_logger.info("✅ Scheduler erfolgreich gestoppt")
|
|
except Exception as e:
|
|
shutdown_logger.error(f"❌ Fehler beim Stoppen des Schedulers: {e}")
|
|
|
|
self.register_cleanup_function(
|
|
func=stop_scheduler,
|
|
name="Job Scheduler",
|
|
priority=1, # Hohe Priorität
|
|
timeout=10
|
|
)
|
|
|
|
def register_database_cleanup(self):
|
|
"""Registriert Datenbank-Cleanup mit robuster Fehlerbehandlung"""
|
|
def safe_database_cleanup():
|
|
try:
|
|
shutdown_logger.info("💾 Führe sicheres Datenbank-Cleanup durch...")
|
|
|
|
# Versuche den neuen DatabaseCleanupManager zu verwenden
|
|
try:
|
|
from utils.database_cleanup import safe_database_cleanup
|
|
|
|
result = safe_database_cleanup(force_mode_switch=False) # Kein riskantes Mode-Switching
|
|
|
|
if result.get("success", False):
|
|
shutdown_logger.info(f"✅ Datenbank-Cleanup erfolgreich: {', '.join(result.get('operations', []))}")
|
|
else:
|
|
shutdown_logger.warning(f"⚠️ Datenbank-Cleanup mit Problemen: {', '.join(result.get('errors', []))}")
|
|
|
|
except ImportError:
|
|
shutdown_logger.info("Fallback: Verwende einfaches WAL-Checkpoint...")
|
|
|
|
# Einfacher Fallback nur mit WAL-Checkpoint
|
|
try:
|
|
from models import create_optimized_engine
|
|
from sqlalchemy import text
|
|
|
|
engine = create_optimized_engine()
|
|
|
|
with engine.connect() as conn:
|
|
result = conn.execute(text("PRAGMA wal_checkpoint(PASSIVE)")).fetchone()
|
|
if result and result[1] > 0:
|
|
shutdown_logger.info(f"WAL-Checkpoint: {result[1]} Seiten übertragen")
|
|
conn.commit()
|
|
|
|
engine.dispose()
|
|
shutdown_logger.info("✅ Einfaches Datenbank-Cleanup abgeschlossen")
|
|
|
|
except Exception as db_error:
|
|
shutdown_logger.error(f"❌ Fehler beim einfachen Datenbank-Cleanup: {db_error}")
|
|
|
|
except Exception as cleanup_error:
|
|
shutdown_logger.error(f"❌ Fehler beim Datenbank-Cleanup: {cleanup_error}")
|
|
|
|
self.register_cleanup_function(
|
|
func=safe_database_cleanup,
|
|
name="Datenbank Cleanup",
|
|
priority=3, # Niedrige Priorität (am Ende)
|
|
timeout=20
|
|
)
|
|
|
|
def register_windows_thread_manager(self):
|
|
"""Registriert Windows Thread Manager falls verfügbar"""
|
|
try:
|
|
if os.name == 'nt':
|
|
from utils.windows_fixes import get_windows_thread_manager
|
|
|
|
thread_manager = get_windows_thread_manager()
|
|
if thread_manager:
|
|
self.register_cleanup_function(
|
|
func=thread_manager.shutdown_all,
|
|
name="Windows Thread Manager",
|
|
priority=2,
|
|
timeout=15
|
|
)
|
|
shutdown_logger.debug("✅ Windows Thread Manager registriert")
|
|
except Exception as e:
|
|
shutdown_logger.debug(f"Windows Thread Manager nicht verfügbar: {e}")
|
|
|
|
@contextmanager
|
|
def cleanup_timeout(self, timeout: int, operation_name: str):
|
|
"""Context Manager für Timeout-überwachte Operationen"""
|
|
start_time = time.time()
|
|
success = False
|
|
|
|
try:
|
|
yield
|
|
success = True
|
|
except Exception as e:
|
|
elapsed = time.time() - start_time
|
|
shutdown_logger.error(f"❌ Fehler bei {operation_name} nach {elapsed:.1f}s: {e}")
|
|
raise
|
|
finally:
|
|
elapsed = time.time() - start_time
|
|
if success:
|
|
shutdown_logger.debug(f"✅ {operation_name} abgeschlossen in {elapsed:.1f}s")
|
|
elif elapsed >= timeout:
|
|
shutdown_logger.warning(f"⏱️ {operation_name} Timeout nach {elapsed:.1f}s")
|
|
|
|
def shutdown(self, exit_code: int = 0):
|
|
"""
|
|
Führt koordiniertes Shutdown aller registrierten Komponenten durch
|
|
|
|
Args:
|
|
exit_code: Exit-Code für sys.exit()
|
|
"""
|
|
if self.is_shutting_down:
|
|
shutdown_logger.debug("Shutdown bereits in Bearbeitung")
|
|
return
|
|
|
|
with self._lock:
|
|
if self.is_shutting_down:
|
|
return
|
|
|
|
self.is_shutting_down = True
|
|
self.shutdown_started = datetime.now()
|
|
|
|
shutdown_logger.info("🔄 Starte koordiniertes System-Shutdown...")
|
|
self.shutdown_event.set()
|
|
|
|
total_start = time.time()
|
|
|
|
try:
|
|
# 1. Komponenten stoppen (nach Priorität)
|
|
self._shutdown_components()
|
|
|
|
# 2. Cleanup-Funktionen ausführen (nach Priorität)
|
|
self._execute_cleanup_functions()
|
|
|
|
total_elapsed = time.time() - total_start
|
|
shutdown_logger.info(f"✅ Koordiniertes Shutdown abgeschlossen in {total_elapsed:.1f}s")
|
|
|
|
except Exception as e:
|
|
shutdown_logger.error(f"❌ Fehler beim koordinierten Shutdown: {e}")
|
|
finally:
|
|
shutdown_logger.info("🏁 System wird beendet...")
|
|
sys.exit(exit_code)
|
|
|
|
def _shutdown_components(self):
|
|
"""Stoppt alle registrierten Komponenten"""
|
|
if not self.components:
|
|
return
|
|
|
|
shutdown_logger.info(f"🔄 Stoppe {len(self.components)} registrierte Komponenten...")
|
|
|
|
for name, config in self.components.items():
|
|
try:
|
|
component = config['component']
|
|
stop_method = config['stop_method']
|
|
|
|
if hasattr(component, stop_method):
|
|
with self.cleanup_timeout(10, f"Komponente {name}"):
|
|
method = getattr(component, stop_method)
|
|
method()
|
|
shutdown_logger.debug(f"✅ Komponente '{name}' gestoppt")
|
|
else:
|
|
shutdown_logger.warning(f"⚠️ Komponente '{name}' hat keine '{stop_method}' Methode")
|
|
|
|
except Exception as e:
|
|
shutdown_logger.error(f"❌ Fehler beim Stoppen der Komponente '{name}': {e}")
|
|
|
|
def _execute_cleanup_functions(self):
|
|
"""Führt alle registrierten Cleanup-Funktionen aus"""
|
|
if not self.cleanup_functions:
|
|
return
|
|
|
|
shutdown_logger.info(f"🧹 Führe {len(self.cleanup_functions)} Cleanup-Funktionen aus...")
|
|
|
|
for cleanup_item in self.cleanup_functions:
|
|
try:
|
|
func = cleanup_item['function']
|
|
name = cleanup_item['name']
|
|
timeout = cleanup_item['timeout']
|
|
args = cleanup_item['args']
|
|
kwargs = cleanup_item['kwargs']
|
|
|
|
shutdown_logger.debug(f"🔄 Führe Cleanup aus: {name}")
|
|
|
|
with self.cleanup_timeout(timeout, f"Cleanup {name}"):
|
|
func(*args, **kwargs)
|
|
shutdown_logger.debug(f"✅ Cleanup '{name}' abgeschlossen")
|
|
|
|
except Exception as e:
|
|
shutdown_logger.error(f"❌ Fehler bei Cleanup '{cleanup_item['name']}': {e}")
|
|
# Weiter mit nächstem Cleanup
|
|
continue
|
|
|
|
def is_shutdown_requested(self) -> bool:
|
|
"""Prüft ob Shutdown angefordert wurde"""
|
|
return self.shutdown_event.is_set()
|
|
|
|
def get_shutdown_time(self) -> Optional[datetime]:
|
|
"""Gibt den Zeitpunkt des Shutdown-Starts zurück"""
|
|
return self.shutdown_started
|
|
|
|
def force_shutdown(self, exit_code: int = 1):
|
|
"""Erzwingt sofortiges Shutdown ohne Cleanup"""
|
|
shutdown_logger.warning("🚨 Erzwinge sofortiges Shutdown ohne Cleanup!")
|
|
sys.exit(exit_code)
|
|
|
|
|
|
# Globaler Shutdown-Manager
|
|
_shutdown_manager: Optional[ShutdownManager] = None
|
|
_manager_lock = threading.Lock()
|
|
|
|
|
|
def get_shutdown_manager(timeout: int = 30) -> ShutdownManager:
|
|
"""
|
|
Singleton-Pattern für globalen Shutdown-Manager
|
|
|
|
Args:
|
|
timeout: Gesamttimeout für Shutdown-Operationen
|
|
|
|
Returns:
|
|
ShutdownManager: Globaler Shutdown-Manager
|
|
"""
|
|
global _shutdown_manager
|
|
|
|
with _manager_lock:
|
|
if _shutdown_manager is None:
|
|
_shutdown_manager = ShutdownManager(timeout=timeout)
|
|
|
|
# Registriere atexit-Handler als Fallback
|
|
atexit.register(_shutdown_manager.shutdown)
|
|
|
|
return _shutdown_manager
|
|
|
|
|
|
def register_for_shutdown(component_or_function,
|
|
name: str,
|
|
component_stop_method: str = "stop",
|
|
priority: int = 1,
|
|
timeout: int = 10):
|
|
"""
|
|
Vereinfachte Registrierung für Shutdown
|
|
|
|
Args:
|
|
component_or_function: Komponente mit Stop-Methode oder Cleanup-Funktion
|
|
name: Beschreibender Name
|
|
component_stop_method: Name der Stop-Methode für Komponenten
|
|
priority: Priorität (1=hoch, 3=niedrig)
|
|
timeout: Timeout in Sekunden
|
|
"""
|
|
manager = get_shutdown_manager()
|
|
|
|
if callable(component_or_function):
|
|
# Es ist eine Funktion
|
|
manager.register_cleanup_function(
|
|
func=component_or_function,
|
|
name=name,
|
|
priority=priority,
|
|
timeout=timeout
|
|
)
|
|
else:
|
|
# Es ist eine Komponente
|
|
manager.register_component(
|
|
name=name,
|
|
component=component_or_function,
|
|
stop_method=component_stop_method
|
|
)
|
|
|
|
|
|
def shutdown_application(exit_code: int = 0):
|
|
"""
|
|
Startet koordiniertes Shutdown der Anwendung
|
|
|
|
Args:
|
|
exit_code: Exit-Code für sys.exit()
|
|
"""
|
|
manager = get_shutdown_manager()
|
|
manager.shutdown(exit_code)
|
|
|
|
|
|
def is_shutdown_requested() -> bool:
|
|
"""Prüft ob Shutdown angefordert wurde"""
|
|
if _shutdown_manager:
|
|
return _shutdown_manager.is_shutdown_requested()
|
|
return False |