Fix flaky TestGetPRHeadDrift stamp-without-stream race (fixes #617) #618
No reviewers
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub!618
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/issue-617"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Test-only fix for #617; no production-code or doc change.
Root cause: the first GetPR enqueues a detached pull-mergeable recompute pass. The test rewrote fake refs + issued GetPR#2 without waiting, so a still-running pass could observe the post-mutation head and stamp + stream the 3->4 force-push on its own goroutine, while sync GetPR#2 early-returned as already-recorded without streaming. Bucket stamp lands before the in-memory stream append in the background refreshHead -> stamp present, stream missing, intermittent under -race (logical ordering flake, no data race).
Fix: waitMergeableDrained (new test helper polling the task table Finished stamp) between GetPR#1 and the ref mutation, so the drift is always stamped + streamed synchronously by GetPR#2. No assertion weakened.
Verify: targeted TestGetPRHeadDrift -race -count=200 green (was failing ~3/30 in-suite); full package -race green incl. 8x -count=30 rounds; gofmt/vet clean; pulls coverage 96.1% (>=95% gate). One transient full-package -count=10 FAIL occurred mid-stress without a captured log and has not reproduced in ~330 subsequent package executions; disclosed, not attributed to this change.
APPROVE — independently verified root cause, fix soundness, and stress numbers.
Root cause CORRECT, verified in code (not assumed): refreshHead (mergeable.go:227 savePR bucket write → :237 appendEvent → :247 s.stream in-memory append) has the bucket-before-stream ordering, and the sync GetPR path (mergeable.go:209-212) early-returns as already-recorded with no stream when a background pass won the CAS first. GetPR#1's enqueueMergeable spawns the detached pull-mergeable pass (ComputeMergeable :126-131 resolves live head + calls refreshHead on drift), so a pass straddling the seedRefs mutation fully explains stamp-without-stream. Both halves check out.
Fix SOUND: waitMergeableDrained polls tasks.get Finished != '' with a 5s deadline → t.Fatal (bounded, no hang). Happens-before holds: end() writes Finished under table+record mutex after ComputeMergeable returns, get() snapshots under the same mutexes — observing Finished orders all pass effects (ref resolve, stamp, stream) before seedRefs. No second-pass gap: OpenPR never enqueues (only GetPR tail :598, refreshHead :250, HandleRefEvent), GetPR#1 creates exactly one pass (second enqueue joins via single-flight begin), and nothing enqueues in the single-threaded drain→seedRefs window. GetPR#1 always enqueues (mergeable cache nil on first read), so no false-timeout path. Lock order table→record preserved; test-only helper.
Scope clean: diff is 2 _test.go files only (fakes_test.go helper +28, pulls_test.go 1 call site), no production code, no assertion/threshold touched, no lock-order impact. gofmt/vet clean.
Uncaptured transient: reviewed all sibling stream assertions — draft613 converted_to_draft/ready_for_review stream synchronously in UpdatePR (service.go:993,1055), merged streams drain via waitTask(MergeTask), mergeable tests poll via fetchConverged. TestGetPRHeadDrift was the only background-pass stream assertion without a drain, now fixed. Noting the one uncaptured mid-stress FAIL without attribution is acceptable; my own runs add no new signal.
My stress numbers: TestGetPRHeadDrift -race -count=30 green; full package -race green; coverage 96.1% (>=95% gate); gofmt/vet clean.
Verdict: APPROVE (no fix commits needed).