From ef21decb7266974524762835a01af88dc157fa91 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Tue, 26 Aug 2025 12:21:23 -0700 Subject: [PATCH] Update web_server.py --- web_server.py | 73 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 11 deletions(-) diff --git a/web_server.py b/web_server.py index 4b68da1b..e6dce329 100644 --- a/web_server.py +++ b/web_server.py @@ -156,6 +156,11 @@ class WebUIDownloadMonitor: self._check_all_downloads() time.sleep(5) # Match GUI's polling interval except Exception as e: + # If we get shutdown errors, stop monitoring gracefully + if "interpreter shutdown" in str(e) or "cannot schedule new futures" in str(e): + print(f"🛑 Monitor detected shutdown, stopping gracefully") + self.monitoring = False + break print(f"❌ Download monitor error: {e}") print(f"🔍 Download monitor loop ended") @@ -192,6 +197,10 @@ class WebUIDownloadMonitor: def _get_live_transfers(self): """Get current transfer status from slskd API""" try: + # Check if we should stop due to shutdown + if not self.monitoring: + return {} + transfers_data = asyncio.run(soulseek_client._make_request('GET', 'transfers/downloads')) if not transfers_data: return {} @@ -207,7 +216,15 @@ class WebUIDownloadMonitor: live_transfers[key] = file_info return live_transfers except Exception as e: - print(f"⚠️ Monitor: Could not fetch live transfers: {e}") + # If we get shutdown-related errors, stop monitoring immediately + if ("interpreter shutdown" in str(e) or + "cannot schedule new futures" in str(e) or + "Event loop is closed" in str(e)): + print(f"🛑 Monitor detected shutdown, stopping immediately") + self.monitoring = False + return {} + else: + print(f"⚠️ Monitor: Could not fetch live transfers: {e}") return {} def _should_retry_task(self, task, live_transfers_lookup, current_time): @@ -236,7 +253,7 @@ class WebUIDownloadMonitor: task['queued_start_time'] = current_time return False elif current_time - task['queued_start_time'] > 90: - print(f"⚠️ Task {task.get('task_id')} stuck in queue for 90+ seconds") + print(f"⚠️ Task stuck in queue for 90+ seconds") return True # Check for downloading at 0% timeout (90 seconds like GUI) @@ -245,7 +262,7 @@ class WebUIDownloadMonitor: task['downloading_start_time'] = current_time return False elif current_time - task['downloading_start_time'] > 90: - print(f"⚠️ Task {task.get('task_id')} stuck at 0% for 90+ seconds") + print(f"⚠️ Task stuck at 0% for 90+ seconds") return True else: # Progress being made, reset timers @@ -286,17 +303,25 @@ class WebUIDownloadMonitor: try: download_id = task.get('download_id') username = task.get('username') or task['track_info'].get('username') + filename = task.get('filename') or task['track_info'].get('filename') - if download_id and username: - print(f"🚫 Cancelling stuck download: {download_id} from {username}") - success = asyncio.run(soulseek_client.cancel_download(download_id, username, remove=False)) - if success: - print(f"✅ Successfully cancelled download {download_id}") - else: - print(f"⚠️ Failed to cancel download {download_id}") + # Only attempt cancellation if we have what looks like a proper download ID + # (not a filename fallback which would be much longer) + if download_id and username and len(download_id) < 100: + print(f"🚫 Attempting to cancel stuck download: {os.path.basename(download_id)} from {username}") + try: + success = asyncio.run(soulseek_client.cancel_download(download_id, username, remove=False)) + if success: + print(f"✅ Successfully cancelled download") + else: + print(f"⚠️ Cancel request failed, proceeding with retry anyway") + except Exception as e: + print(f"⚠️ Cancel error: {str(e)[:100]}, proceeding with retry anyway") + else: + print(f"⚠️ No valid download ID for cancellation, proceeding with retry") except Exception as e: - print(f"❌ Error cancelling download before retry: {e}") + print(f"⚠️ Error in cancellation logic, proceeding with retry: {e}") def _retry_task_with_fallback(self, batch_id, task_id, task): """Retry task with next candidate (matches GUI retry logic)""" @@ -332,6 +357,32 @@ class WebUIDownloadMonitor: # Global download monitor instance download_monitor = WebUIDownloadMonitor() +# Cleanup handler for Flask shutdown/reload +import atexit +import signal +import sys + +def cleanup_monitor(): + """Clean up background monitor on shutdown""" + if download_monitor.monitoring: + print("🛑 Flask shutdown detected, stopping download monitor...") + download_monitor.monitoring = False + download_monitor.monitored_batches.clear() + # Give the thread a moment to exit cleanly + import time + time.sleep(0.5) + +def signal_handler(signum, frame): + """Handle SIGINT (Ctrl+C) and SIGTERM""" + print(f"🛑 Signal {signum} received, cleaning up...") + cleanup_monitor() + sys.exit(0) + +# Register cleanup handlers +atexit.register(cleanup_monitor) +signal.signal(signal.SIGINT, signal_handler) +signal.signal(signal.SIGTERM, signal_handler) + def _update_task_status(task_id, new_status): """Helper to update task status and timestamp for timeout tracking""" with tasks_lock: