manage-your-printer/utils/timeout_force_quit_manager.py
2025-06-04 10:03:22 +02:00

647 lines
23 KiB
Python

#!/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()