From ac654a6f7c3d78e48c19b78ae109015fdac5c725 Mon Sep 17 00:00:00 2001 From: Vitali Graf Date: Sun, 28 Dec 2025 15:20:51 +0100 Subject: [PATCH] =?UTF-8?q?Debugging:=20Verbesserte=20Fehlerdiagnose=20f?= =?UTF-8?q?=C3=BCr=20Saxon=20Worker=20Pool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Leitet stderr jedes Workers in separate Log-Dateien um (worker_N_stderr.log) - Fügt Startup-Health-Check hinzu: Prüft nach 100ms ob Worker noch läuft - Fügt Pre-Transform-Check hinzu: Validiert Worker-Status vor jedem Job - Zeigt stderr-Inhalt in Fehlermeldungen wenn Worker crashen - Erweitert Debug-Logging für Job-Submission und Worker-Antworten Dies hilft, die Ursache der "broken pipe" Fehler zu identifizieren. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/saxon_pool.py | 51 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/saxon_pool.py b/src/saxon_pool.py index f5aa992..7d242a5 100644 --- a/src/saxon_pool.py +++ b/src/saxon_pool.py @@ -207,11 +207,15 @@ class SaxonWorkerPool: # Starte JVM-Prozess mit SaxonWorker cmd = [str(self.java_vm_path), "-cp", full_classpath, "SaxonWorker"] + # Öffne stderr-Log-Datei für diesen Worker + stderr_log = self.temp_dir / f"worker_{i}_stderr.log" + stderr_file = open(stderr_log, "w", encoding="utf-8") + process = subprocess.Popen( cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=stderr_file, # Redirect stderr to file text=True, bufsize=1, # Line buffered ) @@ -219,7 +223,22 @@ class SaxonWorkerPool: self.workers.append(process) self.worker_locks.append(threading.Lock()) - logger.debug(f"Worker {i} gestartet (PID: {process.pid})") + logger.debug(f"Worker {i} gestartet (PID: {process.pid}, stderr: {stderr_log})") + + # Warte kurz damit Worker initialisieren kann + import time + + time.sleep(0.1) + + # Prüfe ob Worker noch läuft + if process.poll() is not None: + # Worker ist bereits beendet - Fehler! + stderr_file.close() + with open(stderr_log, "r") as f: + stderr_content = f.read() + raise RuntimeError( + f"Worker {i} ist sofort beendet (Exit Code: {process.returncode})\nstderr:\n{stderr_content}" + ) except Exception as e: logger.error(f"Fehler beim Starten von Worker {i}: {e}") @@ -259,12 +278,29 @@ class SaxonWorkerPool: try: worker = self.workers[worker_idx] + # Prüfe ob Worker noch läuft + if worker.poll() is not None: + # Worker ist tot! + stderr_log = self.temp_dir / f"worker_{worker_idx}_stderr.log" + try: + with open(stderr_log, "r") as f: + stderr_content = f.read() + error_msg = ( + f"Worker {worker_idx} ist beendet (Exit: {worker.returncode})\nstderr:\n{stderr_content}" + ) + except Exception: + error_msg = f"Worker {worker_idx} ist beendet (Exit: {worker.returncode})" + logger.error(error_msg) + return False, error_msg + # Formatiere Parameter params_str = "|||".join([f"{key}={value}" for key, value in xslt_params.items()]) # Erstelle Job-String (Tab-separated) job = f"{source_xml}\t{xsl_stylesheet}\t{output_fo}\t{params_str}\n" + logger.debug(f"Sende Job an Worker {worker_idx}: {source_xml.name}") + # Sende Job an Worker worker.stdin.write(job) worker.stdin.flush() @@ -272,12 +308,23 @@ class SaxonWorkerPool: # Warte auf Antwort response = worker.stdout.readline().strip() + logger.debug(f"Worker {worker_idx} Antwort: '{response}'") + if response == "OK": return True, "Erfolgreich" elif response.startswith("ERROR:"): error_msg = response[6:].strip() return False, f"Saxon-Fehler: {error_msg}" else: + # Leere Antwort bedeutet Worker ist crashed + if not response: + stderr_log = self.temp_dir / f"worker_{worker_idx}_stderr.log" + try: + with open(stderr_log, "r") as f: + stderr_content = f.read()[-500:] # Letzte 500 Zeichen + return False, f"Worker {worker_idx} crashed (keine Antwort)\nstderr:\n{stderr_content}" + except Exception: + return False, f"Worker {worker_idx} crashed (keine Antwort)" return False, f"Unerwartete Antwort: {response}" except Exception as e: