2bc64e84
fix: the burst's fifth keystroke aged onto the expiry bound exactly
a73x 2026-08-09 18:43
Commit message
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -1236,7 +1236,7 @@ assert_converged "$OUT.p1" "$SOCK5" "line-mode prediction" | |||
| 1236 | # whole queue was flushed once per round trip. If anyone regresses the | 1236 | # whole queue was flushed once per round trip. If anyone regresses the |
| 1237 | # three-way judgment, this scenario is what says so. | 1237 | # three-way judgment, this scenario is what says so. |
| 1238 | set +e | 1238 | set +e |
| 1239 | { sleep 2; for c in b u r s t; do printf '%s' "$c"; sleep 0.2; done; \ | 1239 | { sleep 2; for c in b u r s t; do printf '%s' "$c"; sleep 0.25; done; \ |
| 1240 | sleep 4; printf '\034'; } | \ | 1240 | sleep 4; printf '\034'; } | \ |
| 1241 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ | 1241 | DELAY_MS="$PDELAY" MUX_PREDICT_STATS=1 timeout 40 "$MUX" \ |
| 1242 | --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \ | 1242 | --via "$DELAYPIPE | $MUXD proxy --sock $SOCK5 | $DELAYPIPE" \ |
| @@ -1247,10 +1247,24 @@ set -e | |||
| 1247 | echo "e2e FAIL: burst client exited $RC" | 1247 | echo "e2e FAIL: burst client exited $RC" |
| 1248 | cat "$OUT.pb" "$OUT.pb.err" 2>/dev/null; exit 1; } | 1248 | cat "$OUT.pb" "$OUT.pb.err" 2>/dev/null; exit 1; } |
| 1249 | 1249 | ||
| 1250 | # Five keystrokes typed at 200ms into a 600ms round trip: three are always | 1250 | # Five keystrokes typed at 250ms into a 600ms round trip: three are always |
| 1251 | # outstanding at once. `made` is asserted too, so two keystrokes arriving in | 1251 | # outstanding at once (at t=600, when the first is confirmed, the keys typed |
| 1252 | # one read (which would be suppressed as a multi-byte chunk, not predicted) | 1252 | # at 0, 250 and 500 are all in flight). `made` is asserted too, so two |
| 1253 | # fails loudly instead of quietly weakening the test. | 1253 | # keystrokes arriving in one read (which would be suppressed as a multi-byte |
| 1254 | # chunk, not predicted) fails loudly instead of quietly weakening the test. | ||
| 1255 | # | ||
| 1256 | # The interval is 250 and not 200 because of a bound that has to be COMPUTED | ||
| 1257 | # rather than eyeballed. delaypipe delivers one chunk per DELAY_MS serially | ||
| 1258 | # in EACH direction, so keystrokes closer together than DELAY_MS queue, and | ||
| 1259 | # each one ages (DELAY_MS - interval) longer than the one before it: | ||
| 1260 | # | ||
| 1261 | # age at which keystroke k is confirmed = 2*DELAY_MS + k*(DELAY_MS - interval) | ||
| 1262 | # | ||
| 1263 | # At interval=200 the fifth keystroke lands at 600 + 4*100 = 1000ms, which is | ||
| 1264 | # exactly predict.expire_after_ms: a coin flip, not a margin. It failed 4 runs | ||
| 1265 | # in 20 on an idle machine. At 250 it is 600 + 4*50 = 800ms, measured at | ||
| 1266 | # 798-799 across 12 runs. The same formula puts the SECOND keystroke at | ||
| 1267 | # 1000ms when DELAY_MS=400, which is the confirmed=1 collapse noted above. | ||
| 1254 | want_stat "$OUT.pb.err" made 5 "burst" | 1268 | want_stat "$OUT.pb.err" made 5 "burst" |
| 1255 | # `confirmed` is a LIVENESS counter and the only timing-dependent one here: | 1269 | # `confirmed` is a LIVENESS counter and the only timing-dependent one here: |
| 1256 | # the fifth confirmation can still be in flight when the detach byte lands, | 1270 | # the fifth confirmation can still be in flight when the detach byte lands, |
| @@ -1263,8 +1277,13 @@ want_stat "$OUT.pb.err" made 5 "burst" | |||
| 1263 | # a prediction judged wrong, or one that aged out unjudged, is a defect | 1277 | # a prediction judged wrong, or one that aged out unjudged, is a defect |
| 1264 | # whenever it happens, and nothing about timing makes either acceptable. | 1278 | # whenever it happens, and nothing about timing makes either acceptable. |
| 1265 | want_stat_ge "$OUT.pb.err" confirmed 4 "burst" | 1279 | want_stat_ge "$OUT.pb.err" confirmed 4 "burst" |
| 1266 | want_stat "$OUT.pb.err" contradicted 0 "burst" | 1280 | # `expired` first: an expiry runs through the same abandonAll tail as a |
| 1281 | # refutation and increments BOTH counters, so contradicted >= expired always. | ||
| 1282 | # Asserting contradicted first reports an age-out as "a prediction judged | ||
| 1283 | # wrong" — a different defect with a different cause, and the reason this | ||
| 1284 | # flake was first investigated as a reconcile bug. | ||
| 1267 | want_stat "$OUT.pb.err" expired 0 "burst" | 1285 | want_stat "$OUT.pb.err" expired 0 "burst" |
| 1286 | want_stat "$OUT.pb.err" contradicted 0 "burst" | ||
| 1268 | "$MUXD" dump --sock "$SOCK5" | grep -q "burst" || { | 1287 | "$MUXD" dump --sock "$SOCK5" | grep -q "burst" || { |
| 1269 | echo "e2e FAIL: burst: daemon grid does not hold the typed text"; exit 1; | 1288 | echo "e2e FAIL: burst: daemon grid does not hold the typed text"; exit 1; |
| 1270 | } | 1289 | } |