1414using System . Runtime . InteropServices ;
1515using System . Threading ;
1616using Microsoft . CodeAnalysis ;
17- using Microsoft . CodeAnalysis . Collections . Internal ;
1817using Microsoft . CodeAnalysis . Host ;
1918using Microsoft . CodeAnalysis . Shared . Collections ;
2019using Microsoft . VisualStudio . Shell . Interop ;
@@ -48,9 +47,12 @@ internal sealed partial class VisualStudioMetadataReferenceManager : IWorkspaceS
4847 private readonly object _metadataCacheLock = new ( ) ;
4948
5049 /// <summary>
51- /// Access locked with <see cref="_metadataCacheLock"/>.
50+ /// Access locked with <see cref="_metadataCacheLock"/>. Maps from file path to metadata and the last time the
51+ /// metadata was written to. We keep this around until we see the file has changed on disk, at which point we'll
52+ /// compute the new metadata and update this cache, allowing the old metadata to be released. Note: this does mean
53+ /// that metadata that is no longer used, will be kept around indefinitely.
5254 /// </summary>
53- private readonly Dictionary < FileKey , WeakReference < AssemblyMetadata > > _metadataCache = [ ] ;
55+ private readonly Dictionary < string , ( DateTime lastWriteTime , AssemblyMetadata metadata ) > _metadataCache = new ( StringComparer . OrdinalIgnoreCase ) ;
5456
5557 private readonly ImmutableArray < string > _runtimeDirectories ;
5658 private readonly TemporaryStorageService _temporaryStorageService ;
@@ -90,22 +92,26 @@ public void Dispose()
9092 }
9193 }
9294
93- private bool TryGetMetadata ( FileKey key , [ NotNullWhen ( true ) ] out AssemblyMetadata ? metadata )
95+ private bool TryGetMetadata ( string filePath , DateTime lastWriteTime , [ NotNullWhen ( true ) ] out AssemblyMetadata ? metadata )
9496 {
95- metadata = null ;
9697 lock ( _metadataCacheLock )
9798 {
98- return _metadataCache . TryGetValue ( key , out var weakMetadata ) &&
99- weakMetadata . TryGetTarget ( out metadata ) ;
99+ if ( _metadataCache . TryGetValue ( filePath , out var tuple ) &&
100+ tuple . lastWriteTime == lastWriteTime )
101+ {
102+ metadata = tuple . metadata ;
103+ return true ;
104+ }
100105 }
106+
107+ metadata = null ;
108+ return false ;
101109 }
102110
103111 public IReadOnlyList < TemporaryStorageStreamHandle > ? GetStorageHandles ( string fullPath , DateTime snapshotTimestamp )
104112 {
105- var key = new FileKey ( fullPath , snapshotTimestamp ) ;
106- // check existing metadata
107- if ( TryGetMetadata ( key , out var source ) &&
108- s_metadataToStorageHandles . TryGetValue ( source , out var handles ) )
113+ if ( TryGetMetadata ( fullPath , snapshotTimestamp , out var metadata ) &&
114+ s_metadataToStorageHandles . TryGetValue ( metadata , out var handles ) )
109115 {
110116 return handles ;
111117 }
@@ -143,10 +149,8 @@ private static ImmutableArray<string> GetRuntimeDirectories()
143149 /// <exception cref="BadImageFormatException" />
144150 internal Metadata GetMetadata ( string fullPath , DateTime snapshotTimestamp )
145151 {
146- var key = new FileKey ( fullPath , snapshotTimestamp ) ;
147-
148152 // check existing metadata
149- if ( ! TryGetMetadata ( key , out var metadata ) )
153+ if ( ! TryGetMetadata ( fullPath , snapshotTimestamp , out var metadata ) )
150154 {
151155 // wasn't in the cache. create a new instance.
152156 metadata = GetMetadataWorker ( fullPath ) ;
@@ -157,16 +161,14 @@ internal Metadata GetMetadata(string fullPath, DateTime snapshotTimestamp)
157161 // Now try to create and add the metadata to the cache. If we fail to add it (because some other thread
158162 // beat us to this), then Dispose the metadata we just created and will return the existing metadata
159163 // instead.
160- if ( _metadataCache . TryGetValue ( key , out var weakCachedMetadata ) &&
161- weakCachedMetadata . TryGetTarget ( out var cachedMetadata ) )
164+ if ( TryGetMetadata ( fullPath , snapshotTimestamp , out var cachedMetadata ) )
162165 {
163166 metadata . Dispose ( ) ;
164167 return cachedMetadata ;
165168 }
166169
167- // don't use "Add" since key might already exist with already released metadata
168- _metadataCache [ key ] = new WeakReference < AssemblyMetadata > ( metadata ) ;
169- ClearReleasedMetadata_NoLock ( ) ;
170+ // don't use "Add" since key might already exist with stale metadata
171+ _metadataCache [ fullPath ] = ( snapshotTimestamp , metadata ) ;
170172 return metadata ;
171173 }
172174 }
@@ -184,29 +186,6 @@ AssemblyMetadata GetMetadataWorker(string fullPath)
184186
185187 return metadata ;
186188 }
187-
188- void ClearReleasedMetadata_NoLock ( )
189- {
190- Contract . ThrowIfFalse ( Monitor . IsEntered ( _metadataCacheLock ) ) ;
191-
192- var count = _metadataCache . Count ;
193-
194- // Cleanup when we hit powers of two. This way we don't have to walk too often. But only when we've really
195- // grown. the dictionary by a substantial amount since the last time we cleaned up.
196- if ( count == ( 1 << SegmentedArraySortUtils . Log2 ( ( uint ) count ) ) )
197- {
198- using var keysToRemove = TemporaryArray < FileKey > . Empty ;
199-
200- foreach ( var ( fileKey , weakMetadata ) in _metadataCache )
201- {
202- if ( ! weakMetadata . TryGetTarget ( out _ ) )
203- keysToRemove . Add ( fileKey ) ;
204- }
205-
206- foreach ( var key in keysToRemove )
207- _metadataCache . Remove ( key ) ;
208- }
209- }
210189 }
211190
212191 private static ( ModuleMetadata metadata , TemporaryStorageStreamHandle storageHandle ) GetMetadataFromTemporaryStorage (
@@ -362,9 +341,9 @@ private static (AssemblyMetadata assemblyMetadata, IReadOnlyList<TemporaryStorag
362341 assemblyDir ??= Path . GetDirectoryName ( fullPath ) ;
363342
364343 // Suppression should be removed or addressed https://github.com/dotnet/roslyn/issues/41636
365- var moduleFileKey = PathUtilities . CombineAbsoluteAndRelativePaths ( assemblyDir , moduleName ) ! ;
344+ var moduleFullPath = PathUtilities . CombineAbsoluteAndRelativePaths ( assemblyDir , moduleName ) ! ;
366345
367- var ( moduleMetadata , moduleHandle ) = moduleMetadataFactory ( moduleFileKey ) ;
346+ var ( moduleMetadata , moduleHandle ) = moduleMetadataFactory ( moduleFullPath ) ;
368347 modules . Add ( moduleMetadata ) ;
369348 handles . Add ( moduleHandle ) ;
370349 }
0 commit comments