FAILURE MAP
← Case archive

FA-30516 / HTTP retries / Open access

Retry routing with origin affinity: Success stop violates retry transition semantics · case 01

Success stop violates retry transition semantics

Verified by executionVariant 1 · 7 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

Success stop violates retry transition semantics

VERIFIED REPAIR

Restore the specified transition elif e[0]=='success': terminal=True; active=None.

Unsuccessful approach: The attempted repair changes the faulty site to elif e[0]=='success': terminal=False; active=active but still violates a regression oracle.

Case contract

pool(endpoint IDs) sets ordered available endpoints. begin(request,affinity bool) resets attempted endpoint set and pin. pick selects first healthy untried endpoint; an affinity request must reuse its first endpoint on subsequent retries while healthy. down/up alter health. fail marks current endpoint attempted and clears active route. success finalizes. No route changes are HTTP redirects. Return chosen endpoint or exhausted/terminal/active.

Why this case matters

Offline deterministic model of HTTP request retries.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(events):
    pool=[]; down=set(); tried=set(); pin=None; active=None; affinity=False; terminal=True; out=[]
    for e in events:
        if e[0]=='pool': pool=list(e[1]); down=set()
        elif e[0]=='begin': tried=set(); pin=None; active=None; affinity=e[2]; terminal=False
        elif e[0]=='down': down.add(e[1])
        elif e[0]=='up': down.discard(e[1])
        elif e[0]=='pick':
            if terminal: out.append('terminal'); continue
            if active is not None: out.append('active'); continue
            if affinity and pin is not None:
                options=[pin] if pin not in down and pin in pool else []
            else: options=[p for p in pool if p not in down and p not in tried]
            if not options: out.append('exhausted'); continue
            active=options[0]
            if pin is None: pin=active
            out.append(active)
        elif e[0]=='fail':
            if active is not None: tried.add(active)
            active=None
        elif e[0]=='success': terminal=False; active=None
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [])
check('1', solve([('pool',['b','a','c']),('begin','r',False),('pick',),('pick',),('fail',),('pick',),('fail',),('pick',),('fail',),('pick',)]), ['b','active','a','c','exhausted'])
check('2', solve([('pool',['a','b']),('begin','r',True),('pick',),('fail',),('pick',),('fail',),('down','a'),('pick',)]), ['a','a','exhausted'])
check('3', solve([('pool',['a','b','c']),('down','a'),('down','b'),('up','a'),('begin','r',False),('pick',),('fail',),('pick',)]), ['a','c'])
check('4', solve([('pool',['a','b']),('begin','r',True),('pick',),('fail',),('pool',['b']),('pick',),('begin','s',True),('pick',)]), ['a','exhausted','b'])
check('5', solve([('pool',[N,N+1]),('begin','r',False),('pick',),('success',),('pick',),('begin','s',False),('pick',)]), [N,'terminal',N])
check('6', solve([('pool',['a']),('down','a'),('pool',['a']),('begin','r',False),('pick',)]), ['a'])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
0[][]Passed
1['b', 'active', 'a', 'c', 'exhausted']['b', 'active', 'a', 'c', 'exhausted']Passed
2['a', 'a', 'exhausted']['a', 'a', 'exhausted']Passed
3['a', 'c']['a', 'c']Passed
4['a', 'exhausted', 'b']['a', 'exhausted', 'b']Passed
5[1, 1, 1][1, 'terminal', 1]Failed
6['a']['a']Passed

SHA-256 / be1bd8b264f7098795036c4b9d9a09b3612eec29099be7feacb62823c14d9acb

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(events):
    pool=[]; down=set(); tried=set(); pin=None; active=None; affinity=False; terminal=True; out=[]
    for e in events:
        if e[0]=='pool': pool=list(e[1]); down=set()
        elif e[0]=='begin': tried=set(); pin=None; active=None; affinity=e[2]; terminal=False
        elif e[0]=='down': down.add(e[1])
        elif e[0]=='up': down.discard(e[1])
        elif e[0]=='pick':
            if terminal: out.append('terminal'); continue
            if active is not None: out.append('active'); continue
            if affinity and pin is not None:
                options=[pin] if pin not in down and pin in pool else []
            else: options=[p for p in pool if p not in down and p not in tried]
            if not options: out.append('exhausted'); continue
            active=options[0]
            if pin is None: pin=active
            out.append(active)
        elif e[0]=='fail':
            if active is not None: tried.add(active)
            active=None
        elif e[0]=='success': terminal=False; active=active
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [])
check('1', solve([('pool',['b','a','c']),('begin','r',False),('pick',),('pick',),('fail',),('pick',),('fail',),('pick',),('fail',),('pick',)]), ['b','active','a','c','exhausted'])
check('2', solve([('pool',['a','b']),('begin','r',True),('pick',),('fail',),('pick',),('fail',),('down','a'),('pick',)]), ['a','a','exhausted'])
check('3', solve([('pool',['a','b','c']),('down','a'),('down','b'),('up','a'),('begin','r',False),('pick',),('fail',),('pick',)]), ['a','c'])
check('4', solve([('pool',['a','b']),('begin','r',True),('pick',),('fail',),('pool',['b']),('pick',),('begin','s',True),('pick',)]), ['a','exhausted','b'])
check('5', solve([('pool',[N,N+1]),('begin','r',False),('pick',),('success',),('pick',),('begin','s',False),('pick',)]), [N,'terminal',N])
check('6', solve([('pool',['a']),('down','a'),('pool',['a']),('begin','r',False),('pick',)]), ['a'])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
0[][]Passed
1['b', 'active', 'a', 'c', 'exhausted']['b', 'active', 'a', 'c', 'exhausted']Passed
2['a', 'a', 'exhausted']['a', 'a', 'exhausted']Passed
3['a', 'c']['a', 'c']Passed
4['a', 'exhausted', 'b']['a', 'exhausted', 'b']Passed
5[1, 'active', 1][1, 'terminal', 1]Failed
6['a']['a']Passed

SHA-256 / b239c8dbd8e8a0e596221b80c1a594180676207849d4b2ca1d409d5257ae6892

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(events):
    pool=[]; down=set(); tried=set(); pin=None; active=None; affinity=False; terminal=True; out=[]
    for e in events:
        if e[0]=='pool': pool=list(e[1]); down=set()
        elif e[0]=='begin': tried=set(); pin=None; active=None; affinity=e[2]; terminal=False
        elif e[0]=='down': down.add(e[1])
        elif e[0]=='up': down.discard(e[1])
        elif e[0]=='pick':
            if terminal: out.append('terminal'); continue
            if active is not None: out.append('active'); continue
            if affinity and pin is not None:
                options=[pin] if pin not in down and pin in pool else []
            else: options=[p for p in pool if p not in down and p not in tried]
            if not options: out.append('exhausted'); continue
            active=options[0]
            if pin is None: pin=active
            out.append(active)
        elif e[0]=='fail':
            if active is not None: tried.add(active)
            active=None
        elif e[0]=='success': terminal=True; active=None
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [])
check('1', solve([('pool',['b','a','c']),('begin','r',False),('pick',),('pick',),('fail',),('pick',),('fail',),('pick',),('fail',),('pick',)]), ['b','active','a','c','exhausted'])
check('2', solve([('pool',['a','b']),('begin','r',True),('pick',),('fail',),('pick',),('fail',),('down','a'),('pick',)]), ['a','a','exhausted'])
check('3', solve([('pool',['a','b','c']),('down','a'),('down','b'),('up','a'),('begin','r',False),('pick',),('fail',),('pick',)]), ['a','c'])
check('4', solve([('pool',['a','b']),('begin','r',True),('pick',),('fail',),('pool',['b']),('pick',),('begin','s',True),('pick',)]), ['a','exhausted','b'])
check('5', solve([('pool',[N,N+1]),('begin','r',False),('pick',),('success',),('pick',),('begin','s',False),('pick',)]), [N,'terminal',N])
check('6', solve([('pool',['a']),('down','a'),('pool',['a']),('begin','r',False),('pick',)]), ['a'])
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
0[][]Passed
1['b', 'active', 'a', 'c', 'exhausted']['b', 'active', 'a', 'c', 'exhausted']Passed
2['a', 'a', 'exhausted']['a', 'a', 'exhausted']Passed
3['a', 'c']['a', 'c']Passed
4['a', 'exhausted', 'b']['a', 'exhausted', 'b']Passed
5[1, 'terminal', 1][1, 'terminal', 1]Passed
6['a']['a']Passed

SHA-256 / d433c3d9153f50c3ac08692cbfe212634a37294d0cd82aabb185450f7c4bb3c9

Verification & scope

Stipulated bounded simulator, not a complete HTTP implementation or a standards conformance claim. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:41:53.571081+00:00.

Case digest / 9ecd11500f4d40afd4e32ceb0aa19a70af2671faaf89cd7df7601455e42964cf