Skip to content

fix(proxy): wire circuit breakers into the live request path#1

Merged
Electricitysheep merged 1 commit into
mainfrom
fix/wire-circuit-breaker
Jul 6, 2026
Merged

fix(proxy): wire circuit breakers into the live request path#1
Electricitysheep merged 1 commit into
mainfrom
fix/wire-circuit-breaker

Conversation

@Electricitysheep

Copy link
Copy Markdown
Owner

问题

三态 CircuitBreaker 类实现完整但在服务路径上是死代码:

  • createProxyServer 传给 HealthMonitor 的断路器 map 是空 new Map(),全项目从未实例化过断路器
  • 唯一消费断路器的 isProviderUsable() 没有任何调用者
  • 请求成败从不回记到任何断路器
  • 附带发现:HealthProbe 调用的 provider.chat() / provider.id 在真实 provider 上不存在,所有探测永远报 down
  • 附带发现:半开闭合阈值 off-by-one(需要 successThreshold+1 次成功)

修复

  • 每个 provider 一个 CircuitBreaker 实例,请求路径与健康监控共享;通过 app.locals 暴露(生命周期管理 + 测试),createProxyServer 增加可选 deps 参数支持注入
  • /v1/chat/completions 主 provider 先过 allowRequest() 闸门,每次成败调用 recordSuccess/recordFailure(流式路径同样接入)
  • fallback 链跳过刚失败的主 provider(原会原地重试失败者)并跳过断路器 open 的候选
  • HealthProbe 改为调用 provider 真实存在的 healthCheck()(自带各家 health-check 模型)+ 超时竞速
  • 断路器闭合条件 >>=,与文档语义一致

测试

  • 新增 tests/proxy-breaker.test.ts 集成测试:注入必失败的假 provider,断言 3 次失败后断路器 open、第 4 次请求返回 503 且不再调用 provider、冷却后半开放行并恢复
  • 全套件 38/38 通过,tsc --noEmit 干净

🤖 Generated with Claude Code

The three-state CircuitBreaker class existed but was dead code: the proxy
passed an empty Map to HealthMonitor, isProviderUsable() had no callers,
and request outcomes never fed any breaker.

- createProxyServer now builds one CircuitBreaker per provider, shared by
  the request path and the health monitor (exposed via app.locals for
  lifecycle/tests, plus a deps param for provider injection in tests)
- /v1/chat/completions gates the primary on allowRequest() and records
  success/failure on every outcome (non-stream and stream)
- fallback chain skips the just-failed primary (previously retried it
  immediately) and skips providers whose breaker is open
- HealthProbe now calls the real provider interface (getProviderId()/
  healthCheck()); it previously called provider.chat()/provider.id which
  do not exist, so every probe reported 'down'
- fix off-by-one: breaker closes after successThreshold successes in
  half-open, not successThreshold+1

Integration tests: breaker opens after N failures and blocks traffic,
then recovers through half-open after cooldown.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Electricitysheep
Electricitysheep merged commit 6d0a3ba into main Jul 6, 2026
1 check passed
@Electricitysheep
Electricitysheep deleted the fix/wire-circuit-breaker branch July 6, 2026 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant