Skip to content

Fix #4491: route Target._random through Random.Shared#4492

Merged
jeremydmiller merged 1 commit into
masterfrom
fix/4491-random-shared-target
May 19, 2026
Merged

Fix #4491: route Target._random through Random.Shared#4492
jeremydmiller merged 1 commit into
masterfrom
fix/4491-random-shared-target

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Summary

Closes #4491.

src/Marten.Testing/Documents/Target.cs declared _random as a static new Random(67) shared across the test assembly. Random is not thread-safe; xUnit parallelizes across test classes, so concurrent calls into Random.Next(int, int) could corrupt the internal state and start returning 0 indefinitely. That mapped every Target.Color to the 0 arm of the switch (Blue) and made tests that rely on a color distribution fail intermittently:

Shouldly.ShouldAssertException : greens
    should not be empty but was

Surfaced on the PGLatest + STJ + NET10 CI matrix (the fastest of the four, so widest concurrency overlap) on PRs #4488 and #4490 for DocumentDbTests.Reading.query_plans.use_as_batch. Re-running the matrix passed cleanly both times — classic narrow concurrency-window flake.

Fix

Switch the default source to System.Random.Shared (per-thread, thread-safe, .NET 6+). Preserve the existing ResetRandomSeed(int) contract via a [ThreadStatic] overlay: tests that pin a specific seed get a per-thread Random seeded to that value; other code falls back to Random.Shared. The overlay also keeps seeding from leaking across xUnit-parallelized test classes the way the old shared-static field did.

System.Random.Shared is fully qualified at the call site because Target itself has a static Random() factory method that shadows the type name.

Tradeoff

This removes the (already-fragile) process-wide deterministic stream the old static seed implied. The previous comment block on the static field acknowledged that the "determinism" depended on xUnit test discovery order and was a known source of CI flakes — i.e. it was deterministic in name only. Random.Shared trades that for true thread-safety, which matters more in practice.

ResetRandomSeed has no callers in the current codebase, but the method is preserved for source compatibility. Tests that want a deterministic stream still get one on their own thread.

Test plan

  • dotnet build src/Marten.slnx -c Release — 0 errors
  • DocumentDbTests.Reading.query_plans.* — 2/2 pass on both Newtonsoft and STJ
  • DocumentDbTests.Reading.* — 126/126 pass on net10.0
  • No ResetRandomSeed callers exist in the codebase — the overlay path is exercised only by future seeded-determinism callers

🤖 Generated with Claude Code

src/Marten.Testing/Documents/Target.cs declared `_random` as a
static `new Random(67)` shared across the test assembly. `Random`
is not thread-safe, and xUnit parallelizes across test classes,
so concurrent calls into `Random.Next(int, int)` could corrupt
internal state and start returning 0 indefinitely. That mapped
every Target.Color to the `0` arm of the switch (Blue) and made
tests that rely on a color distribution fail intermittently with

  Shouldly.ShouldAssertException : greens
    should not be empty but was

Surfaced on the PGLatest + STJ + NET10 matrix (the fastest of the
four, so widest concurrency overlap) for both
DocumentDbTests.Reading.query_plans.use_as_batch and other tests
generating bulk Target data. Re-running the matrix passed cleanly
— classic narrow concurrency-window flake.

Switch the default source to `System.Random.Shared` (per-thread,
thread-safe, .NET 6+). Preserve the existing `ResetRandomSeed`
contract via a [ThreadStatic] overlay: tests that pin a specific
seed get a per-thread Random seeded to that value; other code
falls back to Random.Shared. The overlay keeps seeding from
leaking across xUnit-parallelized test classes the way the old
shared-static field did.

System.Random.Shared has to be fully qualified because Target
itself has a static `Random()` factory method that shadows the
type name in this scope.

Tradeoff: this removes the (already-fragile) process-wide
deterministic stream the old static seed implied. The previous
comment block on the static field acknowledged that the
"determinism" depended on xUnit test discovery order and was a
known source of CI flakes — i.e. it was deterministic in name
only. Random.Shared trades that for true thread-safety, which
matters more in practice.

Closes #4491.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@jeremydmiller jeremydmiller merged commit 96ba09f into master May 19, 2026
5 of 6 checks passed
@jeremydmiller jeremydmiller deleted the fix/4491-random-shared-target branch May 19, 2026 13:09
@jeremydmiller jeremydmiller mentioned this pull request May 19, 2026
38 tasks
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.

Replace static Marten.Testing.Documents.Target._random with Random.Shared (CI flake)

1 participant