clean up broken downloads

This commit is contained in:
Broque Thomas 2025-08-12 18:21:13 -07:00
parent 502b0ba01d
commit 4f4f7f8d21
3 changed files with 98 additions and 4 deletions

View file

@ -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)