Fix bookdrop race condition processing files before fully written (#2267) (#2785)

Co-authored-by: acx10 <acx10@users.noreply.github.com>
This commit is contained in:
ACX
2026-02-17 16:27:22 -07:00
committed by GitHub
parent 43094bb8ef
commit 7175f2cff2
3 changed files with 60 additions and 0 deletions

View File

@@ -15,6 +15,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardWatchEventKinds;
@@ -35,6 +36,10 @@ public class BookdropEventHandlerService {
private final AppSettingService appSettingService;
private final BookdropMetadataService bookdropMetadataService;
private static final long STABILITY_CHECK_INTERVAL_MS = 500;
private static final int STABILITY_REQUIRED_CHECKS = 3;
private static final long STABILITY_MAX_WAIT_MS = 30_000;
private final BlockingQueue<BookDropFileEvent> fileQueue = new LinkedBlockingQueue<>();
private volatile boolean running = true;
private Thread workerThread;
@@ -100,6 +105,11 @@ public class BookdropEventHandlerService {
return;
}
if (!waitForFileStability(file)) {
log.warn("File did not stabilize within timeout, skipping: {}", file);
return;
}
log.info("Handling new bookdrop file: {}", file);
int queueSize = fileQueue.size();
@@ -158,4 +168,41 @@ public class BookdropEventHandlerService {
bookdropNotificationService.sendBookdropFileSummaryNotification();
}
}
private boolean waitForFileStability(Path file) {
long startTime = System.currentTimeMillis();
long lastSize = -1;
int stableCount = 0;
while (System.currentTimeMillis() - startTime < STABILITY_MAX_WAIT_MS) {
try {
if (!Files.exists(file)) {
return false;
}
long currentSize = Files.size(file);
if (currentSize == lastSize && currentSize > 0) {
stableCount++;
if (stableCount >= STABILITY_REQUIRED_CHECKS) {
return true;
}
} else {
stableCount = 0;
}
lastSize = currentSize;
Thread.sleep(STABILITY_CHECK_INTERVAL_MS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} catch (IOException e) {
log.warn("Error checking file size for stability: {}", file, e);
return false;
}
}
log.warn("File size did not stabilize after {}ms: {}", STABILITY_MAX_WAIT_MS, file);
return false;
}
}

View File

@@ -21,6 +21,8 @@ import org.springframework.transaction.annotation.Transactional;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
import org.apache.commons.io.FilenameUtils;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
@@ -46,6 +48,12 @@ public class BookdropMetadataService {
public BookdropFileEntity attachInitialMetadata(Long bookdropFileId) throws JacksonException {
BookdropFileEntity entity = getOrThrow(bookdropFileId);
BookMetadata initial = extractInitialMetadata(entity);
if (initial == null) {
log.warn("Metadata extraction returned null for file: {}. Using filename as fallback.", entity.getFileName());
initial = BookMetadata.builder()
.title(FilenameUtils.getBaseName(entity.getFileName()))
.build();
}
extractAndSaveCover(entity);
String initialJson = objectMapper.writeValueAsString(initial);
entity.setOriginalMetadata(initialJson);

View File

@@ -413,6 +413,11 @@ public class MetadataRefreshService {
private FetchMetadataRequest buildFetchMetadataRequestFromBook(Book book) {
BookMetadata metadata = book.getMetadata();
if (metadata == null) {
return FetchMetadataRequest.builder()
.bookId(book.getId())
.build();
}
String isbn = metadata.getIsbn13();
if (isbn == null || isbn.isBlank()) {
isbn = metadata.getIsbn10();