diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.RemoteHostClientService.cs b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.RemoteHostClientService.cs index c38e33f2c2e3631031f296c50befa894efec3f58..3f9282bc8163ca26e5c6a90b1c7db53df7e57983 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.RemoteHostClientService.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.RemoteHostClientService.cs @@ -129,7 +129,7 @@ public Task GetRemoteHostClientAsync(CancellationToken cancell private async Task EnableAsync(CancellationToken cancellationToken) { - await AddGlobalAssetsAsync(cancellationToken).ConfigureAwait(false); + AddGlobalAssets(cancellationToken); // if we reached here, IRemoteHostClientFactory must exist. // this will make VS.Next dll to be loaded @@ -139,7 +139,7 @@ private async Task EnableAsync(CancellationToken cancellationT return instance; } - private async Task AddGlobalAssetsAsync(CancellationToken cancellationToken) + private void AddGlobalAssets(CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.RemoteHostClientService_AddGlobalAssetsAsync, cancellationToken)) { @@ -148,7 +148,7 @@ private async Task AddGlobalAssetsAsync(CancellationToken cancellationToken) foreach (var reference in _analyzerService.GetHostAnalyzerReferences()) { - var asset = await assetBuilder.BuildAsync(reference, cancellationToken).ConfigureAwait(false); + var asset = assetBuilder.Build(reference, cancellationToken); snapshotService.AddGlobalAsset(reference, asset, cancellationToken); } } diff --git a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.SolutionChecksumUpdater.cs b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.SolutionChecksumUpdater.cs index 203eed1968cea7a1e04dc46160e7aaf619ef5f8b..613f72c4e37e18c6845119de4952a532649cdc22 100644 --- a/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.SolutionChecksumUpdater.cs +++ b/src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostClientServiceFactory.SolutionChecksumUpdater.cs @@ -24,6 +24,7 @@ private class SolutionChecksumUpdater : GlobalOperationAwareIdleProcessor // hold onto last snapshot private CancellationTokenSource _globalOperationCancellationSource; private ChecksumScope _lastSnapshot; + private bool _synchronize; public SolutionChecksumUpdater(RemoteHostClientService service, CancellationToken shutdownToken) : base(AggregateAsynchronousOperationListener.CreateEmptyListener(), @@ -54,6 +55,12 @@ protected override async Task ExecuteAsync() // cancel updating solution checksum if a global operation (such as loading solution, building solution and etc) has started await UpdateSolutionChecksumAsync(_globalOperationCancellationSource.Token).ConfigureAwait(false); + + // check whether we had bulk change that require asset synchronization + if (_synchronize) + { + await SynchronizeAssets().ConfigureAwait(false); + } } protected override void PauseOnGlobalOperation() @@ -112,16 +119,23 @@ public override void Shutdown() private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs e) { - // special initial case - if (e.Kind == WorkspaceChangeKind.SolutionAdded) + // special bulk update case + if (e.Kind == WorkspaceChangeKind.SolutionAdded || + e.Kind == WorkspaceChangeKind.ProjectAdded) { - CreateInitialSolutionChecksum(); + _synchronize = true; + EnqueueChecksumUpdate(); return; } // record that we are busy UpdateLastAccessTime(); + EnqueueChecksumUpdate(); + } + + private void EnqueueChecksumUpdate() + { // event will raised sequencially. no concurrency on this handler if (_event.CurrentCount > 0) { @@ -146,27 +160,22 @@ private async Task UpdateSolutionChecksumAsync(CancellationToken cancellationTok } } - private void CreateInitialSolutionChecksum() + private async Task SynchronizeAssets() { - // initial solution checksum creation won't be affected by global operation. - // cancellation can only happen if it is being shutdown. - Task.Run(async () => - { - await UpdateSolutionChecksumAsync(ShutdownCancellationToken).ConfigureAwait(false); + _synchronize = false; - var remoteHostClient = await _service.GetRemoteHostClientAsync(ShutdownCancellationToken).ConfigureAwait(false); - if (remoteHostClient == null) - { - return; - } + var remoteHostClient = await _service.GetRemoteHostClientAsync(ShutdownCancellationToken).ConfigureAwait(false); + if (remoteHostClient == null) + { + return; + } - var solution = _service.Workspace.CurrentSolution; - using (var session = await remoteHostClient.CreateServiceSessionAsync(WellKnownRemoteHostServices.RemoteHostService, solution, ShutdownCancellationToken).ConfigureAwait(false)) - { - // ask remote host to sync initial asset - await session.InvokeAsync(WellKnownRemoteHostServices.RemoteHostService_SynchronizeAsync).ConfigureAwait(false); - } - }, ShutdownCancellationToken); + var solution = _service.Workspace.CurrentSolution; + using (var session = await remoteHostClient.CreateServiceSessionAsync(WellKnownRemoteHostServices.RemoteHostService, solution, ShutdownCancellationToken).ConfigureAwait(false)) + { + // ask remote host to sync initial asset + await session.InvokeAsync(WellKnownRemoteHostServices.RemoteHostService_SynchronizeAsync).ConfigureAwait(false); + } } private static void CancelAndDispose(CancellationTokenSource cancellationSource) diff --git a/src/VisualStudio/Next/Diagnostics/OutOfProcDiagnosticAnalyzerExecutor.cs b/src/VisualStudio/Next/Diagnostics/OutOfProcDiagnosticAnalyzerExecutor.cs index 8954bbb40ceed9d37df82fbd2fe279371909f858..ae10fa3e53802ddc78b77569306a7e04fff3f8e4 100644 --- a/src/VisualStudio/Next/Diagnostics/OutOfProcDiagnosticAnalyzerExecutor.cs +++ b/src/VisualStudio/Next/Diagnostics/OutOfProcDiagnosticAnalyzerExecutor.cs @@ -66,6 +66,12 @@ internal class OutOfProcDiagnosticAnalyzerExecutor : IRemoteHostDiagnosticAnalyz private async Task> AnalyzeInProcAsync(CompilationWithAnalyzers analyzerDriver, Project project, CancellationToken cancellationToken) { + if (analyzerDriver == null) + { + // no analyzers for in proc process + return DiagnosticAnalysisResultMap.Create(ImmutableDictionary.Empty, ImmutableDictionary.Empty); + } + return await InProcCodeAnalysisDiagnosticAnalyzerExecutor.Instance.AnalyzeAsync(analyzerDriver, project, cancellationToken).ConfigureAwait(false); } @@ -106,6 +112,12 @@ internal class OutOfProcDiagnosticAnalyzerExecutor : IRemoteHostDiagnosticAnalyz private CompilationWithAnalyzers CreateAnalyzerDriver(CompilationWithAnalyzers analyzerDriver, Func predicate) { var analyzers = analyzerDriver.Analyzers.Where(predicate).ToImmutableArray(); + if (analyzers.Length == 0) + { + // we can't create analyzer driver with 0 analyzers + return null; + } + return analyzerDriver.Compilation.WithAnalyzers(analyzers, analyzerDriver.AnalysisOptions); } diff --git a/src/Workspaces/Core/Portable/Execution/AssetBuilder.cs b/src/Workspaces/Core/Portable/Execution/AssetBuilder.cs index 16e7ed916dda90ff5dc8a9e988c2bc68704b6eb2..992d4ccfd57b8359c94cc4402301adbbb3228da5 100644 --- a/src/Workspaces/Core/Portable/Execution/AssetBuilder.cs +++ b/src/Workspaces/Core/Portable/Execution/AssetBuilder.cs @@ -24,112 +24,117 @@ public AssetBuilder(IChecksumTreeNode checksumTree) _serializer = checksumTree.Serializer; } - public Task BuildAsync(SolutionState solutionState, CancellationToken cancellationToken) + public Asset Build(SolutionState solutionState, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateAssetAsync(solutionState, GetInfo(solutionState), WellKnownChecksumObjects.SolutionChecksumObjectInfo, CreateSolutionChecksumObjectInfoAsync, cancellationToken); + return _checksumTree.GetOrCreateAsset(solutionState, GetInfo(solutionState), WellKnownChecksumObjects.SolutionChecksumObjectInfo, CreateSolutionChecksumObjectInfo, cancellationToken); } - public Task BuildAsync(ProjectState projectState, CancellationToken cancellationToken) + public Asset Build(ProjectState projectState, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateAssetAsync(projectState, GetInfo(projectState), WellKnownChecksumObjects.ProjectChecksumObjectInfo, CreateProjectChecksumObjectInfoAsync, cancellationToken); + return _checksumTree.GetOrCreateAsset(projectState, GetInfo(projectState), WellKnownChecksumObjects.ProjectChecksumObjectInfo, CreateProjectChecksumObjectInfo, cancellationToken); } - public Task BuildAsync(TextDocumentState document, CancellationToken cancellationToken) + public Asset Build(TextDocumentState document, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateAssetAsync(document, GetInfo(document), WellKnownChecksumObjects.DocumentChecksumObjectInfo, CreateDocumentChecksumObjectInfoAsync, cancellationToken); + return _checksumTree.GetOrCreateAsset(document, GetInfo(document), WellKnownChecksumObjects.DocumentChecksumObjectInfo, CreateDocumentChecksumObjectInfo, cancellationToken); } - public Task BuildAsync(ProjectState projectState, CompilationOptions compilationOptions, CancellationToken cancellationToken) + public Asset Build(ProjectState projectState, CompilationOptions compilationOptions, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateAssetAsync(compilationOptions, projectState, WellKnownChecksumObjects.CompilationOptions, CreateCompilationOptionsAsync, cancellationToken); + return _checksumTree.GetOrCreateAsset(compilationOptions, projectState, WellKnownChecksumObjects.CompilationOptions, CreateCompilationOptions, cancellationToken); } - public Task BuildAsync(ProjectState projectState, ParseOptions parseOptions, CancellationToken cancellationToken) + public Asset Build(ProjectState projectState, ParseOptions parseOptions, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateAssetAsync(parseOptions, projectState, WellKnownChecksumObjects.ParseOptions, CreateParseOptionsAsync, cancellationToken); + return _checksumTree.GetOrCreateAsset(parseOptions, projectState, WellKnownChecksumObjects.ParseOptions, CreateParseOptions, cancellationToken); } - public Task BuildAsync(ProjectReference reference, CancellationToken cancellationToken) + public Asset Build(ProjectReference reference, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateAssetAsync(reference, reference, WellKnownChecksumObjects.ProjectReference, CreateProjectReferenceAsync, cancellationToken); + return _checksumTree.GetOrCreateAsset(reference, reference, WellKnownChecksumObjects.ProjectReference, CreateProjectReference, cancellationToken); } - public Task BuildAsync(MetadataReference reference, CancellationToken cancellationToken) + public Asset Build(MetadataReference reference, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateAssetAsync(reference, reference, WellKnownChecksumObjects.MetadataReference, CreateMetadataReferenceAsync, cancellationToken); + return _checksumTree.GetOrCreateAsset(reference, reference, WellKnownChecksumObjects.MetadataReference, CreateMetadataReference, cancellationToken); } - public Task BuildAsync(AnalyzerReference reference, CancellationToken cancellationToken) + public Asset Build(AnalyzerReference reference, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateAssetAsync(reference, reference, WellKnownChecksumObjects.AnalyzerReference, CreateAnalyzerReferenceAsync, cancellationToken); + return _checksumTree.GetOrCreateAsset(reference, reference, WellKnownChecksumObjects.AnalyzerReference, CreateAnalyzerReference, cancellationToken); } - public Task BuildAsync(TextDocumentState state, SourceText unused, CancellationToken cancellationToken) + public Asset Build(TextDocumentState state, SourceText text, CancellationToken cancellationToken) { // TODO: currently this is a bit wierd not to hold onto source text. // it would be nice if SourceText is changed like how recoverable syntax tree work. - return _checksumTree.GetOrCreateAssetAsync(state, state, WellKnownChecksumObjects.SourceText, CreateSourceTextAsync, cancellationToken); + var asset = _checksumTree.GetOrCreateAsset(state, state, WellKnownChecksumObjects.SourceText, CreateSourceText, cancellationToken); + + // make sure we keep text alive. this is to make sure we don't do any async call in asset builder + GC.KeepAlive(text); + return asset; } - private Task CreateSolutionChecksumObjectInfoAsync(SolutionChecksumObjectInfo info, string kind, CancellationToken cancellationToken) + private Asset CreateSolutionChecksumObjectInfo(SolutionChecksumObjectInfo info, string kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(new Asset(info, kind, _serializer.SerializeSolutionChecksumObjectInfo)); + return new Asset(info, kind, _serializer.SerializeSolutionChecksumObjectInfo); } - private Task CreateProjectChecksumObjectInfoAsync(ProjectChecksumObjectInfo info, string kind, CancellationToken cancellationToken) + private Asset CreateProjectChecksumObjectInfo(ProjectChecksumObjectInfo info, string kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(new Asset(info, kind, _serializer.SerializeProjectChecksumObjectInfo)); + return new Asset(info, kind, _serializer.SerializeProjectChecksumObjectInfo); } - private Task CreateDocumentChecksumObjectInfoAsync(DocumentChecksumObjectInfo info, string kind, CancellationToken cancellationToken) + private Asset CreateDocumentChecksumObjectInfo(DocumentChecksumObjectInfo info, string kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(new Asset(info, kind, _serializer.SerializeDocumentChecksumObjectInfo)); + return new Asset(info, kind, _serializer.SerializeDocumentChecksumObjectInfo); } - private Task CreateCompilationOptionsAsync(ProjectState projectState, string kind, CancellationToken cancellationToken) + private Asset CreateCompilationOptions(ProjectState projectState, string kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(new LanguageSpecificAsset(projectState.Language, projectState.CompilationOptions, kind, _serializer.SerializeCompilationOptions)); + return new LanguageSpecificAsset(projectState.Language, projectState.CompilationOptions, kind, _serializer.SerializeCompilationOptions); } - private Task CreateParseOptionsAsync(ProjectState projectState, string kind, CancellationToken cancellationToken) + private Asset CreateParseOptions(ProjectState projectState, string kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(new LanguageSpecificAsset(projectState.Language, projectState.ParseOptions, kind, _serializer.SerializeParseOptions)); + return new LanguageSpecificAsset(projectState.Language, projectState.ParseOptions, kind, _serializer.SerializeParseOptions); } - private Task CreateProjectReferenceAsync(ProjectReference reference, string kind, CancellationToken cancellationToken) + private Asset CreateProjectReference(ProjectReference reference, string kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - return Task.FromResult(new Asset(reference, kind, _serializer.SerializeProjectReference)); + return new Asset(reference, kind, _serializer.SerializeProjectReference); } - private Task CreateMetadataReferenceAsync(MetadataReference reference, string kind, CancellationToken cancellationToken) + private Asset CreateMetadataReference(MetadataReference reference, string kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var checksum = _serializer.HostSerializationService.CreateChecksum(reference, cancellationToken); - return Task.FromResult(new MetadataReferenceAsset(_serializer, reference, checksum, kind)); + return new MetadataReferenceAsset(_serializer, reference, checksum, kind); } - private Task CreateAnalyzerReferenceAsync(AnalyzerReference reference, string kind, CancellationToken cancellationToken) + private Asset CreateAnalyzerReference(AnalyzerReference reference, string kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); var checksum = _serializer.HostSerializationService.CreateChecksum(reference, cancellationToken); - return Task.FromResult(new AnalyzerReferenceAsset(_serializer, reference, checksum, kind)); + return new AnalyzerReferenceAsset(_serializer, reference, checksum, kind); } - private async Task CreateSourceTextAsync(TextDocumentState state, string kind, CancellationToken cancellationToken) + private Asset CreateSourceText(TextDocumentState state, string kind, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var text = await state.GetTextAsync(cancellationToken).ConfigureAwait(false); - var checksum = new Checksum(text.GetChecksum()); + SourceText text; + Contract.ThrowIfFalse(state.TryGetText(out text)); + var checksum = new Checksum(text.GetChecksum()); return new SourceTextAsset(_serializer, state, checksum, kind); } @@ -182,13 +187,20 @@ public AssetOnlyTreeNode(Solution solution) public Serializer Serializer { get; } - public Task GetOrCreateAssetAsync( - TKey key, TValue value, string kind, Func> valueGetterAsync, CancellationToken cancellationToken) + public Asset GetOrCreateAsset( + TKey key, TValue value, string kind, Func valueGetter, CancellationToken cancellationToken) where TKey : class - where TResult : Asset { Contract.ThrowIfNull(key); - return valueGetterAsync(value, kind, cancellationToken); + return valueGetter(value, kind, cancellationToken); + } + + public TResult GetOrCreateChecksumObjectWithChildren( + TKey key, TValue value, string kind, Func valueGetter, CancellationToken cancellationToken) + where TKey : class + where TResult : ChecksumObjectWithChildren + { + return Contract.FailWithReturn("shouldn't be called"); } public Task GetOrCreateChecksumObjectWithChildrenAsync( diff --git a/src/Workspaces/Core/Portable/Execution/ChecksumObject.cs b/src/Workspaces/Core/Portable/Execution/ChecksumObject.cs index 44a957656a16aa5834d3c16c0d3e77f017ccf6ac..207983d0ea5096d1f918f85d6523842c00bbfb13 100644 --- a/src/Workspaces/Core/Portable/Execution/ChecksumObject.cs +++ b/src/Workspaces/Core/Portable/Execution/ChecksumObject.cs @@ -60,7 +60,8 @@ internal abstract class ChecksumObjectWithChildren : ChecksumObject public override Task WriteObjectToAsync(ObjectWriter writer, CancellationToken cancellationToken) { - return _serializer.SerializeChecksumObjectWithChildrenAsync(this, writer, cancellationToken); + _serializer.SerializeChecksumObjectWithChildren(this, writer, cancellationToken); + return SpecializedTasks.EmptyTask; } private static Checksum CreateChecksum(string kind, object[] children) diff --git a/src/Workspaces/Core/Portable/Execution/ChecksumTreeBuilder.cs b/src/Workspaces/Core/Portable/Execution/ChecksumTreeBuilder.cs index 756d3bb3d54b2ba0b0cf56408a6d37fc3d1aa865..31c8f1786bae8ae43cee452e0fa4f1aecdc9d959 100644 --- a/src/Workspaces/Core/Portable/Execution/ChecksumTreeBuilder.cs +++ b/src/Workspaces/Core/Portable/Execution/ChecksumTreeBuilder.cs @@ -47,25 +47,25 @@ private Task BuildAsync(ImmutableDictionary BuildAsync(IReadOnlyList key, IEnumerable projectReferences, CancellationToken cancellationToken) + private ChecksumCollection Build(IReadOnlyList key, IEnumerable projectReferences, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateChecksumObjectWithChildrenAsync(key, projectReferences, WellKnownChecksumObjects.ProjectReferences, CreateChecksumCollectionsAsync, cancellationToken); + return _checksumTree.GetOrCreateChecksumObjectWithChildren(key, projectReferences, WellKnownChecksumObjects.ProjectReferences, CreateChecksumCollections, cancellationToken); } - private Task BuildAsync(IReadOnlyList key, IEnumerable metadataReferences, CancellationToken cancellationToken) + private ChecksumCollection Build(IReadOnlyList key, IEnumerable metadataReferences, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateChecksumObjectWithChildrenAsync(key, metadataReferences, WellKnownChecksumObjects.MetadataReferences, CreateChecksumCollectionsAsync, cancellationToken); + return _checksumTree.GetOrCreateChecksumObjectWithChildren(key, metadataReferences, WellKnownChecksumObjects.MetadataReferences, CreateChecksumCollections, cancellationToken); } - private Task BuildAsync(IReadOnlyList key, IEnumerable analyzerReferences, CancellationToken cancellationToken) + private ChecksumCollection Build(IReadOnlyList key, IEnumerable analyzerReferences, CancellationToken cancellationToken) { - return _checksumTree.GetOrCreateChecksumObjectWithChildrenAsync(key, analyzerReferences, WellKnownChecksumObjects.AnalyzerReferences, CreateChecksumCollectionsAsync, cancellationToken); + return _checksumTree.GetOrCreateChecksumObjectWithChildren(key, analyzerReferences, WellKnownChecksumObjects.AnalyzerReferences, CreateChecksumCollections, cancellationToken); } private async Task CreateSolutionChecksumObjectAsync(SolutionState key, SolutionState solutionState, string kind, CancellationToken cancellationToken) { var assetBuilder = new AssetBuilder(_checksumTree); - var info = await assetBuilder.BuildAsync(solutionState, cancellationToken).ConfigureAwait(false); + var info = assetBuilder.Build(solutionState, cancellationToken); var subTreeNode = _checksumTree.GetOrCreateSubTreeNode(key); var subSnapshotBuilder = new ChecksumTreeBuilder(subTreeNode); @@ -77,20 +77,20 @@ private async Task CreateSolutionChecksumObjectAsync(Sol private async Task CreateProjectChecksumObjectAsync(ProjectState key, ProjectState projectState, string kind, CancellationToken cancellationToken) { var assetBuilder = new AssetBuilder(_checksumTree); - var info = await assetBuilder.BuildAsync(projectState, cancellationToken).ConfigureAwait(false); + var info = assetBuilder.Build(projectState, cancellationToken); var subTreeNode = _checksumTree.GetOrCreateSubTreeNode(key); var subSnapshotBuilder = new ChecksumTreeBuilder(subTreeNode); var documents = await subSnapshotBuilder.BuildAsync(projectState.DocumentStates, projectState.DocumentIds.Select(id => projectState.DocumentStates[id]), WellKnownChecksumObjects.Documents, cancellationToken).ConfigureAwait(false); - var projectReferences = await subSnapshotBuilder.BuildAsync(projectState.ProjectReferences, projectState.ProjectReferences, cancellationToken).ConfigureAwait(false); - var metadataReferences = await subSnapshotBuilder.BuildAsync(projectState.MetadataReferences, projectState.MetadataReferences, cancellationToken).ConfigureAwait(false); - var analyzerReferences = await subSnapshotBuilder.BuildAsync(projectState.AnalyzerReferences, projectState.AnalyzerReferences, cancellationToken).ConfigureAwait(false); + var projectReferences = subSnapshotBuilder.Build(projectState.ProjectReferences, projectState.ProjectReferences, cancellationToken); + var metadataReferences = subSnapshotBuilder.Build(projectState.MetadataReferences, projectState.MetadataReferences, cancellationToken); + var analyzerReferences = subSnapshotBuilder.Build(projectState.AnalyzerReferences, projectState.AnalyzerReferences, cancellationToken); var additionalDocuments = await subSnapshotBuilder.BuildAsync(projectState.AdditionalDocumentStates, projectState.AdditionalDocumentIds.Select(id => projectState.AdditionalDocumentStates[id]), WellKnownChecksumObjects.TextDocuments, cancellationToken).ConfigureAwait(false); var subAssetBuilder = new AssetBuilder(subTreeNode); - var compilationOptions = await subAssetBuilder.BuildAsync(projectState, projectState.CompilationOptions, cancellationToken).ConfigureAwait(false); - var parseOptions = await subAssetBuilder.BuildAsync(projectState, projectState.ParseOptions, cancellationToken).ConfigureAwait(false); + var compilationOptions = subAssetBuilder.Build(projectState, projectState.CompilationOptions, cancellationToken); + var parseOptions = subAssetBuilder.Build(projectState, projectState.ParseOptions, cancellationToken); return new ProjectChecksumObject( _serializer, info.Checksum, compilationOptions.Checksum, parseOptions.Checksum, @@ -100,10 +100,11 @@ private async Task CreateProjectChecksumObjectAsync(Proje private async Task CreateDocumentChecksumObjectAsync(TextDocumentState key, TextDocumentState documentState, string kind, CancellationToken cancellationToken) { var assetBuilder = new AssetBuilder(_checksumTree); - var info = await assetBuilder.BuildAsync(documentState, cancellationToken).ConfigureAwait(false); + var info = assetBuilder.Build(documentState, cancellationToken); + // TODO: think of a way to skip getting text var sourceText = await key.GetTextAsync(cancellationToken).ConfigureAwait(false); - var text = await assetBuilder.BuildAsync(key, sourceText, cancellationToken).ConfigureAwait(false); + var text = assetBuilder.Build(key, sourceText, cancellationToken); return new DocumentChecksumObject(_serializer, info.Checksum, text.Checksum); } @@ -133,58 +134,93 @@ private async Task CreateDocumentChecksumObjectAsync(Tex return CreateChecksumCollectionsAsync(documentStates, kind, snapshotBuilder.BuildAsync, cancellationToken); } - private Task CreateChecksumCollectionsAsync( + private async Task CreateChecksumCollectionsAsync( + IEnumerable items, string kind, Func> buildAsync, CancellationToken cancellationToken) where TChecksumObject : ChecksumObject + { + using (var pooledObject = Creator.CreateList>()) + { + // create asyn checksums concurrently + var tasks = pooledObject.Object; + foreach (var item in items) + { + tasks.Add(buildAsync(item, cancellationToken)); + } + + await Task.WhenAll(tasks).ConfigureAwait(false); + + var checksums = new Checksum[tasks.Count]; + + for (var i = 0; i < tasks.Count; i++) + { + var task = tasks[i]; + + // we use await here to make sure when exception is raised, especially cancellation exception, + // right exception is raised from task. if we use .Result directly, it will raise aggregated exception + // rather than cancellation exception. + // since task is already completed, when there is no exception for the task. await will be no-op + checksums[i] = (await task.ConfigureAwait(false)).Checksum; + } + + return new ChecksumCollection(_serializer, kind, checksums); + } + } + + private ChecksumCollection CreateChecksumCollections( IReadOnlyList key, IEnumerable references, string kind, CancellationToken cancellationToken) { if (key.Count == 0) { - return GetEmptyChecksumCollectionTask(kind); + return GetEmptyChecksumCollection(kind); } var assetBuilder = new AssetBuilder(_checksumTree.GetOrCreateSubTreeNode(key)); - return CreateChecksumCollectionsAsync(references, kind, assetBuilder.BuildAsync, cancellationToken); + return CreateChecksumCollections(references, kind, assetBuilder.Build, cancellationToken); } - private Task CreateChecksumCollectionsAsync( + private ChecksumCollection CreateChecksumCollections( IReadOnlyList key, IEnumerable references, string kind, CancellationToken cancellationToken) { if (key.Count == 0) { - return GetEmptyChecksumCollectionTask(kind); + return GetEmptyChecksumCollection(kind); } var assetBuilder = new AssetBuilder(_checksumTree.GetOrCreateSubTreeNode(key)); - return CreateChecksumCollectionsAsync(references, kind, assetBuilder.BuildAsync, cancellationToken); + return CreateChecksumCollections(references, kind, assetBuilder.Build, cancellationToken); } - private Task CreateChecksumCollectionsAsync( + private ChecksumCollection CreateChecksumCollections( IReadOnlyList key, IEnumerable references, string kind, CancellationToken cancellationToken) { if (key.Count == 0) { - return GetEmptyChecksumCollectionTask(kind); + return GetEmptyChecksumCollection(kind); } var assetBuilder = new AssetBuilder(_checksumTree.GetOrCreateSubTreeNode(key)); - return CreateChecksumCollectionsAsync(references, kind, assetBuilder.BuildAsync, cancellationToken); + return CreateChecksumCollections(references, kind, assetBuilder.Build, cancellationToken); } - private async Task CreateChecksumCollectionsAsync( - IEnumerable items, string kind, Func> buildAsync, CancellationToken cancellationToken) where TChecksumObject : ChecksumObject + private ChecksumCollection CreateChecksumCollections(IEnumerable items, string kind, Func build, CancellationToken cancellationToken) { var list = new List(); foreach (var item in items) { - var checksumObject = await buildAsync(item, cancellationToken).ConfigureAwait(false); + var checksumObject = build(item, cancellationToken); list.Add(checksumObject.Checksum); } return new ChecksumCollection(_serializer, kind, list.ToArray()); } - private Task GetEmptyChecksumCollectionTask(string kind) + private ChecksumCollection GetEmptyChecksumCollection(string kind) { return ChecksumTreeCollection.GetOrCreateEmptyChecksumCollection(_serializer, kind); } + + private Task GetEmptyChecksumCollectionTask(string kind) + { + return ChecksumTreeCollection.GetOrCreateEmptyChecksumCollectionTask(_serializer, kind); + } } } diff --git a/src/Workspaces/Core/Portable/Execution/ChecksumTreeCollection.TreeNodes.cs b/src/Workspaces/Core/Portable/Execution/ChecksumTreeCollection.TreeNodes.cs index f9ec6db150c5723c4176afde9d6e9c66229e4555..ad9c700ab876a4174211883c58d8057942c529e3 100644 --- a/src/Workspaces/Core/Portable/Execution/ChecksumTreeCollection.TreeNodes.cs +++ b/src/Workspaces/Core/Portable/Execution/ChecksumTreeCollection.TreeNodes.cs @@ -252,13 +252,21 @@ public IChecksumTreeNode GetOrCreateSubTreeNode(TKey key) return await GetOrCreateChecksumObjectAsync(key, value, kind, (v, k, c) => valueGetterAsync(key, v, k, c), cancellationToken).ConfigureAwait(false); } - public Task GetOrCreateAssetAsync( + public TChecksumObject GetOrCreateChecksumObjectWithChildren( TKey key, TValue value, string kind, - Func> valueGetterAsync, CancellationToken cancellationToken) + Func valueGetter, CancellationToken cancellationToken) + where TKey : class + where TChecksumObject : ChecksumObjectWithChildren + { + return GetOrCreateChecksumObject(key, value, kind, (v, k, c) => valueGetter(key, v, k, c), cancellationToken); + } + + public Asset GetOrCreateAsset( + TKey key, TValue value, string kind, + Func valueGetter, CancellationToken cancellationToken) where TKey : class - where TAsset : Asset { - return GetOrCreateChecksumObjectAsync(key, value, kind, valueGetterAsync, cancellationToken); + return GetOrCreateChecksumObject(key, value, kind, valueGetter, cancellationToken); } protected static void AppendChecksumObjects( @@ -266,7 +274,7 @@ public IChecksumTreeNode GetOrCreateSubTreeNode(TKey key) HashSet searchingChecksumsLeft, int currentNodeChecksumCount, IEnumerable currentNodeChecksums, - Func checksumGetterForCurrentNode, + Func checksumGetterForCurrentNode, CancellationToken cancellationToken) { // this will iterate through candidate checksums to see whether that checksum exists in both @@ -362,6 +370,43 @@ private ChecksumObject GetChecksumObjectFromTreeNode(ChecksumObjectCache cache, } } + private TChecksumObject GetOrCreateChecksumObject( + TKey key, TValue value, string kind, + Func valueGetter, CancellationToken cancellationToken) + where TKey : class where TChecksumObject : ChecksumObject + { + using (Logger.LogBlock(FunctionId.ChecksumTreeNode_GetOrCreateChecksumObject, CreateLogMessage, key, kind, cancellationToken)) + { + Contract.ThrowIfNull(key); + + // ask myself + ChecksumObject checksumObject; + var entry = TryGetChecksumObjectEntry(key, kind, cancellationToken); + if (entry != null && entry.TryGetValue(kind, out checksumObject)) + { + return (TChecksumObject)SaveAndReturn(key, checksumObject, entry); + } + + // ask owner + entry = _owner.TryGetChecksumObjectEntry(key, kind, cancellationToken); + if (entry == null) + { + // owner doesn't have it, create one. + checksumObject = valueGetter(value, kind, cancellationToken); + } + else if (!entry.TryGetValue(kind, out checksumObject)) + { + // owner doesn't have this particular kind, create one. + checksumObject = valueGetter(value, kind, cancellationToken); + } + + // record local copy (reference) and return it. + // REVIEW: we can go ref count route rather than this (local copy). but then we need to make sure there is no leak. + // for now, we go local copy route since overhead is small (just duplicated reference pointer), but reduce complexity a lot. + return (TChecksumObject)SaveAndReturn(key, checksumObject, entry); + } + } + private ChecksumObject SaveAndReturn(object key, ChecksumObject checksumObject, ChecksumObjectCache entry = null) { // create new entry if it is not already given diff --git a/src/Workspaces/Core/Portable/Execution/ChecksumTreeCollection.cs b/src/Workspaces/Core/Portable/Execution/ChecksumTreeCollection.cs index 085fac02d90ac54b7eb4b2882855f0b042ae2d51..29db2f0d4da9c62dde4acdc5ea318a4b662ce7b6 100644 --- a/src/Workspaces/Core/Portable/Execution/ChecksumTreeCollection.cs +++ b/src/Workspaces/Core/Portable/Execution/ChecksumTreeCollection.cs @@ -17,7 +17,8 @@ internal partial class ChecksumTreeCollection { // serializer and empty checksum collection task cache - this is to reduce allocations private readonly static ConditionalWeakTable s_serializerCache = new ConditionalWeakTable(); - private readonly static ConditionalWeakTable>> s_emptyChecksumCollectionTaskCache = new ConditionalWeakTable>>(); + private readonly static ConditionalWeakTable> s_emptyChecksumCollectionCache = new ConditionalWeakTable>(); + private readonly static ConditionalWeakTable> s_emptyChecksumCollectionTaskCache = new ConditionalWeakTable>(); /// /// global asset is an asset which life time is same as host @@ -166,24 +167,34 @@ public void UnregisterSnapshot(ChecksumScope snapshot) } private static readonly ConditionalWeakTable.CreateValueCallback s_serializerCallback = s => new Serializer(s); + public static Serializer GetOrCreateSerializer(HostWorkspaceServices services) { return s_serializerCache.GetValue(services, s_serializerCallback); } - private static readonly ConditionalWeakTable>>.CreateValueCallback s_emptyChecksumCollectionCallback = - s => new ConcurrentDictionary>(concurrencyLevel: 2, capacity: 20); - public static Task GetOrCreateEmptyChecksumCollection(Serializer serializer, string kind) + private static readonly ConditionalWeakTable>.CreateValueCallback s_emptyChecksumCollectionCallback = + s => new ConcurrentDictionary(concurrencyLevel: 2, capacity: 20); + + public static ChecksumCollection GetOrCreateEmptyChecksumCollection(Serializer serializer, string kind) { - var map = s_emptyChecksumCollectionTaskCache.GetValue(serializer, s_emptyChecksumCollectionCallback); + var map = s_emptyChecksumCollectionCache.GetValue(serializer, s_emptyChecksumCollectionCallback); - Task task; - if (map.TryGetValue(kind, out task)) + ChecksumCollection collection; + if (map.TryGetValue(kind, out collection)) { - return task; + return collection; } - return map.GetOrAdd(kind, _ => Task.FromResult(new ChecksumCollection(serializer, kind, SpecializedCollections.EmptyArray()))); + return map.GetOrAdd(kind, _ => new ChecksumCollection(serializer, kind, SpecializedCollections.EmptyArray())); + } + + private static readonly ConditionalWeakTable>.CreateValueCallback s_emptyChecksumCollectionTaskCallback = c => Task.FromResult(c); + + public static Task GetOrCreateEmptyChecksumCollectionTask(Serializer serializer, string kind) + { + var collection = GetOrCreateEmptyChecksumCollection(serializer, kind); + return s_emptyChecksumCollectionTaskCache.GetValue(collection, s_emptyChecksumCollectionTaskCallback); } } } diff --git a/src/Workspaces/Core/Portable/Execution/IChecksumTreeNodes.cs b/src/Workspaces/Core/Portable/Execution/IChecksumTreeNodes.cs index 1ca8e877cce60f5aaf3528ab7ad26dfd1f9755f1..9b0a76720c7bae8cd9a3040501b1d4dc33f03935 100644 --- a/src/Workspaces/Core/Portable/Execution/IChecksumTreeNodes.cs +++ b/src/Workspaces/Core/Portable/Execution/IChecksumTreeNodes.cs @@ -29,7 +29,6 @@ internal interface IChecksumTreeNode IChecksumTreeNode GetOrCreateSubTreeNode(TKey key); - // TResult since Task doesn't allow covariant Task GetOrCreateChecksumObjectWithChildrenAsync( TKey key, TValue value, string kind, Func> valueGetterAsync, @@ -37,12 +36,17 @@ internal interface IChecksumTreeNode where TKey : class where TResult : ChecksumObjectWithChildren; - // TResult since Task doesn't allow covariant - Task GetOrCreateAssetAsync( + TResult GetOrCreateChecksumObjectWithChildren( TKey key, TValue value, string kind, - Func> valueGetterAsync, + Func valueGetter, CancellationToken cancellationToken) where TKey : class - where TResult : Asset; + where TResult : ChecksumObjectWithChildren; + + Asset GetOrCreateAsset( + TKey key, TValue value, string kind, + Func valueGetter, + CancellationToken cancellationToken) + where TKey : class; } } diff --git a/src/Workspaces/Core/Portable/Execution/Serializer_ChecksumObjectWithChildren.cs b/src/Workspaces/Core/Portable/Execution/Serializer_ChecksumObjectWithChildren.cs index 2b15a46d23f634442effe014477d4766afcf19ea..75f0d217c2cbce5d23c3b7fa023b172ce233e9e9 100644 --- a/src/Workspaces/Core/Portable/Execution/Serializer_ChecksumObjectWithChildren.cs +++ b/src/Workspaces/Core/Portable/Execution/Serializer_ChecksumObjectWithChildren.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Immutable; using System.Threading; -using System.Threading.Tasks; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Execution @@ -19,7 +18,7 @@ internal partial class Serializer private static readonly ImmutableDictionary> s_creatorMap = CreateCreatorMap(); - public async Task SerializeChecksumObjectWithChildrenAsync(ChecksumObjectWithChildren checksumObject, ObjectWriter writer, CancellationToken cancellationToken) + public void SerializeChecksumObjectWithChildren(ChecksumObjectWithChildren checksumObject, ObjectWriter writer, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -41,7 +40,7 @@ public async Task SerializeChecksumObjectWithChildrenAsync(ChecksumObjectWithChi if (checksumCollection != null) { writer.WriteByte(ChecksumCollectionKind); - await SerializeChecksumObjectWithChildrenAsync(checksumCollection, writer, cancellationToken).ConfigureAwait(false); + SerializeChecksumObjectWithChildren(checksumCollection, writer, cancellationToken); continue; } diff --git a/src/Workspaces/Core/Portable/Log/FunctionId.cs b/src/Workspaces/Core/Portable/Log/FunctionId.cs index 6eeca33218768061797fd131d1270023ae6cbb63..ede12444e17b116ae209ac8e43f2f825a090d82b 100644 --- a/src/Workspaces/Core/Portable/Log/FunctionId.cs +++ b/src/Workspaces/Core/Portable/Log/FunctionId.cs @@ -350,5 +350,6 @@ internal enum FunctionId AssetService_SynchronizeAssetsAsync, AssetService_SynchronizeSolutionAssetsAsync, SolutionChecksumServiceFactory_GetChecksumObjects, + ChecksumTreeNode_GetOrCreateChecksumObject, } } diff --git a/src/Workspaces/CoreTest/Execution/SnapshotSerializationTests.cs b/src/Workspaces/CoreTest/Execution/SnapshotSerializationTests.cs index 209406369e1cf0636df645eeb14d88be7a0c3ee7..78715f33103451210713f679d22acec185945967 100644 --- a/src/Workspaces/CoreTest/Execution/SnapshotSerializationTests.cs +++ b/src/Workspaces/CoreTest/Execution/SnapshotSerializationTests.cs @@ -311,7 +311,7 @@ public async Task MetadataReference_RoundTrip_Test() var trees = new ChecksumTreeCollection(); var assetBuilder = new AssetBuilder(trees.CreateRootTreeNode(workspace.CurrentSolution.State)); - var assetFromFile = await assetBuilder.BuildAsync(reference, CancellationToken.None).ConfigureAwait(false); + var assetFromFile = assetBuilder.Build(reference, CancellationToken.None); var assetFromStorage = await CloneAssetAsync(serializer, assetBuilder, assetFromFile).ConfigureAwait(false); var assetFromStorage2 = await CloneAssetAsync(serializer, assetBuilder, assetFromStorage).ConfigureAwait(false); } @@ -482,7 +482,7 @@ private static async Task CloneAssetAsync(Serializer serializer, AssetBui using (var reader = new ObjectReader(stream)) { var recovered = serializer.Deserialize(WellKnownChecksumObjects.MetadataReference, reader, CancellationToken.None); - var assetFromStorage = await assetBuilder.BuildAsync(recovered, CancellationToken.None).ConfigureAwait(false); + var assetFromStorage = assetBuilder.Build(recovered, CancellationToken.None); Assert.Equal(asset.Checksum, assetFromStorage.Checksum);