Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions src/Weasel.Postgresql.Tests/NpgsqlTypeMappingRegistryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,38 +49,67 @@ public void enumerating_while_registrations_land_neither_throws_nor_tears()
{
const int seeded = 500;
const int added = 5000;
const int passes = 50;
var patience = TimeSpan.FromSeconds(30);

var registry = EmptyRegistry();
for (var i = 0; i < seeded; i++)
{
registry[(NpgsqlDbType)i] = AnyMapping();
}

// Bounded on both sides: the writer stops after a fixed number of registrations and the
// reader stops with it, so the dictionary cannot grow without limit underneath repeated
// full enumerations.
using var writingHasStarted = new ManualResetEventSlim(false);
using var readerIsFinished = new ManualResetEventSlim(false);

// The writer holds the concurrent window open for the reader instead of racing it to the
// finish: it registers the new keys, then keeps re-registering them -- which mutates the
// map without moving the final count -- until the reader has taken its passes. The reader
// in turn does not start until the first registration has landed, so every pass below is
// taken against a registry actively being written to.
//
// The earlier `while (!writer.IsCompleted)` shape had no such handshake: when the pool
// scheduled the writer promptly it finished all 5,000 registrations before the first
// IsCompleted check, the loop body never ran, and the test asserted nothing at all --
// green here, and intermittently red on the trailing `passes > 0` in CI.
var writer = Task.Run(() =>
{
for (var i = 0; i < added; i++)
{
registry[(NpgsqlDbType)(100_000 + i)] = AnyMapping();
writingHasStarted.Set();
}

while (!readerIsFinished.IsSet)
{
for (var i = 0; i < added && !readerIsFinished.IsSet; i++)
{
registry[(NpgsqlDbType)(100_000 + i)] = AnyMapping();
}
}
});

var passes = 0;
while (!writer.IsCompleted)
try
{
writingHasStarted.Wait(patience).ShouldBeTrue();

for (var pass = 0; pass < passes; pass++)
{
// Each read must see a coherent snapshot: never fewer than what was there before
// the writer started, and never a null hole.
var seen = registry.ToList();
seen.Count.ShouldBeGreaterThanOrEqualTo(seeded);
seen.ShouldAllBe(mapping => mapping != null);
}
}
finally
{
// Each read must see a coherent snapshot: never fewer than what was there before the
// writer started, and never a null hole.
var seen = registry.ToList();
seen.Count.ShouldBeGreaterThanOrEqualTo(seeded);
seen.ShouldAllBe(mapping => mapping != null);
passes++;
// In a finally so a failed assertion above releases the writer rather than hanging
// the run in its churn loop.
readerIsFinished.Set();
}

writer.GetAwaiter().GetResult();
registry.Count.ShouldBe(seeded + added);
passes.ShouldBeGreaterThan(0);
}

[Fact]
Expand Down
Loading