Gate ramp requires completions: zero throughput = hold, not +1

Ramping on demand alone was dangerous with slow endpoints: if the 2
in-flight longbench_v2 requests hang, 'no failures + waiters queued'
kept adding +1 every 5s all the way to 96 -- piling prefills onto a
server that had not answered anything. Both ramp paths (demand-driven
and /metrics) now require at least one SUCCESSFUL completion in the
probe interval; hangs hold the gate until read-timeouts fire and the
x0.7 backoff takes over.

Unit-verified: hang 4 probe intervals with 20 waiters -> limit stays 2;
one success -> +1; one failure -> x0.7.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sora 2026-09-15 02:33:21 +00:00
parent 3be48addfc
commit 4eb853c8fc

View File

@ -166,6 +166,11 @@ class AdaptiveGate:
self._loop = None # loop the cond/probe-task are bound to
self._contended = 0 # acquire() waits this probe interval (demand)
self._interval_fails = 0 # failed releases this probe interval
self._interval_ok = 0 # SUCCESSFUL releases this probe interval:
# zero completions = zero throughput, and a
# gate that ramps on demand alone would pile
# 96 concurrent prefills onto a server whose
# first 2 requests have not even answered
self.stats = {'probe': 0, 'ramp': 0, 'hold_queue': 0, 'backoff_fail': 0,
'backoff_queue': 0, 'ramp_demand': 0}
@ -209,7 +214,9 @@ class AdaptiveGate:
def release(self, ok: bool) -> None:
self._inflight = max(0, self._inflight - 1)
if not ok: # multiplicative decrease -- survival first
if ok:
self._interval_ok += 1
else: # multiplicative decrease -- survival first
self._interval_fails += 1
before = self.limit
self.limit = max(self.LO, self.limit * 0.7)
@ -238,12 +245,13 @@ class AdaptiveGate:
ramp while the cap is the binding constraint (callers had to WAIT on
acquire) and the interval was failure-free. Failures still cut x0.7
per release, so a drowning backend shrinks the gate immediately."""
if self._interval_fails == 0 and self._contended > 0 \
and int(self.limit) < self.HI:
if self._interval_fails == 0 and self._interval_ok > 0 \
and self._contended > 0 and int(self.limit) < self.HI:
self.limit = min(self.HI, self.limit + 1)
self.stats['ramp_demand'] += 1
self._push_limit()
self._interval_fails = 0
self._interval_ok = 0
self._contended = 0
self._wake()
@ -278,8 +286,10 @@ class AdaptiveGate:
self.limit = max(self.LO, self.limit - 1)
self.stats['backoff_queue'] += 1
self._push_limit()
elif (queue or 0) == 0 and (running is None or running < max(2, int(self.limit))):
elif (queue or 0) == 0 and (running is None or running < max(2, int(self.limit))) \
and self._interval_ok > 0:
# underfed: no queue and running below our own cap -> ramp up
# (still requires completions this interval: no throughput, no ramp)
self.limit = min(self.HI, self.limit + 1)
self.stats['ramp'] += 1
self._push_limit()