feat: gate in-call demotion on credible alternative health
Round 3 showed unconditional yield-after-two-failures pushes the third attempt onto known-bad sources in heterogeneous pools (83% vs 10% expected success). OutcomeAwareSelector now exposes health(); a failed source only yields when some untried candidate scores at least half its health. Health-blind selectors keep the unconditional rule.
This commit is contained in:
@@ -370,11 +370,17 @@ class RecordingSelector(StaticSelector):
|
||||
def record_outcome(self, source_name, ok):
|
||||
self.outcomes.append((source_name, ok))
|
||||
|
||||
def health(self, source_name):
|
||||
return 1.0
|
||||
|
||||
|
||||
class ExplodingSelector(StaticSelector):
|
||||
def record_outcome(self, source_name, ok):
|
||||
raise RuntimeError("sink boom")
|
||||
|
||||
def health(self, source_name):
|
||||
return 1.0
|
||||
|
||||
|
||||
class TestM25Orchestration:
|
||||
"""M2.5 设计 §3.3: 调用内失败降权 + 健康喂数(先红后绿)。"""
|
||||
@@ -491,3 +497,43 @@ class TestAdaptivePacing:
|
||||
await mw(_REQ)
|
||||
assert pacer._inflight.get("a", 0) == 0
|
||||
assert pacer._inflight.get("b", 0) == 0
|
||||
|
||||
|
||||
class HealthySink(StaticSelector):
|
||||
"""带健康视图的选源器桩(OutcomeAwareSelector 全量实现)。"""
|
||||
|
||||
def __init__(self, health):
|
||||
self._health = health
|
||||
self.outcomes = []
|
||||
|
||||
def record_outcome(self, source_name, ok):
|
||||
self.outcomes.append((source_name, ok))
|
||||
|
||||
def health(self, source_name):
|
||||
return self._health.get(source_name, 1.0)
|
||||
|
||||
|
||||
class TestHealthGatedDemotion:
|
||||
"""迭代 2(设计 §3.36): 降权需可信替代,否则原地重试。"""
|
||||
|
||||
async def test_no_credible_alternative_stays_on_healthy(self):
|
||||
# 替补健康分 0.08 < 0.5×0.9 → 不让位,第三次仍打 a
|
||||
sel = HealthySink({"a": 0.9, "b": 0.08})
|
||||
mw, _, _, transport, _, _ = _harness(
|
||||
[_src("a"), _src("b")],
|
||||
[TransientError("1"), TransientError("2"), _ok()],
|
||||
selector=sel,
|
||||
)
|
||||
resp = await mw(_REQ)
|
||||
assert [n for n, _ in transport.calls] == ["a", "a", "a"]
|
||||
assert resp.source_name == "a"
|
||||
|
||||
async def test_credible_alternative_still_yields(self):
|
||||
sel = HealthySink({"a": 0.9, "b": 0.9})
|
||||
mw, _, _, transport, _, _ = _harness(
|
||||
[_src("a"), _src("b")],
|
||||
[TransientError("1"), TransientError("2"), _ok()],
|
||||
selector=sel,
|
||||
)
|
||||
await mw(_REQ)
|
||||
assert [n for n, _ in transport.calls] == ["a", "a", "b"]
|
||||
|
||||
Reference in New Issue
Block a user