Retry an unlocked directory load only when the manifest moved
A missing or hash-failing page under an unchanged manifest is corruption, not a writer mid-commit, but every InvalidDataException was retried five times and reported as "did not settle" with the real cause buried. The retry now also requires the manifest bytes to have changed; otherwise the corruption error is thrown on the first attempt. A vanished page directory is a race under the same condition. The reload test's writer now commits under every unleased read, so it only settles if the retry takes the lease that commit created. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
04c7c6bcd8
commit
838bbfffec
2 changed files with 42 additions and 6 deletions
|
|
@ -68,7 +68,8 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
|
|||
reports.Clear();
|
||||
// Only pages named by the manifest read under the lease are loaded; orphaned pages are never loaded. With no
|
||||
// lock to lease (none created yet, or a store copied without it) a writer may commit mid-load, so an unleased
|
||||
// load stands only if its manifest is still current and every page it named read back; otherwise it reloads.
|
||||
// load stands only if its manifest is still current; if the manifest moved it reloads, under the lease the
|
||||
// writer created. A page that fails under an unmoved manifest is corruption, not a race.
|
||||
using var lease = AcquireCommitLease(wait: true, create: false);
|
||||
var manifestBytes = ReadManifestBytes();
|
||||
var manifest = ParseManifest(manifestBytes);
|
||||
|
|
@ -89,7 +90,7 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
|
|||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception exception) when (lease == null && IsTornGeneration(exception))
|
||||
catch (Exception exception) when (lease == null && IsTornGeneration(exception) && !SameBytes(manifestBytes, ReadManifestBytes()))
|
||||
{
|
||||
if (attempt == UnleasedLoadAttempts)
|
||||
throw Unsettled(exception);
|
||||
|
|
@ -385,11 +386,12 @@ public sealed class DirectoryMessagePackBackingStore : CacheBackingStore
|
|||
private static bool SameBytes(byte[]? first, byte[]? second) =>
|
||||
first == null ? second == null : second != null && first.SequenceEqual(second);
|
||||
|
||||
// A page the manifest named that vanished or was replaced: a writer committed mid-load.
|
||||
// A page the manifest named that vanished or was replaced. It is a writer committing mid-load only when the manifest
|
||||
// moved too; under an unchanged manifest the same failure is corruption and is thrown as it is.
|
||||
private static bool IsTornGeneration(Exception exception) =>
|
||||
exception is AggregateException aggregate
|
||||
? aggregate.Flatten().InnerExceptions.All(IsTornGeneration)
|
||||
: exception is InvalidDataException or FileNotFoundException;
|
||||
: exception is InvalidDataException or FileNotFoundException or DirectoryNotFoundException;
|
||||
|
||||
private InvalidOperationException Unsettled(Exception? last) => new(
|
||||
$"Directory store {_manifestFile.FullName} changed under every one of {UnleasedLoadAttempts} unlocked loads; it did not settle.", last);
|
||||
|
|
|
|||
Loading…
Reference in a new issue