diff --git a/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs b/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs
index 14dd3d09e..de47a3762 100644
--- a/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs
+++ b/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs
@@ -688,6 +688,42 @@ private void SavePrefetchArgs(string targetCommit, bool hydrate)
}
}
+ ///
+ /// Updates the noop prefetch cache after a successful prefetch that was
+ /// handled externally (e.g. offloaded to the mount process). This mirrors
+ /// the logic in but is callable without a
+ /// BlobPrefetcher instance.
+ ///
+ public static void UpdateNoopCache(
+ FileBasedDictionary prefetchCache,
+ int maxCacheSize,
+ string commitId,
+ List files,
+ List folders,
+ bool hydrate)
+ {
+ if (prefetchCache == null || maxCacheSize <= 0)
+ {
+ return;
+ }
+
+ string cacheKey = ComputeCacheKey(files, folders, hydrate);
+
+ Dictionary allEntries = prefetchCache.GetAllKeysAndValues();
+ if (allEntries.Count >= maxCacheSize && !allEntries.ContainsKey(cacheKey))
+ {
+ using (Dictionary.Enumerator enumerator = allEntries.GetEnumerator())
+ {
+ if (enumerator.MoveNext())
+ {
+ prefetchCache.RemoveAndFlush(enumerator.Current.Key);
+ }
+ }
+ }
+
+ prefetchCache.SetValueAndFlush(cacheKey, commitId);
+ }
+
internal static string ComputeCacheKey(List files, List folders, bool hydrate)
{
List sortedFiles = new List(files);
diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchBlobsOffloadTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchBlobsOffloadTests.cs
index c3fcf977b..98380456f 100644
--- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchBlobsOffloadTests.cs
+++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchBlobsOffloadTests.cs
@@ -42,12 +42,13 @@ public void PrefetchBlobsMountedReportsStats()
public void PrefetchBlobsUnmountedFallsBackToDirectAuth()
{
// Unmount, then blob prefetch should fall back to direct auth
- // and still succeed.
+ // and still succeed. Use a file not prefetched by earlier tests
+ // so the noop cache doesn't short-circuit.
this.Enlistment.UnmountGVFS();
try
{
- string output = this.Enlistment.Prefetch($"--files {Path.Combine("GVFS", "GVFS", "Program.cs")}");
+ string output = this.Enlistment.Prefetch($"--files {Path.Combine("GVFS", "GVFS.Common", "GVFSEnlistment.cs")}");
output.ShouldContain("Matched blobs:");
output.ShouldContain("Downloaded:");
}
@@ -69,12 +70,14 @@ public void PrefetchBlobsMountedWithFolders()
public void PrefetchBlobsMountedAfterRemount()
{
// After unmount + remount, blob prefetch should work via
- // the mount process again.
+ // the mount process again. Since this file was already
+ // prefetched in Order(1), the noop cache correctly detects
+ // there's nothing new to download.
this.Enlistment.UnmountGVFS();
this.Enlistment.MountGVFS();
string output = this.Enlistment.Prefetch($"--files {Path.Combine("GVFS", "GVFS", "Program.cs")}");
- output.ShouldContain("Matched blobs:");
+ output.ShouldContain("Nothing new to prefetch.");
}
}
}
diff --git a/GVFS/GVFS.Mount/InProcessMount.cs b/GVFS/GVFS.Mount/InProcessMount.cs
index 1e1ea3712..f8e054081 100644
--- a/GVFS/GVFS.Mount/InProcessMount.cs
+++ b/GVFS/GVFS.Mount/InProcessMount.cs
@@ -1123,7 +1123,8 @@ private void HandlePrefetchBlobsRequest(NamedPipeMessages.Message message, Named
objectRequestor,
request.Files,
request.Folders,
- lastPrefetchArgs,
+ prefetchCache: null,
+ maxCacheSize: 0,
chunkSize: 4000,
searchThreadCount: maxThreads,
downloadThreadCount: downloadThreads,
diff --git a/GVFS/GVFS/CommandLine/PrefetchVerb.cs b/GVFS/GVFS/CommandLine/PrefetchVerb.cs
index d2a084414..ef6d3ddf9 100644
--- a/GVFS/GVFS/CommandLine/PrefetchVerb.cs
+++ b/GVFS/GVFS/CommandLine/PrefetchVerb.cs
@@ -231,12 +231,15 @@ protected override void Execute(GVFSEnlistment enlistment)
cacheServerFromConfig,
out objectRequestor,
out resolvedCacheServer);
- this.PrefetchBlobs(tracer, enlistment, headCommitId, filesList, foldersList, lastPrefetchArgs, objectRequestor, resolvedCacheServer);
+ this.PrefetchBlobs(tracer, enlistment, headCommitId, filesList, foldersList, prefetchCache, prefetchCacheSize, objectRequestor, resolvedCacheServer);
}
else
{
- // Mount handled download — now hydrate locally
+ // Mount handled download — hydrate locally, then update noop
+ // cache. Cache update is after hydration so a hydration failure
+ // doesn't suppress the retry on the next run.
this.HydrateMatchingFiles(tracer, enlistment, filesList, foldersList);
+ BlobPrefetcher.UpdateNoopCache(prefetchCache, prefetchCacheSize, headCommitId, filesList, foldersList, this.HydrateFiles);
}
}
else if (!this.TryPrefetchBlobsViaMountProcess(tracer, enlistment, filesList, foldersList, headCommitId))
@@ -251,6 +254,11 @@ protected override void Execute(GVFSEnlistment enlistment)
out resolvedCacheServer);
this.PrefetchBlobs(tracer, enlistment, headCommitId, filesList, foldersList, prefetchCache, prefetchCacheSize, objectRequestor, resolvedCacheServer);
}
+ else
+ {
+ // Mount handled download — update noop cache so repeat runs are skipped
+ BlobPrefetcher.UpdateNoopCache(prefetchCache, prefetchCacheSize, headCommitId, filesList, foldersList, hydrate: false);
+ }
}
}
catch (VerbAbortedException)