task-18: 统一图片 spool 生命周期,超时/取消/异常路径删除临时文件

- ImageSpool 增加 closed 状态:close 幂等、close 后 put 拒绝且消息可识别,
  杜绝关闭后再写入导致目录删除失败
- 长预算 prefetchToDisk 任务体补 put 前中断检查:deadline/中断取消后不再
  产生新临时文件,close 可完整删除目录(与 best-effort 路径对齐)
- 长预算路径并入测试 hook 探测,与短预算路径行为一致,生命周期可测
This commit is contained in:
2026-08-29 18:02:50 +08:00
parent e1217c9c85
commit 92858acb4d
2 changed files with 242 additions and 2 deletions
@@ -485,11 +485,15 @@ public class SimilarAsinImageEmbedder {
try {
ResizedImage thumb = readLocalCachedThumb(url);
if (thumb == null) {
thumb = fetchAndResizeDirect(url);
downloaded.incrementAndGet();
thumb = fetchAndResizeDirectForTest(url);
if (thumb == null) {
thumb = fetchAndResizeDirect(url);
downloaded.incrementAndGet();
}
} else {
localHit.incrementAndGet();
}
ensureImageWorkNotInterrupted();
imageSpool.put(url, thumb);
} catch (Exception ex) {
failed.incrementAndGet();
@@ -1233,6 +1237,7 @@ public class SimilarAsinImageEmbedder {
private final Path directory;
private final ConcurrentMap<String, SpoolImage> images = new ConcurrentHashMap<>();
private volatile boolean closed;
public ImageSpool(Path directory) throws IOException {
this.directory = Objects.requireNonNull(directory, "directory must not be null")
@@ -1240,6 +1245,11 @@ public class SimilarAsinImageEmbedder {
Files.createDirectories(this.directory);
}
/** Task 18:暴露临时目录,供生命周期校验与日志。 */
public Path directory() {
return directory;
}
public SpoolImage get(String url) {
if (url == null) {
return null;
@@ -1249,6 +1259,9 @@ public class SimilarAsinImageEmbedder {
}
public SpoolImage put(String url, ResizedImage image) throws IOException {
if (closed) {
throw new IOException("image spool closed: " + directory);
}
if (url == null || url.isBlank() || image == null || image.bytes() == null || image.bytes().length == 0) {
throw new IOException("invalid image spool entry");
}
@@ -1281,6 +1294,7 @@ public class SimilarAsinImageEmbedder {
@Override
public void close() throws IOException {
closed = true;
images.clear();
if (!Files.exists(directory)) {
return;