Skip to content

ci(tsan): timeout-bound the race gate — seeded-race UB hung 1/3 of overnight runs for 6h each#451

Merged
InauguralPhysicist merged 1 commit into
mainfrom
tsan-gate-hang-timeout
Jul 5, 2026
Merged

ci(tsan): timeout-bound the race gate — seeded-race UB hung 1/3 of overnight runs for 6h each#451
InauguralPhysicist merged 1 commit into
mainfrom
tsan-gate-hang-timeout

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

The incident. Overnight 2026-07-05, 5 of ~15 CI runs hung in the TSan job and burned the 6h default job timeout before auto-cancel — including the #443 VM fix and pure docs pushes, so the hang is content-independent. In every hung run all 9 race-free slice tests had already passed; the hang is always in tsan_seeded_race.eigs, the deliberately-racy planted fault. Orphan-process teardown in the logs shows the interpreter still alive at kill time.

Mechanism (hypothesis, consistent with the evidence). The seeded race does unsynchronized set_at on one slot from two threads: both can read the same displaced element and both decref it — double-decref corruption downstream of deliberate UB may legally spin forever. 0/30 local repros on the 2-core dev box vs ~1/3 on 4-core runners, consistent with the CI-TSan-sees-more rule. No evidence of any liveness bug in race-free code.

The fix is gate design, not runtime. An intentionally-UB program may hang; the gate must bound it:

  • every run goes through timeout (default 120s, TSAN_RUN_TIMEOUT overrides)
  • a race-free slice test that times out FAILs loudly in minutes as a liveness bug (was: silent 6h runner burn)
  • the seeded race may be killed after its warnings are counted — TSan emits warnings as they happen, so detection, the property the gate asserts, survives the kill
  • timeout-minutes: 20 on the job as backstop

Planted-fault validation. The first draft returned LAST_RC from a command-substitution subshell — the hang branch could never fire; a planted-hang probe caught it. Final version validated both ways: clean gate 10/10 exit 0; planted hang → FAIL: … HUNG (killed after 3s) exit 1.

🤖 Generated with Claude Code

…ly hang

Overnight 2026-07-05, 5 of ~15 CI runs hung in tsan_seeded_race.eigs
and burned the 6h default job timeout before being auto-cancelled —
including code-touching runs (#443) and pure docs pushes, so the hang
is content-independent. All 9 race-free slice tests had passed in
every hung run; the hang is downstream of the DELIBERATE lost-update
race (two threads set_at the same slot; both can decref the displaced
element — double-decref corruption may legally spin). 0/30 local
repros on the 2-core box vs ~1/3 on 4-core runners — consistent with
the CI-TSan-sees-more rule.

The gate now bounds every run with timeout (default 120s,
TSAN_RUN_TIMEOUT to override):
- a race-free slice test that times out FAILs loudly in minutes as a
  liveness bug (was: silent 6h runner burn),
- the seeded race may be killed after its warnings are counted —
  TSan emits warnings as they happen, so detection (the property the
  gate asserts) survives the kill,
- ci.yml adds timeout-minutes: 20 as the job-level backstop.

tsan_warnings returns via globals, deliberately not echo-and-capture:
w=$(fn) runs fn in a subshell and drops LAST_RC, silently disabling
the hang branch — caught by a planted-hang probe, which now validates
both branches (clean pass exit 0, planted hang exit 1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@InauguralPhysicist InauguralPhysicist merged commit 5050f15 into main Jul 5, 2026
17 checks passed
@InauguralPhysicist InauguralPhysicist deleted the tsan-gate-hang-timeout branch July 5, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant