From 02a9fafd30d7fa1ce307431aba4785c535f7e4ad Mon Sep 17 00:00:00 2001 From: "Andrew Hall (METAL)" Date: Tue, 25 Jul 2023 17:40:48 -0700 Subject: [PATCH 1/3] Remove, and remove async method --- .../DefaultDocumentVersionCache.cs | 99 +++++++++---------- .../DocumentVersionCache.cs | 5 - .../Formatting/HtmlFormatter.cs | 6 +- .../DefaultDocumentContextFactoryTest.cs | 2 +- .../DefaultDocumentVersionCacheTest.cs | 28 +++--- .../Formatting/TestRazorFormattingService.cs | 2 +- .../GeneratedDocumentSynchronizerTest.cs | 2 +- 7 files changed, 66 insertions(+), 78 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs index fa20bfd3ff2..f6356f6f328 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; @@ -16,17 +14,16 @@ internal class DefaultDocumentVersionCache : DocumentVersionCache internal const int MaxDocumentTrackingCount = 20; // Internal for testing - internal readonly Dictionary> DocumentLookup; - private readonly ProjectSnapshotManagerDispatcher _dispatcher; + internal readonly Dictionary> DocumentLookup_NeedsLock; + private readonly ReadWriterLocker _lock = new(); private ProjectSnapshotManagerBase? _projectSnapshotManager; private ProjectSnapshotManagerBase ProjectSnapshotManager => _projectSnapshotManager ?? throw new InvalidOperationException("ProjectSnapshotManager accessed before Initialized was called."); - public DefaultDocumentVersionCache(ProjectSnapshotManagerDispatcher dispatcher) + public DefaultDocumentVersionCache() { - _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher)); - DocumentLookup = new Dictionary>(FilePathComparer.Instance); + DocumentLookup_NeedsLock = new(FilePathComparer.Instance); } public override void TrackDocumentVersion(IDocumentSnapshot documentSnapshot, int version) @@ -36,14 +33,17 @@ public override void TrackDocumentVersion(IDocumentSnapshot documentSnapshot, in throw new ArgumentNullException(nameof(documentSnapshot)); } - _dispatcher.AssertDispatcherThread(); - var filePath = documentSnapshot.FilePath.AssumeNotNull(); + using var upgradeableReadLock = _lock.EnterUpgradeAbleReadLock(); + TrackDocumentVersion(documentSnapshot, version, filePath, upgradeableReadLock); + } - if (!DocumentLookup.TryGetValue(filePath, out var documentEntries)) + private void TrackDocumentVersion(IDocumentSnapshot documentSnapshot, int version, string filePath, ReadWriterLocker.UpgradeableReadLock upgradeableReadLock) + { + if (!DocumentLookup_NeedsLock.TryGetValue(filePath, out var documentEntries)) { documentEntries = new List(); - DocumentLookup[filePath] = documentEntries; + DocumentLookup_NeedsLock[filePath] = documentEntries; } if (documentEntries.Count == MaxDocumentTrackingCount) @@ -66,11 +66,10 @@ public override bool TryGetDocumentVersion(IDocumentSnapshot documentSnapshot, [ throw new ArgumentNullException(nameof(documentSnapshot)); } - _dispatcher.AssertDispatcherThread(); - var filePath = documentSnapshot.FilePath.AssumeNotNull(); + using var _ = _lock.EnterReadLock(); - if (!DocumentLookup.TryGetValue(filePath, out var documentEntries)) + if (!DocumentLookup_NeedsLock.TryGetValue(filePath, out var documentEntries)) { version = null; return false; @@ -98,22 +97,6 @@ public override bool TryGetDocumentVersion(IDocumentSnapshot documentSnapshot, [ return true; } - public override Task TryGetDocumentVersionAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken) - { - if (documentSnapshot is null) - { - throw new ArgumentNullException(nameof(documentSnapshot)); - } - - return _dispatcher.RunOnDispatcherThreadAsync( - () => - { - TryGetDocumentVersion(documentSnapshot, out var version); - return version; - }, - cancellationToken); - } - public override void Initialize(ProjectSnapshotManagerBase projectManager) { if (projectManager is null) @@ -133,18 +116,21 @@ private void ProjectSnapshotManager_Changed(object? sender, ProjectChangeEventAr return; } - _dispatcher.AssertDispatcherThread(); + var upgradeableLock = _lock.EnterUpgradeAbleReadLock(); switch (args.Kind) { case ProjectChangeKind.DocumentChanged: - var documentFilePath = args.DocumentFilePath!; - if (DocumentLookup.ContainsKey(documentFilePath) && - !ProjectSnapshotManager.IsDocumentOpen(documentFilePath)) - { - // Document closed, evict entry. - DocumentLookup.Remove(documentFilePath); - } + var documentFilePath = args.DocumentFilePath!; + if (DocumentLookup_NeedsLock.ContainsKey(documentFilePath) && + !ProjectSnapshotManager.IsDocumentOpen(documentFilePath)) + { + using (upgradeableLock.EnterWriteLock()) + { + // Document closed, evict entry. + DocumentLookup_NeedsLock.Remove(documentFilePath); + } + } break; } @@ -163,27 +149,21 @@ private void ProjectSnapshotManager_Changed(object? sender, ProjectChangeEventAr return; } - CaptureProjectDocumentsAsLatest(project); + CaptureProjectDocumentsAsLatest(project, upgradeableLock); } // Internal for testing internal void MarkAsLatestVersion(IDocumentSnapshot document) { - var filePath = document.FilePath.AssumeNotNull(); - - if (!TryGetLatestVersionFromPath(filePath, out var latestVersion)) - { - return; - } - - // Update our internal tracking state to track the changed document as the latest document. - TrackDocumentVersion(document, latestVersion.Value); + MarkAsLatestVersion(document); } // Internal for testing internal bool TryGetLatestVersionFromPath(string filePath, [NotNullWhen(true)] out int? version) { - if (!DocumentLookup.TryGetValue(filePath, out var documentEntries)) + using var _ = _lock.EnterReadLock(); + + if (!DocumentLookup_NeedsLock.TryGetValue(filePath, out var documentEntries)) { version = null; return false; @@ -195,18 +175,33 @@ internal bool TryGetLatestVersionFromPath(string filePath, [NotNullWhen(true)] o return true; } - private void CaptureProjectDocumentsAsLatest(IProjectSnapshot projectSnapshot) + private void CaptureProjectDocumentsAsLatest(IProjectSnapshot projectSnapshot, ReadWriterLocker.UpgradeableReadLock upgradeableReadLock) { foreach (var documentPath in projectSnapshot.DocumentFilePaths) { - if (DocumentLookup.ContainsKey(documentPath) && + if (DocumentLookup_NeedsLock.ContainsKey(documentPath) && projectSnapshot.GetDocument(documentPath) is { } document) { - MarkAsLatestVersion(document); + MarkAsLatestVersion(document, upgradeableReadLock); } } } + private void MarkAsLatestVersion(IDocumentSnapshot document, ReadWriterLocker.UpgradeableReadLock upgradeableReadLock) + { + var filePath = document.FilePath.AssumeNotNull(); + + if (!DocumentLookup_NeedsLock.TryGetValue(filePath, out var documentEntries)) + { + return; + } + + var latestEntry = documentEntries[^1]; + + // Update our internal tracking state to track the changed document as the latest document. + TrackDocumentVersion(document, latestEntry.Version, document.FilePath.AssumeNotNull(), upgradeableReadLock); + } + internal class DocumentEntry { public DocumentEntry(IDocumentSnapshot document, int version) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentVersionCache.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentVersionCache.cs index 7bc866384f0..6c07873b523 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentVersionCache.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DocumentVersionCache.cs @@ -2,8 +2,6 @@ // Licensed under the MIT license. See License.txt in the project root for license information. using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Razor.ProjectSystem; namespace Microsoft.AspNetCore.Razor.LanguageServer; @@ -11,8 +9,5 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer; internal abstract class DocumentVersionCache : ProjectSnapshotChangeTrigger { public abstract bool TryGetDocumentVersion(IDocumentSnapshot documentSnapshot, [NotNullWhen(true)] out int? version); - - public abstract Task TryGetDocumentVersionAsync(IDocumentSnapshot documentSnapshot, CancellationToken cancellationToken); - public abstract void TrackDocumentVersion(IDocumentSnapshot documentSnapshot, int version); } diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs index 07fd3c2f5dc..c4c15f44bdb 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/HtmlFormatter.cs @@ -35,8 +35,7 @@ public async Task FormatAsync( throw new ArgumentNullException(nameof(context)); } - var documentVersion = await _documentVersionCache.TryGetDocumentVersionAsync(context.OriginalSnapshot, cancellationToken).ConfigureAwait(false); - if (documentVersion is null) + if (!_documentVersionCache.TryGetDocumentVersion(context.OriginalSnapshot, out var documentVersion)) { return Array.Empty(); } @@ -63,8 +62,7 @@ public async Task FormatOnTypeAsync( FormattingContext context, CancellationToken cancellationToken) { - var documentVersion = await _documentVersionCache.TryGetDocumentVersionAsync(context.OriginalSnapshot, cancellationToken).ConfigureAwait(false); - if (documentVersion == null) + if (!_documentVersionCache.TryGetDocumentVersion(context.OriginalSnapshot, out var documentVersion)) { return Array.Empty(); } diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DefaultDocumentContextFactoryTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DefaultDocumentContextFactoryTest.cs index 0b547b83bcf..fb84e80c080 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DefaultDocumentContextFactoryTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DefaultDocumentContextFactoryTest.cs @@ -24,7 +24,7 @@ public class DefaultDocumentContextFactoryTest : LanguageServerTestBase public DefaultDocumentContextFactoryTest(ITestOutputHelper testOutput) : base(testOutput) { - _documentVersionCache = new DefaultDocumentVersionCache(Dispatcher); + _documentVersionCache = new DefaultDocumentVersionCache(); } [Fact] diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DefaultDocumentVersionCacheTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DefaultDocumentVersionCacheTest.cs index 79e28c0f2a0..c3ac13e23fb 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DefaultDocumentVersionCacheTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/DefaultDocumentVersionCacheTest.cs @@ -22,7 +22,7 @@ public DefaultDocumentVersionCacheTest(ITestOutputHelper testOutput) public void MarkAsLatestVersion_UntrackedDocument_Noops() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var document = TestDocumentSnapshot.Create("C:/file.cshtml"); documentVersionCache.TrackDocumentVersion(document, 123); var untrackedDocument = TestDocumentSnapshot.Create("C:/other.cshtml"); @@ -39,7 +39,7 @@ public void MarkAsLatestVersion_UntrackedDocument_Noops() public void MarkAsLatestVersion_KnownDocument_TracksNewDocumentAsLatest() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var documentInitial = TestDocumentSnapshot.Create("C:/file.cshtml"); documentVersionCache.TrackDocumentVersion(documentInitial, 123); var documentLatest = TestDocumentSnapshot.Create(documentInitial.FilePath); @@ -56,7 +56,7 @@ public void MarkAsLatestVersion_KnownDocument_TracksNewDocumentAsLatest() public void TryGetLatestVersionFromPath_TrackedDocument_ReturnsTrue() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var filePath = "C:/file.cshtml"; var document1 = TestDocumentSnapshot.Create(filePath); var document2 = TestDocumentSnapshot.Create(filePath); @@ -75,7 +75,7 @@ public void TryGetLatestVersionFromPath_TrackedDocument_ReturnsTrue() public void TryGetLatestVersionFromPath_UntrackedDocument_ReturnsFalse() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); // Act var result = documentVersionCache.TryGetLatestVersionFromPath("C:/file.cshtml", out var version); @@ -89,7 +89,7 @@ public void TryGetLatestVersionFromPath_UntrackedDocument_ReturnsFalse() public void ProjectSnapshotManager_Changed_DocumentRemoved_DoesNotEvictDocument() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var projectSnapshotManager = TestProjectSnapshotManager.Create(ErrorReporter); projectSnapshotManager.AllowNotifyListeners = true; documentVersionCache.Initialize(projectSnapshotManager); @@ -119,7 +119,7 @@ public void ProjectSnapshotManager_Changed_DocumentRemoved_DoesNotEvictDocument( public void ProjectSnapshotManager_Changed_OpenDocumentRemoved_DoesNotEvictDocument() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var projectSnapshotManager = TestProjectSnapshotManager.Create(ErrorReporter); projectSnapshotManager.AllowNotifyListeners = true; documentVersionCache.Initialize(projectSnapshotManager); @@ -151,7 +151,7 @@ public void ProjectSnapshotManager_Changed_OpenDocumentRemoved_DoesNotEvictDocum public void ProjectSnapshotManager_Changed_DocumentClosed_EvictsDocument() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var projectSnapshotManager = TestProjectSnapshotManager.Create(ErrorReporter); projectSnapshotManager.AllowNotifyListeners = true; documentVersionCache.Initialize(projectSnapshotManager); @@ -183,14 +183,14 @@ public void ProjectSnapshotManager_Changed_DocumentClosed_EvictsDocument() public void TrackDocumentVersion_AddsFirstEntry() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var document = TestDocumentSnapshot.Create("C:/file.cshtml"); // Act documentVersionCache.TrackDocumentVersion(document, 1337); // Assert - var kvp = Assert.Single(documentVersionCache.DocumentLookup); + var kvp = Assert.Single(documentVersionCache.DocumentLookup_NeedsLock); Assert.Equal(document.FilePath, kvp.Key); var entry = Assert.Single(kvp.Value); Assert.True(entry.Document.TryGetTarget(out var actualDocument)); @@ -202,7 +202,7 @@ public void TrackDocumentVersion_AddsFirstEntry() public void TrackDocumentVersion_EvictsOldEntries() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var document = TestDocumentSnapshot.Create("C:/file.cshtml"); for (var i = 0; i < DefaultDocumentVersionCache.MaxDocumentTrackingCount; i++) @@ -214,7 +214,7 @@ public void TrackDocumentVersion_EvictsOldEntries() documentVersionCache.TrackDocumentVersion(document, 1337); // Assert - var kvp = Assert.Single(documentVersionCache.DocumentLookup); + var kvp = Assert.Single(documentVersionCache.DocumentLookup_NeedsLock); Assert.Equal(DefaultDocumentVersionCache.MaxDocumentTrackingCount, kvp.Value.Count); Assert.Equal(1337, kvp.Value.Last().Version); } @@ -223,7 +223,7 @@ public void TrackDocumentVersion_EvictsOldEntries() public void TryGetDocumentVersion_UntrackedDocumentPath_ReturnsFalse() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var document = TestDocumentSnapshot.Create("C:/file.cshtml"); // Act @@ -238,7 +238,7 @@ public void TryGetDocumentVersion_UntrackedDocumentPath_ReturnsFalse() public void TryGetDocumentVersion_EvictedDocument_ReturnsFalse() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var document = TestDocumentSnapshot.Create("C:/file.cshtml"); var evictedDocument = TestDocumentSnapshot.Create(document.FilePath); documentVersionCache.TrackDocumentVersion(document, 1337); @@ -255,7 +255,7 @@ public void TryGetDocumentVersion_EvictedDocument_ReturnsFalse() public void TryGetDocumentVersion_KnownDocument_ReturnsTrue() { // Arrange - var documentVersionCache = new DefaultDocumentVersionCache(LegacyDispatcher); + var documentVersionCache = new DefaultDocumentVersionCache(); var document = TestDocumentSnapshot.Create("C:/file.cshtml"); documentVersionCache.TrackDocumentVersion(document, 1337); diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestRazorFormattingService.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestRazorFormattingService.cs index 2ab14b0adc2..25461fd591b 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestRazorFormattingService.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Formatting/TestRazorFormattingService.cs @@ -29,7 +29,7 @@ public static async Task CreateWithFullSupportAsync( var mappingService = new RazorDocumentMappingService(TestLanguageServerFeatureOptions.Instance, new TestDocumentContextFactory(), loggerFactory); var dispatcher = new LSPProjectSnapshotManagerDispatcher(loggerFactory); - var versionCache = new DefaultDocumentVersionCache(dispatcher); + var versionCache = new DefaultDocumentVersionCache(); if (documentSnapshot is not null) { await dispatcher.RunOnDispatcherThreadAsync(() => diff --git a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/GeneratedDocumentSynchronizerTest.cs b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/GeneratedDocumentSynchronizerTest.cs index 34ff1570f1e..f5afbb8d348 100644 --- a/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/GeneratedDocumentSynchronizerTest.cs +++ b/src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/GeneratedDocumentSynchronizerTest.cs @@ -22,7 +22,7 @@ public class GeneratedDocumentSynchronizerTest : LanguageServerTestBase public GeneratedDocumentSynchronizerTest(ITestOutputHelper testOutput) : base(testOutput) { - _cache = new DefaultDocumentVersionCache(Dispatcher); + _cache = new DefaultDocumentVersionCache(); _publisher = new TestGeneratedDocumentPublisher(); _synchronizer = new GeneratedDocumentSynchronizer(_publisher, _cache, Dispatcher); _document = TestDocumentSnapshot.Create("C:/path/to/file.razor"); From e9be5f14456c7dd7b1d8d6b9389422cebe5df5c3 Mon Sep 17 00:00:00 2001 From: "Andrew Hall (METAL)" Date: Tue, 25 Jul 2023 17:58:23 -0700 Subject: [PATCH 2/3] Fix locking and unintended infinite recursion --- .../DefaultDocumentVersionCache.cs | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs index f6356f6f328..55ebf05cdda 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs @@ -40,23 +40,26 @@ public override void TrackDocumentVersion(IDocumentSnapshot documentSnapshot, in private void TrackDocumentVersion(IDocumentSnapshot documentSnapshot, int version, string filePath, ReadWriterLocker.UpgradeableReadLock upgradeableReadLock) { - if (!DocumentLookup_NeedsLock.TryGetValue(filePath, out var documentEntries)) + using (upgradeableReadLock.EnterWriteLock()) { - documentEntries = new List(); - DocumentLookup_NeedsLock[filePath] = documentEntries; - } + if (!DocumentLookup_NeedsLock.TryGetValue(filePath, out var documentEntries)) + { + documentEntries = new List(); + DocumentLookup_NeedsLock[filePath] = documentEntries; + } - if (documentEntries.Count == MaxDocumentTrackingCount) - { - // Clear the oldest document entry + if (documentEntries.Count == MaxDocumentTrackingCount) + { + // Clear the oldest document entry - // With this approach we'll slowly leak memory as new documents are added to the system. We don't clear up - // document file paths where where all of the corresponding entries are expired. - documentEntries.RemoveAt(0); - } + // With this approach we'll slowly leak memory as new documents are added to the system. We don't clear up + // document file paths where where all of the corresponding entries are expired. + documentEntries.RemoveAt(0); + } - var entry = new DocumentEntry(documentSnapshot, version); - documentEntries.Add(entry); + var entry = new DocumentEntry(documentSnapshot, version); + documentEntries.Add(entry); + } } public override bool TryGetDocumentVersion(IDocumentSnapshot documentSnapshot, [NotNullWhen(true)] out int? version) @@ -155,7 +158,8 @@ private void ProjectSnapshotManager_Changed(object? sender, ProjectChangeEventAr // Internal for testing internal void MarkAsLatestVersion(IDocumentSnapshot document) { - MarkAsLatestVersion(document); + using var upgradeableLock = _lock.EnterUpgradeAbleReadLock(); + MarkAsLatestVersion(document, upgradeableLock); } // Internal for testing From 727f064fbdd198e3c34f43a82de5592bd0a151e3 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Tue, 25 Jul 2023 18:56:07 -0700 Subject: [PATCH 3/3] Update src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs Co-authored-by: David Wengier --- .../DefaultDocumentVersionCache.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs index 55ebf05cdda..766afabde60 100644 --- a/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs +++ b/src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/DefaultDocumentVersionCache.cs @@ -40,6 +40,7 @@ public override void TrackDocumentVersion(IDocumentSnapshot documentSnapshot, in private void TrackDocumentVersion(IDocumentSnapshot documentSnapshot, int version, string filePath, ReadWriterLocker.UpgradeableReadLock upgradeableReadLock) { + // Need to ensure the write lock covers all uses of documentEntries, not just DocumentLookup using (upgradeableReadLock.EnterWriteLock()) { if (!DocumentLookup_NeedsLock.TryGetValue(filePath, out var documentEntries))