Skip to content

test(postgresql): make the registry enumeration test observe the concurrent window - #421

Merged
jeremydmiller merged 1 commit into
masterfrom
fix/flaky-registry-enumeration-test
Aug 2, 2026
Merged

test(postgresql): make the registry enumeration test observe the concurrent window#421
jeremydmiller merged 1 commit into
masterfrom
fix/flaky-registry-enumeration-test

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

The flake

NpgsqlTypeMappingRegistryTests.enumerating_while_registrations_land_neither_throws_nor_tears failed on the "Postgres postgres:15.3-alpine net9.0 Case Sensitive false" job of #418 with Shouldly.ShouldAssertException : passes, and passed on rerun with no code change. It passes consistently locally.

The race was in the test, not the product code. The reader ran while (!writer.IsCompleted) { ...; passes++; } and then asserted passes.ShouldBeGreaterThan(0). When the thread pool scheduled the writer promptly and it completed all 5,000 registrations before the main thread's first IsCompleted evaluation, the loop body never ran, passes stayed 0, and the test failed — while asserting nothing whatsoever about the registry. Nothing about that outcome indicates NpgsqlTypeMappingRegistry misbehaved.

The fix

The concurrent window is now established by handshake instead of by scheduling luck, using two ManualResetEventSlims:

  1. The writer sets writingHasStarted after its first registration; the reader blocks on it before taking any pass, so no pass is taken before writes are landing.
  2. The reader takes a fixed 50 passes, then sets readerIsFinished (in a finally, so a failed assertion releases the writer rather than hanging the run). The writer, after its 5,000 new-key registrations, drops into a churn loop re-registering those same keys until that flag is set.

The writer never blocks — it is assigning into the map for the entire duration of the reader's passes — so the overlap is guaranteed by construction. The vacuous passes > 0 assertion is gone.

Invariants preserved

  • Per pass: Count >= seeded and no null entries, now taken a fixed number of times rather than a scheduling-dependent one.
  • Final Count == seeded + added: churn re-registers existing keys, so it does not move the count. It also slightly strengthens the assertion — under the non-atomic read-modify-write setter this test guards against (weasel#406), a stale-snapshot write during churn can drop keys outright, not just lose new ones.

Verification

  • 200 consecutive local runs of the test alone: 0 failures.
  • All 4 tests in the class pass; the test project builds clean on net8.0/9.0/10.0.

🤖 Generated with Claude Code

…urrent window

enumerating_while_registrations_land_neither_throws_nor_tears raced the writer
to the finish: it read in a `while (!writer.IsCompleted)` loop and then asserted
`passes > 0`. When the pool scheduled the writer promptly it landed all 5,000
registrations before the first IsCompleted check, so the loop body never ran and
the test asserted nothing about the registry -- green locally, and red in CI on
the trailing `passes > 0` with `Shouldly.ShouldAssertException : passes`
(#418, Postgres 15.3-alpine net9.0 job, passed on rerun with no
code change).

The concurrent window is now established by handshake rather than by scheduling
luck. The writer signals after its first registration and the reader blocks on
that before taking any pass; the reader then takes a fixed number of passes and
signals when done; the writer, after registering its new keys, keeps
re-registering them until that signal arrives. It never blocks, so the map is
being actively written to for the whole of the reader's passes, and the vacuous
`passes > 0` assertion is gone.

The invariants the test exists to protect are unchanged: a read never sees fewer
entries than were seeded and never sees a null. Re-registering existing keys does
not move the final count, so `Count == seeded + added` still holds -- and still
fails under the non-atomic read-modify-write setter this test guards against,
where a stale-snapshot write during the churn can drop keys outright.

Verified with 200 consecutive local runs, 0 failures.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jeremydmiller
jeremydmiller merged commit 1966808 into master Aug 2, 2026
16 checks passed
@jeremydmiller
jeremydmiller deleted the fix/flaky-registry-enumeration-test branch August 2, 2026 15:29
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