diff --git a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs index efc0adb965ae39ce4e4b91212f57921c40e4a0af..7f16403b12aecf37524435583ba13d082f07ea26 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/ObjectBrowser/AbstractObjectBrowserLibraryManager.cs @@ -94,10 +94,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) var oldDocument = e.OldSolution.GetDocument(e.DocumentId); var newDocument = e.NewSolution.GetDocument(e.DocumentId); - // make sure we do this in background thread. we don't care about ordering of events - // we just need to refresh OB at some point if it ever needs to be updated - // link to the bug tracking root cause - https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems?id=169649&_a=edit - Task.Run(() => DocumentChangedAsync(oldDocument, newDocument)); + UpdateDocument(oldDocument, newDocument); break; case WorkspaceChangeKind.ProjectAdded: @@ -117,17 +114,21 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) } } - private async Task DocumentChangedAsync(Document oldDocument, Document newDocument) + private void UpdateDocument(Document oldDocument, Document newDocument) { try { - var oldTextVersion = await oldDocument.GetTextVersionAsync(CancellationToken.None).ConfigureAwait(false); - var newTextVersion = await newDocument.GetTextVersionAsync(CancellationToken.None).ConfigureAwait(false); - - if (oldTextVersion != newTextVersion) + // If the versions are the same, avoid updating the object browser. However, avoid + // loading the document to determine the version because it can cause extreme memory + // pressure during batch changes. + if (oldDocument.TryGetTextVersion(out var oldTextVersion) + && newDocument.TryGetTextVersion(out var newTextVersion) + && oldTextVersion == newTextVersion) { - UpdateClassAndMemberVersions(); + return; } + + UpdateClassAndMemberVersions(); } catch (Exception e) when (FatalError.Report(e)) { diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs index 819da6125aff0a49513ba0d8d2ec503bc2742011..2022d1ad3e59c06185c9ab298285fe62689e548a 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/RecoverableTextAndVersion.cs @@ -67,6 +67,10 @@ public bool TryGetTextVersion(out VersionStamp version) { version = textAndVersion.Version; } + else if (_initialSource is ITextVersionable textVersionable) + { + return textVersionable.TryGetTextVersion(out version); + } } return version != default; diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs index bdc8d54bc01930933d265f7025d8738d754c0165..2079e7ebaf6a360cb72233e3221b2bc5a8e01fbb 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/TextDocumentState.cs @@ -97,7 +97,19 @@ protected static ValueSource CreateStrongText(TextLoader loader, } protected static ValueSource CreateRecoverableText(TextAndVersion text, SolutionServices services) - => new RecoverableTextAndVersion(CreateStrongText(text), services.TemporaryStorage); + { + var result = new RecoverableTextAndVersion(CreateStrongText(text), services.TemporaryStorage); + + // This RecoverableTextAndVersion is created directly from a TextAndVersion instance. In its initial state, + // the RecoverableTextAndVersion keeps a strong reference to the initial TextAndVersion, and only + // transitions to a weak reference backed by temporary storage after the first time GetValue (or + // GetValueAsync) is called. Since we know we are creating a RecoverableTextAndVersion for the purpose of + // avoiding problematic address space overhead, we call GetValue immediately to force the object to weakly + // hold its data from the start. + result.GetValue(); + + return result; + } protected static ValueSource CreateRecoverableText(TextLoader loader, DocumentId documentId, SolutionServices services) {