diff --git a/backend/src/main/java/org/raddatz/familienarchiv/service/OcrTrainingService.java b/backend/src/main/java/org/raddatz/familienarchiv/service/OcrTrainingService.java index 820ab2f5..dc6ff043 100644 --- a/backend/src/main/java/org/raddatz/familienarchiv/service/OcrTrainingService.java +++ b/backend/src/main/java/org/raddatz/familienarchiv/service/OcrTrainingService.java @@ -45,6 +45,13 @@ public class OcrTrainingService { List runs ) {} + private void assertNoRunningTraining() { + if (trainingRunRepository.findFirstByStatus(TrainingStatus.RUNNING).isPresent()) { + throw DomainException.conflict(ErrorCode.TRAINING_ALREADY_RUNNING, + "A training run is already in progress"); + } + } + // Not safe for horizontal scaling: training reloads the Kraken model in-process on the // Python OCR service after each run. The DB-level RUNNING constraint (V30 partial unique // index) prevents concurrent training API calls, but cannot prevent two OCR service replicas @@ -53,10 +60,7 @@ public class OcrTrainingService { // Short transaction: guard check + create RUNNING row, then commit immediately. // The DB connection is released before the OCR HTTP call, which can take several minutes. OcrTrainingRun run = Objects.requireNonNull(txTemplate.execute(status -> { - if (trainingRunRepository.findFirstByStatus(TrainingStatus.RUNNING).isPresent()) { - throw DomainException.conflict(ErrorCode.TRAINING_ALREADY_RUNNING, - "A training run is already in progress"); - } + assertNoRunningTraining(); var eligibleBlocks = trainingDataExportService.queryEligibleBlocks(); if (eligibleBlocks.size() < 5) { @@ -120,10 +124,7 @@ public class OcrTrainingService { public OcrTrainingRun triggerSegTraining(UUID triggeredBy) { // Same pattern as triggerTraining: narrow transactions around DB writes only. OcrTrainingRun run = Objects.requireNonNull(txTemplate.execute(status -> { - if (trainingRunRepository.findFirstByStatus(TrainingStatus.RUNNING).isPresent()) { - throw DomainException.conflict(ErrorCode.TRAINING_ALREADY_RUNNING, - "A training run is already in progress"); - } + assertNoRunningTraining(); var segBlocks = segmentationTrainingExportService.querySegmentationBlocks(); if (segBlocks.size() < 5) {