v0.4.19: fix post-move publish race in VideoCache.download
Some checks failed
build / build (push) Has been cancelled
Some checks failed
build / build (push) Has been cancelled
Reviewer-flagged: v0.4.18 only checked epoch BEFORE Files.move. The window between the move completing and READY.put / "완료" chat was still racy — if /videoCache clear landed in that window, clearAll would epoch++ + clear READY + delete files on disk, then the download thread would do READY.put(url, finalPath) anyway, resurrecting a cleared entry and emitting a stale "완료" message. Add a second epoch check immediately AFTER Files.move(): if the epoch changed, delete finalPath and return without publishing. The pre-move check is kept too — it lets the common cancel-during-read case skip the wasted move/delete round-trip.
This commit is contained in:
@@ -5,7 +5,7 @@ org.gradle.configuration-cache=false
|
|||||||
|
|
||||||
# Mod
|
# Mod
|
||||||
mod_id=video_player
|
mod_id=video_player
|
||||||
mod_version=0.4.18
|
mod_version=0.4.19
|
||||||
maven_group=com.ejclaw.videoplayer
|
maven_group=com.ejclaw.videoplayer
|
||||||
archives_base_name=video_player
|
archives_base_name=video_player
|
||||||
|
|
||||||
|
|||||||
@@ -340,11 +340,10 @@ public final class VideoCache {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final cancellation check before publishing. If clearAll ran during the read
|
// Pre-move cancellation check. If clearAll ran during the read loop, abort
|
||||||
// loop's last iteration (after the loop check, before this point), we still
|
// before promoting .part to final — saves a wasted move + delete.
|
||||||
// refuse to publish — otherwise we'd resurrect a cache entry post-clear.
|
|
||||||
if (CACHE_EPOCH.get() != startEpoch) {
|
if (CACHE_EPOCH.get() != startEpoch) {
|
||||||
VideoPlayerMod.LOG.info("[{}] preload: cancelled at publish (clearAll ran) — {}",
|
VideoPlayerMod.LOG.info("[{}] preload: cancelled before move (clearAll ran) — {}",
|
||||||
VideoPlayerMod.MOD_ID, url);
|
VideoPlayerMod.MOD_ID, url);
|
||||||
try { Files.deleteIfExists(partPath); } catch (Throwable ignored) {}
|
try { Files.deleteIfExists(partPath); } catch (Throwable ignored) {}
|
||||||
return;
|
return;
|
||||||
@@ -352,6 +351,21 @@ public final class VideoCache {
|
|||||||
|
|
||||||
Files.move(partPath, finalPath, StandardCopyOption.REPLACE_EXISTING,
|
Files.move(partPath, finalPath, StandardCopyOption.REPLACE_EXISTING,
|
||||||
StandardCopyOption.ATOMIC_MOVE);
|
StandardCopyOption.ATOMIC_MOVE);
|
||||||
|
|
||||||
|
// Post-move cancellation check. clearAll() may have run between the pre-move
|
||||||
|
// check above and the move itself — in that case clearAll's directory scan
|
||||||
|
// either missed our file (it didn't exist yet) or saw the .part and skipped /
|
||||||
|
// failed to delete it. Either way, finalPath now exists on disk but the
|
||||||
|
// user-visible cache state is "cleared", so we must delete the file and skip
|
||||||
|
// both the READY.put and the "완료" chat. Without this check, a clear right at
|
||||||
|
// this window leaves a resurrected file in READY and a stale "완료" message.
|
||||||
|
if (CACHE_EPOCH.get() != startEpoch) {
|
||||||
|
VideoPlayerMod.LOG.info("[{}] preload: cancelled after move (clearAll ran) — {}",
|
||||||
|
VideoPlayerMod.MOD_ID, url);
|
||||||
|
try { Files.deleteIfExists(finalPath); } catch (Throwable ignored) {}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
READY.put(url, finalPath);
|
READY.put(url, finalPath);
|
||||||
VideoPlayerMod.LOG.info("[{}] preload: cached {} ({} bytes) -> {}",
|
VideoPlayerMod.LOG.info("[{}] preload: cached {} ({} bytes) -> {}",
|
||||||
VideoPlayerMod.MOD_ID, url, total, finalPath.getFileName());
|
VideoPlayerMod.MOD_ID, url, total, finalPath.getFileName());
|
||||||
|
|||||||
Reference in New Issue
Block a user