Feature: s9api-basierte SaxonWorkerPool-Variante für XSLT 2.0/3.0
Die JAXP-basierte SaxonWorkerPool-Implementierung ist nur für XSLT 1.0 vollständig spezifiziert und kann bei XSLT 2.0/3.0 zu fehlerhaften Ausgaben führen. Änderungen: - Neue SaxonWorkerPoolS9Api-Klasse mit Saxon s9api für XSLT 2.0/3.0 - XsltVersion-Enum in conf.py (XSLT_1_0, XSLT_2_0_3_0) - ComboBox in Performance-Einstellungen zur XSLT-Version-Auswahl - MainWindow wählt automatisch richtige Worker-Pool-Variante - Verbesserte Classpath-Behandlung und Fehlerbehandlung Standard-Einstellung: XSLT 2.0/3.0 (s9api) - empfohlen für moderne Stylesheets Fallback: XSLT 1.0 (JAXP) - verfügbar für Legacy-Stylesheets 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,12 @@ class SSLMode(str, Enum):
|
|||||||
VERIFY_FULL = "verify-full"
|
VERIFY_FULL = "verify-full"
|
||||||
|
|
||||||
|
|
||||||
|
class XsltVersion(str, Enum):
|
||||||
|
"""XSLT-Version für Saxon-Transformationen."""
|
||||||
|
XSLT_1_0 = "1.0" # JAXP API (nur XSLT 1.0)
|
||||||
|
XSLT_2_0_3_0 = "2.0/3.0" # s9api (XSLT 2.0 und 3.0)
|
||||||
|
|
||||||
|
|
||||||
class PostgreSqlDb(BaseModel):
|
class PostgreSqlDb(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
@@ -144,6 +150,7 @@ class AppSettings(BaseSettings):
|
|||||||
theme: str | None = None
|
theme: str | None = None
|
||||||
max_workers: int = 8 # Anzahl paralleler Worker für Transformationen (Standard: 8)
|
max_workers: int = 8 # Anzahl paralleler Worker für Transformationen (Standard: 8)
|
||||||
use_saxon_worker_pool: bool = True # SaxonWorkerPool aktivieren (schneller, benötigt JDK)
|
use_saxon_worker_pool: bool = True # SaxonWorkerPool aktivieren (schneller, benötigt JDK)
|
||||||
|
saxon_xslt_version: XsltVersion = XsltVersion.XSLT_2_0_3_0 # XSLT-Version für Saxon (Standard: 2.0/3.0 mit s9api)
|
||||||
use_fop_worker_pool: bool = True # FopWorkerPool aktivieren (schneller, benötigt JDK)
|
use_fop_worker_pool: bool = True # FopWorkerPool aktivieren (schneller, benötigt JDK)
|
||||||
|
|
||||||
# UI-Zustand
|
# UI-Zustand
|
||||||
|
|||||||
@@ -0,0 +1,443 @@
|
|||||||
|
"""
|
||||||
|
Saxon Worker Pool (s9api) - Persistente JVM-Prozesse für XSLT 2.0/3.0 Transformationen.
|
||||||
|
|
||||||
|
Diese Variante verwendet die Saxon s9api API anstatt JAXP und ist für XSLT 2.0 und 3.0 geeignet.
|
||||||
|
Eliminiert JVM-Startup-Overhead durch Vorinitialisierung von N Worker-Prozessen.
|
||||||
|
Jeder Worker läuft als Daemon und verarbeitet mehrere Transformationen nacheinander.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
|
from queue import Queue
|
||||||
|
from typing import Optional
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Java-Worker-Code für s9api (wird zur Laufzeit kompiliert)
|
||||||
|
SAXON_S9API_WORKER_JAVA = """
|
||||||
|
import net.sf.saxon.s9api.*;
|
||||||
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public class SaxonS9ApiWorker {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||||
|
String line;
|
||||||
|
|
||||||
|
// Create Processor once and reuse (equivalent to TransformerFactory)
|
||||||
|
Processor processor = new Processor(false);
|
||||||
|
|
||||||
|
System.err.println("SaxonS9ApiWorker started and ready (using s9api for XSLT 2.0/3.0)");
|
||||||
|
System.err.flush();
|
||||||
|
|
||||||
|
try {
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
System.err.println("DEBUG: Received line: " + line.substring(0, Math.min(100, line.length())));
|
||||||
|
System.err.flush();
|
||||||
|
|
||||||
|
if ("EXIT".equals(line.trim())) {
|
||||||
|
System.err.println("SaxonS9ApiWorker exiting");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Parse job
|
||||||
|
System.err.println("DEBUG: Parsing job...");
|
||||||
|
System.err.flush();
|
||||||
|
|
||||||
|
String[] parts = line.split("\\\\t");
|
||||||
|
System.err.println("DEBUG: Parts count: " + parts.length);
|
||||||
|
System.err.flush();
|
||||||
|
|
||||||
|
if (parts.length < 3) {
|
||||||
|
System.out.println("ERROR: Invalid job format");
|
||||||
|
System.out.flush();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String sourceXml = parts[0];
|
||||||
|
String xslStylesheet = parts[1];
|
||||||
|
String outputFo = parts[2];
|
||||||
|
|
||||||
|
System.err.println("DEBUG: Compiling stylesheet...");
|
||||||
|
System.err.flush();
|
||||||
|
|
||||||
|
// Compile stylesheet
|
||||||
|
XsltCompiler compiler = processor.newXsltCompiler();
|
||||||
|
XsltExecutable executable = compiler.compile(new StreamSource(new File(xslStylesheet)));
|
||||||
|
|
||||||
|
System.err.println("DEBUG: Creating transformer...");
|
||||||
|
System.err.flush();
|
||||||
|
|
||||||
|
// Create transformer
|
||||||
|
XsltTransformer transformer = executable.load();
|
||||||
|
|
||||||
|
// Set source
|
||||||
|
transformer.setSource(new StreamSource(new File(sourceXml)));
|
||||||
|
|
||||||
|
// Set destination
|
||||||
|
Serializer serializer = processor.newSerializer(new File(outputFo));
|
||||||
|
transformer.setDestination(serializer);
|
||||||
|
|
||||||
|
// Set parameters if present
|
||||||
|
if (parts.length > 3 && !parts[3].isEmpty()) {
|
||||||
|
String[] params = parts[3].split("\\\\|\\\\|\\\\|");
|
||||||
|
for (String param : params) {
|
||||||
|
if (!param.isEmpty() && param.contains("=")) {
|
||||||
|
String[] kv = param.split("=", 2);
|
||||||
|
transformer.setParameter(new QName(kv[0]), new XdmAtomicValue(kv[1]));
|
||||||
|
System.err.println("DEBUG: Set parameter: " + kv[0] + " = " + kv[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.err.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
System.err.println("DEBUG: Running transformation...");
|
||||||
|
System.err.flush();
|
||||||
|
|
||||||
|
// Transform
|
||||||
|
transformer.transform();
|
||||||
|
|
||||||
|
System.err.println("DEBUG: Transformation completed");
|
||||||
|
System.err.flush();
|
||||||
|
|
||||||
|
System.out.println("OK");
|
||||||
|
System.out.flush();
|
||||||
|
|
||||||
|
} catch (SaxonApiException e) {
|
||||||
|
System.err.println("DEBUG: SaxonApiException: " + e.getClass().getName());
|
||||||
|
System.err.flush();
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
|
||||||
|
String errorMsg = e.getMessage();
|
||||||
|
if (errorMsg == null || errorMsg.isEmpty()) {
|
||||||
|
errorMsg = e.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
System.out.println("ERROR: " + errorMsg);
|
||||||
|
System.out.flush();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("DEBUG: Job processing exception: " + e.getClass().getName());
|
||||||
|
System.err.flush();
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
System.out.println("ERROR: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getName()));
|
||||||
|
System.out.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("SaxonS9ApiWorker I/O error: " + e.getMessage());
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class SaxonWorkerPoolS9Api:
|
||||||
|
"""
|
||||||
|
Pool von lang-laufenden JVM-Prozessen für Saxon-Transformationen mit s9api.
|
||||||
|
|
||||||
|
Diese Variante verwendet die Saxon s9api API anstatt JAXP und unterstützt
|
||||||
|
vollständig XSLT 2.0 und 3.0 Transformationen.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
num_workers: int,
|
||||||
|
java_vm_path: Path,
|
||||||
|
saxon_jar_path: Path,
|
||||||
|
classpath_cache: dict[Path, str],
|
||||||
|
log_dir: Optional[Path] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Initialisiert den Saxon-Worker-Pool mit s9api.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
num_workers: Anzahl der Worker-Prozesse
|
||||||
|
java_vm_path: Pfad zur Java VM Binary
|
||||||
|
saxon_jar_path: Pfad zur Saxon JAR-Datei
|
||||||
|
classpath_cache: Cache für Saxon-Classpaths
|
||||||
|
log_dir: Optionales Verzeichnis für Worker-Logs (Standard: temp_dir/temp)
|
||||||
|
"""
|
||||||
|
self.num_workers = num_workers
|
||||||
|
self.java_vm_path = java_vm_path
|
||||||
|
self.saxon_jar_path = saxon_jar_path
|
||||||
|
self.classpath_cache = classpath_cache
|
||||||
|
self.log_dir = log_dir
|
||||||
|
|
||||||
|
# Worker-Prozesse und Queues
|
||||||
|
self.workers: list[subprocess.Popen] = []
|
||||||
|
self.job_queue: Queue = Queue()
|
||||||
|
self.result_queue: Queue = Queue()
|
||||||
|
self.worker_locks: list[threading.Lock] = []
|
||||||
|
|
||||||
|
# Temporäres Verzeichnis für kompilierte Java-Klasse
|
||||||
|
self.temp_dir: Optional[Path] = None
|
||||||
|
self.worker_class_path: Optional[Path] = None
|
||||||
|
self.worker_log_dir: Optional[Path] = None
|
||||||
|
|
||||||
|
# Initialisierung
|
||||||
|
self._compile_worker_class()
|
||||||
|
self._start_workers()
|
||||||
|
|
||||||
|
logger.info(f"SaxonWorkerPoolS9Api initialisiert mit {num_workers} Workern (XSLT 2.0/3.0)")
|
||||||
|
|
||||||
|
def _compile_worker_class(self):
|
||||||
|
"""Kompiliert die SaxonS9ApiWorker-Java-Klasse."""
|
||||||
|
try:
|
||||||
|
# Erstelle temporäres Verzeichnis
|
||||||
|
self.temp_dir = Path(tempfile.mkdtemp(prefix="saxon_s9api_worker_"))
|
||||||
|
|
||||||
|
# Schreibe Java-Quellcode
|
||||||
|
java_file = self.temp_dir / "SaxonS9ApiWorker.java"
|
||||||
|
java_file.write_text(SAXON_S9API_WORKER_JAVA, encoding="utf-8")
|
||||||
|
|
||||||
|
# Hole Classpath
|
||||||
|
saxon_dir = self.saxon_jar_path.parent
|
||||||
|
if saxon_dir in self.classpath_cache:
|
||||||
|
classpath = self.classpath_cache[saxon_dir]
|
||||||
|
else:
|
||||||
|
# Fallback: Baue Classpath neu
|
||||||
|
import glob
|
||||||
|
import sys
|
||||||
|
|
||||||
|
all_jars = glob.glob(str(saxon_dir / "*.jar"))
|
||||||
|
lib_dir = saxon_dir / "lib"
|
||||||
|
if lib_dir.exists():
|
||||||
|
all_jars.extend(glob.glob(str(lib_dir / "*.jar")))
|
||||||
|
|
||||||
|
classpath_separator = ";" if sys.platform == "win32" else ":"
|
||||||
|
classpath = classpath_separator.join(all_jars)
|
||||||
|
|
||||||
|
# Kompiliere Java-Klasse
|
||||||
|
javac_cmd = [str(self.java_vm_path).replace("java", "javac"), "-cp", classpath, str(java_file)]
|
||||||
|
|
||||||
|
logger.debug(f"Kompiliere SaxonS9ApiWorker: {' '.join(javac_cmd)}")
|
||||||
|
|
||||||
|
result = subprocess.run(javac_cmd, capture_output=True, text=True, timeout=30)
|
||||||
|
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(f"Java-Kompilierung fehlgeschlagen: {result.stderr}")
|
||||||
|
|
||||||
|
self.worker_class_path = self.temp_dir
|
||||||
|
|
||||||
|
logger.info(f"SaxonS9ApiWorker erfolgreich kompiliert: {self.temp_dir}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fehler beim Kompilieren von SaxonS9ApiWorker: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _start_workers(self):
|
||||||
|
"""Startet N Worker-Prozesse."""
|
||||||
|
# Hole Classpath
|
||||||
|
saxon_dir = self.saxon_jar_path.parent
|
||||||
|
if saxon_dir in self.classpath_cache:
|
||||||
|
classpath = self.classpath_cache[saxon_dir]
|
||||||
|
else:
|
||||||
|
# Fallback: Baue Classpath neu (sollte nicht nötig sein, aber zur Sicherheit)
|
||||||
|
import glob
|
||||||
|
import sys
|
||||||
|
|
||||||
|
all_jars = glob.glob(str(saxon_dir / "*.jar"))
|
||||||
|
lib_dir = saxon_dir / "lib"
|
||||||
|
if lib_dir.exists():
|
||||||
|
all_jars.extend(glob.glob(str(lib_dir / "*.jar")))
|
||||||
|
|
||||||
|
classpath_separator = ";" if sys.platform == "win32" else ":"
|
||||||
|
classpath = classpath_separator.join(all_jars)
|
||||||
|
|
||||||
|
# Cache für zukünftige Verwendung
|
||||||
|
self.classpath_cache[saxon_dir] = classpath
|
||||||
|
logger.debug(f"Classpath für {saxon_dir} neu erstellt und gecacht")
|
||||||
|
|
||||||
|
# Füge Worker-Classpath hinzu
|
||||||
|
import sys
|
||||||
|
|
||||||
|
classpath_separator = ";" if sys.platform == "win32" else ":"
|
||||||
|
full_classpath = str(self.worker_class_path) + classpath_separator + classpath
|
||||||
|
|
||||||
|
logger.debug(f"S9Api Worker Classpath: {full_classpath[:200]}...")
|
||||||
|
|
||||||
|
# Bestimme Log-Verzeichnis
|
||||||
|
self.worker_log_dir = self.log_dir if self.log_dir else self.temp_dir
|
||||||
|
if self.log_dir:
|
||||||
|
self.worker_log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for i in range(self.num_workers):
|
||||||
|
try:
|
||||||
|
# Starte JVM-Prozess mit SaxonS9ApiWorker
|
||||||
|
cmd = [str(self.java_vm_path), "-cp", full_classpath, "SaxonS9ApiWorker"]
|
||||||
|
|
||||||
|
# Öffne stderr-Log-Datei für diesen Worker
|
||||||
|
stderr_log = self.worker_log_dir / f"s9api_worker_{i}_stderr.log"
|
||||||
|
stderr_file = open(stderr_log, "w", encoding="utf-8")
|
||||||
|
|
||||||
|
process = subprocess.Popen(
|
||||||
|
cmd,
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=stderr_file, # Redirect stderr to file
|
||||||
|
text=True,
|
||||||
|
bufsize=1, # Line buffered
|
||||||
|
)
|
||||||
|
|
||||||
|
self.workers.append(process)
|
||||||
|
self.worker_locks.append(threading.Lock())
|
||||||
|
|
||||||
|
logger.debug(f"S9Api 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"S9Api Worker {i} ist sofort beendet (Exit Code: {process.returncode})\nstderr:\n{stderr_content}"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fehler beim Starten von S9Api Worker {i}: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
logger.info(f"{len(self.workers)} Saxon-S9Api-Worker erfolgreich gestartet")
|
||||||
|
|
||||||
|
def transform(
|
||||||
|
self, source_xml: Path, xsl_stylesheet: Path, output_fo: Path, xslt_params: dict[str, str]
|
||||||
|
) -> tuple[bool, str]:
|
||||||
|
"""
|
||||||
|
Führt eine XSLT-Transformation mit einem Worker aus dem Pool aus.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_xml: Pfad zur XML-Eingabedatei
|
||||||
|
xsl_stylesheet: Pfad zur XSL-Stylesheet-Datei
|
||||||
|
output_fo: Pfad zur FO-Ausgabedatei
|
||||||
|
xslt_params: Dictionary mit XSLT-Parametern
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[bool, str]: (Erfolg, Fehlermeldung/Info)
|
||||||
|
"""
|
||||||
|
# Finde freien Worker
|
||||||
|
worker_idx = None
|
||||||
|
for i, lock in enumerate(self.worker_locks):
|
||||||
|
if lock.acquire(blocking=False):
|
||||||
|
worker_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
if worker_idx is None:
|
||||||
|
# Kein freier Worker, warte auf ersten verfügbaren
|
||||||
|
for i, lock in enumerate(self.worker_locks):
|
||||||
|
lock.acquire()
|
||||||
|
worker_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
|
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.worker_log_dir / f"s9api_worker_{worker_idx}_stderr.log"
|
||||||
|
try:
|
||||||
|
with open(stderr_log, "r") as f:
|
||||||
|
stderr_content = f.read()
|
||||||
|
error_msg = (
|
||||||
|
f"S9Api Worker {worker_idx} ist beendet (Exit: {worker.returncode})\nstderr:\n{stderr_content}"
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
error_msg = f"S9Api 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 S9Api Worker {worker_idx}: {source_xml.name}")
|
||||||
|
|
||||||
|
# Sende Job an Worker
|
||||||
|
worker.stdin.write(job)
|
||||||
|
worker.stdin.flush()
|
||||||
|
|
||||||
|
# Warte auf Antwort
|
||||||
|
response = worker.stdout.readline().strip()
|
||||||
|
|
||||||
|
logger.debug(f"S9Api 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 (s9api): {error_msg}"
|
||||||
|
else:
|
||||||
|
# Leere Antwort bedeutet Worker ist crashed
|
||||||
|
if not response:
|
||||||
|
stderr_log = self.worker_log_dir / f"s9api_worker_{worker_idx}_stderr.log"
|
||||||
|
try:
|
||||||
|
with open(stderr_log, "r") as f:
|
||||||
|
stderr_content = f.read()[-500:] # Letzte 500 Zeichen
|
||||||
|
return False, f"S9Api Worker {worker_idx} crashed (keine Antwort)\nstderr:\n{stderr_content}"
|
||||||
|
except Exception:
|
||||||
|
return False, f"S9Api Worker {worker_idx} crashed (keine Antwort)"
|
||||||
|
return False, f"Unerwartete Antwort: {response}"
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fehler bei S9Api Worker {worker_idx}: {e}")
|
||||||
|
return False, f"Worker-Fehler: {str(e)}"
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Gebe Worker-Lock frei
|
||||||
|
self.worker_locks[worker_idx].release()
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
"""Beendet alle Worker-Prozesse sauber."""
|
||||||
|
logger.info("Beende Saxon-S9Api-Worker-Pool...")
|
||||||
|
|
||||||
|
for i, worker in enumerate(self.workers):
|
||||||
|
try:
|
||||||
|
# Sende EXIT-Befehl
|
||||||
|
if worker.stdin and not worker.stdin.closed:
|
||||||
|
worker.stdin.write("EXIT\n")
|
||||||
|
worker.stdin.flush()
|
||||||
|
|
||||||
|
# Warte auf Beendigung (max 2 Sekunden)
|
||||||
|
worker.wait(timeout=2)
|
||||||
|
logger.debug(f"S9Api Worker {i} beendet")
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
# Force kill falls nötig
|
||||||
|
worker.kill()
|
||||||
|
logger.warning(f"S9Api Worker {i} musste gekillt werden")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Fehler beim Beenden von S9Api Worker {i}: {e}")
|
||||||
|
|
||||||
|
# Lösche temporäres Verzeichnis
|
||||||
|
if self.temp_dir and self.temp_dir.exists():
|
||||||
|
try:
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
shutil.rmtree(self.temp_dir)
|
||||||
|
logger.debug(f"Temporäres Verzeichnis gelöscht: {self.temp_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Konnte temporäres Verzeichnis nicht löschen: {e}")
|
||||||
|
|
||||||
|
logger.info("Saxon-S9Api-Worker-Pool beendet")
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
"""Context manager entry."""
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
"""Context manager exit."""
|
||||||
|
self.shutdown()
|
||||||
+4
-2
@@ -15,18 +15,20 @@ from typing import Any, Optional, TYPE_CHECKING
|
|||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from saxon_pool import SaxonWorkerPool
|
from saxon_pool import SaxonWorkerPool
|
||||||
|
from saxon_pool_s9api import SaxonWorkerPoolS9Api
|
||||||
from fop_pool import FopWorkerPool
|
from fop_pool import FopWorkerPool
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Globaler Saxon-Worker-Pool (wird von MainWindow initialisiert)
|
# Globaler Saxon-Worker-Pool (wird von MainWindow initialisiert)
|
||||||
_saxon_worker_pool: Optional["SaxonWorkerPool"] = None
|
# Kann entweder JAXP oder s9api Variante sein
|
||||||
|
_saxon_worker_pool: Optional["SaxonWorkerPool | SaxonWorkerPoolS9Api"] = None
|
||||||
|
|
||||||
# Globaler FOP-Worker-Pool (wird von MainWindow initialisiert)
|
# Globaler FOP-Worker-Pool (wird von MainWindow initialisiert)
|
||||||
_fop_worker_pool: Optional["FopWorkerPool"] = None
|
_fop_worker_pool: Optional["FopWorkerPool"] = None
|
||||||
|
|
||||||
|
|
||||||
def set_saxon_worker_pool(pool: Optional["SaxonWorkerPool"]):
|
def set_saxon_worker_pool(pool: Optional["SaxonWorkerPool | SaxonWorkerPoolS9Api"]):
|
||||||
"""Setzt den globalen Saxon-Worker-Pool."""
|
"""Setzt den globalen Saxon-Worker-Pool."""
|
||||||
global _saxon_worker_pool
|
global _saxon_worker_pool
|
||||||
_saxon_worker_pool = pool
|
_saxon_worker_pool = pool
|
||||||
|
|||||||
+14
-1
@@ -10,7 +10,7 @@ from ui.ApacheFopConfigDialog import ApacheFopConfigDialog
|
|||||||
from ui.XslDirConfigDialog import XslDirConfigDialog
|
from ui.XslDirConfigDialog import XslDirConfigDialog
|
||||||
from ui.PostgreSqlConfigDialog import PostgreSqlConfigDialog
|
from ui.PostgreSqlConfigDialog import PostgreSqlConfigDialog
|
||||||
from ui.PdfProject import PdfProjectDlg
|
from ui.PdfProject import PdfProjectDlg
|
||||||
from conf import AppSettings, JavaVm, DiffPdf, SaxonJar, ApacheFop, XslDir, Project, PostgreSqlDb
|
from conf import AppSettings, JavaVm, DiffPdf, SaxonJar, ApacheFop, XslDir, Project, PostgreSqlDb, XsltVersion
|
||||||
|
|
||||||
|
|
||||||
class AppSettingsDlg(QDialog):
|
class AppSettingsDlg(QDialog):
|
||||||
@@ -282,6 +282,12 @@ class AppSettingsDlg(QDialog):
|
|||||||
# SaxonWorkerPool-Checkbox setzen
|
# SaxonWorkerPool-Checkbox setzen
|
||||||
self.ui.checkBoxUseSaxonPool.setChecked(self.settings.use_saxon_worker_pool)
|
self.ui.checkBoxUseSaxonPool.setChecked(self.settings.use_saxon_worker_pool)
|
||||||
|
|
||||||
|
# XSLT-Version ComboBox setzen
|
||||||
|
if self.settings.saxon_xslt_version == XsltVersion.XSLT_1_0:
|
||||||
|
self.ui.comboBoxXsltVersion.setCurrentIndex(0)
|
||||||
|
else: # XSLT_2_0_3_0
|
||||||
|
self.ui.comboBoxXsltVersion.setCurrentIndex(1)
|
||||||
|
|
||||||
# FopWorkerPool-Checkbox setzen
|
# FopWorkerPool-Checkbox setzen
|
||||||
self.ui.checkBoxUseFopPool.setChecked(self.settings.use_fop_worker_pool)
|
self.ui.checkBoxUseFopPool.setChecked(self.settings.use_fop_worker_pool)
|
||||||
|
|
||||||
@@ -740,6 +746,13 @@ class AppSettingsDlg(QDialog):
|
|||||||
# Performance-Einstellungen übernehmen
|
# Performance-Einstellungen übernehmen
|
||||||
self.settings.max_workers = self.ui.spinBoxWorkerCount.value()
|
self.settings.max_workers = self.ui.spinBoxWorkerCount.value()
|
||||||
self.settings.use_saxon_worker_pool = self.ui.checkBoxUseSaxonPool.isChecked()
|
self.settings.use_saxon_worker_pool = self.ui.checkBoxUseSaxonPool.isChecked()
|
||||||
|
|
||||||
|
# XSLT-Version übernehmen
|
||||||
|
if self.ui.comboBoxXsltVersion.currentIndex() == 0:
|
||||||
|
self.settings.saxon_xslt_version = XsltVersion.XSLT_1_0
|
||||||
|
else:
|
||||||
|
self.settings.saxon_xslt_version = XsltVersion.XSLT_2_0_3_0
|
||||||
|
|
||||||
self.settings.use_fop_worker_pool = self.ui.checkBoxUseFopPool.isChecked()
|
self.settings.use_fop_worker_pool = self.ui.checkBoxUseFopPool.isChecked()
|
||||||
|
|
||||||
self.settings.save()
|
self.settings.save()
|
||||||
|
|||||||
+64
-2
@@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>833</width>
|
<width>833</width>
|
||||||
<height>446</height>
|
<height>526</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>0</number>
|
<number>7</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="elideMode">
|
<property name="elideMode">
|
||||||
<enum>Qt::TextElideMode::ElideRight</enum>
|
<enum>Qt::TextElideMode::ElideRight</enum>
|
||||||
@@ -580,6 +580,55 @@ Deaktivieren Sie diese Option, wenn:
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayoutXsltVersion">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="labelXsltVersion">
|
||||||
|
<property name="text">
|
||||||
|
<string>XSLT-Version:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboBoxXsltVersion">
|
||||||
|
<property name="toolTip">
|
||||||
|
<string>Wählen Sie die XSLT-Version für Saxon-Transformationen:
|
||||||
|
|
||||||
|
XSLT 1.0 (JAXP): Verwendet die JAXP Transformer API
|
||||||
|
• Nur für XSLT 1.0 vollständig spezifiziert
|
||||||
|
• Kann bei XSLT 2.0/3.0 zu fehlerhaften Ausgaben führen
|
||||||
|
|
||||||
|
XSLT 2.0/3.0 (s9api): Verwendet die Saxon s9api
|
||||||
|
• Vollständige Unterstützung für XSLT 2.0 und 3.0
|
||||||
|
• Empfohlen für moderne XSLT-Stylesheets</string>
|
||||||
|
</property>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>XSLT 1.0 (JAXP)</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>XSLT 2.0/3.0 (s9api) - Empfohlen</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacerXsltVersion">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="labelSaxonPoolInfo">
|
<widget class="QLabel" name="labelSaxonPoolInfo">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@@ -629,6 +678,19 @@ Deaktivieren Sie diese Option, wenn:
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Orientation::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label">
|
<widget class="QLabel" name="label">
|
||||||
<property name="mouseTracking">
|
<property name="mouseTracking">
|
||||||
|
|||||||
@@ -15,17 +15,17 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
|
|||||||
QFont, QFontDatabase, QGradient, QIcon,
|
QFont, QFontDatabase, QGradient, QIcon,
|
||||||
QImage, QKeySequence, QLinearGradient, QPainter,
|
QImage, QKeySequence, QLinearGradient, QPainter,
|
||||||
QPalette, QPixmap, QRadialGradient, QTransform)
|
QPalette, QPixmap, QRadialGradient, QTransform)
|
||||||
from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QDialog,
|
from PySide6.QtWidgets import (QAbstractButton, QApplication, QCheckBox, QComboBox,
|
||||||
QDialogButtonBox, QFrame, QGroupBox, QHBoxLayout,
|
QDialog, QDialogButtonBox, QFrame, QGroupBox,
|
||||||
QHeaderView, QLabel, QPushButton, QSizePolicy,
|
QHBoxLayout, QHeaderView, QLabel, QPushButton,
|
||||||
QSpacerItem, QSpinBox, QTabWidget, QTableWidget,
|
QSizePolicy, QSpacerItem, QSpinBox, QTabWidget,
|
||||||
QTableWidgetItem, QVBoxLayout, QWidget)
|
QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget)
|
||||||
|
|
||||||
class Ui_Dialog(object):
|
class Ui_Dialog(object):
|
||||||
def setupUi(self, Dialog):
|
def setupUi(self, Dialog):
|
||||||
if not Dialog.objectName():
|
if not Dialog.objectName():
|
||||||
Dialog.setObjectName(u"Dialog")
|
Dialog.setObjectName(u"Dialog")
|
||||||
Dialog.resize(833, 446)
|
Dialog.resize(833, 526)
|
||||||
self.verticalLayout = QVBoxLayout(Dialog)
|
self.verticalLayout = QVBoxLayout(Dialog)
|
||||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
self.verticalLayout.setObjectName(u"verticalLayout")
|
||||||
self.tabSettings = QTabWidget(Dialog)
|
self.tabSettings = QTabWidget(Dialog)
|
||||||
@@ -336,6 +336,27 @@ class Ui_Dialog(object):
|
|||||||
|
|
||||||
self.verticalLayout_11.addWidget(self.checkBoxUseSaxonPool)
|
self.verticalLayout_11.addWidget(self.checkBoxUseSaxonPool)
|
||||||
|
|
||||||
|
self.horizontalLayoutXsltVersion = QHBoxLayout()
|
||||||
|
self.horizontalLayoutXsltVersion.setObjectName(u"horizontalLayoutXsltVersion")
|
||||||
|
self.labelXsltVersion = QLabel(self.groupBoxSaxonPool)
|
||||||
|
self.labelXsltVersion.setObjectName(u"labelXsltVersion")
|
||||||
|
|
||||||
|
self.horizontalLayoutXsltVersion.addWidget(self.labelXsltVersion)
|
||||||
|
|
||||||
|
self.comboBoxXsltVersion = QComboBox(self.groupBoxSaxonPool)
|
||||||
|
self.comboBoxXsltVersion.addItem("")
|
||||||
|
self.comboBoxXsltVersion.addItem("")
|
||||||
|
self.comboBoxXsltVersion.setObjectName(u"comboBoxXsltVersion")
|
||||||
|
|
||||||
|
self.horizontalLayoutXsltVersion.addWidget(self.comboBoxXsltVersion)
|
||||||
|
|
||||||
|
self.horizontalSpacerXsltVersion = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
|
||||||
|
|
||||||
|
self.horizontalLayoutXsltVersion.addItem(self.horizontalSpacerXsltVersion)
|
||||||
|
|
||||||
|
|
||||||
|
self.verticalLayout_11.addLayout(self.horizontalLayoutXsltVersion)
|
||||||
|
|
||||||
self.labelSaxonPoolInfo = QLabel(self.groupBoxSaxonPool)
|
self.labelSaxonPoolInfo = QLabel(self.groupBoxSaxonPool)
|
||||||
self.labelSaxonPoolInfo.setObjectName(u"labelSaxonPoolInfo")
|
self.labelSaxonPoolInfo.setObjectName(u"labelSaxonPoolInfo")
|
||||||
self.labelSaxonPoolInfo.setWordWrap(True)
|
self.labelSaxonPoolInfo.setWordWrap(True)
|
||||||
@@ -363,6 +384,10 @@ class Ui_Dialog(object):
|
|||||||
|
|
||||||
self.verticalLayout_9.addWidget(self.groupBoxFopPool)
|
self.verticalLayout_9.addWidget(self.groupBoxFopPool)
|
||||||
|
|
||||||
|
self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
|
||||||
|
|
||||||
|
self.verticalLayout_9.addItem(self.verticalSpacer)
|
||||||
|
|
||||||
self.label = QLabel(self.tabPerformance)
|
self.label = QLabel(self.tabPerformance)
|
||||||
self.label.setObjectName(u"label")
|
self.label.setObjectName(u"label")
|
||||||
self.label.setMouseTracking(True)
|
self.label.setMouseTracking(True)
|
||||||
@@ -392,7 +417,7 @@ class Ui_Dialog(object):
|
|||||||
self.buttonBox.accepted.connect(Dialog.accept)
|
self.buttonBox.accepted.connect(Dialog.accept)
|
||||||
self.buttonBox.rejected.connect(Dialog.reject)
|
self.buttonBox.rejected.connect(Dialog.reject)
|
||||||
|
|
||||||
self.tabSettings.setCurrentIndex(0)
|
self.tabSettings.setCurrentIndex(7)
|
||||||
|
|
||||||
|
|
||||||
QMetaObject.connectSlotsByName(Dialog)
|
QMetaObject.connectSlotsByName(Dialog)
|
||||||
@@ -438,6 +463,21 @@ class Ui_Dialog(object):
|
|||||||
"\u2022 Sie die Funktion testen m\u00f6chten", None))
|
"\u2022 Sie die Funktion testen m\u00f6chten", None))
|
||||||
#endif // QT_CONFIG(tooltip)
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.checkBoxUseSaxonPool.setText(QCoreApplication.translate("Dialog", u"SaxonWorkerPool verwenden (empfohlen)", None))
|
self.checkBoxUseSaxonPool.setText(QCoreApplication.translate("Dialog", u"SaxonWorkerPool verwenden (empfohlen)", None))
|
||||||
|
self.labelXsltVersion.setText(QCoreApplication.translate("Dialog", u"XSLT-Version:", None))
|
||||||
|
self.comboBoxXsltVersion.setItemText(0, QCoreApplication.translate("Dialog", u"XSLT 1.0 (JAXP)", None))
|
||||||
|
self.comboBoxXsltVersion.setItemText(1, QCoreApplication.translate("Dialog", u"XSLT 2.0/3.0 (s9api) - Empfohlen", None))
|
||||||
|
|
||||||
|
#if QT_CONFIG(tooltip)
|
||||||
|
self.comboBoxXsltVersion.setToolTip(QCoreApplication.translate("Dialog", u"W\u00e4hlen Sie die XSLT-Version f\u00fcr Saxon-Transformationen:\n"
|
||||||
|
"\n"
|
||||||
|
"XSLT 1.0 (JAXP): Verwendet die JAXP Transformer API\n"
|
||||||
|
"\u2022 Nur f\u00fcr XSLT 1.0 vollst\u00e4ndig spezifiziert\n"
|
||||||
|
"\u2022 Kann bei XSLT 2.0/3.0 zu fehlerhaften Ausgaben f\u00fchren\n"
|
||||||
|
"\n"
|
||||||
|
"XSLT 2.0/3.0 (s9api): Verwendet die Saxon s9api\n"
|
||||||
|
"\u2022 Vollst\u00e4ndige Unterst\u00fctzung f\u00fcr XSLT 2.0 und 3.0\n"
|
||||||
|
"\u2022 Empfohlen f\u00fcr moderne XSLT-Stylesheets", None))
|
||||||
|
#endif // QT_CONFIG(tooltip)
|
||||||
self.labelSaxonPoolInfo.setText(QCoreApplication.translate("Dialog", u"<i>Hinweis: SaxonWorkerPool ben\u00f6tigt ein JDK (Java Development Kit).<br>Mit JRE allein werden Transformationen im Fallback-Modus ausgef\u00fchrt.</i>", None))
|
self.labelSaxonPoolInfo.setText(QCoreApplication.translate("Dialog", u"<i>Hinweis: SaxonWorkerPool ben\u00f6tigt ein JDK (Java Development Kit).<br>Mit JRE allein werden Transformationen im Fallback-Modus ausgef\u00fchrt.</i>", None))
|
||||||
self.groupBoxFopPool.setTitle(QCoreApplication.translate("Dialog", u"FopWorkerPool Einstellungen", None))
|
self.groupBoxFopPool.setTitle(QCoreApplication.translate("Dialog", u"FopWorkerPool Einstellungen", None))
|
||||||
#if QT_CONFIG(tooltip)
|
#if QT_CONFIG(tooltip)
|
||||||
|
|||||||
+19
-3
@@ -28,9 +28,10 @@ from ui.PdfProject import PdfProjectDlg
|
|||||||
from ui.TreeNodeEditDialog import TreeNodeEditDialog
|
from ui.TreeNodeEditDialog import TreeNodeEditDialog
|
||||||
from ui.XslFileEditDialog import XslFileEditDialog
|
from ui.XslFileEditDialog import XslFileEditDialog
|
||||||
from ui.XmlToXslAssignDialog import XmlToXslAssignDialog
|
from ui.XmlToXslAssignDialog import XmlToXslAssignDialog
|
||||||
from conf import app_settings, Project, ProjectData, TreeNode, XslFile, XmlFile
|
from conf import app_settings, Project, ProjectData, TreeNode, XslFile, XmlFile, XsltVersion
|
||||||
from transform import TransformationJob, set_saxon_worker_pool
|
from transform import TransformationJob, set_saxon_worker_pool
|
||||||
from saxon_pool import SaxonWorkerPool
|
from saxon_pool import SaxonWorkerPool
|
||||||
|
from saxon_pool_s9api import SaxonWorkerPoolS9Api
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
@@ -727,9 +728,13 @@ class MainWindow(QMainWindow):
|
|||||||
logger.warning("Java VM oder Saxon JAR nicht gefunden, Pool nicht initialisiert")
|
logger.warning("Java VM oder Saxon JAR nicht gefunden, Pool nicht initialisiert")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Erstelle Worker-Pool
|
# Erstelle Worker-Pool (wähle richtige Variante basierend auf XSLT-Version)
|
||||||
num_workers = app_settings.max_workers
|
num_workers = app_settings.max_workers
|
||||||
log_dir = self.project.project_dir / "temp"
|
log_dir = self.project.project_dir / "temp"
|
||||||
|
|
||||||
|
# Wähle die richtige Worker-Pool-Implementierung
|
||||||
|
if app_settings.saxon_xslt_version == XsltVersion.XSLT_1_0:
|
||||||
|
# JAXP-basierte Variante für XSLT 1.0
|
||||||
pool = SaxonWorkerPool(
|
pool = SaxonWorkerPool(
|
||||||
num_workers=num_workers,
|
num_workers=num_workers,
|
||||||
java_vm_path=java_vm.path_to_binary_file,
|
java_vm_path=java_vm.path_to_binary_file,
|
||||||
@@ -737,12 +742,23 @@ class MainWindow(QMainWindow):
|
|||||||
classpath_cache=TransformationJob._classpath_cache,
|
classpath_cache=TransformationJob._classpath_cache,
|
||||||
log_dir=log_dir,
|
log_dir=log_dir,
|
||||||
)
|
)
|
||||||
|
pool_type = "JAXP (XSLT 1.0)"
|
||||||
|
else:
|
||||||
|
# s9api-basierte Variante für XSLT 2.0/3.0
|
||||||
|
pool = SaxonWorkerPoolS9Api(
|
||||||
|
num_workers=num_workers,
|
||||||
|
java_vm_path=java_vm.path_to_binary_file,
|
||||||
|
saxon_jar_path=saxon_jar.path_to_jar_file,
|
||||||
|
classpath_cache=TransformationJob._classpath_cache,
|
||||||
|
log_dir=log_dir,
|
||||||
|
)
|
||||||
|
pool_type = "s9api (XSLT 2.0/3.0)"
|
||||||
|
|
||||||
# Setze globalen Pool
|
# Setze globalen Pool
|
||||||
set_saxon_worker_pool(pool)
|
set_saxon_worker_pool(pool)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Saxon-Worker-Pool initialisiert: {num_workers} Worker "
|
f"Saxon-Worker-Pool initialisiert: {num_workers} Worker mit {pool_type} "
|
||||||
f"(erwartet: {num_workers}x schneller für Saxon-Transformationen)"
|
f"(erwartet: {num_workers}x schneller für Saxon-Transformationen)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user