🎉 Improved backend functionality & documentation, optimized database files, and introduced shutdown management 🧹

This commit is contained in:
2025-06-01 02:42:15 +02:00
parent ea12a470c9
commit 486647fade
10 changed files with 1052 additions and 407 deletions

View File

@ -81,7 +81,7 @@ class PrinterQueueManager:
Verbesserte Version mit ordnungsgemäßem Thread-Management für Windows.
"""
def __init__(self):
def __init__(self, register_signal_handlers: bool = True):
self.is_running = False
self.monitor_thread = None
self.shutdown_event = threading.Event() # Sauberes Shutdown-Signal
@ -89,65 +89,109 @@ class PrinterQueueManager:
self.last_status_cache = {} # Cache für letzten bekannten Status
self.notification_cooldown = {} # Verhindert Spam-Benachrichtigungen
self._lock = threading.Lock() # Thread-Sicherheit
self._signal_handlers_registered = False
# Windows-spezifische Signal-Handler registrieren
if os.name == 'nt':
# Signal-Handler nur registrieren wenn explizit gewünscht
# (Verhindert Interferenzen mit zentralem Shutdown-Manager)
if register_signal_handlers and os.name == 'nt':
self._register_signal_handlers()
def _register_signal_handlers(self):
"""Windows-spezifische Signal-Handler registrieren (nur wenn gewünscht)"""
if self._signal_handlers_registered:
return
try:
# Prüfe ob bereits zentrale Signal-Handler existieren
try:
from utils.shutdown_manager import is_shutdown_requested
if is_shutdown_requested is not None:
queue_logger.info("🔄 Zentrale Signal-Handler erkannt - deaktiviere lokale Handler")
return
except ImportError:
pass # Kein zentraler Manager verfügbar, verwende lokale Handler
signal.signal(signal.SIGINT, self._signal_handler)
signal.signal(signal.SIGTERM, self._signal_handler)
self._signal_handlers_registered = True
queue_logger.debug("✅ Lokale Signal-Handler für Queue Manager registriert")
except Exception as e:
queue_logger.warning(f"⚠️ Lokale Signal-Handler konnten nicht registriert werden: {e}")
def _signal_handler(self, signum, frame):
"""Signal-Handler für ordnungsgemäßes Shutdown."""
"""Signal-Handler für ordnungsgemäßes Shutdown (nur als Fallback)."""
queue_logger.warning(f"🛑 Signal {signum} empfangen - stoppe Queue Manager...")
self.stop()
def start(self):
"""Startet den Queue-Manager mit verbessertem Thread-Management."""
with self._lock:
if not self.is_running:
self.is_running = True
self.shutdown_event.clear()
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=False)
self.monitor_thread.name = "PrinterQueueMonitor"
# Windows Thread-Manager verwenden falls verfügbar
if os.name == 'nt' and get_windows_thread_manager:
try:
thread_manager = get_windows_thread_manager()
thread_manager.register_thread(self.monitor_thread)
thread_manager.register_cleanup_function(self.stop)
queue_logger.debug("✅ Queue Manager bei Windows Thread-Manager registriert")
except Exception as e:
queue_logger.warning(f"⚠️ Windows Thread-Manager nicht verfügbar: {str(e)}")
self.monitor_thread.start()
queue_logger.info("✅ Printer Queue Manager erfolgreich gestartet")
def stop(self):
"""Stoppt den Queue-Manager ordnungsgemäß."""
"""Startet den Queue-Manager mit verbessertem Shutdown-Handling."""
with self._lock:
if self.is_running:
queue_logger.info("🔄 Beende Queue Manager...")
self.is_running = False
self.shutdown_event.set()
queue_logger.warning("Queue-Manager läuft bereits")
return self
queue_logger.info("🚀 Starte Printer Queue Manager...")
self.is_running = True
self.shutdown_event.clear()
# Monitor-Thread mit Daemon-Flag für automatische Beendigung
self.monitor_thread = threading.Thread(
target=self._monitor_loop,
name="PrinterQueueMonitor",
daemon=True # Automatische Beendigung bei Programm-Ende
)
self.monitor_thread.start()
queue_logger.info("✅ Printer Queue Manager gestartet")
return self
def stop(self):
"""Stoppt den Queue-Manager ordnungsgemäß mit verbessertem Timeout-Handling."""
with self._lock:
if not self.is_running:
queue_logger.debug("Queue-Manager ist bereits gestoppt")
return
if self.monitor_thread and self.monitor_thread.is_alive():
queue_logger.debug("⏳ Warte auf Thread-Beendigung...")
self.monitor_thread.join(timeout=10)
queue_logger.info("🔄 Beende Queue Manager...")
self.is_running = False
self.shutdown_event.set()
if self.monitor_thread and self.monitor_thread.is_alive():
queue_logger.debug("⏳ Warte auf Thread-Beendigung...")
# Verbessertes Timeout-Handling
try:
self.monitor_thread.join(timeout=5.0) # Reduziertes Timeout
if self.monitor_thread.is_alive():
queue_logger.warning("⚠️ Thread konnte nicht ordnungsgemäß beendet werden")
queue_logger.warning("⚠️ Thread konnte nicht in 5s beendet werden - setze als Daemon")
# Thread als Daemon markieren für automatische Beendigung
self.monitor_thread.daemon = True
else:
queue_logger.info("✅ Monitor-Thread erfolgreich beendet")
self.monitor_thread = None
queue_logger.info("Printer Queue Manager gestoppt")
except Exception as e:
queue_logger.error(f"Fehler beim Thread-Join: {e}")
self.monitor_thread = None
queue_logger.info("❌ Printer Queue Manager gestoppt")
def _monitor_loop(self):
"""Hauptschleife für die Überwachung der Drucker mit verbessertem Shutdown-Handling."""
queue_logger.info(f"🔄 Queue-Überwachung gestartet (Intervall: {self.check_interval} Sekunden)")
while self.is_running and not self.shutdown_event.is_set():
try:
# Prüfe auf zentrales Shutdown-Signal
try:
from utils.shutdown_manager import is_shutdown_requested
if is_shutdown_requested():
queue_logger.info("🛑 Zentrales Shutdown-Signal empfangen - beende Monitor-Loop")
break
except ImportError:
pass # Kein zentraler Manager verfügbar
self._check_waiting_jobs()
# Verwende Event.wait() statt time.sleep() für unterbrechbares Warten
@ -379,10 +423,37 @@ def get_queue_manager() -> PrinterQueueManager:
return _queue_manager_instance
def start_queue_manager():
"""Startet den globalen Queue-Manager."""
manager = get_queue_manager()
manager.start()
return manager
"""Startet den globalen Queue-Manager sicher und ohne Signal-Handler-Interferenzen."""
global _queue_manager_instance
with _queue_manager_lock:
if _queue_manager_instance is not None:
queue_logger.warning("Queue-Manager läuft bereits")
return _queue_manager_instance
try:
queue_logger.info("🚀 Initialisiere neuen Queue-Manager...")
# Prüfe ob zentraler Shutdown-Manager verfügbar ist
register_signals = True
try:
from utils.shutdown_manager import is_shutdown_requested
if is_shutdown_requested is not None:
queue_logger.info("🔄 Zentrale Shutdown-Verwaltung erkannt - deaktiviere lokale Signal-Handler")
register_signals = False
except ImportError:
queue_logger.debug("Kein zentraler Shutdown-Manager verfügbar - verwende lokale Signal-Handler")
# Erstelle Queue-Manager ohne Signal-Handler wenn zentraler Manager vorhanden
_queue_manager_instance = PrinterQueueManager(register_signal_handlers=register_signals)
_queue_manager_instance.start()
queue_logger.info("✅ Queue-Manager erfolgreich gestartet")
return _queue_manager_instance
except Exception as e:
queue_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}")
_queue_manager_instance = None
raise
def stop_queue_manager():
"""Stoppt den globalen Queue-Manager definitiv und sicher."""

View File

@ -0,0 +1,442 @@
#!/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