Skip to content

test: harden G4 snapshot sampler against flake - #13

Merged
maxence2997 merged 2 commits into
mainfrom
bugfix/snapshot-sampler-liveness
Jul 3, 2026
Merged

maxence2997 merged 2 commits into
mainfrom
bugfix/snapshot-sampler-liveness

Conversation

@maxence2997

@maxence2997 maxence2997 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Fix the flaky TestRingQueue_G4_ConcurrentEnqueueAndSnapshot, the sole cause of the weekly -race -count=500 stress failures (Issue Race condition detected — 2026-07-02 #12). It was a test bug, not a data race and not a Snapshot defect — the race detector reported nothing.
  • Gate the producer on a start channel released after the sampler's first snapshot, so the enqueue/snapshot ordering is deterministic instead of scheduler-dependent.
  • Drop the racy assert.Greater(samples, 0) liveness guard (the sample-first loop now guarantees at least one snapshot) and add runtime.Gosched() so the sampler no longer busy-polls and starves the producer/consumer.
  • Production code is unchanged.

Related issues

Closes #12

Motivation

The weekly stress workflow (-race -count=500) intermittently failed and auto-opened Issue #12 with a race-condition label, but the race detector reported nothing. The real failure was a liveness assertion — "0" is not greater than "0" / "sampler should have observed at least one snapshot".

Go's select never chooses default when another case is ready. When the producer's 500 non-blocking ForceEnqueue calls closed producerDone before the sampler goroutine reached its first select, the sampling branch never ran, so samples stayed 0 and assert.Greater(t, samples, 0) failed. This is a scheduler-ordering race in the test, not a wall-clock issue and not a production concurrency bug: RingQueue.Snapshot and ForceEnqueue both hold q.mu for the whole operation and return a copy.

Changes

  • test: harden TestRingQueue_G4_ConcurrentEnqueueAndSnapshot
    • Gate the producer on a start barrier released after the first snapshot.
    • Replace the racy select-default liveness guard with a sample-first loop.
    • Yield via runtime.Gosched() so the sampler interleaves with the producer and consumer instead of busy-polling.

Test plan

  • RED first: forcing the producer-finishes-first ordering on the old structure reproduces "0" is not greater than "0" deterministically, matching the CI failure.
  • GREEN after the fix: TestRingQueue_G4_ConcurrentEnqueueAndSnapshot passes GOMAXPROCS=1 -race -count=2000 (the regime that most aggressively reproduces the flake). The Gosched yield also cut that run from ~98s to ~2s.
  • All TestRingQueue tests pass at -race -count=300.
  • make check (fmt + lint + race test) passes.
  • No production .go file changed.

Checklist

Required

  • Full test suite passes (make check)
  • Each commit represents exactly one logical change (single commit)
  • Commit messages follow the project's commit format
  • No unrelated code reformatting in this PR (surgical — G4 only)
  • No secrets committed
  • CHANGELOG updated — N/A (test-only, not user-facing)

Notes

  • The start barrier makes the liveness deterministic; the enqueue/snapshot interleaving stays intentionally nondeterministic (the point of a concurrency stress test). Ordering correctness is covered deterministically by G6.
  • Rejected alternatives: testing/synctest (needs Go 1.24+; module floor is go 1.22.0) and an explicit time.After / context.WithTimeout guard (would reintroduce a wall-clock timing dependency — the exact anti-pattern this fix removes).
  • Out of scope: the stress workflow labels every failure race-condition even when it is not; retuning that auto-issue labeling is a possible follow-up.

1.producer may finish first -> gate on start
2.racy samples>0 guard -> sample-first loop
3.busy-poll burned CPU -> yield in sampler
Copilot AI review requested due to automatic review settings July 3, 2026 01:34
@github-actions github-actions Bot added the fix label Jul 3, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the RingQueue G4 concurrency test to eliminate a scheduler-ordering flake that caused intermittent failures in the weekly -race -count=500 stress runs (Issue #12). Production code remains unchanged; only the test behavior and scheduling determinism are adjusted.

Changes:

  • Add a start barrier so the producer can’t finish enqueues before the sampler takes its first snapshot.
  • Replace the prior select { ... default: ... } liveness guard with a sample-first loop that always snapshots.
  • Add runtime.Gosched() to avoid busy-polling and improve interleaving between sampler/producer/consumer.

Comment thread ringqueue_test.go
1.barrier wait ignored ctx -> add ctx.Done case
2.match consumer ctx handling -> symmetric shutdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@maxence2997
maxence2997 merged commit fa7105a into main Jul 3, 2026
3 checks passed
@maxence2997
maxence2997 deleted the bugfix/snapshot-sampler-liveness branch July 3, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Race condition detected — 2026-07-02

2 participants