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
|
var keysToWrite = _dirtyKeys.Keys
|
||||||
.OrderBy(value => value, StringComparer.Ordinal)
|
.OrderBy(value => value, StringComparer.Ordinal)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
var written = new List<CultStoredDocument>(keysToWrite.Length);
|
||||||
foreach (var key in keysToWrite)
|
foreach (var key in keysToWrite)
|
||||||
{
|
{
|
||||||
if (!Entries.TryGetValue(key, out var stored))
|
if (!Entries.TryGetValue(key, out var stored))
|
||||||
|
|
@ -281,11 +282,15 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
|
||||||
WriteFileAtomically(
|
WriteFileAtomically(
|
||||||
ContentAddressedRecordPath(indexRecord),
|
ContentAddressedRecordPath(indexRecord),
|
||||||
pagePayload);
|
pagePayload);
|
||||||
|
written.Add(stored);
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteManifest(targetCatalog, currentIndex.Values
|
WriteManifest(targetCatalog, currentIndex.Values
|
||||||
.OrderBy(record => record.Key, StringComparer.Ordinal)
|
.OrderBy(record => record.Key, StringComparer.Ordinal)
|
||||||
.ToArray());
|
.ToArray());
|
||||||
|
// The manifest names the new pages: each was written under its own descriptor.
|
||||||
|
foreach (var stored in written)
|
||||||
|
MarkWritten(stored);
|
||||||
|
|
||||||
DeleteUnreferencedRecordPages(currentIndex.Values);
|
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`
|
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
|
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
|
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
|
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
|
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.
|
read before that rewrite fails until it re-reads the store.
|
||||||
|
|
|
||||||
|
|
@ -393,7 +393,8 @@ namespace GameCult.Caching
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The schema id this record is durably stored under, as its backing store last read or wrote it. It differs from
|
/// 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
|
/// <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>
|
/// </summary>
|
||||||
public string PersistedSchemaId => _persisted.SchemaId;
|
public string PersistedSchemaId => _persisted.SchemaId;
|
||||||
|
|
||||||
|
|
@ -411,7 +412,7 @@ namespace GameCult.Caching
|
||||||
|
|
||||||
internal CultStoredDocument Resolved(object document) => new(Key, StoredAt, Descriptor, Variant!, document, _persisted);
|
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;
|
internal void MarkWritten() => _persisted.SchemaId = Descriptor.SchemaId;
|
||||||
|
|
||||||
private sealed class PersistedIdentity
|
private sealed class PersistedIdentity
|
||||||
|
|
@ -3322,6 +3323,16 @@ namespace GameCult.Caching
|
||||||
{
|
{
|
||||||
IsDirty = false;
|
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
|
public abstract class SingleFileBackingStore : CacheBackingStore
|
||||||
|
|
@ -3416,7 +3427,7 @@ namespace GameCult.Caching
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var entry in Entries.Values)
|
foreach (var entry in Entries.Values)
|
||||||
entry.MarkWritten();
|
MarkWritten(entry);
|
||||||
MarkFlushSucceeded();
|
MarkFlushSucceeded();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -3465,7 +3476,7 @@ namespace GameCult.Caching
|
||||||
if (!ontoDisk)
|
if (!ontoDisk)
|
||||||
{
|
{
|
||||||
foreach (var entry in Entries.Values)
|
foreach (var entry in Entries.Values)
|
||||||
entry.MarkWritten();
|
MarkWritten(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
MarkFlushSucceeded();
|
MarkFlushSucceeded();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue