diff --git a/.vscode/settings.json b/.vscode/settings.json index 29dfa3b4..39644ba2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -98,6 +98,7 @@ "tmpfilename", "ungroup", "unnegated", + "unpickleable", "Unraid", "upgrader", "urandom", diff --git a/app/library/Download.py b/app/library/Download.py index 990821ac..39de81d2 100644 --- a/app/library/Download.py +++ b/app/library/Download.py @@ -167,6 +167,9 @@ class Download: self.status_queue.put({"id": self.id, "filename": filename}) def _download(self): + if not self._notify: + self._notify = EventBus.get_instance() + cookie_file = None try: @@ -360,6 +363,13 @@ class Download: self.cancel_in_progress = True procId: int | None = self.proc.ident + if not procId: + if self.proc: + self.proc.close() + self.proc = None + self.logger.warning("Attempted to close download process, but it is not running.") + return False + self.logger.info(f"Closing PID='{procId}' download process.") try: @@ -570,3 +580,14 @@ class Download: return True return False + + def __getstate__(self): + state = self.__dict__.copy() + + # Exclude (unpickleable) keys during pickling, this issue arise mostly on Windows. + excluded_keys = ("_notify",) + for key in excluded_keys: + if key in state: + state[key] = None + + return state diff --git a/app/library/DownloadQueue.py b/app/library/DownloadQueue.py index 4ac2cd09..6521d2ca 100644 --- a/app/library/DownloadQueue.py +++ b/app/library/DownloadQueue.py @@ -1115,9 +1115,12 @@ class DownloadQueue(metaclass=Singleton): LOG.exception(e) LOG.error(f"Failed to retry item '{item_ref}'. {e!s}") - def _handle_task_exception(self, task: asyncio.Task): + def _handle_task_exception(self, task: asyncio.Task) -> None: if task.cancelled(): return if exc := task.exception(): - LOG.error(f"Unhandled exception in background task: {exc!s}") + import traceback + + task_name: str = task.get_name() if task.get_name() else "unknown_task" + LOG.error(f"Unhandled exception in background task '{task_name}': {exc!s}. {traceback.format_exc()}") diff --git a/app/library/Utils.py b/app/library/Utils.py index e1e949ba..3ec83650 100644 --- a/app/library/Utils.py +++ b/app/library/Utils.py @@ -1377,3 +1377,34 @@ def ytdlp_reject(entry: dict, yt_params: dict) -> tuple[bool, str]: return (False, f'Video "{entry.get("title", "unknown")}" is age restricted.') return (True, "") + + +def find_unpickleable(obj, name="root", seen=None): + import pickle + + if seen is None: + seen = set() + + if id(obj) in seen: + return + + seen.add(id(obj)) + + try: + pickle.dumps(obj) + except Exception as e: + LOG.error(f"[UNPICKLEABLE] {name}: {e}") + + if isinstance(obj, dict): + for k, v in obj.items(): + find_unpickleable(v, f"{name}[{repr(k)!s}]", seen) + elif hasattr(obj, "__dict__"): + for attr in vars(obj): + try: + value = getattr(obj, attr) + find_unpickleable(value, f"{name}.{attr}", seen) + except Exception as ie: + LOG.error(f"[ERROR] Accessing {name}.{attr}: {ie}") + elif isinstance(obj, (list, tuple, set)): + for idx, item in enumerate(obj): + find_unpickleable(item, f"{name}[{idx}]", seen)