Skip to content

Commit 1966808

Browse files
Merge pull request #421 from JasperFx/fix/flaky-registry-enumeration-test
test(postgresql): make the registry enumeration test observe the concurrent window
2 parents b491fa6 + c5189ca commit 1966808

1 file changed

Lines changed: 41 additions & 12 deletions

File tree

src/Weasel.Postgresql.Tests/NpgsqlTypeMappingRegistryTests.cs

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,38 +49,67 @@ public void enumerating_while_registrations_land_neither_throws_nor_tears()
4949
{
5050
const int seeded = 500;
5151
const int added = 5000;
52+
const int passes = 50;
53+
var patience = TimeSpan.FromSeconds(30);
5254

5355
var registry = EmptyRegistry();
5456
for (var i = 0; i < seeded; i++)
5557
{
5658
registry[(NpgsqlDbType)i] = AnyMapping();
5759
}
5860

59-
// Bounded on both sides: the writer stops after a fixed number of registrations and the
60-
// reader stops with it, so the dictionary cannot grow without limit underneath repeated
61-
// full enumerations.
61+
using var writingHasStarted = new ManualResetEventSlim(false);
62+
using var readerIsFinished = new ManualResetEventSlim(false);
63+
64+
// The writer holds the concurrent window open for the reader instead of racing it to the
65+
// finish: it registers the new keys, then keeps re-registering them -- which mutates the
66+
// map without moving the final count -- until the reader has taken its passes. The reader
67+
// in turn does not start until the first registration has landed, so every pass below is
68+
// taken against a registry actively being written to.
69+
//
70+
// The earlier `while (!writer.IsCompleted)` shape had no such handshake: when the pool
71+
// scheduled the writer promptly it finished all 5,000 registrations before the first
72+
// IsCompleted check, the loop body never ran, and the test asserted nothing at all --
73+
// green here, and intermittently red on the trailing `passes > 0` in CI.
6274
var writer = Task.Run(() =>
6375
{
6476
for (var i = 0; i < added; i++)
6577
{
6678
registry[(NpgsqlDbType)(100_000 + i)] = AnyMapping();
79+
writingHasStarted.Set();
80+
}
81+
82+
while (!readerIsFinished.IsSet)
83+
{
84+
for (var i = 0; i < added && !readerIsFinished.IsSet; i++)
85+
{
86+
registry[(NpgsqlDbType)(100_000 + i)] = AnyMapping();
87+
}
6788
}
6889
});
6990

70-
var passes = 0;
71-
while (!writer.IsCompleted)
91+
try
92+
{
93+
writingHasStarted.Wait(patience).ShouldBeTrue();
94+
95+
for (var pass = 0; pass < passes; pass++)
96+
{
97+
// Each read must see a coherent snapshot: never fewer than what was there before
98+
// the writer started, and never a null hole.
99+
var seen = registry.ToList();
100+
seen.Count.ShouldBeGreaterThanOrEqualTo(seeded);
101+
seen.ShouldAllBe(mapping => mapping != null);
102+
}
103+
}
104+
finally
72105
{
73-
// Each read must see a coherent snapshot: never fewer than what was there before the
74-
// writer started, and never a null hole.
75-
var seen = registry.ToList();
76-
seen.Count.ShouldBeGreaterThanOrEqualTo(seeded);
77-
seen.ShouldAllBe(mapping => mapping != null);
78-
passes++;
106+
// In a finally so a failed assertion above releases the writer rather than hanging
107+
// the run in its churn loop.
108+
readerIsFinished.Set();
79109
}
80110

81111
writer.GetAwaiter().GetResult();
82112
registry.Count.ShouldBe(seeded + added);
83-
passes.ShouldBeGreaterThan(0);
84113
}
85114

86115
[Fact]

0 commit comments

Comments
 (0)