99using System . Collections . Generic ;
1010using System . IO ;
1111using System . Linq ;
12+ using System . Security . Cryptography ;
13+ using System . Text ;
1214using System . Threading ;
1315
1416namespace GVFS . Common . Prefetch
@@ -32,7 +34,13 @@ public class BlobPrefetcher
3234 private const string AreaPath = nameof ( BlobPrefetcher ) ;
3335 private static string pathSeparatorString = Path . DirectorySeparatorChar . ToString ( ) ;
3436
35- private FileBasedDictionary < string , string > lastPrefetchArgs ;
37+ public const string BlobPrefetchCacheFile = "BlobPrefetchCache.dat" ;
38+ public const string PrefetchCacheSizeConfigKey = GVFSConstants . GitConfig . GVFSPrefix + "prefetch-cache-size" ;
39+ public const int DefaultPrefetchCacheSize = 100 ;
40+ public const int MaxPrefetchCacheSize = 1000 ;
41+
42+ private FileBasedDictionary < string , string > prefetchCache ;
43+ private int maxCacheSize ;
3644
3745 public BlobPrefetcher (
3846 ITracer tracer ,
@@ -42,7 +50,7 @@ public BlobPrefetcher(
4250 int searchThreadCount ,
4351 int downloadThreadCount ,
4452 int indexThreadCount )
45- : this ( tracer , enlistment , objectRequestor , null , null , null , chunkSize , searchThreadCount , downloadThreadCount , indexThreadCount )
53+ : this ( tracer , enlistment , objectRequestor , null , null , null , DefaultPrefetchCacheSize , chunkSize , searchThreadCount , downloadThreadCount , indexThreadCount )
4654 {
4755 }
4856
@@ -52,7 +60,8 @@ public BlobPrefetcher(
5260 GitObjectsHttpRequestor objectRequestor ,
5361 List < string > fileList ,
5462 List < string > folderList ,
55- FileBasedDictionary < string , string > lastPrefetchArgs ,
63+ FileBasedDictionary < string , string > prefetchCache ,
64+ int maxCacheSize ,
5665 int chunkSize ,
5766 int searchThreadCount ,
5867 int downloadThreadCount ,
@@ -70,7 +79,8 @@ public BlobPrefetcher(
7079 this . FileList = fileList ?? new List < string > ( ) ;
7180 this . FolderList = folderList ?? new List < string > ( ) ;
7281
73- this . lastPrefetchArgs = lastPrefetchArgs ;
82+ this . prefetchCache = prefetchCache ;
83+ this . maxCacheSize = maxCacheSize ;
7484
7585 // We never want to update config settings for a GVFSEnlistment
7686 this . SkipConfigUpdate = enlistment is GVFSEnlistment ;
@@ -127,39 +137,26 @@ public static bool TryLoadFileList(Enlistment enlistment, string filesInput, str
127137
128138 public static bool IsNoopPrefetch (
129139 ITracer tracer ,
130- FileBasedDictionary < string , string > lastPrefetchArgs ,
140+ FileBasedDictionary < string , string > prefetchCache ,
131141 string commitId ,
132142 List < string > files ,
133143 List < string > folders ,
134144 bool hydrateFilesAfterDownload )
135145 {
136- if ( lastPrefetchArgs != null &&
137- lastPrefetchArgs . TryGetValue ( PrefetchArgs . CommitId , out string lastCommitId ) &&
138- lastPrefetchArgs . TryGetValue ( PrefetchArgs . Files , out string lastFilesString ) &&
139- lastPrefetchArgs . TryGetValue ( PrefetchArgs . Folders , out string lastFoldersString ) &&
140- lastPrefetchArgs . TryGetValue ( PrefetchArgs . Hydrate , out string lastHydrateString ) )
146+ if ( prefetchCache != null )
141147 {
142- string newFilesString = GVFSJsonOptions . Serialize ( files ) ;
143- string newFoldersString = GVFSJsonOptions . Serialize ( folders ) ;
144- bool isNoop =
145- commitId == lastCommitId &&
146- hydrateFilesAfterDownload . ToString ( ) == lastHydrateString &&
147- newFilesString == lastFilesString &&
148- newFoldersString == lastFoldersString ;
148+ string cacheKey = ComputeCacheKey ( files , folders , hydrateFilesAfterDownload ) ;
149+ bool hasEntry = prefetchCache . TryGetValue ( cacheKey , out string cachedCommitId ) ;
150+ bool isNoop = hasEntry && commitId == cachedCommitId ;
149151
150152 tracer . RelatedEvent (
151153 EventLevel . Informational ,
152154 "BlobPrefetcher.IsNoopPrefetch" ,
153155 new EventMetadata
154156 {
155- { "Last" + PrefetchArgs . CommitId , lastCommitId } ,
156- { "Last" + PrefetchArgs . Files , lastFilesString } ,
157- { "Last" + PrefetchArgs . Folders , lastFoldersString } ,
158- { "Last" + PrefetchArgs . Hydrate , lastHydrateString } ,
159- { "New" + PrefetchArgs . CommitId , commitId } ,
160- { "New" + PrefetchArgs . Files , newFilesString } ,
161- { "New" + PrefetchArgs . Folders , newFoldersString } ,
162- { "New" + PrefetchArgs . Hydrate , hydrateFilesAfterDownload . ToString ( ) } ,
157+ { "CacheKey" , cacheKey } ,
158+ { "CachedCommitId" , cachedCommitId ?? "(none)" } ,
159+ { "NewCommitId" , commitId } ,
163160 { "Result" , isNoop } ,
164161 } ) ;
165162
@@ -583,33 +580,50 @@ private bool IsSymbolicRef(string targetCommitish)
583580
584581 private void SavePrefetchArgs ( string targetCommit , bool hydrate )
585582 {
586- if ( this . lastPrefetchArgs != null )
583+ if ( this . prefetchCache != null && this . maxCacheSize > 0 )
587584 {
588- this . lastPrefetchArgs . SetValuesAndFlush (
589- new [ ]
585+ string cacheKey = ComputeCacheKey ( this . FileList , this . FolderList , hydrate ) ;
586+
587+ Dictionary < string , string > allEntries = this . prefetchCache . GetAllKeysAndValues ( ) ;
588+ if ( allEntries . Count >= this . maxCacheSize && ! allEntries . ContainsKey ( cacheKey ) )
589+ {
590+ // Evict one arbitrary entry to make room
591+ using ( Dictionary < string , string > . Enumerator enumerator = allEntries . GetEnumerator ( ) )
590592 {
591- new KeyValuePair < string , string > ( PrefetchArgs . CommitId , targetCommit ) ,
592- new KeyValuePair < string , string > ( PrefetchArgs . Files , GVFSJsonOptions . Serialize ( this . FileList ) ) ,
593- new KeyValuePair < string , string > ( PrefetchArgs . Folders , GVFSJsonOptions . Serialize ( this . FolderList ) ) ,
594- new KeyValuePair < string , string > ( PrefetchArgs . Hydrate , hydrate . ToString ( ) ) ,
595- } ) ;
593+ if ( enumerator . MoveNext ( ) )
594+ {
595+ this . prefetchCache . RemoveAndFlush ( enumerator . Current . Key ) ;
596+ }
597+ }
598+ }
599+
600+ this . prefetchCache . SetValueAndFlush ( cacheKey , targetCommit ) ;
596601 }
597602 }
598603
604+ internal static string ComputeCacheKey ( List < string > files , List < string > folders , bool hydrate )
605+ {
606+ List < string > sortedFiles = new List < string > ( files ) ;
607+ sortedFiles . Sort ( StringComparer . Ordinal ) ;
608+
609+ List < string > sortedFolders = new List < string > ( folders ) ;
610+ sortedFolders . Sort ( StringComparer . Ordinal ) ;
611+
612+ string compositeInput = string . Join ( "\n " ,
613+ GVFSJsonOptions . Serialize ( sortedFiles ) ,
614+ GVFSJsonOptions . Serialize ( sortedFolders ) ,
615+ hydrate . ToString ( ) ) ;
616+
617+ byte [ ] hashBytes = SHA256 . HashData ( Encoding . UTF8 . GetBytes ( compositeInput ) ) ;
618+ return Convert . ToHexString ( hashBytes ) ;
619+ }
620+
599621 public class FetchException : Exception
600622 {
601623 public FetchException ( string format , params object [ ] args )
602624 : base ( string . Format ( format , args ) )
603625 {
604626 }
605627 }
606-
607- private static class PrefetchArgs
608- {
609- public const string CommitId = "CommitId" ;
610- public const string Files = "Files" ;
611- public const string Folders = "Folders" ;
612- public const string Hydrate = "Hydrate" ;
613- }
614628 }
615629}
0 commit comments