Fix #54: Modellabruf ueber Generation-Counter gegen veraltete Ergebnisse absichern
Bei mehrfachem Provider-Wechsel oder Modelle-Neu-Laden konnten parallele HTTP-Threads ihre Ergebnisse in dieselbe Meldungsliste schreiben. Mit einem AtomicLong-Generationszaehler wird vor jedem Lauf eine Generation festgehalten; bei der UI-Auslieferung auf dem JavaFX Application Thread wird verworfen, was nicht mehr zur aktuellen Generation gehoert. Damit ueberschreiben veraltete Worker den UI-Zustand nicht mehr. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+29
-2
@@ -5,6 +5,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@@ -35,6 +36,13 @@ import javafx.application.Platform;
|
||||
* completed retrieval attempt, so later GUI layers can display the result.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Parallele Abrufanfragen (z. B. durch schnellen Provider-Wechsel oder mehrfaches Klicken
|
||||
* auf „Modelle neu laden") werden durch einen Generationszähler entschärft: Jede neue Anfrage
|
||||
* erhöht den Zähler. Wenn das Ergebnis eines Hintergrund-Threads auf dem JavaFX-Thread
|
||||
* verarbeitet wird, prüft der Coordinator, ob die Generationsnummer noch aktuell ist. Veraltete
|
||||
* Ergebnisse (aus einer früheren Anfrage) werden verworfen, sodass stets nur das Ergebnis der
|
||||
* jüngsten Anfrage in die Meldungsliste und die Feldcontainer geschrieben wird.
|
||||
* <p>
|
||||
* The worker thread factory is injectable so tests can supply a synchronous or latch-guarded
|
||||
* executor without spinning a real OS thread.
|
||||
* <p>
|
||||
@@ -62,6 +70,14 @@ public final class GuiModelCatalogCoordinator {
|
||||
private final Map<AiProviderFamily, GuiModelFieldContainer> fieldContainers =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Generationszähler zur Erkennung veralteter Abruf-Ergebnisse.
|
||||
* Wird bei jeder neuen Anfrage in {@link #triggerModelRetrieval} atomar erhöht.
|
||||
* Hintergrund-Threads erfassen die Generation beim Start; auf dem JavaFX-Thread wird
|
||||
* das Ergebnis verworfen, wenn die gespeicherte Generation nicht mehr aktuell ist.
|
||||
*/
|
||||
private final AtomicLong retrievalGeneration = new AtomicLong(0);
|
||||
|
||||
/**
|
||||
* Consumer that delivers the retrieval result. In production this wraps the call in
|
||||
* {@code Platform.runLater}. In tests it can be replaced with a direct call so the result
|
||||
@@ -144,12 +160,23 @@ public final class GuiModelCatalogCoordinator {
|
||||
// Build the request from the current editor state.
|
||||
ModelCatalogRequest request = buildRequest(family, providerState);
|
||||
|
||||
LOG.info("GUI-Modellabruf: Modelllistenabruf für Provider '{}' gestartet.",
|
||||
family.getIdentifier());
|
||||
// Generationsnummer erhöhen – laufende Hintergrund-Threads mit einer älteren
|
||||
// Generationsnummer verwerfen ihr Ergebnis, sobald sie auf dem FX-Thread ankommen.
|
||||
long currentGeneration = retrievalGeneration.incrementAndGet();
|
||||
|
||||
LOG.info("GUI-Modellabruf: Modelllistenabruf für Provider '{}' gestartet (Generation {}).",
|
||||
family.getIdentifier(), currentGeneration);
|
||||
|
||||
Runnable task = () -> {
|
||||
ModelCatalogResult result = modelCatalogPort.fetchAvailableModels(request);
|
||||
resultDelivery.accept(() -> {
|
||||
// Veraltetes Ergebnis verwerfen, wenn inzwischen eine neuere Anfrage gestartet wurde.
|
||||
if (retrievalGeneration.get() != currentGeneration) {
|
||||
LOG.debug("GUI-Modellabruf: Ergebnis für Provider '{}' verworfen"
|
||||
+ " (Generation {} ist nicht mehr aktuell).",
|
||||
family.getIdentifier(), currentGeneration);
|
||||
return;
|
||||
}
|
||||
applyResult(family, container, result, previousManualValue);
|
||||
postResultCallback.run();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user