FAILURE MAP
← Case archive

FA-29571 / HTTP retries / Open access

Late response fencing between retry attempts: A response arriving after cancellation has the wrong terminal disposition · case 01

A response arriving after cancellation has the wrong terminal disposition

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

ROOT CAUSE

A response arriving after cancellation has the wrong terminal disposition

VERIFIED REPAIR

Restore the specified transition if cancelled: out.append('cancelled'); continue if e[1].

Unsuccessful approach: The attempted repair changes the faulty site to if cancelled: out.append('stale'); continue if e[1] but still violates a regression oracle.

Case contract

start allocates a strictly increasing generation. complete(generation,value) publishes only the current active generation and terminates it; duplicate/stale results are rejected. timeout(generation) deactivates only that generation. cancel terminates the logical request permanently. Return accepted/rejected results plus current generation and cancellation flag.

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):
    generation=0; active=False; cancelled=False; out=[]
    for e in events:
        if e[0]=='start':
            if cancelled: out.append('cancelled'); continue
            generation+=1
            active=True
            out.append(['started',generation])
        elif e[0]=='timeout':
            if e[1]==generation: active=False
        elif e[0]=='cancel':
            cancelled=True
            active=False
        elif e[0]=='complete':
            if cancelled: out.append(['accepted',e[2]]); continue
            if e[1]!=generation or not active:
                out.append('stale'); continue
            out.append(['accepted',e[2]])
            active=False
    return [out,generation,cancelled]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [[],0,False])
check('1', solve([('start',),('complete',1,N),('complete',1,N+1)]), [[['started',1],['accepted',N],'stale'],1,False])
check('2', solve([('start',),('start',),('timeout',1),('complete',1,0),('complete',2,N+5)]), [[['started',1],['started',2],'stale',['accepted',N+5]],2,False])
check('3', solve([('start',),('timeout',1),('complete',1,N)]), [[['started',1],'stale'],1,False])
check('4', solve([('start',),('cancel',),('complete',1,N),('start',)]), [[['started',1],'cancelled','cancelled'],1,True])
check('5', solve([('start',),('complete',2,N),('complete',1,N+3)]), [[['started',1],'stale',['accepted',N+3]],1,False])
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[[], 0, False][[], 0, False]Passed
1[[['started', 1], ['accepted', 1], 'stale'], 1, False][[['started', 1], ['accepted', 1], 'stale'], 1, False]Passed
2[[['started', 1], ['started', 2], 'stale', ['accepted', 6]], 2, False][[['started', 1], ['started', 2], 'stale', ['accepted', 6]], 2, False]Passed
3[[['started', 1], 'stale'], 1, False][[['started', 1], 'stale'], 1, False]Passed
4[[['started', 1], ['accepted', 1], 'cancelled'], 1, True][[['started', 1], 'cancelled', 'cancelled'], 1, True]Failed
5[[['started', 1], 'stale', ['accepted', 4]], 1, False][[['started', 1], 'stale', ['accepted', 4]], 1, False]Passed

SHA-256 / dd33a21212c0a5a8c20df12bfb43cf2d43ed4cc53383d139081f383450124d5a

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(events):
    generation=0; active=False; cancelled=False; out=[]
    for e in events:
        if e[0]=='start':
            if cancelled: out.append('cancelled'); continue
            generation+=1
            active=True
            out.append(['started',generation])
        elif e[0]=='timeout':
            if e[1]==generation: active=False
        elif e[0]=='cancel':
            cancelled=True
            active=False
        elif e[0]=='complete':
            if cancelled: out.append('stale'); continue
            if e[1]!=generation or not active:
                out.append('stale'); continue
            out.append(['accepted',e[2]])
            active=False
    return [out,generation,cancelled]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [[],0,False])
check('1', solve([('start',),('complete',1,N),('complete',1,N+1)]), [[['started',1],['accepted',N],'stale'],1,False])
check('2', solve([('start',),('start',),('timeout',1),('complete',1,0),('complete',2,N+5)]), [[['started',1],['started',2],'stale',['accepted',N+5]],2,False])
check('3', solve([('start',),('timeout',1),('complete',1,N)]), [[['started',1],'stale'],1,False])
check('4', solve([('start',),('cancel',),('complete',1,N),('start',)]), [[['started',1],'cancelled','cancelled'],1,True])
check('5', solve([('start',),('complete',2,N),('complete',1,N+3)]), [[['started',1],'stale',['accepted',N+3]],1,False])
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[[], 0, False][[], 0, False]Passed
1[[['started', 1], ['accepted', 1], 'stale'], 1, False][[['started', 1], ['accepted', 1], 'stale'], 1, False]Passed
2[[['started', 1], ['started', 2], 'stale', ['accepted', 6]], 2, False][[['started', 1], ['started', 2], 'stale', ['accepted', 6]], 2, False]Passed
3[[['started', 1], 'stale'], 1, False][[['started', 1], 'stale'], 1, False]Passed
4[[['started', 1], 'stale', 'cancelled'], 1, True][[['started', 1], 'cancelled', 'cancelled'], 1, True]Failed
5[[['started', 1], 'stale', ['accepted', 4]], 1, False][[['started', 1], 'stale', ['accepted', 4]], 1, False]Passed

SHA-256 / bf29e444d67b0a4526c6d171767d5b72c8880919ee007bf2bd71b8e3687f3089

3 / The verified repair

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

N = 1
observations = []
def solve(events):
    generation=0; active=False; cancelled=False; out=[]
    for e in events:
        if e[0]=='start':
            if cancelled: out.append('cancelled'); continue
            generation+=1
            active=True
            out.append(['started',generation])
        elif e[0]=='timeout':
            if e[1]==generation: active=False
        elif e[0]=='cancel':
            cancelled=True
            active=False
        elif e[0]=='complete':
            if cancelled: out.append('cancelled'); continue
            if e[1]!=generation or not active:
                out.append('stale'); continue
            out.append(['accepted',e[2]])
            active=False
    return [out,generation,cancelled]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
check('0', solve([]), [[],0,False])
check('1', solve([('start',),('complete',1,N),('complete',1,N+1)]), [[['started',1],['accepted',N],'stale'],1,False])
check('2', solve([('start',),('start',),('timeout',1),('complete',1,0),('complete',2,N+5)]), [[['started',1],['started',2],'stale',['accepted',N+5]],2,False])
check('3', solve([('start',),('timeout',1),('complete',1,N)]), [[['started',1],'stale'],1,False])
check('4', solve([('start',),('cancel',),('complete',1,N),('start',)]), [[['started',1],'cancelled','cancelled'],1,True])
check('5', solve([('start',),('complete',2,N),('complete',1,N+3)]), [[['started',1],'stale',['accepted',N+3]],1,False])
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[[], 0, False][[], 0, False]Passed
1[[['started', 1], ['accepted', 1], 'stale'], 1, False][[['started', 1], ['accepted', 1], 'stale'], 1, False]Passed
2[[['started', 1], ['started', 2], 'stale', ['accepted', 6]], 2, False][[['started', 1], ['started', 2], 'stale', ['accepted', 6]], 2, False]Passed
3[[['started', 1], 'stale'], 1, False][[['started', 1], 'stale'], 1, False]Passed
4[[['started', 1], 'cancelled', 'cancelled'], 1, True][[['started', 1], 'cancelled', 'cancelled'], 1, True]Passed
5[[['started', 1], 'stale', ['accepted', 4]], 1, False][[['started', 1], 'stale', ['accepted', 4]], 1, False]Passed

SHA-256 / 8e8a2bf9e28bc971355127f54fa2c3ceb07446320764b1c5ecca727355503ee9

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:44.441807+00:00.

Case digest / d870cf8ca39cc4b4a4b48e6bb414a3ad8b48a2a61e7051932985b4f77dcc2bb2