From 1913181d02147e1af32f872ff693468ae57ae41d Mon Sep 17 00:00:00 2001 From: tkrmagid Date: Sat, 16 May 2026 22:54:20 +0900 Subject: [PATCH] v0.4.20: close reindex publish race + harden preload guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewer-flagged: the existing-cache reindex branch in VideoCache.download only ran one epoch check before READY.put, so a /videoCache clear landing between the check and the put could leave a stale entry pointing at a now-deleted file. Same pattern as the post-move fix in v0.4.19, applied to the reindex path: pre-check, put, post-check + rollback on mismatch. Also: preload() previously gated on READY.containsKey(url), which silently blocks a re-preload if READY holds a stale key whose backing file is gone (e.g. user deleted the file manually, or the cleanup half of a clear race). Switched to lookup(url) — same intent, but lookup verifies the file actually exists on disk, so stale keys self-heal on the next preload. --- gradle.properties | 2 +- .../client/playback/VideoCache.java | 20 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 660711a..8a81f11 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.configuration-cache=false # Mod mod_id=video_player -mod_version=0.4.19 +mod_version=0.4.20 maven_group=com.ejclaw.videoplayer archives_base_name=video_player diff --git a/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java b/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java index 87a902a..42947ba 100644 --- a/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java +++ b/src/main/java/com/ejclaw/videoplayer/client/playback/VideoCache.java @@ -190,7 +190,11 @@ public final class VideoCache { public static void preload(String url) { if (url == null || url.isEmpty()) return; if (!(url.startsWith("http://") || url.startsWith("https://"))) return; - if (READY.containsKey(url)) { + // Use lookup() (which also verifies the file exists on disk) instead of + // READY.containsKey — defends against READY containing a stale entry whose + // backing file was removed by clearAll() / a user-side file delete. Without this, + // a stale key would silently block a re-preload. + if (lookup(url) != null) { VideoPlayerMod.LOG.info("[{}] preload: already cached {}", VideoPlayerMod.MOD_ID, url); notifyChat("[videopreload] 이미 캐시됨: " + url, ChatFormatting.GRAY); return; @@ -255,8 +259,22 @@ public final class VideoCache { // Resume-friendly: if the file's already on disk from an earlier session, just // index it without re-downloading. if (Files.exists(finalPath) && Files.size(finalPath) > 0) { + // Pre-publish check — bail if clearAll has run since submit. if (CACHE_EPOCH.get() != startEpoch) return; READY.put(url, finalPath); + // Post-publish re-check: same window as the post-move check on the download + // path. If clearAll landed between the epoch read above and the READY.put, + // it would have wiped READY + the file, and our put() resurrected a stale + // entry pointing at a now-deleted path. Roll it back: remove our entry + // (compareAndRemove via remove(key, value) to avoid clobbering a concurrent + // legitimate re-put) and best-effort delete the file. + if (CACHE_EPOCH.get() != startEpoch) { + READY.remove(url, finalPath); + try { Files.deleteIfExists(finalPath); } catch (Throwable ignored) {} + VideoPlayerMod.LOG.info("[{}] preload: reindex cancelled (clearAll ran) — {}", + VideoPlayerMod.MOD_ID, url); + return; + } VideoPlayerMod.LOG.info("[{}] preload: indexed existing cache {} -> {}", VideoPlayerMod.MOD_ID, url, finalPath.getFileName()); notifyChat("[videopreload] 기존 캐시 사용: " + url, ChatFormatting.GREEN);