feat: Implement timeout handling and background processing for update feature

This commit is contained in:
claudi 2026-01-30 12:09:03 +01:00
parent c97301728c
commit f4eb511a1c
7 changed files with 849 additions and 94 deletions

View file

@ -159,10 +159,13 @@ class UpdateManager:
try:
logger.info(f"Checking for updates from {self.api_endpoint}")
# Run in thread pool to avoid blocking
# Run in thread pool with aggressive timeout
loop = asyncio.get_event_loop()
response = await loop.run_in_executor(
None, self._fetch_release
response = await asyncio.wait_for(
loop.run_in_executor(
None, self._fetch_release
),
timeout=8 # Timeout after network call also has timeout
)
if not response:
@ -180,8 +183,8 @@ class UpdateManager:
self._save_cache(response)
return release
except URLError as e:
logger.error(f"Network error checking updates: {e}")
except asyncio.TimeoutError:
logger.warning("Update check timed out - API server not responding")
return None
except Exception as e:
logger.error(f"Error checking for updates: {e}")
@ -194,7 +197,9 @@ class UpdateManager:
Release data dict or None on error
"""
try:
with urlopen(self.api_endpoint, timeout=10) as response:
logger.debug(f"Fetching release from {self.api_endpoint}")
# Use aggressive timeout: 5 seconds for connection, 5 seconds for read
with urlopen(self.api_endpoint, timeout=5) as response:
data = json.loads(response.read())
return {
"tag_name": data["tag_name"],
@ -204,8 +209,8 @@ class UpdateManager:
"assets": data.get("assets", []),
"published_at": data.get("published_at", ""),
}
except URLError as e:
logger.error(f"Failed to fetch release: {e}")
except Exception as e:
logger.error(f"Failed to fetch release: {type(e).__name__}: {e}")
return None
async def download_update(
@ -242,13 +247,16 @@ class UpdateManager:
try:
logger.info(f"Downloading {installer_asset['name']}")
# Run in thread pool to avoid blocking
# Run in thread pool with 5-minute timeout for large files
loop = asyncio.get_event_loop()
success = await loop.run_in_executor(
None,
self._download_file,
installer_asset["browser_download_url"],
output_file,
success = await asyncio.wait_for(
loop.run_in_executor(
None,
self._download_file,
installer_asset["browser_download_url"],
output_file,
),
timeout=300
)
if success:
@ -256,6 +264,11 @@ class UpdateManager:
return output_file
return None
except asyncio.TimeoutError:
logger.error(f"Download timed out: {installer_asset['name']}")
if output_file.exists():
output_file.unlink()
return None
except Exception as e:
logger.error(f"Error downloading update: {e}")
if output_file.exists():
@ -309,12 +322,15 @@ class UpdateManager:
try:
logger.info("Verifying checksum...")
# Download checksum file
# Download checksum file with 30 second timeout
loop = asyncio.get_event_loop()
checksum_content = await loop.run_in_executor(
None,
self._download_checksum,
checksum_asset["browser_download_url"],
checksum_content = await asyncio.wait_for(
loop.run_in_executor(
None,
self._download_checksum,
checksum_asset["browser_download_url"],
),
timeout=30
)
if not checksum_content:
@ -339,6 +355,9 @@ class UpdateManager:
)
return False
except asyncio.TimeoutError:
logger.error("Checksum verification timed out")
return False
except Exception as e:
logger.error(f"Error verifying checksum: {e}")
return False