From 4f4f7f8d2185380f779ad1e5171badba5c0154aa Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Tue, 12 Aug 2025 18:21:13 -0700 Subject: [PATCH] clean up broken downloads --- .../__pycache__/downloads.cpython-310.pyc | Bin 290161 -> 294453 bytes .../__pycache__/downloads.cpython-312.pyc | Bin 534515 -> 538682 bytes ui/pages/downloads.py | 102 +++++++++++++++++- 3 files changed, 98 insertions(+), 4 deletions(-) diff --git a/ui/pages/__pycache__/downloads.cpython-310.pyc b/ui/pages/__pycache__/downloads.cpython-310.pyc index 0498c91359391e93fb1223176c05367b765af973..eaee025023488c697915e90fa8c9ad8216f36234 100644 GIT binary patch delta 11634 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 aUTWRkB%WpX4n|(JS=_^_t@E3a4){M8c-<@j delta 7339 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 Heog%kQj&=n diff --git a/ui/pages/__pycache__/downloads.cpython-312.pyc b/ui/pages/__pycache__/downloads.cpython-312.pyc index a1efd24c259225671a8ecf4b6dfc8cdc5f81bfbb..478165054f9f863568158d481f74f15225687e8e 100644 GIT binary patch delta 7917 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 diff --git a/ui/pages/downloads.py b/ui/pages/downloads.py index a745a00d..b80981ea 100644 --- a/ui/pages/downloads.py +++ b/ui/pages/downloads.py @@ -9612,6 +9612,7 @@ class DownloadsPage(QWidget): def handle_status_update_v2(transfers_data): """Enhanced status update handler with robust state management""" + import time try: if not transfers_data: return @@ -9647,6 +9648,13 @@ class DownloadsPage(QWidget): # Update UI counters self.download_queue.update_tab_counts() + + # Enhanced logging: periodic queue health summary + if not hasattr(self, '_last_queue_summary_time'): + self._last_queue_summary_time = 0 + if time.time() - self._last_queue_summary_time > 30: # Every 30 seconds + self._log_queue_health_summary(download_items) + self._last_queue_summary_time = time.time() except Exception as e: print(f"[ERROR] Status update v2 failed: {e}") @@ -9669,6 +9677,45 @@ class DownloadsPage(QWidget): except Exception as e: print(f"[ERROR] Failed to start status update thread v2: {e}") + def _log_queue_health_summary(self, download_items): + """Log periodic queue health summary for debugging stuck downloads""" + import time + + try: + if not download_items: + return + + # Count downloads by status + status_counts = {} + stuck_downloads = [] + + for item in download_items: + status = item.status.lower() + status_counts[status] = status_counts.get(status, 0) + 1 + + # Check for potentially stuck downloads + if hasattr(item, 'queue_start_time') and item.queue_start_time: + queue_age = time.time() - item.queue_start_time + if queue_age > 15: # More than 15 seconds in queue + stuck_downloads.append((item.title, queue_age)) + + # Check for items with high API missing counts + if hasattr(item, 'api_missing_count_v2') and item.api_missing_count_v2 > 1: + stuck_downloads.append((item.title, f"API missing {item.api_missing_count_v2} cycles")) + + # Log summary + print(f"📊 Queue Health: {status_counts}") + if stuck_downloads: + print(f"⚠️ Potentially stuck downloads: {len(stuck_downloads)}") + for title, issue in stuck_downloads[:3]: # Show first 3 + if isinstance(issue, str): + print(f" - {title}: {issue}") + else: + print(f" - {title}: queued for {issue:.1f}s") + + except Exception as e: + pass # Silent logging failures + def _find_matching_transfer_v2(self, download_item, all_transfers, matched_ids): """Enhanced transfer matching with better ID tracking""" # Primary: ID-based matching @@ -9694,10 +9741,16 @@ class DownloadsPage(QWidget): return None def _process_transfer_match_v2(self, download_item, transfer): - """Process matched transfer with atomic state updates""" + """Process matched transfer with atomic state updates and progressive timeout logic""" + import time + state = transfer.get('state', 'Unknown') progress = min(100, max(0, transfer.get('percentComplete', 0))) + # Initialize queue tracking if needed + if not hasattr(download_item, 'queue_start_time'): + download_item.queue_start_time = None + # Determine new status if 'Completed' in state or 'Succeeded' in state: new_status = 'completed' @@ -9711,6 +9764,16 @@ class DownloadsPage(QWidget): else: new_status = 'queued' + # Track queue state transitions for progressive timeout + if new_status in ['queued', 'initializing'] and download_item.queue_start_time is None: + download_item.queue_start_time = time.time() + print(f"🕐 Download entered queue: {download_item.title}") + elif new_status in ['downloading', 'completed', 'cancelled', 'failed']: + if download_item.queue_start_time: + queue_duration = time.time() - download_item.queue_start_time + print(f"⏱️ Download '{download_item.title}' was in queue for {queue_duration:.1f}s") + download_item.queue_start_time = None # Reset queue timer + # Atomic status update self._queue_manager.atomic_state_transition( download_item, new_status, @@ -9725,6 +9788,11 @@ class DownloadsPage(QWidget): file_path=transfer.get('filename', download_item.file_path) ) + # Reset API missing count when transfer is found and add enhanced logging + if hasattr(download_item, 'api_missing_count_v2') and download_item.api_missing_count_v2 > 0: + print(f"✅ Download reconnected to API after {download_item.api_missing_count_v2} missing cycles: {download_item.title}") + download_item.api_missing_count_v2 = 0 + # Update ID mapping if needed transfer_id = transfer.get('id') if transfer_id and (not hasattr(download_item, 'download_id') or download_item.download_id != transfer_id): @@ -9751,14 +9819,40 @@ class DownloadsPage(QWidget): self._schedule_fallback_cleanup_check() def _handle_missing_transfer_v2(self, download_item): - """Handle downloads missing from API with improved grace period""" + """Handle downloads missing from API with improved grace period and queue age tracking""" + import time + + # Initialize tracking attributes if not hasattr(download_item, 'api_missing_count_v2'): download_item.api_missing_count_v2 = 0 + if not hasattr(download_item, 'queue_start_time'): + download_item.queue_start_time = None + + # Track when downloads first enter queued state + current_status = download_item.status.lower() + if current_status in ['queued', 'initializing'] and download_item.queue_start_time is None: + download_item.queue_start_time = time.time() + print(f"🕐 Started tracking queue time for: {download_item.title}") + elif current_status not in ['queued', 'initializing']: + download_item.queue_start_time = None # Reset if no longer queued download_item.api_missing_count_v2 += 1 - # Longer grace period for stability - if download_item.api_missing_count_v2 >= 5: # 5 polling cycles + # Enhanced timeout logic with queue age consideration + queue_timeout_exceeded = False + if download_item.queue_start_time: + queue_age = time.time() - download_item.queue_start_time + queue_timeout = 180.0 # 3 minutes max in queue + if queue_age > queue_timeout: + queue_timeout_exceeded = True + print(f"⏰ Queue timeout exceeded: {download_item.title} stuck in queue for {queue_age:.1f}s") + + # Fail download if API missing for 3 cycles OR queue timeout exceeded + if download_item.api_missing_count_v2 >= 3 or queue_timeout_exceeded: + if queue_timeout_exceeded: + print(f"⚠️ Download failed due to queue timeout: {download_item.title}") + else: + print(f"⚠️ Download missing from API for {download_item.api_missing_count_v2} cycles: {download_item.title}") self._queue_manager.atomic_state_transition(download_item, 'failed') self.download_queue.move_to_finished(download_item)