From 7d25d92bd7d7d9e70a85b7dc12ee26c9e18f4253 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 23 Oct 2026 01:46:09 +0200 Subject: [PATCH] CachelessSingleFileTests: a call keeps its registry while another call waits at the gate Co-Authored-By: Claude Sonnet 5.5 --- .../CachelessSingleFileTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/GameCult.Caching.Tests/CachelessSingleFileTests.cs b/tests/GameCult.Caching.Tests/CachelessSingleFileTests.cs index ea153a96..a9e9cfda 100644 --- a/tests/GameCult.Caching.Tests/CachelessSingleFileTests.cs +++ b/tests/GameCult.Caching.Tests/CachelessSingleFileTests.cs @@ -60,6 +60,8 @@ namespace GameCult.Caching.Tests private int _mostInside; public readonly ManualResetEventSlim Entered = new(); public readonly ManualResetEventSlim Release = new(); + public CultDocumentRegistry? RegistryBeforeWait; + public CultDocumentRegistry? RegistryAfterWait; public GatedStore(string path) : base(path) { } @@ -83,7 +85,11 @@ namespace GameCult.Caching.Tests try { if (Interlocked.Exchange(ref _held, 1) == 0) + { + RegistryBeforeWait = Registry; Release.Wait(TimeSpan.FromSeconds(30)); + RegistryAfterWait = Registry; + } var store = CultDocumentMessagePackSerialization.ReadStore(data); CultDocumentMessagePackSerialization.RequireSingleFileFormat(store.Snapshot); return store; @@ -121,6 +127,28 @@ namespace GameCult.Caching.Tests Assert.That(second.Result!.Records, Has.Length.EqualTo(1)); } + // A call's registry is attached under the store's gate: a second call with another registry waits for the first, so the + // first sees its own registry from its start to its end. + [Test] + public async Task ACallKeepsItsRegistryWhileAnotherCallWaits() + { + var mine = CultDocumentRegistry.ForTypes(new[] { typeof(IdDeck) }); + var other = CultDocumentRegistry.ForTypes(new[] { typeof(DeclaredBase) }); + new SingleFileMessagePackBackingStore(_path).WriteRecord(Record("a", One, 1), One); + var store = new GatedStore(_path); + + var first = Task.Run(() => store.WriteDocument(new CultRecordKey("first"), new IdDeck { Name = "first" }, registry: mine)); + Assert.That(store.Entered.Wait(TimeSpan.FromSeconds(10)), Is.True, "the first call reached the reader"); + var second = Task.Run(() => store.WriteDocument(new CultRecordKey("second"), new DeclaredBase { Name = "second" }, registry: other)); + await Task.Delay(500); + store.Release.Set(); + await Task.WhenAll(first, second); + + Assert.That(store.RegistryBeforeWait, Is.SameAs(mine)); + Assert.That(store.RegistryAfterWait, Is.SameAs(mine), "the second call's registry did not replace the first's mid-call"); + Assert.That(new SingleFileMessagePackBackingStore(_path).ReadStoreSnapshot()!.Records.Select(record => record.Key), Is.EquivalentTo(new[] { "a", "first", "second" })); + } + // WriteDocument stores the document as T: the schema id, the id plan, the header and the bytes all come from the one // declared type, as they did when CultMesh encoded typeof(T). [Test]