fix: reparent governance backend failures under GatewayUnavailableError (issue #7)
A fail-closed limiter or breaker backend means the scope cannot emit a single request, which is exactly scope-level unavailability. But the error sat directly under PolyGatewayError, so a caller writing only `except GatewayUnavailableError` dropped it into the catch-all branch: Redis blips once and a backlog of tasks burns its business failure budget into the dead letter queue, over a fault a restart would clear. Three gate paths leak to callers rather than being absorbed by _record_quietly (try_acquire, try_enter, progress_age_s); each is now pinned by a test, since none of them had one before. The two unknown-source sites move to SourceNotConfiguredError instead of following along. They report a misconfigured source name, not an outage, and letting them into the retryable family would be the mirror of the bug being fixed here: the task would retry forever and never surface.
This commit is contained in:
@@ -367,7 +367,7 @@ class RedisGate:
|
||||
keys=[self._key(source_name)], args=[owner, self._probe_ttl_ms]
|
||||
)
|
||||
except RedisError as exc:
|
||||
raise GovernanceBackendError(f"熔断后端 try_enter 失败: {exc}") from exc
|
||||
raise GovernanceBackendError(f"熔断后端 try_enter 失败: {exc}", scope=self._scope) from exc
|
||||
return self._decision(source_name, result)
|
||||
|
||||
async def record_success(
|
||||
@@ -385,7 +385,7 @@ class RedisGate:
|
||||
try:
|
||||
result = await self._success_lua(keys=[self._key(entry.source_name)], args=args)
|
||||
except RedisError as exc:
|
||||
raise GovernanceBackendError(f"熔断后端 record_success 失败: {exc}") from exc
|
||||
raise GovernanceBackendError(f"熔断后端 record_success 失败: {exc}", scope=self._scope) from exc
|
||||
return self._update(result)
|
||||
|
||||
async def record_failure(
|
||||
@@ -407,7 +407,7 @@ class RedisGate:
|
||||
try:
|
||||
result = await self._failure_lua(keys=[self._key(entry.source_name)], args=args)
|
||||
except RedisError as exc:
|
||||
raise GovernanceBackendError(f"熔断后端 record_failure 失败: {exc}") from exc
|
||||
raise GovernanceBackendError(f"熔断后端 record_failure 失败: {exc}", scope=self._scope) from exc
|
||||
return self._update(result)
|
||||
|
||||
async def release_probe(self, entry: GateDecision) -> GateUpdate:
|
||||
@@ -419,7 +419,7 @@ class RedisGate:
|
||||
keys=[self._key(entry.source_name)], args=[entry.epoch, entry.probe_owner]
|
||||
)
|
||||
except RedisError as exc:
|
||||
raise GovernanceBackendError(f"熔断后端 release_probe 失败: {exc}") from exc
|
||||
raise GovernanceBackendError(f"熔断后端 release_probe 失败: {exc}", scope=self._scope) from exc
|
||||
return self._update(result)
|
||||
|
||||
async def retry_after_s(self, sources: tuple[str, ...]) -> float:
|
||||
@@ -429,7 +429,7 @@ class RedisGate:
|
||||
try:
|
||||
result = await self._retry_after_lua(keys=[self._key(s) for s in sources])
|
||||
except RedisError as exc:
|
||||
raise GovernanceBackendError(f"熔断后端 retry_after_s 失败: {exc}") from exc
|
||||
raise GovernanceBackendError(f"熔断后端 retry_after_s 失败: {exc}", scope=self._scope) from exc
|
||||
return int(result) / 1000.0
|
||||
|
||||
async def aclose(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user