Caching: an empty direct flush still applies as before; only a write that checked keys and has nothing else to write writes no file. Tests: the delisting writer declares the widget; the checked-key flush also writes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
649ac1860e
commit
cec09806b0
2 changed files with 7 additions and 3 deletions
|
|
@ -4164,7 +4164,7 @@ namespace GameCult.Caching
|
|||
|
||||
// Flush, commit and Mesh's write all end here, with the durable store they read under the lock they hold. First, a key the write
|
||||
// names or checks whose durable record is under an id the writer does not declare refuses the write (RefuseUndeclaredOnDisk); a
|
||||
// write that then has nothing to write (it only checked keys) writes no file. Then a store that holds
|
||||
// write that then has nothing to write but keys it checked writes no file. Then a store that holds
|
||||
// a variant, or would after this write, is written only while its record versions are what this store last read: a variant
|
||||
// resolves against other records, and a write decodes none it did not stage, so it cannot judge another writer's. Versions
|
||||
// only: a write hashes other records' bytes, never decodes them. Otherwise the whole write is refused and what it refused is
|
||||
|
|
@ -4175,7 +4175,7 @@ namespace GameCult.Caching
|
|||
Durable? durable, IReadOnlyCollection<CultStagedRecord> upserts, IReadOnlyCollection<string> removals, IEnumerable<string> checks)
|
||||
{
|
||||
RefuseUndeclaredOnDisk(durable, upserts, removals, checks);
|
||||
if (upserts.Count == 0 && removals.Count == 0)
|
||||
if (upserts.Count == 0 && removals.Count == 0 && (checks.Any() || StagedChecks.Count > 0))
|
||||
return Array.Empty<(CultStagedRecord, string)>();
|
||||
RefuseWriteIntoMovedVariantStore(durable, upserts, removals);
|
||||
var durableRecords = durable?.Snapshot.Records ?? Array.Empty<CultPersistedRecord>();
|
||||
|
|
|
|||
|
|
@ -1125,6 +1125,7 @@ namespace GameCult.Caching.Tests
|
|||
using (var seed = Open(path, Full, directory))
|
||||
seed.Commit(batch => batch.Upsert(Deck, DeckOf("d"), D));
|
||||
using var a = Open(path, DeckOnly, directory);
|
||||
a.UpsertAsync(Deck, DeckOf("e"), E).GetAwaiter().GetResult();
|
||||
Assert.That(a.Remove(W), Is.False);
|
||||
using (var b = Open(path, Full, directory))
|
||||
b.Commit(batch => batch.Upsert(Deck, DeckOf("theirs"), W));
|
||||
|
|
@ -1132,6 +1133,7 @@ namespace GameCult.Caching.Tests
|
|||
|
||||
Assert.DoesNotThrow(() => a.FlushAsync().GetAwaiter().GetResult());
|
||||
|
||||
Assert.That(Read(path).Records.Select(record => record.Key), Is.EquivalentTo(new[] { "d", "e", "w" }), "the staged write landed beside the checked key");
|
||||
Assert.That(Stored(path, W), Is.EqualTo(theirs), "the record at the checked key is still the other writer's bytes");
|
||||
using (var c = Open(path, Full, directory))
|
||||
c.Commit(batch => batch.Upsert(Widget, WidgetOf("now a widget"), W));
|
||||
|
|
@ -1173,7 +1175,9 @@ namespace GameCult.Caching.Tests
|
|||
var path = Seeded("delist.cc", directory);
|
||||
using var a = Open(path, DeckOnly, directory);
|
||||
Assert.That(StoreOf(a).ForeignRecords.Select(record => record.Key), Is.EqualTo(new[] { "w" }));
|
||||
Move(path, W, "moved", directory);
|
||||
// The writer declares the widget's id, so it may replace the widget with a deck record under an id a reads but does not declare.
|
||||
using (var other = Open(path, CultDocumentRegistry.ForTypes(new[] { MovingDeck, Widget }), directory))
|
||||
Assert.That(other.Commit(batch => batch.Upsert(MovingDeck, New(MovingDeck, ("Name", "moved")), W)), Is.True);
|
||||
|
||||
Assert.That(a.Remove(W), Is.False);
|
||||
var refusal = Assert.Throws<CultSchemaConflictException>(() => a.FlushAsync().GetAwaiter().GetResult())!;
|
||||
|
|
|
|||
Loading…
Reference in a new issue