diff --git a/evalharness/model/pool.py b/evalharness/model/pool.py index 6ce27cb..9b95c2a 100644 --- a/evalharness/model/pool.py +++ b/evalharness/model/pool.py @@ -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()