#!/usr/bin/env python3 """ Timeout Force-Quit Manager mit Terminal-Countdown Spezialiserter Manager für Force-Quit-Timeouts mit visueller Terminal-Anzeige und robuster Datenbankbereinigung (WAL/SHM-Dateien). Funktionen: - Terminal-Countdown mit Fortschrittsbalken - Automatische Datenbankbereinigung - Force-Quit bei Timeout - Integration mit bestehendem Timer-System - Robuste WAL/SHM-Dateibereinigung Autor: System Erstellt: 2025 """ import os import sys import threading import time import signal import shutil from datetime import datetime, timedelta from typing import Optional, Callable, Dict, Any from contextlib import contextmanager # Logging try: from utils.logging_config import get_logger logger = get_logger("timeout_force_quit") except ImportError: import logging logger = logging.getLogger("timeout_force_quit") logging.basicConfig(level=logging.INFO) # Timer-System Integration try: from utils.timer_manager import ( get_timer_manager, TimerType, ForceQuitAction, TimerStatus ) from models import SystemTimer, get_cached_session TIMER_SYSTEM_AVAILABLE = True except ImportError: logger.warning("Timer-System nicht verfügbar - verwende Fallback-Implementation") TIMER_SYSTEM_AVAILABLE = False # Datenbank-Cleanup try: from utils.database_cleanup import safe_database_cleanup DATABASE_CLEANUP_AVAILABLE = True except ImportError: logger.warning("Database-Cleanup-Manager nicht verfügbar - verwende Basis-Cleanup") DATABASE_CLEANUP_AVAILABLE = False class TimeoutForceQuitManager: """ Manager für Timeout-basierte Force-Quit-Operationen mit Terminal-Countdown. Bietet: - Visueller Terminal-Countdown - Automatische Datenbankbereinigung - Robuste WAL/SHM-Dateibereinigung - Konfigurierbare Timeout-Aktionen """ def __init__(self, timeout_seconds: int = 45, warning_seconds: int = 15, database_cleanup: bool = True, force_wal_cleanup: bool = True): """ Initialisiert den Timeout Force-Quit Manager. Args: timeout_seconds: Gesamttimeout in Sekunden warning_seconds: Warnzeit vor Force-Quit in Sekunden database_cleanup: Datenbankbereinigung aktivieren force_wal_cleanup: Aggressive WAL/SHM-Bereinigung """ self.timeout_seconds = timeout_seconds self.warning_seconds = warning_seconds self.database_cleanup = database_cleanup self.force_wal_cleanup = force_wal_cleanup # Countdown-Status self.is_active = False self.start_time = None self.timer_thread = None self.countdown_thread = None self.shutdown_callback: Optional[Callable] = None # Terminal-Kontrolle self.show_terminal_countdown = True self.terminal_lock = threading.Lock() logger.info(f"🔧 Timeout Force-Quit Manager initialisiert - Timeout: {timeout_seconds}s, Warnung: {warning_seconds}s") def set_shutdown_callback(self, callback: Callable): """Setzt eine Callback-Funktion für den Shutdown""" self.shutdown_callback = callback logger.debug("Shutdown-Callback registriert") def start_timeout(self, reason: str = "System-Timeout") -> bool: """ Startet den Timeout-Countdown. Args: reason: Grund für den Timeout Returns: bool: True wenn erfolgreich gestartet """ if self.is_active: logger.warning("Timeout bereits aktiv") return False try: self.is_active = True self.start_time = datetime.now() logger.warning(f"🚨 TIMEOUT GESTARTET - {reason}") logger.warning(f"⏱️ Force-Quit in {self.timeout_seconds} Sekunden") # Timer für Force-Quit self.timer_thread = threading.Thread( target=self._timeout_worker, args=(reason,), name="TimeoutForceQuit-Timer", daemon=True ) self.timer_thread.start() # Terminal-Countdown (nur wenn stdout verfügbar) if self.show_terminal_countdown and sys.stdout.isatty(): self.countdown_thread = threading.Thread( target=self._terminal_countdown_worker, name="TimeoutForceQuit-Countdown", daemon=True ) self.countdown_thread.start() # Integration mit Timer-System falls verfügbar if TIMER_SYSTEM_AVAILABLE: self._create_system_timer(reason) return True except Exception as e: logger.error(f"❌ Fehler beim Starten des Timeouts: {e}") self.is_active = False return False def cancel_timeout(self) -> bool: """ Bricht den laufenden Timeout ab. Returns: bool: True wenn erfolgreich abgebrochen """ if not self.is_active: return False try: self.is_active = False logger.info("✅ Timeout abgebrochen") # Terminal-Ausgabe löschen if self.show_terminal_countdown and sys.stdout.isatty(): with self.terminal_lock: print("\r" + " " * 80 + "\r", end="", flush=True) print("✅ Timeout abgebrochen") return True except Exception as e: logger.error(f"❌ Fehler beim Abbrechen des Timeouts: {e}") return False def extend_timeout(self, additional_seconds: int) -> bool: """ Verlängert den laufenden Timeout. Args: additional_seconds: Zusätzliche Sekunden Returns: bool: True wenn erfolgreich verlängert """ if not self.is_active: logger.warning("Kein aktiver Timeout zum Verlängern") return False try: self.timeout_seconds += additional_seconds logger.info(f"⏰ Timeout um {additional_seconds} Sekunden verlängert") return True except Exception as e: logger.error(f"❌ Fehler beim Verlängern des Timeouts: {e}") return False def _timeout_worker(self, reason: str): """Worker-Thread für den eigentlichen Timeout""" try: # Warte bis zum Timeout time.sleep(self.timeout_seconds) if self.is_active: logger.critical(f"🚨 FORCE-QUIT TIMEOUT ERREICHT - {reason}") self._execute_force_quit() except Exception as e: logger.error(f"❌ Fehler im Timeout-Worker: {e}") def _terminal_countdown_worker(self): """Worker-Thread für den visuellen Terminal-Countdown""" try: while self.is_active: elapsed = (datetime.now() - self.start_time).total_seconds() remaining = max(0, self.timeout_seconds - elapsed) if remaining <= 0: break # Fortschrittsbalken und Countdown progress = 1.0 - (remaining / self.timeout_seconds) bar_width = 40 filled_width = int(bar_width * progress) # Warnung-Status is_warning = remaining <= self.warning_seconds warning_icon = "🚨" if is_warning else "⏳" # Terminal-Ausgabe mit Lock with self.terminal_lock: bar = "█" * filled_width + "░" * (bar_width - filled_width) countdown_text = ( f"\r{warning_icon} FORCE-QUIT in: {int(remaining):3d}s " f"[{bar}] {progress*100:6.1f}% " ) print(countdown_text, end="", flush=True) # Warnung ausgeben if is_warning and int(remaining) % 5 == 0: logger.warning(f"⚠️ WARNUNG: Force-Quit in {int(remaining)} Sekunden!") time.sleep(0.1) # 100ms Update-Intervall # Letzte Ausgabe if self.is_active: with self.terminal_lock: print("\r🚨 FORCE-QUIT WIRD AUSGEFÜHRT!" + " " * 30, flush=True) except Exception as e: logger.error(f"❌ Fehler im Terminal-Countdown: {e}") def _create_system_timer(self, reason: str): """Erstellt einen System-Timer für Integration mit bestehendem Timer-System""" try: timer_manager = get_timer_manager() timer_name = f"force_quit_{int(time.time())}" timer = timer_manager.create_timer( name=timer_name, timer_type=TimerType.SYSTEM, duration_seconds=self.timeout_seconds, force_quit_action=ForceQuitAction.SHUTDOWN, auto_start=True, warning_message=f"Force-Quit wegen: {reason}", force_quit_warning_seconds=self.warning_seconds ) if timer: logger.debug(f"System-Timer '{timer_name}' erstellt") except Exception as e: logger.warning(f"System-Timer konnte nicht erstellt werden: {e}") def _execute_force_quit(self): """Führt den Force-Quit aus""" try: logger.critical("🚨 FORCE-QUIT WIRD AUSGEFÜHRT") # Terminal-Ausgabe stoppen self.is_active = False if self.show_terminal_countdown and sys.stdout.isatty(): with self.terminal_lock: print("\r🚨 FORCE-QUIT AKTIV - DATENBANKBEREINIGUNG..." + " " * 20, flush=True) # 1. Shutdown-Callback ausführen (falls gesetzt) if self.shutdown_callback: try: logger.info("📞 Führe Shutdown-Callback aus...") self.shutdown_callback() except Exception as e: logger.error(f"❌ Fehler im Shutdown-Callback: {e}") # 2. Datenbankbereinigung if self.database_cleanup: self._perform_database_cleanup() # 3. System beenden logger.critical("💀 FORCE-QUIT ABGESCHLOSSEN - SYSTEM WIRD BEENDET") if self.show_terminal_countdown and sys.stdout.isatty(): with self.terminal_lock: print("💀 FORCE-QUIT ABGESCHLOSSEN", flush=True) # Kurze Verzögerung für Log-Ausgabe time.sleep(1) # System beenden os._exit(1) except Exception as e: logger.critical(f"❌ KRITISCHER FEHLER IM FORCE-QUIT: {e}") # Notfall-Exit os._exit(1) def _perform_database_cleanup(self): """Führt robuste Datenbankbereinigung durch""" try: logger.info("💾 Starte Datenbankbereinigung...") if self.show_terminal_countdown and sys.stdout.isatty(): with self.terminal_lock: print("\r💾 Datenbankbereinigung läuft..." + " " * 30, flush=True) # 1. Verwende modernen DatabaseCleanupManager falls verfügbar if DATABASE_CLEANUP_AVAILABLE: logger.info("🔧 Verwende DatabaseCleanupManager...") result = safe_database_cleanup( force_mode_switch=True, # Aggressive Bereinigung max_cleanup_time=10 # 10 Sekunden Maximum ) if result.get("success", False): logger.info(f"✅ Database-Cleanup erfolgreich: {', '.join(result.get('operations', []))}") else: logger.warning(f"⚠️ Database-Cleanup mit Problemen: {', '.join(result.get('errors', []))}") # Fallback verwenden self._fallback_database_cleanup() else: # 2. Fallback: Direkter SQLite-Cleanup self._fallback_database_cleanup() # 3. WAL/SHM-Dateien manuell bereinigen falls gewünscht if self.force_wal_cleanup: self._force_wal_shm_cleanup() logger.info("✅ Datenbankbereinigung abgeschlossen") except Exception as e: logger.error(f"❌ Fehler bei Datenbankbereinigung: {e}") # Versuche trotzdem WAL/SHM-Cleanup if self.force_wal_cleanup: try: self._force_wal_shm_cleanup() except: pass def _fallback_database_cleanup(self): """Fallback-Datenbankbereinigung mit direkten SQLite-Befehlen""" try: from models import create_optimized_engine from sqlalchemy import text logger.info("🔄 Fallback Database-Cleanup...") engine = create_optimized_engine() with engine.connect() as conn: # WAL-Checkpoint (TRUNCATE für vollständige Bereinigung) result = conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)")).fetchone() if result and result[1] > 0: logger.info(f"WAL-Checkpoint: {result[1]} Seiten übertragen") # Alle ausstehenden Transaktionen committen conn.commit() # Verbindung optimieren conn.execute(text("PRAGMA optimize")) logger.info("✅ Fallback Database-Cleanup abgeschlossen") # Engine ordnungsgemäß schließen engine.dispose() except Exception as e: logger.error(f"❌ Fehler im Fallback Database-Cleanup: {e}") def _force_wal_shm_cleanup(self): """Aggressive Bereinigung von WAL/SHM-Dateien""" try: from config.settings import DATABASE_PATH logger.info("🧹 Force WAL/SHM-Cleanup...") if self.show_terminal_countdown and sys.stdout.isatty(): with self.terminal_lock: print("\r🧹 WAL/SHM-Dateien werden bereinigt..." + " " * 20, flush=True) # Kurze Pause um sicherzustellen, dass alle DB-Verbindungen geschlossen sind time.sleep(0.5) # WAL-Datei wal_path = DATABASE_PATH + "-wal" if os.path.exists(wal_path): try: # Versuche erst normales Löschen os.remove(wal_path) logger.info(f"✅ WAL-Datei gelöscht: {wal_path}") except OSError: # Falls blockiert, versuche Umbenennung und Löschung try: backup_path = wal_path + f".backup_{int(time.time())}" shutil.move(wal_path, backup_path) os.remove(backup_path) logger.info(f"✅ WAL-Datei über Backup gelöscht: {wal_path}") except Exception as e: logger.warning(f"⚠️ WAL-Datei konnte nicht gelöscht werden: {e}") # SHM-Datei shm_path = DATABASE_PATH + "-shm" if os.path.exists(shm_path): try: os.remove(shm_path) logger.info(f"✅ SHM-Datei gelöscht: {shm_path}") except OSError: try: backup_path = shm_path + f".backup_{int(time.time())}" shutil.move(shm_path, backup_path) os.remove(backup_path) logger.info(f"✅ SHM-Datei über Backup gelöscht: {shm_path}") except Exception as e: logger.warning(f"⚠️ SHM-Datei konnte nicht gelöscht werden: {e}") logger.info("✅ Force WAL/SHM-Cleanup abgeschlossen") except Exception as e: logger.error(f"❌ Fehler bei Force WAL/SHM-Cleanup: {e}") def get_status(self) -> Dict[str, Any]: """Gibt den aktuellen Status zurück""" if not self.is_active: return { "active": False, "remaining_seconds": 0, "progress_percent": 0.0 } elapsed = (datetime.now() - self.start_time).total_seconds() remaining = max(0, self.timeout_seconds - elapsed) progress = 1.0 - (remaining / self.timeout_seconds) if self.timeout_seconds > 0 else 1.0 return { "active": True, "remaining_seconds": int(remaining), "progress_percent": round(progress * 100, 1), "is_warning": remaining <= self.warning_seconds, "start_time": self.start_time.isoformat() if self.start_time else None } # ===== GLOBALER MANAGER UND UTILITY-FUNKTIONEN ===== _timeout_manager: Optional[TimeoutForceQuitManager] = None _manager_lock = threading.Lock() def get_timeout_manager(timeout_seconds: int = 45, warning_seconds: int = 15, database_cleanup: bool = True, force_wal_cleanup: bool = True) -> TimeoutForceQuitManager: """ Singleton-Pattern für globalen Timeout-Manager. Args: timeout_seconds: Gesamttimeout in Sekunden warning_seconds: Warnzeit vor Force-Quit database_cleanup: Datenbankbereinigung aktivieren force_wal_cleanup: Aggressive WAL/SHM-Bereinigung Returns: TimeoutForceQuitManager: Globaler Timeout-Manager """ global _timeout_manager with _manager_lock: if _timeout_manager is None: _timeout_manager = TimeoutForceQuitManager( timeout_seconds=timeout_seconds, warning_seconds=warning_seconds, database_cleanup=database_cleanup, force_wal_cleanup=force_wal_cleanup ) return _timeout_manager def start_force_quit_timeout(reason: str = "System-Timeout", timeout_seconds: int = 45, warning_seconds: int = 15, database_cleanup: bool = True, force_wal_cleanup: bool = True) -> bool: """ Startet einen Force-Quit-Timeout mit Terminal-Countdown. Args: reason: Grund für den Timeout timeout_seconds: Gesamttimeout in Sekunden warning_seconds: Warnzeit vor Force-Quit database_cleanup: Datenbankbereinigung aktivieren force_wal_cleanup: Aggressive WAL/SHM-Bereinigung Returns: bool: True wenn erfolgreich gestartet """ manager = get_timeout_manager(timeout_seconds, warning_seconds, database_cleanup, force_wal_cleanup) return manager.start_timeout(reason) def cancel_force_quit_timeout() -> bool: """ Bricht den aktuellen Force-Quit-Timeout ab. Returns: bool: True wenn erfolgreich abgebrochen """ global _timeout_manager if _timeout_manager: return _timeout_manager.cancel_timeout() return False def extend_force_quit_timeout(additional_seconds: int) -> bool: """ Verlängert den aktuellen Force-Quit-Timeout. Args: additional_seconds: Zusätzliche Sekunden Returns: bool: True wenn erfolgreich verlängert """ global _timeout_manager if _timeout_manager: return _timeout_manager.extend_timeout(additional_seconds) return False def get_force_quit_status() -> Dict[str, Any]: """ Gibt den Status des aktuellen Force-Quit-Timeouts zurück. Returns: Dict: Status-Informationen """ global _timeout_manager if _timeout_manager: return _timeout_manager.get_status() return {"active": False, "remaining_seconds": 0, "progress_percent": 0.0} @contextmanager def timeout_context(timeout_seconds: int = 45, reason: str = "Operation-Timeout", auto_cancel: bool = True): """ Context-Manager für automatischen Timeout-Schutz. Args: timeout_seconds: Timeout in Sekunden reason: Grund für den Timeout auto_cancel: Automatisch abbrechen beim Verlassen des Contexts Usage: with timeout_context(30, "Datenbank-Migration"): # Lange Operation... pass """ manager = get_timeout_manager(timeout_seconds) success = manager.start_timeout(reason) try: yield manager finally: if success and auto_cancel: manager.cancel_timeout() def register_shutdown_callback(callback: Callable): """ Registriert eine Callback-Funktion für den Shutdown. Args: callback: Callback-Funktion die beim Shutdown ausgeführt wird """ manager = get_timeout_manager() manager.set_shutdown_callback(callback) # ===== INTEGRATION MIT SHUTDOWN-MANAGER ===== def integrate_with_shutdown_manager(): """Integriert den Timeout-Manager mit dem bestehenden Shutdown-Manager""" try: from utils.shutdown_manager import get_shutdown_manager shutdown_manager = get_shutdown_manager() # Force-Quit-Timeout als Cleanup-Funktion registrieren def timeout_cleanup(): global _timeout_manager if _timeout_manager and _timeout_manager.is_active: logger.info("🔄 Timeout-Manager wird im Shutdown-Prozess gestoppt") _timeout_manager.cancel_timeout() shutdown_manager.register_cleanup_function( func=timeout_cleanup, name="Timeout Force-Quit Manager", priority=1, # Hohe Priorität timeout=5 ) logger.debug("✅ Timeout-Manager in Shutdown-Manager integriert") except ImportError: logger.debug("Shutdown-Manager nicht verfügbar - keine Integration") except Exception as e: logger.warning(f"Fehler bei Shutdown-Manager-Integration: {e}") # Automatische Integration beim Import integrate_with_shutdown_manager()