Skip to content

Commit c817b2b

Browse files
committed
Judge only the conformance loop on failures that are merely consecutive
The consecutive-failure arm was applied to both loops on one threshold, and the threshold was calibrated on conformance data because no unit-loop equivalent existed: the highest failure count on a functionality that then recovered was four, so six looked safe. It was not. On the unit side it fired on loops that were working. Two renders showed the same signature -- unit=7 unit_failed=7 unit_max_repeat=1, seven failures with none of them alike -- and in both the arm restarted the functionality and the render scored 0/10. One of them burned its whole restart budget and gave up in under five minutes. Every render without a unit restart scored 2 or 3. Seven different failures is a loop working through issues one at a time; restarting throws all of it away, which is a far worse trade than the one the conformance side makes by discarding a single test file. In both harmful cases unit_max_repeat was 1, so the streak arm would never have fired. That arm stays: across three renders every healthy functionality finished with unit_max_repeat=1, and nothing was observed between that and the 17 reached by the one that wedged, so a streak of three is a state healthy renders do not enter. The conformance loop keeps both arms. There the consecutive arm is what catches a loop failing 40 times out of 40 with a longest identical run of two, which no streak threshold reaches, and its remedy costs one regenerated test rather than a functionality's implementation. Removing it does not make such a unit loop run forever; it stops where it always did, at the attempt limit.
1 parent f27465f commit c817b2b

2 files changed

Lines changed: 55 additions & 14 deletions

File tree

‎render_machine/fix_loop_metrics.py‎

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,36 @@ def render_summary(self) -> List[str]:
168168
STRATEGY_SWITCH_PREFIX = "[strategy-switch]"
169169

170170

171+
# Which loops a run of failures — as opposed to a run of *identical* failures — is taken
172+
# as evidence against. Conformance only, and the asymmetry is measured rather than
173+
# cautious. On the conformance side the arm is what catches a loop failing 40 times out of
174+
# 40 with a longest identical run of two, which no streak threshold reaches. On the unit
175+
# side it fired on loops that were working: two renders showed `unit=7 unit_failed=7
176+
# unit_max_repeat=1` — seven failures, none alike — and both had their functionality
177+
# restarted and scored 0/10, where every render without a restart scored 2–3. Six was
178+
# calibrated on conformance recoveries (the highest failure count on a functionality that
179+
# then recovered was four) and there was never a unit-loop equivalent to calibrate on.
180+
#
181+
# The unit loop keeps the streak arm, where the margin is not in doubt: across three
182+
# renders every healthy functionality finished with unit_max_repeat=1.
183+
LOOPS_JUDGED_ON_CONSECUTIVE_FAILURES = (CONFORMANCE_LOOP,)
184+
185+
171186
def stalled_reason(metrics: "FixLoopMetrics", loop: str, module: str, frid: Optional[str]) -> Optional[str]:
172187
"""Why this loop looks stuck, or None if it still looks like it is working.
173188
174-
One definition for both loops. The streak arm fires soonest when a loop is
175-
re-submitting the same fix; the consecutive arm is the catch-all for a loop that
176-
fails every time while the failures keep changing shape.
189+
The streak arm applies to both loops: a loop re-submitting the same fix is stuck
190+
whichever loop it is. The consecutive arm applies only where failing every time has
191+
been shown to mean stuck rather than busy.
177192
"""
178193
streak = metrics.current_streak(loop, module, frid)
179194
if streak >= REPEATED_FAILURE_WARNING_THRESHOLD:
180195
return f"repeated_failure streak={streak}"
181196

182-
failures = metrics.consecutive_failures(loop, module, frid)
183-
if failures >= CONSECUTIVE_FAILURE_THRESHOLD:
184-
return f"no_progress consecutive_failures={failures}"
197+
if loop in LOOPS_JUDGED_ON_CONSECUTIVE_FAILURES:
198+
failures = metrics.consecutive_failures(loop, module, frid)
199+
if failures >= CONSECUTIVE_FAILURE_THRESHOLD:
200+
return f"no_progress consecutive_failures={failures}"
185201

186202
return None
187203

‎tests/test_unit_strategy_switch.py‎

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@
22
33
The unit loop has the same problem as the conformance one and a different remedy: its
44
escape hatch restarts the functionality from scratch rather than discarding a test file.
5-
That is destructive enough to be worth a careful threshold, and the benchmark data makes
6-
the call easy — across three renders every healthy functionality finished with
7-
`unit_max_repeat=1`, nothing was ever observed between that and the 17 reached by the one
8-
that wedged. So a streak of three is a state healthy renders do not enter, and reaching
9-
the same decision on it saves the eleven minutes that render spent grinding to the
10-
attempt limit.
5+
That is destructive enough that only one kind of evidence justifies it. Repetition does:
6+
across three renders every healthy functionality finished with `unit_max_repeat=1`, and
7+
nothing was observed between that and the 17 reached by the one that wedged, so a streak
8+
of three is a state healthy renders do not enter.
9+
10+
A run of failures that are merely consecutive does not. That arm was applied here too at
11+
first, calibrated on conformance recoveries because no unit-loop equivalent existed, and
12+
it fired on loops that were working: `unit=7 unit_failed=7 unit_max_repeat=1` in two
13+
renders, both restarted, both 0/10, against 2-3 for every render without a restart. It now
14+
applies to the conformance loop only.
1115
"""
1216

1317
from unittest.mock import MagicMock, patch
@@ -62,9 +66,30 @@ def test_a_healthy_loop_is_left_alone():
6266
assert gave_up(context(identical_failures=0)) is False
6367

6468

65-
def test_a_unit_loop_that_always_fails_gives_up_without_a_repeat():
69+
def test_a_unit_loop_failing_every_time_but_differently_is_left_alone():
70+
"""Measured, not cautious. Two renders showed `unit=7 unit_failed=7
71+
unit_max_repeat=1` — seven failures, none alike — and both had their functionality
72+
restarted and scored 0/10, where every render without a restart scored 2-3. Seven
73+
different failures is a loop working through issues one at a time, and restarting
74+
discards all of it.
75+
76+
The conformance loop keeps this arm; there, failing every time really does mean
77+
stuck, and it is what catches a 40-out-of-40 run whose longest identical streak is
78+
two."""
6679
instance = context()
67-
for index in range(CONSECUTIVE_FAILURE_THRESHOLD):
80+
for index in range(CONSECUTIVE_FAILURE_THRESHOLD * 2):
81+
instance.fix_loop_metrics.record(
82+
UNIT_LOOP, module=MODULE, frid=FRID, passed=False, output=f"failure number {index}"
83+
)
84+
85+
assert gave_up(instance) is False
86+
87+
88+
def test_the_attempt_limit_still_catches_a_unit_loop_that_never_repeats():
89+
"""Removing the arm does not make such a loop run forever: it stops where it always
90+
did, at the attempt limit."""
91+
instance = context(attempts=MAX_UNITTEST_FIX_ATTEMPTS)
92+
for index in range(CONSECUTIVE_FAILURE_THRESHOLD * 2):
6893
instance.fix_loop_metrics.record(
6994
UNIT_LOOP, module=MODULE, frid=FRID, passed=False, output=f"failure number {index}"
7095
)

0 commit comments

Comments
 (0)