diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 9ba50a1064826..787450c63bb57 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -13,18 +13,18 @@ - + https://github.com/dotnet/arcade - c3e0e4070f6cd3873b775e3f4136e29f4d66cb49 + c7d6bd607715f334cda90e01967bb0c02dee09be https://github.com/dotnet/roslyn 5b972bceb846f5d15f991a479e285067a75103e4 - + https://github.com/dotnet/arcade - c3e0e4070f6cd3873b775e3f4136e29f4d66cb49 + c7d6bd607715f334cda90e01967bb0c02dee09be diff --git a/global.json b/global.json index 8355bc0c4e997..ac386b3a4ad17 100644 --- a/global.json +++ b/global.json @@ -12,7 +12,7 @@ "xcopy-msbuild": "16.8.0-preview2.1" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21277.1", - "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21277.1" + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.21281.1", + "Microsoft.DotNet.Helix.Sdk": "6.0.0-beta.21281.1" } } diff --git a/src/Deployment/Properties/launchSettings.json b/src/Deployment/Properties/launchSettings.json index 01d32bd540287..570c6c4af71fb 100644 --- a/src/Deployment/Properties/launchSettings.json +++ b/src/Deployment/Properties/launchSettings.json @@ -5,6 +5,14 @@ "executablePath": "$(DevEnvDir)devenv.exe", "commandLineArgs": "/rootsuffix $(VSSDKTargetPlatformRegRootSuffix) /log" }, + "Visual Studio Extension (with ServiceHub Debugging)": { + "commandName": "Executable", + "executablePath": "$(DevEnvDir)devenv.exe", + "commandLineArgs": "/rootsuffix $(VSSDKTargetPlatformRegRootSuffix) /log", + "environmentVariables": { + "SERVICEHUBDEBUGHOSTONSTARTUP": "All" + } + }, "Visual Studio Extension (with Native Debugging)": { "commandName": "Executable", "executablePath": "$(DevEnvDir)devenv.exe", diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs deleted file mode 100644 index 6002a22d63a53..0000000000000 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticDataSerializerTests.cs +++ /dev/null @@ -1,364 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Diagnostics.EngineV2; -using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Test.Utilities; -using Roslyn.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics -{ - [UseExportProvider] - public class DiagnosticDataSerializerTests : TestBase - { - [Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)] - public async Task SerializationTest_Document() - { - using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures.AddParts( - typeof(TestPersistentStorageServiceFactory))); - - var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", ""); - - var diagnostics = new[] - { - new DiagnosticData( - id: "test1", - category: "Test", - message: "test1 message", - enuMessageForBingSearch: "test1 message format", - severity: DiagnosticSeverity.Info, - defaultSeverity:DiagnosticSeverity.Info, - isEnabledByDefault: false, - warningLevel: 1, - customTags: ImmutableArray.Empty, - properties: ImmutableDictionary.Empty, - document.Project.Id, - new DiagnosticDataLocation(document.Id, new TextSpan(10, 20), "originalFile1", 30, 30, 40, 40, "mappedFile1", 10, 10, 20, 20), - language: LanguageNames.CSharp), - - new DiagnosticData( - id: "test2", - category: "Test", - message: "test2 message", - enuMessageForBingSearch: "test2 message format", - severity: DiagnosticSeverity.Warning, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - warningLevel: 0, - customTags: ImmutableArray.Create("Test2"), - properties: ImmutableDictionary.Empty.Add("propertyKey", "propertyValue"), - document.Project.Id, - new DiagnosticDataLocation(document.Id, new TextSpan(30, 40), "originalFile2", 70, 70, 80, 80, "mappedFile2", 50, 50, 60, 60), - language: "VB", - title: "test2 title", - description: "test2 description", - helpLink: "http://test2link"), - - new DiagnosticData( - id: "test3", - category: "Test", - message: "test3 message", - enuMessageForBingSearch: "test3 message format", - severity:DiagnosticSeverity.Error, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - warningLevel: 2, - customTags: ImmutableArray.Create("Test3", "Test3_2"), - properties: ImmutableDictionary.Empty.Add("p1Key", "p1Value").Add("p2Key", "p2Value"), - document.Project.Id, - new DiagnosticDataLocation(document.Id, new TextSpan(50, 60), "originalFile3", 110, 110, 120, 120, "mappedFile3", 90, 90, 100, 100), - title: "test3 title", - description: "test3 description", - helpLink: "http://test3link"), - - }.ToImmutableArray(); - - var utcTime = DateTime.UtcNow; - var analyzerVersion = VersionStamp.Create(utcTime); - var version = VersionStamp.Create(utcTime.AddDays(1)); - - var key = "document"; - - var persistentService = workspace.Services.GetRequiredService(); - var serializer = new CodeAnalysis.Workspaces.Diagnostics.DiagnosticDataSerializer(analyzerVersion, version); - - Assert.True(await serializer.SerializeAsync(persistentService, document.Project, document, key, diagnostics, CancellationToken.None).ConfigureAwait(false)); - - var recovered = await serializer.DeserializeAsync(persistentService, document.Project, document, key, CancellationToken.None); - - AssertDiagnostics(diagnostics, recovered); - } - - [Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)] - public async Task SerializationTest_Project() - { - using var workspace = new TestWorkspace(composition: EditorTestCompositions.EditorFeatures.AddParts( - typeof(TestPersistentStorageServiceFactory))); - - var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", ""); - - var diagnostics = new[] - { - new DiagnosticData( - id: "test1", - category: "Test", - message: "test1 message", - enuMessageForBingSearch: "test1 message format", - severity: DiagnosticSeverity.Info, - defaultSeverity: DiagnosticSeverity.Info, - isEnabledByDefault: false, - warningLevel: 1, - customTags: ImmutableArray.Empty, - properties: ImmutableDictionary.Empty, - projectId: document.Project.Id, - language: LanguageNames.VisualBasic, - description: "test1 description", - helpLink: "http://test1link"), - - new DiagnosticData( - id: "test2", - category: "Test", - message: "test2 message", - enuMessageForBingSearch: "test2 message format", - severity: DiagnosticSeverity.Warning, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - warningLevel: 0, - customTags: ImmutableArray.Create("Test2"), - properties: ImmutableDictionary.Empty.Add("p1Key", "p2Value"), - projectId: document.Project.Id), - - new DiagnosticData( - id: "test3", - category: "Test", - message: "test3 message", - enuMessageForBingSearch: "test3 message format", - severity: DiagnosticSeverity.Error, - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true, - warningLevel: 2, - customTags: ImmutableArray.Create("Test3", "Test3_2"), - properties: ImmutableDictionary.Empty.Add("p2Key", "p2Value").Add("p1Key", "p1Value"), - projectId: document.Project.Id, - description: "test3 description", - helpLink: "http://test3link"), - - }.ToImmutableArray(); - - var utcTime = DateTime.UtcNow; - var analyzerVersion = VersionStamp.Create(utcTime); - var version = VersionStamp.Create(utcTime.AddDays(1)); - - var key = "project"; - var persistentService = workspace.Services.GetRequiredService(); - var serializer = new CodeAnalysis.Workspaces.Diagnostics.DiagnosticDataSerializer(analyzerVersion, version); - - Assert.True(await serializer.SerializeAsync(persistentService, document.Project, document, key, diagnostics, CancellationToken.None).ConfigureAwait(false)); - var recovered = await serializer.DeserializeAsync(persistentService, document.Project, document, key, CancellationToken.None); - - AssertDiagnostics(diagnostics, recovered); - } - - [WorkItem(6104, "https://github.com/dotnet/roslyn/issues/6104")] - [Fact] - public void DiagnosticEquivalence() - { -#if DEBUG - var source = -@"class C -{ - static int F(string s) { return 1; } - static int x = F(new { }); - static int y = F(new { A = 1 }); -}"; - var tree = SyntaxFactory.ParseSyntaxTree(source); - var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, concurrentBuild: false); - var compilation = CSharpCompilation.Create(GetUniqueName(), new[] { tree }, new[] { MscorlibRef }, options); - var model = compilation.GetSemanticModel(tree); - - // Each call to GetDiagnostics will bind field initializers - // (see https://github.com/dotnet/roslyn/issues/6264). - var diagnostics1 = model.GetDiagnostics().ToArray(); - var diagnostics2 = model.GetDiagnostics().ToArray(); - - diagnostics1.Verify( - // (4,22): error CS1503: Argument 1: cannot convert from '' to 'string' - // static int x = F(new { }); - Diagnostic(1503, "new { }").WithArguments("1", "", "string").WithLocation(4, 22), - // (5,22): error CS1503: Argument 1: cannot convert from '' to 'string' - // static int y = F(new { A = 1 }); - Diagnostic(1503, "new { A = 1 }").WithArguments("1", "", "string").WithLocation(5, 22)); - - Assert.NotSame(diagnostics1[0], diagnostics2[0]); - Assert.NotSame(diagnostics1[1], diagnostics2[1]); - Assert.Equal(diagnostics1, diagnostics2); - Assert.True(AnalyzerHelper.AreEquivalent(diagnostics1, diagnostics2)); - - // Verify that not all collections are treated as equivalent. - diagnostics1 = new[] { diagnostics1[0] }; - diagnostics2 = new[] { diagnostics2[1] }; - - Assert.NotEqual(diagnostics1, diagnostics2); - Assert.False(AnalyzerHelper.AreEquivalent(diagnostics1, diagnostics2)); -#endif - } - - private static void AssertDiagnostics(ImmutableArray items1, ImmutableArray items2) - { - Assert.Equal(items1.Length, items2.Length); - - for (var i = 0; i < items1.Length; i++) - { - AssertDiagnostics(items1[i], items2[i]); - } - } - - private static void AssertDiagnostics(DiagnosticData item1, DiagnosticData item2) - { - Assert.Equal(item1.Id, item2.Id); - Assert.Equal(item1.Category, item2.Category); - Assert.Equal(item1.Message, item2.Message); - Assert.Equal(item1.ENUMessageForBingSearch, item2.ENUMessageForBingSearch); - Assert.Equal(item1.Severity, item2.Severity); - Assert.Equal(item1.IsEnabledByDefault, item2.IsEnabledByDefault); - Assert.Equal(item1.WarningLevel, item2.WarningLevel); - Assert.Equal(item1.DefaultSeverity, item2.DefaultSeverity); - - Assert.Equal(item1.CustomTags.Length, item2.CustomTags.Length); - for (var j = 0; j < item1.CustomTags.Length; j++) - Assert.Equal(item1.CustomTags[j], item2.CustomTags[j]); - - Assert.Equal(item1.Properties.Count, item2.Properties.Count); - Assert.True(item1.Properties.SetEquals(item2.Properties)); - - Assert.Equal(item1.ProjectId, item2.ProjectId); - Assert.Equal(item1.DocumentId, item2.DocumentId); - - Assert.Equal(item1.HasTextSpan, item2.HasTextSpan); - if (item1.HasTextSpan) - { - Assert.Equal(item1.GetTextSpan(), item2.GetTextSpan()); - } - - Assert.Equal(item1.DataLocation?.MappedFilePath, item2.DataLocation?.MappedFilePath); - Assert.Equal(item1.DataLocation?.MappedStartLine, item2.DataLocation?.MappedStartLine); - Assert.Equal(item1.DataLocation?.MappedStartColumn, item2.DataLocation?.MappedStartColumn); - Assert.Equal(item1.DataLocation?.MappedEndLine, item2.DataLocation?.MappedEndLine); - Assert.Equal(item1.DataLocation?.MappedEndColumn, item2.DataLocation?.MappedEndColumn); - - Assert.Equal(item1.DataLocation?.OriginalFilePath, item2.DataLocation?.OriginalFilePath); - Assert.Equal(item1.DataLocation?.OriginalStartLine, item2.DataLocation?.OriginalStartLine); - Assert.Equal(item1.DataLocation?.OriginalStartColumn, item2.DataLocation?.OriginalStartColumn); - Assert.Equal(item1.DataLocation?.OriginalEndLine, item2.DataLocation?.OriginalEndLine); - Assert.Equal(item1.DataLocation?.OriginalEndColumn, item2.DataLocation?.OriginalEndColumn); - - Assert.Equal(item1.Description, item2.Description); - Assert.Equal(item1.HelpLink, item2.HelpLink); - } - - [ExportWorkspaceServiceFactory(typeof(IPersistentStorageService), ServiceLayer.Test), Shared, PartNotDiscoverable] - public class TestPersistentStorageServiceFactory : IWorkspaceServiceFactory - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestPersistentStorageServiceFactory() - { - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new Service(); - - public class Service : IPersistentStorageService - { - private readonly Storage _instance = new(); - - IPersistentStorage IPersistentStorageService.GetStorage(Solution solution) - => _instance; - - ValueTask IPersistentStorageService.GetStorageAsync(Solution solution, CancellationToken cancellationToken) - => new(_instance); - - internal class Storage : IPersistentStorage - { - private readonly Dictionary _map = new(); - - public Task ReadStreamAsync(string name, CancellationToken cancellationToken = default) - { - var stream = _map[name]; - stream.Position = 0; - - return Task.FromResult(stream); - } - - public Task ReadStreamAsync(Project project, string name, CancellationToken cancellationToken = default) - { - var stream = _map[Tuple.Create(project, name)]; - stream.Position = 0; - - return Task.FromResult(stream); - } - - public Task ReadStreamAsync(Document document, string name, CancellationToken cancellationToken = default) - { - var stream = _map[Tuple.Create(document, name)]; - stream.Position = 0; - - return Task.FromResult(stream); - } - - public Task WriteStreamAsync(string name, Stream stream, CancellationToken cancellationToken = default) - { - _map[name] = new MemoryStream(); - stream.CopyTo(_map[name]); - - return SpecializedTasks.True; - } - - public Task WriteStreamAsync(Project project, string name, Stream stream, CancellationToken cancellationToken = default) - { - _map[Tuple.Create(project, name)] = new MemoryStream(); - stream.CopyTo(_map[Tuple.Create(project, name)]); - - return SpecializedTasks.True; - } - - public Task WriteStreamAsync(Document document, string name, Stream stream, CancellationToken cancellationToken = default) - { - _map[Tuple.Create(document, name)] = new MemoryStream(); - stream.CopyTo(_map[Tuple.Create(document, name)]); - - return SpecializedTasks.True; - } - - public void Dispose() - { - } - - public ValueTask DisposeAsync() - { - return ValueTaskFactory.CompletedTask; - } - } - } - } - } -} diff --git a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs index 387dda2d9bc76..62a6da55c689b 100644 --- a/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs +++ b/src/Features/Core/Portable/Diagnostics/DiagnosticAnalyzerService_BuildSynchronization.cs @@ -16,7 +16,7 @@ internal partial class DiagnosticAnalyzerService /// /// Synchronize build errors with live error. /// - public Task SynchronizeWithBuildAsync( + public ValueTask SynchronizeWithBuildAsync( Workspace workspace, ImmutableDictionary> diagnostics, @@ -24,12 +24,9 @@ public Task SynchronizeWithBuildAsync( bool onBuildCompleted, CancellationToken cancellationToken) { - if (_map.TryGetValue(workspace, out var analyzer)) - { - return analyzer.SynchronizeWithBuildAsync(diagnostics, postBuildAndErrorListRefreshTaskQueue, onBuildCompleted, cancellationToken); - } - - return Task.CompletedTask; + return _map.TryGetValue(workspace, out var analyzer) + ? analyzer.SynchronizeWithBuildAsync(diagnostics, postBuildAndErrorListRefreshTaskQueue, onBuildCompleted, cancellationToken) + : default; } } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs index 8db109536a919..d9c11caf65950 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.AnalysisData.cs @@ -109,7 +109,7 @@ public ProjectAnalysisData( public DiagnosticAnalysisResult GetResult(DiagnosticAnalyzer analyzer) => GetResultOrEmpty(Result, analyzer, ProjectId, Version); - public static async Task CreateAsync(IPersistentStorageService persistentService, Project project, IEnumerable stateSets, bool avoidLoadingData, CancellationToken cancellationToken) + public static async Task CreateAsync(Project project, IEnumerable stateSets, bool avoidLoadingData, CancellationToken cancellationToken) { VersionStamp? version = null; @@ -117,7 +117,7 @@ public static async Task CreateAsync(IPersistentStorageServ foreach (var stateSet in stateSets) { var state = stateSet.GetOrCreateProjectState(project.Id); - var result = await state.GetAnalysisDataAsync(persistentService, project, avoidLoadingData, cancellationToken).ConfigureAwait(false); + var result = await state.GetAnalysisDataAsync(project, avoidLoadingData, cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(project.Id == result.ProjectId); if (!version.HasValue) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index ca002cf383572..f12d8156595e4 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -102,7 +102,7 @@ private async Task GetProjectAnalysisDataAsync( // PERF: We need to flip this to false when we do actual diffing. var avoidLoadingData = true; var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var existingData = await ProjectAnalysisData.CreateAsync(PersistentStorageService, project, stateSets, avoidLoadingData, cancellationToken).ConfigureAwait(false); + var existingData = await ProjectAnalysisData.CreateAsync(project, stateSets, avoidLoadingData, cancellationToken).ConfigureAwait(false); // We can't return here if we have open file only analyzers since saved data for open file only analyzer // is incomplete -- it only contains info on open files rather than whole project. diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs index 4447e2cad8116..4c8861098d704 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.ProjectState.cs @@ -47,7 +47,7 @@ public bool IsEmpty(DocumentId documentId) /// /// Return all diagnostics for the given project stored in this state /// - public async Task GetAnalysisDataAsync(IPersistentStorageService persistentService, Project project, bool avoidLoadingData, CancellationToken cancellationToken) + public async Task GetAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; @@ -55,7 +55,7 @@ public async Task GetAnalysisDataAsync(IPersistentStor if (lastResult.IsDefault) { - return await LoadInitialAnalysisDataAsync(persistentService, project, cancellationToken).ConfigureAwait(false); + return await LoadInitialAnalysisDataAsync(project, cancellationToken).ConfigureAwait(false); } RoslynDebug.Assert(lastResult.DocumentIds != null); @@ -74,8 +74,8 @@ public async Task GetAnalysisDataAsync(IPersistentStor return DiagnosticAnalysisResult.CreateEmpty(lastResult.ProjectId, lastResult.Version); } - // loading data can be cancelled any time. - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); + // loading data can be canceled any time. + var serializerVersion = lastResult.Version; var builder = new Builder(project, lastResult.Version, lastResult.DocumentIds); foreach (var documentId in lastResult.DocumentIds) @@ -84,11 +84,9 @@ public async Task GetAnalysisDataAsync(IPersistentStor var document = project.GetDocument(documentId); if (document == null) - { continue; - } - if (!await TryDeserializeDocumentDiagnosticsAsync(persistentService, serializer, document, builder, cancellationToken).ConfigureAwait(false)) + if (!await TryGetDiagnosticsFromInMemoryStorageAsync(serializerVersion, document, builder, cancellationToken).ConfigureAwait(false)) { Debug.Assert(lastResult.Version == VersionStamp.Default); @@ -98,7 +96,7 @@ public async Task GetAnalysisDataAsync(IPersistentStor } } - if (!await TryDeserializeProjectDiagnosticsAsync(persistentService, serializer, project, builder, cancellationToken).ConfigureAwait(false)) + if (!await TryGetProjectDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, builder, cancellationToken).ConfigureAwait(false)) { // this can happen if SaveAsync is not yet called but active file merge happened. one of case is if user did build before the very first // analysis happened. @@ -110,7 +108,7 @@ public async Task GetAnalysisDataAsync(IPersistentStor /// /// Return all diagnostics for the given document stored in this state including non local diagnostics for this document /// - public async Task GetAnalysisDataAsync(IPersistentStorageService persistentService, TextDocument document, bool avoidLoadingData, CancellationToken cancellationToken) + public async Task GetAnalysisDataAsync(TextDocument document, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; @@ -118,7 +116,7 @@ public async Task GetAnalysisDataAsync(IPersistentStor if (lastResult.IsDefault) { - return await LoadInitialAnalysisDataAsync(persistentService, document, cancellationToken).ConfigureAwait(false); + return await LoadInitialAnalysisDataAsync(document, cancellationToken).ConfigureAwait(false); } var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); @@ -133,11 +131,11 @@ public async Task GetAnalysisDataAsync(IPersistentStor return DiagnosticAnalysisResult.CreateEmpty(lastResult.ProjectId, lastResult.Version); } - // loading data can be cancelled any time. - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); + // loading data can be canceled any time. + var serializerVersion = lastResult.Version; var builder = new Builder(document.Project, lastResult.Version); - if (!await TryDeserializeDocumentDiagnosticsAsync(persistentService, serializer, document, builder, cancellationToken).ConfigureAwait(false)) + if (!await TryGetDiagnosticsFromInMemoryStorageAsync(serializerVersion, document, builder, cancellationToken).ConfigureAwait(false)) { Debug.Assert(lastResult.Version == VersionStamp.Default); @@ -151,7 +149,7 @@ public async Task GetAnalysisDataAsync(IPersistentStor /// /// Return all no location diagnostics for the given project stored in this state /// - public async Task GetProjectAnalysisDataAsync(IPersistentStorageService persistentService, Project project, bool avoidLoadingData, CancellationToken cancellationToken) + public async Task GetProjectAnalysisDataAsync(Project project, bool avoidLoadingData, CancellationToken cancellationToken) { // make a copy of last result. var lastResult = _lastResult; @@ -159,7 +157,7 @@ public async Task GetProjectAnalysisDataAsync(IPersist if (lastResult.IsDefault) { - return await LoadInitialProjectAnalysisDataAsync(persistentService, project, cancellationToken).ConfigureAwait(false); + return await LoadInitialProjectAnalysisDataAsync(project, cancellationToken).ConfigureAwait(false); } var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); @@ -168,17 +166,17 @@ public async Task GetProjectAnalysisDataAsync(IPersist return lastResult; } - // if given document doesnt have any diagnostics, return empty. + // if given document doesn't have any diagnostics, return empty. if (lastResult.IsEmpty) { return DiagnosticAnalysisResult.CreateEmpty(lastResult.ProjectId, lastResult.Version); } - // loading data can be cancelled any time. - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, lastResult.Version); + // loading data can be canceled any time. + var serializerVersion = lastResult.Version; var builder = new Builder(project, lastResult.Version); - if (!await TryDeserializeProjectDiagnosticsAsync(persistentService, serializer, project, builder, cancellationToken).ConfigureAwait(false)) + if (!await TryGetProjectDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, builder, cancellationToken).ConfigureAwait(false)) { // this can happen if SaveAsync is not yet called but active file merge happened. one of case is if user did build before the very first // analysis happened. @@ -187,13 +185,7 @@ public async Task GetProjectAnalysisDataAsync(IPersist return builder.ToResult(); } - public Task SaveAsync(IPersistentStorageService persistentService, Project project, DiagnosticAnalysisResult result) - => SaveCoreAsync(project, result, persistentService); - - public Task SaveInMemoryCacheAsync(Project project, DiagnosticAnalysisResult result) - => SaveCoreAsync(project, result, persistentService: null); - - private async Task SaveCoreAsync(Project project, DiagnosticAnalysisResult result, IPersistentStorageService? persistentService) + public async ValueTask SaveToInMemoryStorageAsync(Project project, DiagnosticAnalysisResult result) { Contract.ThrowIfTrue(result.IsAggregatedForm); Contract.ThrowIfNull(result.DocumentIds); @@ -203,8 +195,8 @@ private async Task SaveCoreAsync(Project project, DiagnosticAnalysisResult resul // save last aggregated form of analysis result _lastResult = result.ToAggregatedForm(); - // serialization can't be cancelled. - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, result.Version); + // serialization can't be canceled. + var serializerVersion = result.Version; foreach (var documentId in result.DocumentIds) { var document = project.GetTextDocument(documentId); @@ -220,12 +212,12 @@ private async Task SaveCoreAsync(Project project, DiagnosticAnalysisResult resul continue; } - await SerializeAsync(persistentService, serializer, project, document, document.Id, _owner.SyntaxStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Syntax)).ConfigureAwait(false); - await SerializeAsync(persistentService, serializer, project, document, document.Id, _owner.SemanticStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Semantic)).ConfigureAwait(false); - await SerializeAsync(persistentService, serializer, project, document, document.Id, _owner.NonLocalStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.NonLocal)).ConfigureAwait(false); + await AddToInMemoryStorageAsync(serializerVersion, project, document, document.Id, _owner.SyntaxStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Syntax)).ConfigureAwait(false); + await AddToInMemoryStorageAsync(serializerVersion, project, document, document.Id, _owner.SemanticStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Semantic)).ConfigureAwait(false); + await AddToInMemoryStorageAsync(serializerVersion, project, document, document.Id, _owner.NonLocalStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.NonLocal)).ConfigureAwait(false); } - await SerializeAsync(persistentService, serializer, project, document: null, result.ProjectId, _owner.NonLocalStateName, result.GetOtherDiagnostics()).ConfigureAwait(false); + await AddToInMemoryStorageAsync(serializerVersion, project, document: null, result.ProjectId, _owner.NonLocalStateName, result.GetOtherDiagnostics()).ConfigureAwait(false); } public void ResetVersion() @@ -234,7 +226,7 @@ public void ResetVersion() _lastResult = _lastResult.Reset(); } - public async Task MergeAsync(IPersistentStorageService persistentService, ActiveFileState state, TextDocument document) + public async ValueTask MergeAsync(ActiveFileState state, TextDocument document) { Contract.ThrowIfFalse(state.DocumentId == document.Id); @@ -269,12 +261,12 @@ public async Task MergeAsync(IPersistentStorageService persistentService, Active // we have mixed versions or full analysis is off, set it to default so that it can be re-calculated next time so data can be in sync. var version = VersionStamp.Default; - // serialization can't be cancelled. - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); + // serialization can't be canceled. + var serializerVersion = version; // save active file diagnostics back to project state - await SerializeAsync(persistentService, serializer, project, document, document.Id, _owner.SyntaxStateName, syntax.Items).ConfigureAwait(false); - await SerializeAsync(persistentService, serializer, project, document, document.Id, _owner.SemanticStateName, semantic.Items).ConfigureAwait(false); + await AddToInMemoryStorageAsync(serializerVersion, project, document, document.Id, _owner.SyntaxStateName, syntax.Items).ConfigureAwait(false); + await AddToInMemoryStorageAsync(serializerVersion, project, document, document.Id, _owner.SemanticStateName, semantic.Items).ConfigureAwait(false); // save last aggregated form of analysis result _lastResult = _lastResult.UpdateAggregatedResult(version, state.DocumentId, fromBuild); @@ -292,41 +284,37 @@ public bool OnProjectRemoved(ProjectId id) return !IsEmpty(); } - private async Task LoadInitialAnalysisDataAsync(IPersistentStorageService persistentService, Project project, CancellationToken cancellationToken) + private async Task LoadInitialAnalysisDataAsync(Project project, CancellationToken cancellationToken) { - // loading data can be cancelled any time. + // loading data can be canceled any time. var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); + var serializerVersion = version; var builder = new Builder(project, version); foreach (var document in project.Documents) { cancellationToken.ThrowIfCancellationRequested(); - if (!await TryDeserializeDocumentDiagnosticsAsync(persistentService, serializer, document, builder, cancellationToken).ConfigureAwait(false)) - { + if (!await TryGetDiagnosticsFromInMemoryStorageAsync(serializerVersion, document, builder, cancellationToken).ConfigureAwait(false)) continue; - } } - if (!await TryDeserializeProjectDiagnosticsAsync(persistentService, serializer, project, builder, cancellationToken).ConfigureAwait(false)) - { + if (!await TryGetProjectDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, builder, cancellationToken).ConfigureAwait(false)) return DiagnosticAnalysisResult.CreateEmpty(project.Id, VersionStamp.Default); - } return builder.ToResult(); } - private async Task LoadInitialAnalysisDataAsync(IPersistentStorageService persistentService, TextDocument document, CancellationToken cancellationToken) + private async Task LoadInitialAnalysisDataAsync(TextDocument document, CancellationToken cancellationToken) { - // loading data can be cancelled any time. + // loading data can be canceled any time. var project = document.Project; var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); + var serializerVersion = version; var builder = new Builder(project, version); - if (!await TryDeserializeDocumentDiagnosticsAsync(persistentService, serializer, document, builder, cancellationToken).ConfigureAwait(false)) + if (!await TryGetDiagnosticsFromInMemoryStorageAsync(serializerVersion, document, builder, cancellationToken).ConfigureAwait(false)) { return DiagnosticAnalysisResult.CreateEmpty(project.Id, VersionStamp.Default); } @@ -334,45 +322,35 @@ private async Task LoadInitialAnalysisDataAsync(IPersi return builder.ToResult(); } - private async Task LoadInitialProjectAnalysisDataAsync(IPersistentStorageService persistentService, Project project, CancellationToken cancellationToken) + private async Task LoadInitialProjectAnalysisDataAsync(Project project, CancellationToken cancellationToken) { - // loading data can be cancelled any time. + // loading data can be canceled any time. var version = await GetDiagnosticVersionAsync(project, cancellationToken).ConfigureAwait(false); - var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); + var serializerVersion = version; var builder = new Builder(project, version); - if (!await TryDeserializeProjectDiagnosticsAsync(persistentService, serializer, project, builder, cancellationToken).ConfigureAwait(false)) - { + if (!await TryGetProjectDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, builder, cancellationToken).ConfigureAwait(false)) return DiagnosticAnalysisResult.CreateEmpty(project.Id, VersionStamp.Default); - } return builder.ToResult(); } - private async Task SerializeAsync(IPersistentStorageService? persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument? document, object key, string stateKey, ImmutableArray diagnostics) + private ValueTask AddToInMemoryStorageAsync(VersionStamp serializerVersion, Project project, TextDocument? document, object key, string stateKey, ImmutableArray diagnostics) { Contract.ThrowIfFalse(document == null || document.Project == project); - // try to serialize it - if (persistentService != null && - await serializer.SerializeAsync(persistentService, project, document, stateKey, diagnostics, CancellationToken.None).ConfigureAwait(false)) - { - // we succeeded saving it to persistent storage. remove it from in memory cache if it exists - RemoveInMemoryCacheEntry(key, stateKey); - return; - } - // if serialization fail, hold it in the memory - InMemoryStorage.Cache(_owner.Analyzer, (key, stateKey), new CacheEntry(serializer.Version, diagnostics)); + InMemoryStorage.Cache(_owner.Analyzer, (key, stateKey), new CacheEntry(serializerVersion, diagnostics)); + return default; } - private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, TextDocument document, Builder builder, CancellationToken cancellationToken) + private async ValueTask TryGetDiagnosticsFromInMemoryStorageAsync(VersionStamp serializerVersion, TextDocument document, Builder builder, CancellationToken cancellationToken) { var success = true; var project = document.Project; var documentId = document.Id; - var diagnostics = await DeserializeDiagnosticsAsync(persistentService, serializer, project, document, documentId, _owner.SyntaxStateName, cancellationToken).ConfigureAwait(false); + var diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document, documentId, _owner.SyntaxStateName, cancellationToken).ConfigureAwait(false); if (!diagnostics.IsDefault) { builder.AddSyntaxLocals(documentId, diagnostics); @@ -382,7 +360,7 @@ private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStora success = false; } - diagnostics = await DeserializeDiagnosticsAsync(persistentService, serializer, project, document, documentId, _owner.SemanticStateName, cancellationToken).ConfigureAwait(false); + diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document, documentId, _owner.SemanticStateName, cancellationToken).ConfigureAwait(false); if (!diagnostics.IsDefault) { builder.AddSemanticLocals(documentId, diagnostics); @@ -392,7 +370,7 @@ private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStora success = false; } - diagnostics = await DeserializeDiagnosticsAsync(persistentService, serializer, project, document, documentId, _owner.NonLocalStateName, cancellationToken).ConfigureAwait(false); + diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document, documentId, _owner.NonLocalStateName, cancellationToken).ConfigureAwait(false); if (!diagnostics.IsDefault) { builder.AddNonLocals(documentId, diagnostics); @@ -405,9 +383,9 @@ private async Task TryDeserializeDocumentDiagnosticsAsync(IPersistentStora return success; } - private async Task TryDeserializeProjectDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, Builder builder, CancellationToken cancellationToken) + private async ValueTask TryGetProjectDiagnosticsFromInMemoryStorageAsync(VersionStamp serializerVersion, Project project, Builder builder, CancellationToken cancellationToken) { - var diagnostics = await DeserializeDiagnosticsAsync(persistentService, serializer, project, document: null, project.Id, _owner.NonLocalStateName, cancellationToken).ConfigureAwait(false); + var diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document: null, project.Id, _owner.NonLocalStateName, cancellationToken).ConfigureAwait(false); if (!diagnostics.IsDefault) { builder.AddOthers(diagnostics); @@ -417,16 +395,14 @@ private async Task TryDeserializeProjectDiagnosticsAsync(IPersistentStorag return false; } - private ValueTask> DeserializeDiagnosticsAsync(IPersistentStorageService persistentService, DiagnosticDataSerializer serializer, Project project, TextDocument? document, object key, string stateKey, CancellationToken cancellationToken) + private ValueTask> GetDiagnosticsFromInMemoryStorageAsync( + VersionStamp serializerVersion, Project project, TextDocument? document, object key, string stateKey, CancellationToken _) { Contract.ThrowIfFalse(document == null || document.Project == project); - if (InMemoryStorage.TryGetValue(_owner.Analyzer, (key, stateKey), out var entry) && serializer.Version == entry.Version) - { - return ValueTaskFactory.FromResult(entry.Diagnostics); - } - - return serializer.DeserializeAsync(persistentService, project, document, stateKey, cancellationToken); + return InMemoryStorage.TryGetValue(_owner.Analyzer, (key, stateKey), out var entry) && serializerVersion == entry.Version + ? new(entry.Diagnostics) + : default; } private void RemoveInMemoryCache(DiagnosticAnalysisResult lastResult) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index a20a81d9b4241..3d398a1b76fc5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -21,7 +21,6 @@ internal partial class DiagnosticIncrementalAnalyzer /// private partial class StateManager { - private readonly IPersistentStorageService _persistentStorageService; private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache; /// @@ -40,9 +39,8 @@ private partial class StateManager /// public event EventHandler? ProjectAnalyzerReferenceChanged; - public StateManager(IPersistentStorageService persistentStorageService, DiagnosticAnalyzerInfoCache analyzerInfoCache) + public StateManager(DiagnosticAnalyzerInfoCache analyzerInfoCache) { - _persistentStorageService = persistentStorageService; _analyzerInfoCache = analyzerInfoCache; _hostAnalyzerStateMap = ImmutableDictionary.Empty; @@ -190,53 +188,6 @@ public ImmutableArray CreateBuildOnlyProjectStateSet(Project project) return stateSets.ToImmutable(); } - public static bool OnDocumentReset(IEnumerable stateSets, TextDocument document) - { - // can not be cancelled - var removed = false; - foreach (var stateSet in stateSets) - { - removed |= stateSet.OnDocumentReset(document); - } - - return removed; - } - - public async Task OnDocumentOpenedAsync(IEnumerable stateSets, TextDocument document) - { - // can not be cancelled - var opened = false; - foreach (var stateSet in stateSets) - { - opened |= await stateSet.OnDocumentOpenedAsync(_persistentStorageService, document).ConfigureAwait(false); - } - - return opened; - } - - public async Task OnDocumentClosedAsync(IEnumerable stateSets, TextDocument document) - { - // can not be cancelled - var removed = false; - foreach (var stateSet in stateSets) - { - removed |= await stateSet.OnDocumentClosedAsync(_persistentStorageService, document).ConfigureAwait(false); - } - - return removed; - } - - public static bool OnDocumentRemoved(IEnumerable stateSets, DocumentId documentId) - { - var removed = false; - foreach (var stateSet in stateSets) - { - removed |= stateSet.OnDocumentRemoved(documentId); - } - - return removed; - } - public bool OnProjectRemoved(IEnumerable stateSets, ProjectId projectId) { var removed = false; diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs index ef05f8fe17921..62753183d32f5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateSet.cs @@ -24,8 +24,6 @@ private sealed class StateSet public readonly DiagnosticAnalyzer Analyzer; public readonly string ErrorSourceName; - // analyzer version this state belong to - public readonly VersionStamp AnalyzerVersion; private readonly PersistentNames _persistentNames; private readonly ConcurrentDictionary _activeFileStates; @@ -37,9 +35,6 @@ public StateSet(string language, DiagnosticAnalyzer analyzer, string errorSource Analyzer = analyzer; ErrorSourceName = errorSourceName; - var (_, version) = Analyzer.GetAnalyzerIdAndVersion(); - AnalyzerVersion = version; - _persistentNames = PersistentNames.Create(Analyzer); _activeFileStates = new ConcurrentDictionary(concurrencyLevel: 2, capacity: 10); @@ -132,7 +127,7 @@ public ActiveFileState GetOrCreateActiveFileState(DocumentId documentId) public ProjectState GetOrCreateProjectState(ProjectId projectId) => _projectStates.GetOrAdd(projectId, id => new ProjectState(this, id)); - public async Task OnDocumentOpenedAsync(IPersistentStorageService persistentStorageService, TextDocument document) + public async Task OnDocumentOpenedAsync(TextDocument document) { // can not be cancelled if (!TryGetProjectState(document.Project.Id, out var projectState) || @@ -142,7 +137,7 @@ public async Task OnDocumentOpenedAsync(IPersistentStorageService persiste return false; } - var result = await projectState.GetAnalysisDataAsync(persistentStorageService, document, avoidLoadingData: false, CancellationToken.None).ConfigureAwait(false); + var result = await projectState.GetAnalysisDataAsync(document, avoidLoadingData: false, CancellationToken.None).ConfigureAwait(false); // store analysis result to active file state: var activeFileState = GetOrCreateActiveFileState(document.Id); @@ -153,7 +148,7 @@ public async Task OnDocumentOpenedAsync(IPersistentStorageService persiste return true; } - public async Task OnDocumentClosedAsync(IPersistentStorageService persistentStorageService, TextDocument document) + public async Task OnDocumentClosedAsync(TextDocument document) { // can not be cancelled // remove active file state and put it in project state @@ -164,7 +159,7 @@ public async Task OnDocumentClosedAsync(IPersistentStorageService persiste // active file exist, put it in the project state var projectState = GetOrCreateProjectState(document.Project.Id); - await projectState.MergeAsync(persistentStorageService, activeFileState, document).ConfigureAwait(false); + await projectState.MergeAsync(activeFileState, document).ConfigureAwait(false); return true; } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 71198eb522c26..1366c6d094351 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -38,7 +38,6 @@ internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2 internal DiagnosticAnalyzerService AnalyzerService { get; } internal Workspace Workspace { get; } - internal IPersistentStorageService PersistentStorageService { get; } [Obsolete(MefConstruction.FactoryMethodMessage, error: true)] public DiagnosticIncrementalAnalyzer( @@ -51,12 +50,11 @@ public DiagnosticIncrementalAnalyzer( AnalyzerService = analyzerService; Workspace = workspace; - PersistentStorageService = workspace.Services.GetRequiredService(); _documentTrackingService = workspace.Services.GetRequiredService(); _correlationId = correlationId; - _stateManager = new StateManager(PersistentStorageService, analyzerInfoCache); + _stateManager = new StateManager(analyzerInfoCache); _stateManager.ProjectAnalyzerReferenceChanged += OnProjectAnalyzerReferenceChanged; _telemetry = new DiagnosticAnalyzerTelemetry(); diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs index 91c5cc2eb3392..11f101e46b0b5 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_BuildSynchronization.cs @@ -21,7 +21,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2 { internal partial class DiagnosticIncrementalAnalyzer { - public async Task SynchronizeWithBuildAsync( + public async ValueTask SynchronizeWithBuildAsync( ImmutableDictionary> buildDiagnostics, TaskQueue postBuildAndErrorListRefreshTaskQueue, @@ -65,14 +65,7 @@ public async Task SynchronizeWithBuildAsync( var state = stateSet.GetOrCreateProjectState(project.Id); var result = GetResultOrEmpty(newResult, stateSet.Analyzer, project.Id, VersionStamp.Default); - // Save into in-memory cache. - await state.SaveInMemoryCacheAsync(project, result).ConfigureAwait(false); - - // Save into persistent storage on a separate post-build and post error list refresh task queue. - _ = postBuildAndErrorListRefreshTaskQueue.ScheduleTask( - nameof(SynchronizeWithBuildAsync), - () => state.SaveAsync(PersistentStorageService, project, result), - cancellationToken); + await state.SaveToInMemoryStorageAsync(project, result).ConfigureAwait(false); } // Raise diagnostic updated events after the new diagnostics have been stored into the in-memory cache. diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index c9632f514a68d..7ede412128fb9 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -179,7 +179,7 @@ public async Task> GetSpecificDiagnosticsAsync(Di return IncludeSuppressedDiagnostics ? diagnostics : diagnostics.WhereAsArray(d => !d.IsSuppressed); } - private async Task> GetDiagnosticsAsync(StateSet stateSet, Project project, DocumentId? documentId, AnalysisKind kind, CancellationToken cancellationToken) + private static async Task> GetDiagnosticsAsync(StateSet stateSet, Project project, DocumentId? documentId, AnalysisKind kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -193,7 +193,7 @@ private async Task> GetDiagnosticsAsync(StateSet return await GetProjectStateDiagnosticsAsync(stateSet, project, documentId, kind, cancellationToken).ConfigureAwait(false); } - private async Task> GetProjectStateDiagnosticsAsync(StateSet stateSet, Project project, DocumentId? documentId, AnalysisKind kind, CancellationToken cancellationToken) + private static async Task> GetProjectStateDiagnosticsAsync(StateSet stateSet, Project project, DocumentId? documentId, AnalysisKind kind, CancellationToken cancellationToken) { if (!stateSet.TryGetProjectState(project.Id, out var state)) { @@ -210,12 +210,12 @@ private async Task> GetProjectStateDiagnosticsAsy return ImmutableArray.Empty; } - var result = await state.GetAnalysisDataAsync(Owner.PersistentStorageService, document, avoidLoadingData: false, cancellationToken: cancellationToken).ConfigureAwait(false); + var result = await state.GetAnalysisDataAsync(document, avoidLoadingData: false, cancellationToken).ConfigureAwait(false); return result.GetDocumentDiagnostics(documentId, kind); } Contract.ThrowIfFalse(kind == AnalysisKind.NonLocal); - var nonLocalResult = await state.GetProjectAnalysisDataAsync(Owner.PersistentStorageService, project, avoidLoadingData: false, cancellationToken: cancellationToken).ConfigureAwait(false); + var nonLocalResult = await state.GetProjectAnalysisDataAsync(project, avoidLoadingData: false, cancellationToken: cancellationToken).ConfigureAwait(false); return nonLocalResult.GetOtherDiagnostics(); } } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index 2b9812266dfa6..89b9e32a07948 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -8,15 +8,12 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics.Telemetry; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Options; -using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Workspaces.Diagnostics; using Roslyn.Utilities; @@ -158,7 +155,7 @@ private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, C { var state = stateSet.GetOrCreateProjectState(project.Id); - await state.SaveAsync(PersistentStorageService, project, result.GetResult(stateSet.Analyzer)).ConfigureAwait(false); + await state.SaveToInMemoryStorageAsync(project, result.GetResult(stateSet.Analyzer)).ConfigureAwait(false); } RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.OldResult, result.Result); @@ -183,7 +180,10 @@ private async Task TextDocumentOpenAsync(TextDocument document, CancellationToke // let other component knows about this event ClearCompilationsWithAnalyzersCache(); - await _stateManager.OnDocumentOpenedAsync(stateSets, document).ConfigureAwait(false); + + // can not be canceled + foreach (var stateSet in stateSets) + await stateSet.OnDocumentOpenedAsync(document).ConfigureAwait(false); } } @@ -201,7 +201,12 @@ private async Task TextDocumentCloseAsync(TextDocument document, CancellationTok // let other components knows about this event ClearCompilationsWithAnalyzersCache(); - var documentHadDiagnostics = await _stateManager.OnDocumentClosedAsync(stateSets, document).ConfigureAwait(false); + + // can not be canceled + var documentHadDiagnostics = false; + foreach (var stateSet in stateSets) + documentHadDiagnostics |= await stateSet.OnDocumentClosedAsync(document).ConfigureAwait(false); + RaiseDiagnosticsRemovedIfRequiredForClosedOrResetDocument(document, stateSets, documentHadDiagnostics); } } @@ -220,7 +225,11 @@ private Task TextDocumentResetAsync(TextDocument document, CancellationToken can // let other components knows about this event ClearCompilationsWithAnalyzersCache(); - var documentHadDiagnostics = StateManager.OnDocumentReset(stateSets, document); + // can not be canceled + var documentHadDiagnostics = false; + foreach (var stateSet in stateSets) + documentHadDiagnostics |= stateSet.OnDocumentReset(document); + RaiseDiagnosticsRemovedIfRequiredForClosedOrResetDocument(document, stateSets, documentHadDiagnostics); } @@ -255,14 +264,15 @@ public Task RemoveDocumentAsync(DocumentId documentId, CancellationToken cancell // let other components knows about this event ClearCompilationsWithAnalyzersCache(); - var changed = StateManager.OnDocumentRemoved(stateSets, documentId); + + var changed = false; + foreach (var stateSet in stateSets) + changed |= stateSet.OnDocumentRemoved(documentId); // if there was no diagnostic reported for this document, nothing to clean up // this is Perf to reduce raising events unnecessarily. if (changed) - { RaiseDiagnosticsRemovedForDocument(documentId, stateSets); - } } return Task.CompletedTask; diff --git a/src/Interactive/Host/Interactive/Core/InteractiveHost.cs b/src/Interactive/Host/Interactive/Core/InteractiveHost.cs index 1bb5b525bc595..a7d291b934226 100644 --- a/src/Interactive/Host/Interactive/Core/InteractiveHost.cs +++ b/src/Interactive/Host/Interactive/Core/InteractiveHost.cs @@ -307,6 +307,7 @@ private static JsonRpc CreateRpc(Stream stream, object? incomingCallTarget) var rpc = new JsonRpc(new HeaderDelimitedMessageHandler(stream, jsonFormatter)) { CancelLocallyInvokedMethodsWhenConnectionIsClosed = true, + ExceptionStrategy = ExceptionProcessing.ISerializable, }; if (incomingCallTarget != null) diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs index 89ac90eb8844b..f1184deabce3d 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactory.cs @@ -20,19 +20,22 @@ internal sealed class InheritanceGlyphFactory : IGlyphFactory private readonly ClassificationTypeMap _classificationTypeMap; private readonly IClassificationFormatMap _classificationFormatMap; private readonly IUIThreadOperationExecutor _operationExecutor; + private readonly IWpfTextView _textView; public InheritanceGlyphFactory( IThreadingContext threadingContext, IStreamingFindUsagesPresenter streamingFindUsagesPresenter, ClassificationTypeMap classificationTypeMap, IClassificationFormatMap classificationFormatMap, - IUIThreadOperationExecutor operationExecutor) + IUIThreadOperationExecutor operationExecutor, + IWpfTextView textView) { _threadingContext = threadingContext; _streamingFindUsagesPresenter = streamingFindUsagesPresenter; _classificationTypeMap = classificationTypeMap; _classificationFormatMap = classificationFormatMap; _operationExecutor = operationExecutor; + _textView = textView; } public UIElement? GenerateGlyph(IWpfTextViewLine line, IGlyphTag tag) @@ -41,13 +44,18 @@ public InheritanceGlyphFactory( { var membersOnLine = inheritanceMarginTag.MembersOnLine; Contract.ThrowIfTrue(membersOnLine.IsEmpty); + + // ZoomLevel of textView is percentage based. (e.g. 20 -> 400 means 20% -> 400%) + // and the scaleFactor of CrispImage is 1 based. (e.g 1 means 100%) + var scaleFactor = _textView.ZoomLevel / 100; return new MarginGlyph.InheritanceMargin( _threadingContext, _streamingFindUsagesPresenter, _classificationTypeMap, _classificationFormatMap, _operationExecutor, - inheritanceMarginTag); + inheritanceMarginTag, + scaleFactor); } return null; diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs index e14d61fe847a6..7988c9c9683bd 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/InheritanceGlyphFactoryProvider.cs @@ -52,7 +52,8 @@ public IGlyphFactory GetGlyphFactory(IWpfTextView view, IWpfTextViewMargin margi _streamingFindUsagesPresenter, _classificationTypeMap, _classificationFormatMapService.GetClassificationFormatMap("tooltip"), - _operationExecutor); + _operationExecutor, + view); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml index 69c2ac3fed355..c93bf9692e9eb 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml @@ -78,7 +78,7 @@ - + diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs index 4907f6845103c..d2003b6644329 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMargin.xaml.cs @@ -33,7 +33,8 @@ public InheritanceMargin( ClassificationTypeMap classificationTypeMap, IClassificationFormatMap classificationFormatMap, IUIThreadOperationExecutor operationExecutor, - InheritanceMarginTag tag) + InheritanceMarginTag tag, + double scaleFactor) { _threadingContext = threadingContext; _streamingFindUsagesPresenter = streamingFindUsagesPresenter; @@ -41,7 +42,7 @@ public InheritanceMargin( _operationExecutor = operationExecutor; InitializeComponent(); - var viewModel = InheritanceMarginViewModel.Create(classificationTypeMap, classificationFormatMap, tag); + var viewModel = InheritanceMarginViewModel.Create(classificationTypeMap, classificationFormatMap, tag, scaleFactor); DataContext = viewModel; ContextMenu.DataContext = viewModel; ToolTip = new ToolTip { Content = viewModel.ToolTipTextBlock, Style = (Style)FindResource("ToolTipStyle") }; diff --git a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs index 45bb4c41355a3..3a466c45183fe 100644 --- a/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs +++ b/src/VisualStudio/Core/Def/Implementation/InheritanceMargin/MarginGlyph/InheritanceMarginViewModel.cs @@ -37,23 +37,31 @@ internal class InheritanceMarginViewModel /// public ImmutableArray MenuItemViewModels { get; } + /// + /// Scale factor for the margin. + /// + public double ScaleFactor { get; } + // Internal for testing purpose internal InheritanceMarginViewModel( ImageMoniker imageMoniker, TextBlock toolTipTextBlock, string automationName, + double scaleFactor, ImmutableArray menuItemViewModels) { ImageMoniker = imageMoniker; ToolTipTextBlock = toolTipTextBlock; AutomationName = automationName; MenuItemViewModels = menuItemViewModels; + ScaleFactor = scaleFactor; } public static InheritanceMarginViewModel Create( ClassificationTypeMap classificationTypeMap, IClassificationFormatMap classificationFormatMap, - InheritanceMarginTag tag) + InheritanceMarginTag tag, + double scaleFactor) { var members = tag.MembersOnLine; if (members.Length == 1) @@ -74,7 +82,7 @@ public static InheritanceMarginViewModel Create( var automationName = string.Format(ServicesVSResources._0_is_inherited, member.DisplayTexts.JoinText()); var menuItemViewModels = InheritanceMarginHelpers.CreateMenuItemViewModelsForSingleMember(member.TargetItems); - return new InheritanceMarginViewModel(tag.Moniker, toolTipTextBlock, automationName, menuItemViewModels); + return new InheritanceMarginViewModel(tag.Moniker, toolTipTextBlock, automationName, scaleFactor, menuItemViewModels); } else { @@ -86,7 +94,7 @@ public static InheritanceMarginViewModel Create( // Same automation name can't be set for control for accessibility purpose. So add the line number info. var automationName = string.Format(ServicesVSResources.Multiple_members_are_inherited_on_line_0, tag.LineNumber); var menuItemViewModels = InheritanceMarginHelpers.CreateMenuItemViewModelsForMultipleMembers(tag.MembersOnLine); - return new InheritanceMarginViewModel(tag.Moniker, textBlock, automationName, menuItemViewModels); + return new InheritanceMarginViewModel(tag.Moniker, textBlock, automationName, scaleFactor, menuItemViewModels); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs index 181f16f555280..aedcb21c1a2a6 100644 --- a/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs +++ b/src/VisualStudio/Core/Def/Implementation/LanguageClient/AbstractInProcLanguageClient.cs @@ -195,7 +195,11 @@ internal static async Task CreateAsync( var jsonMessageFormatter = new JsonMessageFormatter(); VSExtensionUtilities.AddVSExtensionConverters(jsonMessageFormatter.JsonSerializer); - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter)); + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream, jsonMessageFormatter)) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; + var serverTypeName = languageClient.GetType().Name; var logger = await CreateLoggerAsync(asyncServiceProvider, serverTypeName, clientName, jsonRpc, cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 4a5452e089c6e..37edecb1bc305 100644 --- a/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/Implementation/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -208,7 +208,7 @@ public void ClearErrors(ProjectId projectId) return; - async Task ClearErrorsCoreAsync(ProjectId projectId, Solution solution, InProgressState? state) + async ValueTask ClearErrorsCoreAsync(ProjectId projectId, Solution solution, InProgressState? state) { Debug.Assert(state == null || !state.WereProjectErrorsCleared(projectId)); @@ -311,9 +311,7 @@ internal void OnSolutionBuildCompleted() // pause live analyzer using var operation = _notificationService.Start("BuildDone"); if (_diagnosticService is DiagnosticAnalyzerService diagnosticService) - { await SyncBuildErrorsAndReportOnBuildCompletedAsync(diagnosticService, inProgressState).ConfigureAwait(false); - } // Mark build as complete. OnBuildProgressChanged(inProgressState, BuildProgress.Done); @@ -330,7 +328,7 @@ internal void OnSolutionBuildCompleted() /// It raises diagnostic update events for both the Build-only diagnostics and Build + Intellisense diagnostics /// in the error list. /// - private async Task SyncBuildErrorsAndReportOnBuildCompletedAsync(DiagnosticAnalyzerService diagnosticService, InProgressState inProgressState) + private ValueTask SyncBuildErrorsAndReportOnBuildCompletedAsync(DiagnosticAnalyzerService diagnosticService, InProgressState inProgressState) { var solution = inProgressState.Solution; var cancellationToken = inProgressState.CancellationToken; @@ -357,7 +355,7 @@ private async Task SyncBuildErrorsAndReportOnBuildCompletedAsync(DiagnosticAnaly } // Report pending live errors - await diagnosticService.SynchronizeWithBuildAsync(_workspace, pendingLiveErrorsToSync, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: true, cancellationToken).ConfigureAwait(false); + return diagnosticService.SynchronizeWithBuildAsync(_workspace, pendingLiveErrorsToSync, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: true, cancellationToken); } private void ReportBuildErrors(T item, Solution solution, ImmutableArray buildErrors) @@ -429,9 +427,7 @@ public void AddNewErrors( await ReportPreviousProjectErrorsIfRequiredAsync(projectId, state).ConfigureAwait(false); foreach (var kv in documentErrorMap) - { state.AddErrors(kv.Key, kv.Value); - } state.AddErrors(projectId, projectErrors); }, state.CancellationToken); @@ -445,30 +441,34 @@ public void AddNewErrors( /// This ensures that error list keeps getting refreshed while a build is in progress, as opposed to doing all the work /// and a single refresh when the build completes. /// - private async Task ReportPreviousProjectErrorsIfRequiredAsync(ProjectId projectId, InProgressState state) + private ValueTask ReportPreviousProjectErrorsIfRequiredAsync(ProjectId projectId, InProgressState state) { if (state.TryGetLastProjectWithReportedErrors() is ProjectId lastProjectId && lastProjectId != projectId) { - await SetLiveErrorsForProjectAsync(lastProjectId, state).ConfigureAwait(false); + return SetLiveErrorsForProjectAsync(lastProjectId, state); } + + return default; } - private async Task SetLiveErrorsForProjectAsync(ProjectId projectId, InProgressState state) + private async ValueTask SetLiveErrorsForProjectAsync(ProjectId projectId, InProgressState state) { var diagnostics = state.GetLiveErrorsForProject(projectId); await SetLiveErrorsForProjectAsync(projectId, diagnostics, state.CancellationToken).ConfigureAwait(false); state.MarkLiveErrorsReported(projectId); } - private async Task SetLiveErrorsForProjectAsync(ProjectId projectId, ImmutableArray diagnostics, CancellationToken cancellationToken) + private ValueTask SetLiveErrorsForProjectAsync(ProjectId projectId, ImmutableArray diagnostics, CancellationToken cancellationToken) { if (_diagnosticService is DiagnosticAnalyzerService diagnosticAnalyzerService) { // make those errors live errors var map = ProjectErrorMap.Empty.Add(projectId, diagnostics); - await diagnosticAnalyzerService.SynchronizeWithBuildAsync(_workspace, map, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: false, cancellationToken).ConfigureAwait(false); + return diagnosticAnalyzerService.SynchronizeWithBuildAsync(_workspace, map, _postBuildAndErrorListRefreshTaskQueue, onBuildCompleted: false, cancellationToken); } + + return default; } private CancellationToken GetApplicableCancellationToken(InProgressState? state) diff --git a/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs b/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs index c86284df6c6b9..0067a33d28229 100644 --- a/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/LspDiagnosticsTests.cs @@ -375,7 +375,10 @@ public async Task ClearAllDiagnosticsForMappedFileToManyDocumentsTestAsync() // Notification target for tests to receive the notification details var callback = new Callback(expectedNumberOfCallbacks); - using var jsonRpc = new JsonRpc(clientStream, clientStream, callback); + using var jsonRpc = new JsonRpc(clientStream, clientStream, callback) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; // The json rpc messages won't necessarily come back in order by default. // So use a synchronization context to preserve the original ordering. @@ -399,7 +402,10 @@ static VisualStudioInProcLanguageServer CreateLanguageServer(Stream inputStream, var lspWorkspaceRegistrationService = workspace.ExportProvider.GetExportedValue(); var capabilitiesProvider = workspace.ExportProvider.GetExportedValue(); - var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream)); + var jsonRpc = new JsonRpc(new HeaderDelimitedMessageHandler(outputStream, inputStream)) + { + ExceptionStrategy = ExceptionProcessing.ISerializable, + }; var languageServer = new VisualStudioInProcLanguageServer( dispatcherFactory, diff --git a/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb b/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb index bd354581dfabb..d8c2d3b1bf24c 100644 --- a/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb +++ b/src/VisualStudio/Core/Test/InheritanceMargin/InheritanceMarginViewModelTests.vb @@ -72,7 +72,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.InheritanceMargin Dim acutalTag = acutalLineToTagDictionary(lineNumber) Dim actualViewModel = InheritanceMarginViewModel.Create( - classificationTypeMap, classificationFormatMap.GetClassificationFormatMap("tooltip"), acutalTag) + classificationTypeMap, classificationFormatMap.GetClassificationFormatMap("tooltip"), acutalTag, 1) VerifyTwoViewModelAreSame(expectedViewModel, actualViewModel) Next @@ -155,11 +155,13 @@ public class Bar : IBar KnownMonikers.Implemented, CreateTextBlock(tooltipTextForIBar), tooltipTextForIBar, + 1, targetForIBar)}, {5, New InheritanceMarginViewModel( KnownMonikers.Implementing, CreateTextBlock(tooltipTextForBar), tooltipTextForBar, + 1, targetForBar)}}) End Function @@ -197,21 +199,25 @@ public class Bar : AbsBar KnownMonikers.Implemented, CreateTextBlock(tooltipTextForAbsBar), tooltipTextForAbsBar, + 1, targetForAbsBar)}, {4, New InheritanceMarginViewModel( KnownMonikers.Overridden, CreateTextBlock(tooltipTextForAbstractFoo), tooltipTextForAbstractFoo, + 1, targetForAbsFoo)}, {7, New InheritanceMarginViewModel( KnownMonikers.Implementing, CreateTextBlock(tooltipTextForBar), tooltipTextForBar, + 1, targetForBar)}, {9, New InheritanceMarginViewModel( KnownMonikers.Overriding, CreateTextBlock(tooltipTextForOverrideFoo), tooltipTextForOverrideFoo, + 1, targetForOverrideFoo)}}) End Function @@ -244,16 +250,19 @@ public interface IBar3 : IBar2 { } KnownMonikers.Implemented, CreateTextBlock(tooltipTextForIBar1), tooltipTextForIBar1, + 1, targetForIBar1)}, {3, New InheritanceMarginViewModel( KnownMonikers.Implemented, CreateTextBlock(tooltipTextForIBar2), tooltipTextForIBar2, + 1, targetForIBar2)}, {4, New InheritanceMarginViewModel( KnownMonikers.Implementing, CreateTextBlock(tooltipTextForIBar3), tooltipTextForIBar3, + 1, targetForIBar3)}}) End Function @@ -300,21 +309,25 @@ public class BarSample : IBar1 KnownMonikers.Implemented, CreateTextBlock(tooltipTextForIBar1), tooltipTextForIBar1, + 1, targetForIBar1)}, {5, New InheritanceMarginViewModel( KnownMonikers.Implemented, CreateTextBlock(tooltipTextForE1AndE2InInterface), String.Format(ServicesVSResources.Multiple_members_are_inherited_on_line_0, 5), + 1, targetForE1AndE2InInterface)}, {8, New InheritanceMarginViewModel( KnownMonikers.Implementing, CreateTextBlock(tooltipTextForBarSample), tooltipTextForBarSample, + 1, targetForBarSample)}, {10, New InheritanceMarginViewModel( KnownMonikers.Implementing, CreateTextBlock(tooltipTextForE1AndE2InBarSample), String.Format(ServicesVSResources.Multiple_members_are_inherited_on_line_0, 10), + 1, targetForE1AndE2InInBarSample)}}) End Function End Class diff --git a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs b/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs deleted file mode 100644 index 69c9dae08b888..0000000000000 --- a/src/Workspaces/Core/Portable/Diagnostics/DiagnosticDataSerializer.cs +++ /dev/null @@ -1,368 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Workspaces.Diagnostics -{ - /// - /// DiagnosticData serializer - /// - internal readonly struct DiagnosticDataSerializer - { - // version of serialized format - private const int FormatVersion = 1; - - // version of analyzer that produced this data - public readonly VersionStamp AnalyzerVersion; - - // version of project this data belong to - public readonly VersionStamp Version; - - public DiagnosticDataSerializer(VersionStamp analyzerVersion, VersionStamp version) - { - AnalyzerVersion = analyzerVersion; - Version = version; - } - - public async Task SerializeAsync(IPersistentStorageService persistentService, Project project, TextDocument? textDocument, string key, ImmutableArray items, CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(textDocument == null || textDocument.Project == project); - - using var stream = SerializableBytes.CreateWritableStream(); - - using (var writer = new ObjectWriter(stream, leaveOpen: true, cancellationToken)) - { - WriteDiagnosticData(writer, items, cancellationToken); - } - - var storage = await persistentService.GetStorageAsync(project.Solution, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); - - stream.Position = 0; - - var writeTask = (textDocument != null) ? - textDocument is Document document ? - storage.WriteStreamAsync(document, key, stream, cancellationToken) : - storage.WriteStreamAsync(GetSerializationKeyForNonSourceDocument(textDocument, key), stream, cancellationToken) : - storage.WriteStreamAsync(project, key, stream, cancellationToken); - - return await writeTask.ConfigureAwait(false); - } - - private static string GetSerializationKeyForNonSourceDocument(TextDocument document, string key) - => document.Id + ";" + key; - - public async ValueTask> DeserializeAsync(IPersistentStorageService persistentService, Project project, TextDocument? textDocument, string key, CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(textDocument == null || textDocument.Project == project); - - var storage = await persistentService.GetStorageAsync(project.Solution, cancellationToken).ConfigureAwait(false); - await using var _ = storage.ConfigureAwait(false); - - var readTask = (textDocument != null) ? - textDocument is Document document ? - storage.ReadStreamAsync(document, key, cancellationToken) : - storage.ReadStreamAsync(GetSerializationKeyForNonSourceDocument(textDocument, key), cancellationToken) : - storage.ReadStreamAsync(project, key, cancellationToken); - - using var stream = await readTask.ConfigureAwait(false); - using var reader = ObjectReader.TryGetReader(stream, cancellationToken: cancellationToken); - - if (reader == null || - !TryReadDiagnosticData(reader, project, textDocument, cancellationToken, out var data)) - { - return default; - } - - return data; - } - - public void WriteDiagnosticData(ObjectWriter writer, ImmutableArray items, CancellationToken cancellationToken) - { - writer.WriteInt32(FormatVersion); - - AnalyzerVersion.WriteTo(writer); - Version.WriteTo(writer); - - writer.WriteInt32(items.Length); - - foreach (var item in items) - { - cancellationToken.ThrowIfCancellationRequested(); - - writer.WriteString(item.Id); - writer.WriteString(item.Category); - - writer.WriteString(item.Message); - writer.WriteString(item.ENUMessageForBingSearch); - writer.WriteString(item.Title); - writer.WriteString(item.Description); - writer.WriteString(item.HelpLink); - writer.WriteInt32((int)item.Severity); - writer.WriteInt32((int)item.DefaultSeverity); - writer.WriteBoolean(item.IsEnabledByDefault); - writer.WriteBoolean(item.IsSuppressed); - writer.WriteInt32(item.WarningLevel); - - // unused - writer.WriteInt32(0); - writer.WriteInt32(0); - - WriteLocation(writer, item.DataLocation); - WriteAdditionalLocations(writer, item.AdditionalLocations, cancellationToken); - - writer.WriteInt32(item.CustomTags.Length); - foreach (var tag in item.CustomTags) - writer.WriteString(tag); - - writer.WriteInt32(item.Properties.Count); - foreach (var property in item.Properties) - { - writer.WriteString(property.Key); - writer.WriteString(property.Value); - } - } - } - - private static void WriteAdditionalLocations(ObjectWriter writer, IReadOnlyCollection additionalLocations, CancellationToken cancellationToken) - { - writer.WriteInt32(additionalLocations.Count); - - foreach (var location in additionalLocations) - { - cancellationToken.ThrowIfCancellationRequested(); - WriteLocation(writer, location); - } - } - - private static void WriteLocation(ObjectWriter writer, DiagnosticDataLocation? item) - { - if (item == null) - { - writer.WriteBoolean(false); - return; - } - - writer.WriteBoolean(true); - - if (item.SourceSpan.HasValue) - { - writer.WriteBoolean(true); - writer.WriteInt32(item.SourceSpan.Value.Start); - writer.WriteInt32(item.SourceSpan.Value.Length); - } - else - { - writer.WriteBoolean(false); - } - - writer.WriteString(item.OriginalFilePath); - writer.WriteInt32(item.OriginalStartLine); - writer.WriteInt32(item.OriginalStartColumn); - writer.WriteInt32(item.OriginalEndLine); - writer.WriteInt32(item.OriginalEndColumn); - - writer.WriteString(item.MappedFilePath); - writer.WriteInt32(item.MappedStartLine); - writer.WriteInt32(item.MappedStartColumn); - writer.WriteInt32(item.MappedEndLine); - writer.WriteInt32(item.MappedEndColumn); - } - - public bool TryReadDiagnosticData( - ObjectReader reader, - Project project, - TextDocument? document, - CancellationToken cancellationToken, - out ImmutableArray data) - { - data = default; - - try - { - var format = reader.ReadInt32(); - if (format != FormatVersion) - { - return false; - } - - // saved data is for same analyzer of different version of dll - var analyzerVersion = VersionStamp.ReadFrom(reader); - if (analyzerVersion != AnalyzerVersion) - { - return false; - } - - var version = VersionStamp.ReadFrom(reader); - if (version != VersionStamp.Default && version != Version) - { - return false; - } - - data = ReadDiagnosticDataArray(reader, project, document, cancellationToken); - return true; - } - catch (EndOfStreamException) when (cancellationToken.IsCancellationRequested) - { - // The reader was closed due to a cancellation request while in the process of reading a value. Make - // sure to propagate the exception as cancellation and not an error. - cancellationToken.ThrowIfCancellationRequested(); - throw ExceptionUtilities.Unreachable; - } - catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex)) - { - return false; - } - } - - private static ImmutableArray ReadDiagnosticDataArray(ObjectReader reader, Project project, TextDocument? document, CancellationToken cancellationToken) - { - var count = reader.ReadInt32(); - if (count == 0) - { - return ImmutableArray.Empty; - } - - var builder = ArrayBuilder.GetInstance(count); - - for (var i = 0; i < count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - var id = reader.ReadString(); - var category = reader.ReadString(); - - var message = reader.ReadString(); - var messageFormat = reader.ReadString(); - var title = reader.ReadString(); - var description = reader.ReadString(); - var helpLink = reader.ReadString(); - var severity = (DiagnosticSeverity)reader.ReadInt32(); - var defaultSeverity = (DiagnosticSeverity)reader.ReadInt32(); - var isEnabledByDefault = reader.ReadBoolean(); - var isSuppressed = reader.ReadBoolean(); - var warningLevel = reader.ReadInt32(); - - // these fields are unused - the actual span is read in ReadLocation - _ = reader.ReadInt32(); - _ = reader.ReadInt32(); - - var location = ReadLocation(project, reader, document); - var additionalLocations = ReadAdditionalLocations(project, reader); - - var customTagsCount = reader.ReadInt32(); - var customTags = GetCustomTags(reader, customTagsCount); - - var propertiesCount = reader.ReadInt32(); - var properties = GetProperties(reader, propertiesCount); - - builder.Add(new DiagnosticData( - id: id, - category: category, - message: message, - enuMessageForBingSearch: messageFormat, - severity: severity, - defaultSeverity: defaultSeverity, - isEnabledByDefault: isEnabledByDefault, - warningLevel: warningLevel, - customTags: customTags, - properties: properties, - projectId: project.Id, - location: location, - additionalLocations: additionalLocations, - language: project.Language, - title: title, - description: description, - helpLink: helpLink, - isSuppressed: isSuppressed)); - } - - return builder.ToImmutableAndFree(); - } - - private static DiagnosticDataLocation? ReadLocation(Project project, ObjectReader reader, TextDocument? document) - { - var exists = reader.ReadBoolean(); - if (!exists) - { - return null; - } - - TextSpan? sourceSpan = null; - if (reader.ReadBoolean()) - { - sourceSpan = new TextSpan(reader.ReadInt32(), reader.ReadInt32()); - } - - var originalFile = reader.ReadString(); - var originalStartLine = reader.ReadInt32(); - var originalStartColumn = reader.ReadInt32(); - var originalEndLine = reader.ReadInt32(); - var originalEndColumn = reader.ReadInt32(); - - var mappedFile = reader.ReadString(); - var mappedStartLine = reader.ReadInt32(); - var mappedStartColumn = reader.ReadInt32(); - var mappedEndLine = reader.ReadInt32(); - var mappedEndColumn = reader.ReadInt32(); - - var documentId = document != null - ? document.Id - : project.Solution.GetDocumentIdsWithFilePath(originalFile).FirstOrDefault(documentId => documentId.ProjectId == project.Id); - - return new DiagnosticDataLocation(documentId, sourceSpan, - originalFile, originalStartLine, originalStartColumn, originalEndLine, originalEndColumn, - mappedFile, mappedStartLine, mappedStartColumn, mappedEndLine, mappedEndColumn); - } - - private static ImmutableArray ReadAdditionalLocations(Project project, ObjectReader reader) - { - var count = reader.ReadInt32(); - using var _ = ArrayBuilder.GetInstance(count, out var result); - for (var i = 0; i < count; i++) - result.AddIfNotNull(ReadLocation(project, reader, document: null)); - - return result.ToImmutable(); - } - - private static ImmutableDictionary GetProperties(ObjectReader reader, int count) - { - if (count > 0) - { - var properties = ImmutableDictionary.CreateBuilder(); - for (var i = 0; i < count; i++) - { - properties.Add(reader.ReadString(), reader.ReadString()); - } - - return properties.ToImmutable(); - } - - return ImmutableDictionary.Empty; - } - - private static ImmutableArray GetCustomTags(ObjectReader reader, int count) - { - using var _ = ArrayBuilder.GetInstance(count, out var tags); - for (var i = 0; i < count; i++) - tags.Add(reader.ReadString()); - - return tags.ToImmutable(); - } - } -} diff --git a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs index ff0557b384d9d..061470a34a43b 100644 --- a/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs +++ b/src/Workspaces/Core/Portable/Storage/SQLite/v2/SQLitePersistentStorage_Threading.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.SQLite.v2 { @@ -28,12 +29,30 @@ private static async Task PerformTaskAsync( // Read tasks go to the concurrent-scheduler where they can run concurrently with other read // tasks. private Task PerformReadAsync(Func func, TArg arg, CancellationToken cancellationToken) where TArg : struct - => PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ConcurrentScheduler, cancellationToken); + { + // Suppress ExecutionContext flow for asynchronous operations that write to the database. In addition to + // avoiding ExecutionContext allocations, this clears the LogicalCallContext and avoids the need to clone + // data set by CallContext.LogicalSetData at each yielding await in the task tree. + // + // ⚠ DO NOT AWAIT INSIDE THE USING. The Dispose method that restores ExecutionContext flow must run on the + // same thread where SuppressFlow was originally run. + using var _ = FlowControlHelper.TrySuppressFlow(); + return PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ConcurrentScheduler, cancellationToken); + } // Write tasks go to the exclusive-scheduler so they run exclusively of all other threading // tasks we need to do. public Task PerformWriteAsync(Func func, TArg arg, CancellationToken cancellationToken) where TArg : struct - => PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ExclusiveScheduler, cancellationToken); + { + // Suppress ExecutionContext flow for asynchronous operations that write to the database. In addition to + // avoiding ExecutionContext allocations, this clears the LogicalCallContext and avoids the need to clone + // data set by CallContext.LogicalSetData at each yielding await in the task tree. + // + // ⚠ DO NOT AWAIT INSIDE THE USING. The Dispose method that restores ExecutionContext flow must run on the + // same thread where SuppressFlow was originally run. + using var _ = FlowControlHelper.TrySuppressFlow(); + return PerformTaskAsync(func, arg, _connectionPoolService.Scheduler.ExclusiveScheduler, cancellationToken); + } public Task PerformWriteAsync(Action action, CancellationToken cancellationToken) => PerformWriteAsync(vt => diff --git a/src/Workspaces/Core/Portable/Utilities/FlowControlHelper.cs b/src/Workspaces/Core/Portable/Utilities/FlowControlHelper.cs new file mode 100644 index 0000000000000..686c87f456b96 --- /dev/null +++ b/src/Workspaces/Core/Portable/Utilities/FlowControlHelper.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; + +namespace Roslyn.Utilities +{ + internal static class FlowControlHelper + { + public static AsyncFlowControlHelper TrySuppressFlow() + => new(ExecutionContext.IsFlowSuppressed() ? default : ExecutionContext.SuppressFlow()); + + public struct AsyncFlowControlHelper : IDisposable + { + private readonly AsyncFlowControl _asyncFlowControl; + + public AsyncFlowControlHelper(AsyncFlowControl asyncFlowControl) + { + _asyncFlowControl = asyncFlowControl; + } + + public void Dispose() + { + if (_asyncFlowControl != default) + { + _asyncFlowControl.Dispose(); + } + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 0b2fdc942ef0b..fcd5eab374c23 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -190,7 +190,8 @@ public SolutionState WithNewWorkspace(Workspace workspace, int workspaceVersion) /// public IReadOnlyList ProjectIds { get; } - // [Conditional("DEBUG")] + // Only run this in debug builds; even the .Any() call across all projects can be expensive when there's a lot of them. + [Conditional("DEBUG")] private void CheckInvariants() { Contract.ThrowIfFalse(_projectIdToProjectStateMap.Count == ProjectIds.Count); diff --git a/src/Workspaces/Remote/Core/RemoteEndPoint.cs b/src/Workspaces/Remote/Core/RemoteEndPoint.cs index 83fc6538e543b..824b62a96c2ba 100644 --- a/src/Workspaces/Remote/Core/RemoteEndPoint.cs +++ b/src/Workspaces/Remote/Core/RemoteEndPoint.cs @@ -66,7 +66,8 @@ public RemoteEndPoint(Stream stream, TraceSource logger, object? incomingCallTar _rpc = new JsonRpc(new HeaderDelimitedMessageHandler(stream, jsonFormatter)) { CancelLocallyInvokedMethodsWhenConnectionIsClosed = true, - TraceSource = logger + TraceSource = logger, + ExceptionStrategy = ExceptionProcessing.ISerializable, }; if (incomingCallTarget != null)