feat: add AIMD adaptive concurrency pacing per source
P6 round 2 showed routing convergence turns account-level 429s into the binding constraint (62% throttle rate at full concurrency). Each source now carries a local AIMD limit: multiplicative cut on 429, additive growth on success. Over-limit picks queue via the existing quota-wait poll instead of burning retry budget or tripping the circuit-open verdict.
This commit is contained in:
@@ -72,3 +72,43 @@ class TestHealthAwareSelector:
|
||||
def test_missing_stats_defaults_to_zero_inflight(self):
|
||||
sel = HealthAwareSelector(rng=lambda: 0.0)
|
||||
assert [s.name for s in sel.order([_src("s1")], {})] == ["s1"]
|
||||
|
||||
|
||||
class TestAdaptivePacer:
|
||||
"""AIMD 自适应并发(M2.5 设计 §3.35): 429 乘性削减,成功加性增长。"""
|
||||
|
||||
def test_initial_and_bounds(self):
|
||||
from polygateway.sources import AdaptivePacer
|
||||
|
||||
pacer = AdaptivePacer(ceiling=32.0)
|
||||
assert pacer.limit("s1") == pytest.approx(8.0)
|
||||
for _ in range(200):
|
||||
pacer.on_backpressure("s1")
|
||||
assert pacer.limit("s1") == pytest.approx(1.0) # 下限 1
|
||||
for _ in range(2000):
|
||||
pacer.on_success("s1")
|
||||
assert pacer.limit("s1") == pytest.approx(32.0) # 上限 = ceiling
|
||||
|
||||
def test_cut_and_growth_math(self):
|
||||
from polygateway.sources import AdaptivePacer
|
||||
|
||||
pacer = AdaptivePacer(ceiling=32.0)
|
||||
pacer.on_backpressure("s1")
|
||||
assert pacer.limit("s1") == pytest.approx(8.0 * 0.7)
|
||||
before = pacer.limit("s1")
|
||||
pacer.on_success("s1")
|
||||
assert pacer.limit("s1") == pytest.approx(before + 1.0 / before)
|
||||
|
||||
def test_inflight_gate(self):
|
||||
from polygateway.sources import AdaptivePacer
|
||||
|
||||
pacer = AdaptivePacer(ceiling=32.0)
|
||||
for _ in range(200):
|
||||
pacer.on_backpressure("s1") # limit → 1
|
||||
assert pacer.admit("s1") is True
|
||||
pacer.enter("s1")
|
||||
assert pacer.admit("s1") is False # 在途 1 ≥ limit 1
|
||||
pacer.leave("s1")
|
||||
assert pacer.admit("s1") is True
|
||||
pacer.leave("s1") # 多余 leave 不下穿 0
|
||||
assert pacer._inflight.get("s1", 0) == 0
|
||||
|
||||
Reference in New Issue
Block a user