Preserve dirty CultCache state during pulls
This commit is contained in:
parent
3093b536d4
commit
49c43baa4c
15 changed files with 117 additions and 5 deletions
|
|
@ -14,7 +14,7 @@
|
|||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<Copyright>© Meta Vens and contributors</Copyright>
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<Version>1.0.17</Version>
|
||||
<Version>1.0.18</Version>
|
||||
|
||||
<!-- Symbols/debugging -->
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
|
|||
|
||||
foreach (var pair in loaded)
|
||||
{
|
||||
if (_dirtyKeys.ContainsKey(pair.Key) || _deletedKeys.ContainsKey(pair.Key))
|
||||
continue;
|
||||
|
||||
if (Entries.TryGetValue(pair.Key, out var existing) &&
|
||||
string.Equals(existing.StoredAt, pair.Value.StoredAt, StringComparison.Ordinal) &&
|
||||
string.Equals(existing.Descriptor.SchemaId, pair.Value.Descriptor.SchemaId, StringComparison.Ordinal))
|
||||
|
|
@ -91,14 +94,15 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
|
|||
|
||||
foreach (var removedKey in Entries.Keys.Where(key => !loaded.ContainsKey(key)).ToArray())
|
||||
{
|
||||
if (_dirtyKeys.ContainsKey(removedKey) || _deletedKeys.ContainsKey(removedKey))
|
||||
continue;
|
||||
|
||||
if (Entries.TryRemove(removedKey, out var removed))
|
||||
EntryDeleted.OnNext(removed);
|
||||
}
|
||||
|
||||
SetLastSchemaMigrationReports(reports);
|
||||
_dirtyKeys.Clear();
|
||||
_deletedKeys.Clear();
|
||||
IsDirty = false;
|
||||
IsDirty = !_dirtyKeys.IsEmpty || !_deletedKeys.IsEmpty;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
|
|||
|
|
@ -2092,6 +2092,13 @@ namespace GameCult.Caching
|
|||
return;
|
||||
}
|
||||
|
||||
// A single-file snapshot cannot distinguish local dirty keys from clean keys.
|
||||
// Pulling while local mutations are staged would let an older disk snapshot
|
||||
// erase those mutations before the next flush. Flush first; clean readers can
|
||||
// still poll external snapshots incrementally.
|
||||
if (IsDirty)
|
||||
return;
|
||||
|
||||
CultPersistedStoreSnapshot snapshot;
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -107,6 +107,53 @@ namespace GameCult.Caching.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SingleFileMessagePackBackingStore_PullAll_DoesNotEraseUnflushedLocalMutations()
|
||||
{
|
||||
var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.msgpack");
|
||||
|
||||
try
|
||||
{
|
||||
using (var seed = await CultCacheMessagePack.OpenAsync(filePath))
|
||||
{
|
||||
await seed.UpsertAsync(new NamedTestEntry
|
||||
{
|
||||
Name = "persisted",
|
||||
Value = "before-local-write"
|
||||
});
|
||||
await seed.FlushAsync();
|
||||
}
|
||||
|
||||
CultRecordHandle<NamedTestEntry> localHandle;
|
||||
using (var cache = await CultCacheMessagePack.OpenAsync(filePath))
|
||||
{
|
||||
localHandle = await cache.UpsertAsync(new NamedTestEntry
|
||||
{
|
||||
Name = "local",
|
||||
Value = "must-survive-pull"
|
||||
});
|
||||
|
||||
Assert.That(cache.IsDirty, Is.True);
|
||||
await cache.PullAllBackingStoresAsync();
|
||||
|
||||
Assert.That(cache.Get<NamedTestEntry>(localHandle.Key)?.Value,
|
||||
Is.EqualTo("must-survive-pull"));
|
||||
Assert.That(cache.IsDirty, Is.True,
|
||||
"pulling a clean disk snapshot must not pardon staged local mutations");
|
||||
await cache.FlushAsync();
|
||||
}
|
||||
|
||||
using var reopened = await CultCacheMessagePack.OpenAsync(filePath);
|
||||
Assert.That(reopened.Get<NamedTestEntry>(localHandle.Key)?.Value,
|
||||
Is.EqualTo("must-survive-pull"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task CultCache_DirtyState_Tracks_Mutations_And_ExplicitFlush()
|
||||
{
|
||||
|
|
@ -425,6 +472,60 @@ namespace GameCult.Caching.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DirectoryMessagePackBackingStore_PullAll_PreservesUnflushedLocalKeys()
|
||||
{
|
||||
var filePath = Path.Combine(Path.GetTempPath(), $"cultlib-tests-{Guid.NewGuid():N}.cc");
|
||||
var recordsPath = DirectoryMessagePackBackingStore.DefaultRecordDirectoryPath(filePath);
|
||||
|
||||
try
|
||||
{
|
||||
using (var seed = await CultCacheMessagePack.OpenAsync(
|
||||
filePath,
|
||||
new CultCacheOpenOptions { UseDirectoryStore = true }))
|
||||
{
|
||||
await seed.UpsertAsync(new NamedTestEntry
|
||||
{
|
||||
Name = "persisted",
|
||||
Value = "before-local-write"
|
||||
});
|
||||
await seed.FlushAsync();
|
||||
}
|
||||
|
||||
CultRecordHandle<NamedTestEntry> localHandle;
|
||||
using (var cache = await CultCacheMessagePack.OpenAsync(
|
||||
filePath,
|
||||
new CultCacheOpenOptions { UseDirectoryStore = true }))
|
||||
{
|
||||
localHandle = await cache.UpsertAsync(new NamedTestEntry
|
||||
{
|
||||
Name = "local",
|
||||
Value = "must-survive-pull"
|
||||
});
|
||||
|
||||
await cache.PullAllBackingStoresAsync();
|
||||
|
||||
Assert.That(cache.Get<NamedTestEntry>(localHandle.Key)?.Value,
|
||||
Is.EqualTo("must-survive-pull"));
|
||||
Assert.That(cache.IsDirty, Is.True);
|
||||
await cache.FlushAsync();
|
||||
}
|
||||
|
||||
using var reopened = await CultCacheMessagePack.OpenAsync(
|
||||
filePath,
|
||||
new CultCacheOpenOptions { UseDirectoryStore = true });
|
||||
Assert.That(reopened.Get<NamedTestEntry>(localHandle.Key)?.Value,
|
||||
Is.EqualTo("must-survive-pull"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(filePath))
|
||||
File.Delete(filePath);
|
||||
if (Directory.Exists(recordsPath))
|
||||
Directory.Delete(recordsPath, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task DirectoryMessagePackBackingStore_Remains_Dirty_When_Deletion_Fails()
|
||||
{
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "org.gamecult.cultlib",
|
||||
"displayName": "CultLib",
|
||||
"version": "1.0.17",
|
||||
"version": "1.0.18",
|
||||
"unity": "2021.3",
|
||||
"description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.",
|
||||
"license": "MIT",
|
||||
|
|
|
|||
Loading…
Reference in a new issue