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)