diff --git a/src/services/mpris.py b/src/services/mpris.py index fa74641..c678220 100644 --- a/src/services/mpris.py +++ b/src/services/mpris.py @@ -175,6 +175,7 @@ class PlayerService(Service): self._current_artwork_hash = "" self._current_artwork_path = "" self._is_cleaning_up = False + self._artwork_generation = 0 self._signal_ids = [] self._last_emitted_status = "" @@ -449,9 +450,12 @@ class PlayerService(Service): if existing and Path(existing).exists(): self._set_artwork(existing) return + # Capture generation so the thread can bail out if cleanup runs + # while the download is in-flight. + gen = self._artwork_generation threading.Thread( target=self._download_artwork, - args=(art_url, artwork_hash, metadata), + args=(art_url, artwork_hash, metadata, gen), daemon=True, ).start() @@ -472,9 +476,7 @@ class PlayerService(Service): cache_dir.mkdir(parents=True, exist_ok=True) return cache_dir / f"{artwork_hash}{suffix}" - def _download_artwork(self, art_url: str, artwork_hash: str, metadata): - if self._is_cleaning_up: - return + def _download_artwork(self, art_url: str, artwork_hash: str, metadata, gen: int): try: with urllib.request.urlopen(art_url, timeout=5) as response: data = response.read() @@ -487,7 +489,9 @@ class PlayerService(Service): tmp.write_bytes(data) tmp.replace(target) - if not self._is_cleaning_up: + # Bail out if cleanup ran while we were downloading — the idle + # callback would reference a stale service. + if not self._is_cleaning_up and gen == self._artwork_generation: GLib.idle_add(self._set_artwork, str(target)) except Exception as e: @@ -497,6 +501,9 @@ class PlayerService(Service): if self._is_cleaning_up: return self._is_cleaning_up = True + # Invalidate any in-flight artwork download threads so they won't + # call _set_artwork on a stale service. + self._artwork_generation += 1 try: if hasattr(self, "pos_fabricator"): diff --git a/src/services/nightlight.py b/src/services/nightlight.py index 1e10292..d37089b 100644 --- a/src/services/nightlight.py +++ b/src/services/nightlight.py @@ -134,6 +134,10 @@ class NightLightService(Service): self._process.wait_async(None, self._on_daemon_exit) def _on_daemon_exit(self, process: Gio.Subprocess, task: Gio.Task): + # Ignore exit from a stale subprocess — a new daemon may have been + # started since this one was spawned. + if self._process is not process: + return self._process = None self._daemon_running = False self._set_active(False)