Configurable Jellyfin API timeout and retries

Add a configurable API timeout for Jellyfin bulk requests and improve fetch retry behavior. UI: adds an "API Timeout (seconds)" field (15–300s, default 30) in webui/index.html and persists it via webui/static/script.js (load/save). Client: jellyfin_client.py now reads api_timeout from config_manager, uses it as the bulk timeout and computes a sensible non-bulk timeout (max(5, bulk_timeout//6)). Fetch loops for tracks and albums were hardened: reducing batch size now resets consecutive failure counters, log messages were clarified, and the stopping/retry thresholds were adjusted to avoid premature aborts at minimum batch sizes.
This commit is contained in:
Broque Thomas 2026-02-26 08:21:20 -08:00
parent 3893d75ad0
commit 70156bdb98
3 changed files with 34 additions and 19 deletions

View file

@ -444,16 +444,18 @@ class JellyfinClient:
"""Make authenticated request to Jellyfin API"""
if not self.base_url or not self.api_key:
return None
url = f"{self.base_url}{endpoint}"
headers = {
'X-Emby-Token': self.api_key,
'Content-Type': 'application/json'
}
# Use longer timeout for bulk operations (lots of data)
# Use configurable timeout for bulk operations (lots of data)
is_bulk_operation = params and params.get('Limit', 0) > 1000
timeout = 30 if is_bulk_operation else 5
config = config_manager.get_jellyfin_config()
bulk_timeout = int(config.get('api_timeout', 30))
timeout = bulk_timeout if is_bulk_operation else max(5, bulk_timeout // 6)
try:
response = requests.get(url, headers=headers, params=params, timeout=timeout)
@ -505,17 +507,18 @@ class JellyfinClient:
if not response:
consecutive_failures += 1
if consecutive_failures >= 3:
logger.warning("🚨 Multiple track fetch failures - stopping")
break
if limit > 1000:
limit = limit // 2
logger.warning(f"⚠️ Track fetch timeout - reducing batch size to {limit}")
consecutive_failures = 0 # Reset — give the smaller batch a fair chance
logger.warning(f"⚠️ Track fetch failed - reducing batch size to {limit}")
continue
else:
elif consecutive_failures >= 2:
logger.warning("🚨 Multiple track fetch failures at minimum batch size - stopping")
break
else:
logger.warning("⚠️ Track fetch failed at minimum batch size - retrying once")
continue
consecutive_failures = 0
batch_tracks = response.get('Items', [])
if not batch_tracks:
@ -568,17 +571,18 @@ class JellyfinClient:
if not response:
consecutive_failures += 1
if consecutive_failures >= 3:
logger.warning("🚨 Multiple album fetch failures - stopping")
break
if limit > 1000:
limit = limit // 2
logger.warning(f"⚠️ Album fetch timeout - reducing batch size to {limit}")
consecutive_failures = 0 # Reset — give the smaller batch a fair chance
logger.warning(f"⚠️ Album fetch failed - reducing batch size to {limit}")
continue
else:
elif consecutive_failures >= 2:
logger.warning("🚨 Multiple album fetch failures at minimum batch size - stopping")
break
else:
logger.warning("⚠️ Album fetch failed at minimum batch size - retrying once")
continue
consecutive_failures = 0
batch_albums = response.get('Items', [])
if not batch_albums:

View file

@ -2945,6 +2945,15 @@
<button class="test-button" onclick="testConnection('jellyfin')">Test</button>
</div>
<div class="form-group">
<label>API Timeout (seconds):</label>
<input type="number" id="jellyfin-timeout" placeholder="30" min="15"
max="300" value="30">
<small style="color: #888; display: block; margin-top: 5px;">Timeout for
bulk API requests during database sync (15-300s). Increase if your
Jellyfin server is slow to respond.</small>
</div>
<div class="form-group" id="jellyfin-user-selector-container"
style="display: none;">
<label>Jellyfin User</label>

View file

@ -1787,6 +1787,7 @@ async function loadSettingsData() {
// Populate Jellyfin settings
document.getElementById('jellyfin-url').value = settings.jellyfin?.base_url || '';
document.getElementById('jellyfin-api-key').value = settings.jellyfin?.api_key || '';
document.getElementById('jellyfin-timeout').value = settings.jellyfin?.api_timeout || 30;
// Populate Navidrome settings
document.getElementById('navidrome-url').value = settings.navidrome?.base_url || '';
@ -2456,7 +2457,8 @@ async function saveSettings(quiet = false) {
},
jellyfin: {
base_url: document.getElementById('jellyfin-url').value,
api_key: document.getElementById('jellyfin-api-key').value
api_key: document.getElementById('jellyfin-api-key').value,
api_timeout: parseInt(document.getElementById('jellyfin-timeout').value) || 30
},
navidrome: {
base_url: document.getElementById('navidrome-url').value,