From dcd386d4aa5537a0066944427d4d753e95317910 Mon Sep 17 00:00:00 2001 From: iomgaa Date: Tue, 21 Jul 2026 12:08:37 -0400 Subject: [PATCH] fix: insert demoted source after credible alternatives, not at tail Round 4 showed tail placement routes the third attempt to junk sources whenever the credible alternative is gate-skipped (tight-RPM source admitted as credible, then skipped by the limiter, falling through to the flapping watchdog source). Demoted sources now sit between credible and non-credible candidates. --- src/polygateway/middleware/retry.py | 39 ++++++++++++++++++++++++++--- tests/unit/test_retry.py | 20 +++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/polygateway/middleware/retry.py b/src/polygateway/middleware/retry.py index 34ac8aa..ef0af52 100644 --- a/src/polygateway/middleware/retry.py +++ b/src/polygateway/middleware/retry.py @@ -87,14 +87,45 @@ def _demote_call_failures( demoted = [s for s in ordered if attempt_fails.get(s.name, 0) >= 2] if not demoted or len(demoted) == len(ordered): return ordered - if health is not None: - demoted = _credible_demotions(ordered, demoted, attempt_fails, health) - if not demoted: - return ordered + if health is None: + return _move_to_tail(ordered, demoted) + return _health_gated_reorder(ordered, demoted, attempt_fails, health) + + +def _move_to_tail(ordered: list[SourceConfig], demoted: list[SourceConfig]) -> list[SourceConfig]: + """无健康视图: 无条件移尾(冷启动保护原语义)。""" names = {d.name for d in demoted} return [s for s in ordered if s.name not in names] + demoted +def _health_gated_reorder( + ordered: list[SourceConfig], + demoted: list[SourceConfig], + attempt_fails: dict[str, int], + health: Callable[[str], float], +) -> list[SourceConfig]: + """健康门槛降权: 无可信替代则原地重试;有则插到可信替代之后。""" + demoted = _credible_demotions(ordered, demoted, attempt_fails, health) + if not demoted: + return ordered + names = {d.name for d in demoted} + rest = [s for s in ordered if s.name not in names] + return _insert_after_credible(rest, demoted, health) + + +def _insert_after_credible( + rest: list[SourceConfig], + demoted: list[SourceConfig], + health: Callable[[str], float], +) -> list[SourceConfig]: + """插入位置(第四轮教训): 被降权源排在可信替代之后、不可信源之前—— + 可信替代被限流闸/熔断跳过时,下一候选是失败源本身而非垃圾源。""" + bar = 0.5 * max(health(d.name) for d in demoted) + credible = [s for s in rest if health(s.name) >= bar] + junk = [s for s in rest if health(s.name) < bar] + return credible + demoted + junk + + def _credible_demotions( ordered: list[SourceConfig], demoted: list[SourceConfig], diff --git a/tests/unit/test_retry.py b/tests/unit/test_retry.py index 81165f0..8b8823a 100644 --- a/tests/unit/test_retry.py +++ b/tests/unit/test_retry.py @@ -537,3 +537,23 @@ class TestHealthGatedDemotion: ) await mw(_REQ) assert [n for n, _ in transport.calls] == ["a", "a", "b"] + + +class TestDemotionInsertPosition: + """迭代 3(设计 §3.36 补): 被降权源插在可信替代之后、不可信源之前。""" + + async def test_demoted_lands_before_junk_sources(self): + # a 失败 2 次;b 可信(0.9)但会被跳过时,第三候选应是 a 而非垃圾源 c + from polygateway.middleware.retry import _demote_call_failures + + srcs = [_src("a"), _src("b"), _src("c")] + health = {"a": 0.9, "b": 0.9, "c": 0.05}.__getitem__ + out = _demote_call_failures(srcs, {"a": 2}, health) + assert [s.name for s in out] == ["b", "a", "c"] + + async def test_health_blind_demotion_still_tail(self): + from polygateway.middleware.retry import _demote_call_failures + + srcs = [_src("a"), _src("b"), _src("c")] + out = _demote_call_failures(srcs, {"a": 2}, None) + assert [s.name for s in out] == ["b", "c", "a"]