fix: address independent verification findings
Classify empty completions as transient per human ruling (fixes flaky real-gateway smoke and prevents caching empty responses), rename the factory injection parameter gate to breaker per the frozen design, rewrite the probe-entry cleanup without except BaseException, declare python-dotenv explicitly, add a mid-backoff cancellation test, and record all implementation errata in the design and architecture docs.
This commit is contained in:
@@ -298,6 +298,39 @@ class TestCancellation:
|
||||
await task
|
||||
assert (await limiter.source_stats("a")).inflight == 0 # finally 释放
|
||||
|
||||
async def test_cancel_mid_backoff_propagates_with_no_held_permit(self):
|
||||
"""退避 sleep 中取消: CancelledError 穿透,且 permit 早已在 finally 释放。"""
|
||||
clock = FakeClock()
|
||||
src = _src("a", max_concurrency=1)
|
||||
limiter = InMemoryLimiter(
|
||||
scope="llm",
|
||||
sources={"a": src},
|
||||
global_limits=_NO_GLOBAL,
|
||||
lease_ttl_s=100.0,
|
||||
now=clock,
|
||||
)
|
||||
mw = RetryMW(
|
||||
scope="llm",
|
||||
sources=[src],
|
||||
selector=RoundRobinSelector(),
|
||||
limiter=limiter,
|
||||
gate=InMemoryGate(config=_BREAKER, now=clock),
|
||||
transport=FakeTransport([TransientError("x"), _ok()]),
|
||||
retry=RetryPolicy(max_attempts=3, backoff_base_s=30.0, backoff_max_s=60.0),
|
||||
backpressure=BackpressurePolicy(300.0, 0.01),
|
||||
cooldown_memo=SourceCooldownMemo(now=clock),
|
||||
emitter=None,
|
||||
now=clock,
|
||||
sleep=asyncio.sleep,
|
||||
rng=lambda: 0.5,
|
||||
)
|
||||
task = asyncio.ensure_future(mw(_REQ))
|
||||
await asyncio.sleep(0.05) # 第一次失败后进入 30s 真实退避
|
||||
task.cancel()
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
assert (await limiter.source_stats("a")).inflight == 0 # 退避期不占并发槽
|
||||
|
||||
async def test_cancel_probe_releases_probe_lease(self):
|
||||
clock = FakeClock()
|
||||
mw, _, gate, _, _, _ = _harness(
|
||||
|
||||
Reference in New Issue
Block a user