From 45b87adb33e00699e9e3dc863136470ba7448aa8 Mon Sep 17 00:00:00 2001 From: pax Date: Sat, 11 Apr 2026 23:01:35 -0500 Subject: [PATCH] media_controller: cancel stale prefetch spirals on new click Each prefetch_adjacent() call now bumps a generation counter. Running spirals check the counter at each iteration and exit when superseded. Previously, rapid clicks between posts stacked up concurrent download loops that never cancelled, accumulating HTTP connections and response buffers. Also incrementally updates the search controller's cached-names set when a download completes, avoiding a full directory rescan. behavior change: only the most recent click's prefetch spiral runs; older ones exit at their next iteration. --- booru_viewer/gui/media_controller.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/booru_viewer/gui/media_controller.py b/booru_viewer/gui/media_controller.py index 7837ce0..58ec696 100644 --- a/booru_viewer/gui/media_controller.py +++ b/booru_viewer/gui/media_controller.py @@ -73,6 +73,7 @@ class MediaController: self._prefetch_pause = asyncio.Event() self._prefetch_pause.set() # not paused self._last_evict_check = 0.0 # monotonic timestamp + self._prefetch_gen = 0 # incremented on each prefetch_adjacent call # -- Post activation (media load) -- @@ -162,6 +163,13 @@ class MediaController: idx = self._app._grid.selected_index if 0 <= idx < len(self._app._grid._thumbs): self._app._grid._thumbs[idx]._cached_path = path + # Keep the search controller's cached-names set current so + # subsequent _drain_append_queue calls see newly downloaded files + # without a full directory rescan. + cn = self._app._search_ctrl._cached_names + if cn is not None: + from pathlib import Path as _P + cn.add(_P(path).name) self._app._popout_ctrl.update_media(path, info) self.auto_evict_cache() @@ -207,7 +215,12 @@ class MediaController: self._app._grid._thumbs[index].set_prefetch_progress(progress) def prefetch_adjacent(self, index: int) -> None: - """Prefetch posts around the given index.""" + """Prefetch posts around the given index. + + Bumps a generation counter so any previously running spiral + exits at its next iteration instead of continuing to download + stale adjacencies. + """ total = len(self._app._posts) if total == 0: return @@ -215,9 +228,16 @@ class MediaController: mode = self._app._db.get_setting("prefetch_mode") order = compute_prefetch_order(index, total, cols, mode) + self._prefetch_gen += 1 + gen = self._prefetch_gen + async def _prefetch_spiral(): for adj in order: + if self._prefetch_gen != gen: + return # superseded by a newer prefetch await self._prefetch_pause.wait() + if self._prefetch_gen != gen: + return if 0 <= adj < len(self._app._posts) and self._app._posts[adj].file_url: self._app._signals.prefetch_progress.emit(adj, 0.0) try: @@ -251,7 +271,7 @@ class MediaController: for fav in self._app._db.get_bookmarks(limit=999999): if fav.cached_path: protected.add(fav.cached_path) - evicted = evict_oldest(max_bytes, protected) + evicted = evict_oldest(max_bytes, protected, current_bytes=current) if evicted: log.info(f"Auto-evicted {evicted} cached files") max_thumb_mb = self._app._db.get_setting_int("max_thumb_cache_mb") or 500