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:
parent
3be48addfc
commit
4eb853c8fc
@ -166,6 +166,11 @@ class AdaptiveGate:
|
|||||||
self._loop = None # loop the cond/probe-task are bound to
|
self._loop = None # loop the cond/probe-task are bound to
|
||||||
self._contended = 0 # acquire() waits this probe interval (demand)
|
self._contended = 0 # acquire() waits this probe interval (demand)
|
||||||
self._interval_fails = 0 # failed releases this probe interval
|
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,
|
self.stats = {'probe': 0, 'ramp': 0, 'hold_queue': 0, 'backoff_fail': 0,
|
||||||
'backoff_queue': 0, 'ramp_demand': 0}
|
'backoff_queue': 0, 'ramp_demand': 0}
|
||||||
|
|
||||||
@ -209,7 +214,9 @@ class AdaptiveGate:
|
|||||||
|
|
||||||
def release(self, ok: bool) -> None:
|
def release(self, ok: bool) -> None:
|
||||||
self._inflight = max(0, self._inflight - 1)
|
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
|
self._interval_fails += 1
|
||||||
before = self.limit
|
before = self.limit
|
||||||
self.limit = max(self.LO, self.limit * 0.7)
|
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
|
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
|
acquire) and the interval was failure-free. Failures still cut x0.7
|
||||||
per release, so a drowning backend shrinks the gate immediately."""
|
per release, so a drowning backend shrinks the gate immediately."""
|
||||||
if self._interval_fails == 0 and self._contended > 0 \
|
if self._interval_fails == 0 and self._interval_ok > 0 \
|
||||||
and int(self.limit) < self.HI:
|
and self._contended > 0 and int(self.limit) < self.HI:
|
||||||
self.limit = min(self.HI, self.limit + 1)
|
self.limit = min(self.HI, self.limit + 1)
|
||||||
self.stats['ramp_demand'] += 1
|
self.stats['ramp_demand'] += 1
|
||||||
self._push_limit()
|
self._push_limit()
|
||||||
self._interval_fails = 0
|
self._interval_fails = 0
|
||||||
|
self._interval_ok = 0
|
||||||
self._contended = 0
|
self._contended = 0
|
||||||
self._wake()
|
self._wake()
|
||||||
|
|
||||||
@ -278,8 +286,10 @@ class AdaptiveGate:
|
|||||||
self.limit = max(self.LO, self.limit - 1)
|
self.limit = max(self.LO, self.limit - 1)
|
||||||
self.stats['backoff_queue'] += 1
|
self.stats['backoff_queue'] += 1
|
||||||
self._push_limit()
|
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
|
# 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.limit = min(self.HI, self.limit + 1)
|
||||||
self.stats['ramp'] += 1
|
self.stats['ramp'] += 1
|
||||||
self._push_limit()
|
self._push_limit()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user