Skip to content

Commit 6327cba

Browse files
Merge pull request #73990 from CyrusNajmabadi/metadataWrites
2 parents 6aca46d + e389b5b commit 6327cba

File tree

1 file changed

+23
-44
lines changed

1 file changed

+23
-44
lines changed

src/VisualStudio/Core/Def/ProjectSystem/MetadataReferences/VisualStudioMetadataReferenceManager.cs

Lines changed: 23 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
using System.Runtime.InteropServices;
1515
using System.Threading;
1616
using Microsoft.CodeAnalysis;
17-
using Microsoft.CodeAnalysis.Collections.Internal;
1817
using Microsoft.CodeAnalysis.Host;
1918
using Microsoft.CodeAnalysis.Shared.Collections;
2019
using 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

Comments
 (0)