From 79f144d9eccea2b3377a4a7233e3432fff35ecec Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 06:16:57 +0200 Subject: [PATCH] CultNetDatabase.Dispose publishes what a writer logged and had not yet published The dispose test showed journal-first alone still lost the change: Dispose won the race against the writer's publication and cleared the stash. Dispose now drains the stash through the publisher after the journal is disposed (so nothing is stashed afterwards) and before the subject completes. Co-Authored-By: Claude Sonnet 5.5 --- src/GameCult.Networking/CultNetDatabase.cs | 17 ++++++++++++++++- .../CultNetJournalTests.cs | 4 ++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/GameCult.Networking/CultNetDatabase.cs b/src/GameCult.Networking/CultNetDatabase.cs index 1cb1a1e0..bedf91c6 100644 --- a/src/GameCult.Networking/CultNetDatabase.cs +++ b/src/GameCult.Networking/CultNetDatabase.cs @@ -1180,10 +1180,22 @@ namespace GameCult.Networking } _disposed = true; + // The journal first: disposing it waits for a journal in flight, so nothing is stashed after this line. What a + // writer has logged and not yet published is then published here, so a change the log holds is never dropped. _cacheJournal.Dispose(); _cacheChanges.Dispose(); + Publication[] unpublished; lock (_logGate) + { + unpublished = _stash.OrderBy(entry => entry.Key).Select(entry => entry.Value).ToArray(); _stash.Clear(); + } + + foreach (var publication in unpublished) + { + Publish(publication); + } + _changes.Dispose(); } @@ -1288,6 +1300,10 @@ namespace GameCult.Networking } } + Publish(publication); + } + + private void Publish(Publication publication) => PublishUntyped( publication.DocumentType, publication.Kind, @@ -1296,7 +1312,6 @@ namespace GameCult.Networking publication.Shard, publication.Document, publication.PreviousDocument); - } private void RecordReplicatedEntry( CultNetShardDescriptor shard, diff --git a/tests/GameCult.Networking.Tests/CultNetJournalTests.cs b/tests/GameCult.Networking.Tests/CultNetJournalTests.cs index 7ba70dc8..d6f9d6d6 100644 --- a/tests/GameCult.Networking.Tests/CultNetJournalTests.cs +++ b/tests/GameCult.Networking.Tests/CultNetJournalTests.cs @@ -1022,8 +1022,8 @@ namespace GameCult.Networking.Tests Assert.That(logger.Errors, Is.Empty); } - // The journal is disposed before the observer: an admission the journal logs while the database is being disposed is - // still published, never logged and then dropped. + // A change the log holds is published, however Dispose interleaves with the write: Dispose waits for a journal in + // flight (disposing the journal first) and publishes whatever the writer had logged and not yet published. [Test] public async Task ADatabaseDisposedWhileAWriteIsInItsLogAppendStillPublishesWhatItLogged() {