fix - count circuit breaker failures per logical call NOT per retry + actionable errors and placeholder in lidarr connection test
This commit is contained in:
@@ -244,10 +244,6 @@ def with_retry(
|
|||||||
last_exception = e
|
last_exception = e
|
||||||
elapsed_ms = int((time.time() - attempt_start) * 1000)
|
elapsed_ms = int((time.time() - attempt_start) * 1000)
|
||||||
|
|
||||||
is_non_breaking = isinstance(e, non_breaking_exceptions) if non_breaking_exceptions else False
|
|
||||||
if circuit_breaker and (not is_non_breaking or circuit_breaker.state == CircuitState.HALF_OPEN):
|
|
||||||
await circuit_breaker.arecord_failure()
|
|
||||||
|
|
||||||
if attempt >= max_attempts:
|
if attempt >= max_attempts:
|
||||||
total_elapsed_ms = int((time.time() - start_time) * 1000)
|
total_elapsed_ms = int((time.time() - start_time) * 1000)
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -295,6 +291,11 @@ def with_retry(
|
|||||||
|
|
||||||
await asyncio.sleep(delay)
|
await asyncio.sleep(delay)
|
||||||
|
|
||||||
|
if circuit_breaker and last_exception:
|
||||||
|
is_non_breaking = isinstance(last_exception, non_breaking_exceptions) if non_breaking_exceptions else False
|
||||||
|
if not is_non_breaking or circuit_breaker.state == CircuitState.HALF_OPEN:
|
||||||
|
await circuit_breaker.arecord_failure()
|
||||||
|
|
||||||
raise last_exception
|
raise last_exception
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|||||||
@@ -132,10 +132,19 @@ class SettingsService:
|
|||||||
root_folders=root_folders
|
root_folders=root_folders
|
||||||
)
|
)
|
||||||
except ExternalServiceError as e:
|
except ExternalServiceError as e:
|
||||||
logger.warning(f"Lidarr connection test failed: {e}")
|
detail = str(e)
|
||||||
|
logger.warning(f"Lidarr connection test failed: {detail}")
|
||||||
|
if "No address associated with hostname" in detail or "Name or service not known" in detail:
|
||||||
|
hint = "DNS resolution failed — check the hostname is reachable from inside the container"
|
||||||
|
elif "Connection refused" in detail:
|
||||||
|
hint = "Connection refused — check the port and that Lidarr is running"
|
||||||
|
elif "timed out" in detail.lower() or "timeout" in detail.lower():
|
||||||
|
hint = "Connection timed out — check network/firewall settings"
|
||||||
|
else:
|
||||||
|
hint = detail
|
||||||
return LidarrVerifyResponse(
|
return LidarrVerifyResponse(
|
||||||
success=False,
|
success=False,
|
||||||
message="Couldn't reach Lidarr",
|
message=f"Couldn't reach Lidarr: {hint}",
|
||||||
quality_profiles=[],
|
quality_profiles=[],
|
||||||
metadata_profiles=[],
|
metadata_profiles=[],
|
||||||
root_folders=[]
|
root_folders=[]
|
||||||
@@ -144,7 +153,7 @@ class SettingsService:
|
|||||||
logger.exception(f"Failed to verify Lidarr connection: {e}")
|
logger.exception(f"Failed to verify Lidarr connection: {e}")
|
||||||
return LidarrVerifyResponse(
|
return LidarrVerifyResponse(
|
||||||
success=False,
|
success=False,
|
||||||
message="Couldn't finish the connection test",
|
message=f"Couldn't finish the connection test: {e}",
|
||||||
quality_profiles=[],
|
quality_profiles=[],
|
||||||
metadata_profiles=[],
|
metadata_profiles=[],
|
||||||
root_folders=[]
|
root_folders=[]
|
||||||
|
|||||||
@@ -68,10 +68,15 @@ async def test_breaking_exception_still_trips_circuit():
|
|||||||
call_count += 1
|
call_count += 1
|
||||||
raise _ServiceDown("down")
|
raise _ServiceDown("down")
|
||||||
|
|
||||||
|
# Each call records one failure after all retries exhausted (not per retry)
|
||||||
with pytest.raises(_ServiceDown):
|
with pytest.raises(_ServiceDown):
|
||||||
await fail()
|
await fail()
|
||||||
|
assert cb.failure_count == 1
|
||||||
|
assert cb.state == CircuitState.CLOSED
|
||||||
|
|
||||||
assert call_count == 3
|
call_count = 0
|
||||||
|
with pytest.raises(_ServiceDown):
|
||||||
|
await fail()
|
||||||
assert cb.state == CircuitState.OPEN
|
assert cb.state == CircuitState.OPEN
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@
|
|||||||
type="url"
|
type="url"
|
||||||
bind:value={form.data.lidarr_url}
|
bind:value={form.data.lidarr_url}
|
||||||
class="input input-bordered w-full"
|
class="input input-bordered w-full"
|
||||||
placeholder="http://localhost:8686"
|
placeholder="http://lidarr:8686 or http://<host-ip>:8686"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user