docs: correct how a wait-mode call actually dies on a dead source
Branch review caught the docs claiming something the code does not do. CHANGELOG, README and the design's behaviour matrix all said a force-opened source under circuit_open=wait waits out the full stall window. It does not: the probe let through after each cooldown is a real attempt, so it burns a max_attempts slot like any other, and a 401 source usually runs out of retry budget first -- reason is retry_exhausted, not stalled. Which budget wins depends on max_attempts against the cooldowns and the stall window. The behaviour is right; only the prose was wrong. Charging the probe to the retry budget is exactly the split issue #8 settled: the question is who spends max_attempts, and a probe does send a real request. A test now pins it so the claim cannot drift again. Also drops the planned "woke up" log line. Each wait round already logs on entry with its duration, and a still-blocked wake-up logs the next round immediately, so a second line would only double the volume.
This commit is contained in:
@@ -16,6 +16,7 @@ from polygateway.errors import (
|
||||
CircuitOpenError,
|
||||
GatewayUnavailableError,
|
||||
GovernanceBackendError,
|
||||
SourceDeadError,
|
||||
SourceNotConfiguredError,
|
||||
TransientError,
|
||||
)
|
||||
@@ -742,6 +743,39 @@ class TestCircuitOpenPolicy:
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await task
|
||||
|
||||
async def test_wait_does_not_exempt_probes_from_the_retry_budget(self):
|
||||
"""wait 档不豁免重试预算: 探针是**真实尝试**,失败照样烧 max_attempts。
|
||||
|
||||
故 force_open 的源(401/403/欠费一击即熔,不看任何阈值)在 wait 档下并
|
||||
**不是**"等满 stall 窗口才死"——两个预算哪个先耗尽就以哪个的 reason
|
||||
失败。这里 max_attempts=3 而冷却只累计 120s < stall_window=300s,故
|
||||
先到的是重试预算。参数换成"冷却累计超过 stall 预算"则先到 stalled
|
||||
(见 test_wait_still_dies_when_cooldown_outlasts_the_stall_budget)。
|
||||
|
||||
这与 issue #8 确立的划分一致: 划分依据是"谁消耗重试预算",探针发出了
|
||||
真实请求,理应记在重试预算上而不是 stall 账上。
|
||||
"""
|
||||
clock = FakeClock()
|
||||
src = make_source()
|
||||
sleep = BoundedSleep()
|
||||
|
||||
async def advance(_n):
|
||||
clock.advance(sleep.delays[-1])
|
||||
|
||||
sleep._side_effect = advance
|
||||
mw = _mw(
|
||||
[src],
|
||||
self._free_limiter(clock, src),
|
||||
[SourceDeadError("401"), SourceDeadError("401"), SourceDeadError("401")],
|
||||
clock=clock,
|
||||
sleep=sleep,
|
||||
circuit_open="wait",
|
||||
)
|
||||
with pytest.raises(AllSourcesExhausted) as ei:
|
||||
await mw(_REQ)
|
||||
assert ei.value.reason == "retry_exhausted"
|
||||
assert clock.t - 1000.0 < _STALL # 远未等满 stall 窗口
|
||||
|
||||
async def test_half_open_rejection_does_not_blacklist_a_recovered_source(self):
|
||||
"""issue #14 §1.3 回归: 探针成功后本进程立即可再选该源。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user