Merge pull request 'fix: keep UpdateCheckWorker alive until its thread finishes (#32)' (#42) from fix/32-about-worker-lifetime into main
CI / Lint (ruff) (push) Successful in 7s
CI / Tests (py3.10) (push) Successful in 8s
CI / Tests (py3.12) (push) Successful in 8s

This commit was merged in pull request #42.
This commit is contained in:
2026-07-12 13:00:22 -04:00
+21
View File
@@ -13,6 +13,7 @@ from __future__ import annotations
import sys import sys
import time import time
from pathlib import Path from pathlib import Path
from typing import ClassVar
from PySide6.QtCore import QRect, QSettings, QSize, Qt, QThread, QTimer, QUrl, Signal from PySide6.QtCore import QRect, QSettings, QSize, Qt, QThread, QTimer, QUrl, Signal
from PySide6.QtGui import ( from PySide6.QtGui import (
@@ -172,10 +173,30 @@ class UpdateCheckWorker(QThread):
running binary. Fails quiet — emits None on any network problem — so running binary. Fails quiet — emits None on any network problem — so
it's safe to fire unattended from a silent startup check as well as from it's safe to fire unattended from a silent startup check as well as from
the About dialog's "Check for updates" button. the About dialog's "Check for updates" button.
Lifetime: the class keeps every instance alive in `_live` until its
thread has finished. Without this, a caller whose own reference dies
early (the About dialog is a temporary — closing it mid-check used to
GC the dialog and the running QThread with it) crashes the process with
"QThread: Destroyed while thread is still running". Callers may drop
their reference at any time; signal connections to a destroyed receiver
are disconnected by Qt, so a late result is simply discarded.
""" """
done = Signal(object) # dict | None done = Signal(object) # dict | None
_live: ClassVar[set[UpdateCheckWorker]] = set()
def __init__(self):
super().__init__()
UpdateCheckWorker._live.add(self)
self.finished.connect(self._release_keepalive)
def _release_keepalive(self):
# Delivered on the main thread after run() has returned; only now is
# it safe for the last reference to drop.
UpdateCheckWorker._live.discard(self)
def run(self): def run(self):
self.done.emit(core.fetch_latest_release()) self.done.emit(core.fetch_latest_release())