CultCache: every backing store marks the records it rewrites, through a protected CacheBackingStore.MarkWritten
The directory store's write of a generation marks each page it wrote once the manifest names it, so a Push+PushAll of a loaded drifted entry no longer leaves its persisted schema id stale. The single-file store marks through the same member, and a store outside GameCult.Caching now has a supported way to mark its own rewrite. The getter's doc names how a store records a rewrite. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
3a320d144d
commit
73d438dbcf
3 changed files with 24 additions and 5 deletions
|
|
@ -263,6 +263,7 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
|
|||
var keysToWrite = _dirtyKeys.Keys
|
||||
.OrderBy(value => value, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
var written = new List<CultStoredDocument>(keysToWrite.Length);
|
||||
foreach (var key in keysToWrite)
|
||||
{
|
||||
if (!Entries.TryGetValue(key, out var stored))
|
||||
|
|
@ -281,11 +282,15 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
|
|||
WriteFileAtomically(
|
||||
ContentAddressedRecordPath(indexRecord),
|
||||
pagePayload);
|
||||
written.Add(stored);
|
||||
}
|
||||
|
||||
WriteManifest(targetCatalog, currentIndex.Values
|
||||
.OrderBy(record => record.Key, StringComparer.Ordinal)
|
||||
.ToArray());
|
||||
// The manifest names the new pages: each was written under its own descriptor.
|
||||
foreach (var stored in written)
|
||||
MarkWritten(stored);
|
||||
|
||||
DeleteUnreferencedRecordPages(currentIndex.Values);
|
||||
|
||||
|
|
|
|||
|
|
@ -231,7 +231,10 @@ the id the record is persisted under (`CultStoredDocument.PersistedSchemaId`),
|
|||
not the one this cache decodes it as. A value change mints a `storedAt`
|
||||
strictly later than the record it replaces; writers bump a minted timestamp by
|
||||
one tick when it is not later. An entry loaded under compatible schema drift
|
||||
keeps its persisted id until its store rewrites it. A whole-file flush or
|
||||
keeps its persisted id until its store rewrites it; a store records each
|
||||
record its completed write rewrote with the protected
|
||||
`CacheBackingStore.MarkWritten`, and one that does not leaves the entry's
|
||||
`Expect` failing closed until the next pull. A whole-file flush or
|
||||
unconditional commit rewrites every record under the writer's current schema
|
||||
and keeps its `storedAt`, so another cache's `Expect` on a drifted record it
|
||||
read before that rewrite fails until it re-reads the store.
|
||||
|
|
|
|||
|
|
@ -393,7 +393,8 @@ namespace GameCult.Caching
|
|||
/// <summary>
|
||||
/// The schema id this record is durably stored under, as its backing store last read or wrote it. It differs from
|
||||
/// <see cref="CultDocumentDescriptor.SchemaId"/> only for a record loaded under compatible schema drift that its
|
||||
/// store has not rewritten since. Conditional commit compares this id and <see cref="StoredAt"/> with the disk.
|
||||
/// store has not rewritten since; a store records its rewrite with <see cref="CacheBackingStore.MarkWritten"/>.
|
||||
/// Conditional commit compares this id and <see cref="StoredAt"/> with the disk.
|
||||
/// </summary>
|
||||
public string PersistedSchemaId => _persisted.SchemaId;
|
||||
|
||||
|
|
@ -411,7 +412,7 @@ namespace GameCult.Caching
|
|||
|
||||
internal CultStoredDocument Resolved(object document) => new(Key, StoredAt, Descriptor, Variant!, document, _persisted);
|
||||
|
||||
// Only a store calls this, under the hold, after its completed durable write rewrote the record under its descriptor.
|
||||
// Reached only through CacheBackingStore.MarkWritten, the one way a store records a rewrite.
|
||||
internal void MarkWritten() => _persisted.SchemaId = Descriptor.SchemaId;
|
||||
|
||||
private sealed class PersistedIdentity
|
||||
|
|
@ -3322,6 +3323,16 @@ namespace GameCult.Caching
|
|||
{
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records that this store's completed durable write stored <paramref name="entry"/> under its descriptor, so its
|
||||
/// <see cref="CultStoredDocument.PersistedSchemaId"/> is <see cref="CultDocumentDescriptor.SchemaId"/> again. Call it
|
||||
/// under <c>Held</c>, once the write is durable, for each record the write rewrote; a record whose bytes the write
|
||||
/// kept is not marked. A rewrite left unmarked makes conditional commit compare the schema id the record was read
|
||||
/// under, which fails closed until the next pull.
|
||||
/// </summary>
|
||||
protected static void MarkWritten(CultStoredDocument entry) =>
|
||||
(entry ?? throw new ArgumentNullException(nameof(entry))).MarkWritten();
|
||||
}
|
||||
|
||||
public abstract class SingleFileBackingStore : CacheBackingStore
|
||||
|
|
@ -3416,7 +3427,7 @@ namespace GameCult.Caching
|
|||
}
|
||||
|
||||
foreach (var entry in Entries.Values)
|
||||
entry.MarkWritten();
|
||||
MarkWritten(entry);
|
||||
MarkFlushSucceeded();
|
||||
});
|
||||
}
|
||||
|
|
@ -3465,7 +3476,7 @@ namespace GameCult.Caching
|
|||
if (!ontoDisk)
|
||||
{
|
||||
foreach (var entry in Entries.Values)
|
||||
entry.MarkWritten();
|
||||
MarkWritten(entry);
|
||||
}
|
||||
|
||||
MarkFlushSucceeded();
|
||||
|
|
|
|||
Loading…
Reference in a new issue