diff --git a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs index 2f3390cc0cd62..328e663df7978 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs @@ -11,6 +11,18 @@ namespace Microsoft.CodeAnalysis.ErrorReporting { + /// + /// Thrown when async code must cancel the current execution but does not have access to the of the passed to the code. + /// Should be used in very rare cases where the is out of our control (e.g. owned but not exposed by JSON RPC in certain call-back scenarios). + /// + internal sealed class OperationCanceledIgnoringCallerTokenException : OperationCanceledException + { + public OperationCanceledIgnoringCallerTokenException(Exception innerException) + : base(innerException.Message, innerException) + { + } + } + internal static class FatalError { public delegate void ErrorReporterHandler(Exception exception, ErrorSeverity severity, bool forceDump); @@ -124,7 +136,7 @@ public static bool ReportAndPropagateUnlessCanceled(Exception exception, ErrorSe [DebuggerHidden] public static bool ReportAndPropagateUnlessCanceled(Exception exception, CancellationToken contextCancellationToken, ErrorSeverity severity = ErrorSeverity.Uncategorized) { - if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken)) + if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken) || exception is OperationCanceledIgnoringCallerTokenException) { return false; } @@ -200,7 +212,7 @@ public static bool ReportAndCatchUnlessCanceled(Exception exception, ErrorSeveri [DebuggerHidden] public static bool ReportAndCatchUnlessCanceled(Exception exception, CancellationToken contextCancellationToken, ErrorSeverity severity = ErrorSeverity.Uncategorized) { - if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken)) + if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken) || exception is OperationCanceledIgnoringCallerTokenException) { return false; } diff --git a/src/Workspaces/Remote/Core/RemoteCallback.cs b/src/Workspaces/Remote/Core/RemoteCallback.cs index 5444ee902333c..5c9cdadd97a99 100644 --- a/src/Workspaces/Remote/Core/RemoteCallback.cs +++ b/src/Workspaces/Remote/Core/RemoteCallback.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.ServiceHub.Framework; using Roslyn.Utilities; using StreamJsonRpc; @@ -17,8 +18,6 @@ namespace Microsoft.CodeAnalysis.Remote /// Wraps calls from a remote brokered service back to the client or to an in-proc brokered service. /// The purpose of this type is to handle exceptions thrown by the underlying remoting infrastructure /// in manner that's compatible with our exception handling policies. - /// - /// TODO: This wrapper might not be needed once https://github.com/microsoft/vs-streamjsonrpc/issues/246 is fixed. /// internal readonly struct RemoteCallback where T : class @@ -30,6 +29,36 @@ public RemoteCallback(T callback) _callback = callback; } + /// + /// Use to perform a callback from ServiceHub process to an arbitrary brokered service hosted in the original process (usually devenv). + /// + public static async ValueTask InvokeServiceAsync( + ServiceBrokerClient client, + ServiceRpcDescriptor serviceDescriptor, + Func, CancellationToken, ValueTask> invocation, + CancellationToken cancellationToken) + { + ServiceBrokerClient.Rental rental; + try + { + rental = await client.GetProxyAsync(serviceDescriptor, cancellationToken).ConfigureAwait(false); + } + catch (ObjectDisposedException e) + { + // When a connection is dropped ServiceHub's ServiceManager disposes the brokered service, which in turn disposes the ServiceBrokerClient. + cancellationToken.ThrowIfCancellationRequested(); + throw new OperationCanceledIgnoringCallerTokenException(e); + } + + Contract.ThrowIfNull(rental.Proxy); + var callback = new RemoteCallback(rental.Proxy); + + return await invocation(callback, cancellationToken).ConfigureAwait(false); + } + + /// + /// Invokes API on the callback object hosted in the original process (usually devenv) associated with the currently executing brokered service hosted in ServiceHub process. + /// public async ValueTask InvokeAsync(Func invocation, CancellationToken cancellationToken) { try @@ -42,6 +71,9 @@ public async ValueTask InvokeAsync(Func invocat } } + /// + /// Invokes API on the callback object hosted in the original process (usually devenv) associated with the currently executing brokered service hosted in ServiceHub process. + /// public async ValueTask InvokeAsync(Func> invocation, CancellationToken cancellationToken) { try @@ -55,7 +87,8 @@ public async ValueTask InvokeAsync(Func - /// Invokes a remote API that streams results back to the caller. + /// Invokes API on the callback object hosted in the original process (usually devenv) associated with the currently executing brokered service hosted in ServiceHub process. + /// The API streams results back to the caller. /// /// public async ValueTask InvokeAsync( @@ -100,12 +133,9 @@ private static bool ReportUnexpectedException(Exception exception, CancellationT return FatalError.ReportAndCatch(exception); } - // When a connection is dropped and CancelLocallyInvokedMethodsWhenConnectionIsClosed is - // set ConnectionLostException should not be thrown. Instead the cancellation token should be - // signaled and OperationCancelledException should be thrown. - // Seems to not work in all cases currently, so we need to cancel ourselves (bug https://github.com/microsoft/vs-streamjsonrpc/issues/551). - // Once this issue is fixed we can remov this if statement and fall back to reporting NFW - // as any observation of ConnectionLostException indicates a bug (e.g. https://github.com/microsoft/vs-streamjsonrpc/issues/549). + // When a connection is dropped we can see ConnectionLostException even though CancelLocallyInvokedMethodsWhenConnectionIsClosed is set. + // That's because there might be a delay between the JsonRpc detecting the disconnect and the call attempting to send a message. + // Catch the ConnectionLostException exception here and convert it to OperationCanceledException. if (exception is ConnectionLostException) { return true; @@ -121,7 +151,7 @@ private static Exception OnUnexpectedException(Exception exception, Cancellation if (exception is ConnectionLostException) { - throw new OperationCanceledException(exception.Message, exception); + throw new OperationCanceledIgnoringCallerTokenException(exception); } // If this is hit the cancellation token passed to the service implementation did not use the correct token, diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 5a57598030c04..5485e3977233d 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Remote { @@ -32,26 +33,24 @@ private readonly struct SolutionCreator private readonly AssetProvider _assetProvider; private readonly Solution _baseSolution; - private readonly CancellationToken _cancellationToken; - public SolutionCreator(HostServices hostServices, AssetProvider assetService, Solution baseSolution, CancellationToken cancellationToken) + public SolutionCreator(HostServices hostServices, AssetProvider assetService, Solution baseSolution) { _hostServices = hostServices; _assetProvider = assetService; _baseSolution = baseSolution; - _cancellationToken = cancellationToken; } - public async Task IsIncrementalUpdateAsync(Checksum newSolutionChecksum) + public async Task IsIncrementalUpdateAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) { - var newSolutionChecksums = await _assetProvider.GetAssetAsync(newSolutionChecksum, _cancellationToken).ConfigureAwait(false); - var newSolutionInfo = await _assetProvider.GetAssetAsync(newSolutionChecksums.Attributes, _cancellationToken).ConfigureAwait(false); + var newSolutionChecksums = await _assetProvider.GetAssetAsync(newSolutionChecksum, cancellationToken).ConfigureAwait(false); + var newSolutionInfo = await _assetProvider.GetAssetAsync(newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); // if either solution id or file path changed, then we consider it as new solution return _baseSolution.Id == newSolutionInfo.Id && _baseSolution.FilePath == newSolutionInfo.FilePath; } - public async Task CreateSolutionAsync(Checksum newSolutionChecksum) + public async Task CreateSolutionAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) { try { @@ -61,12 +60,12 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum) // if needed again later. solution = solution.WithoutFrozenSourceGeneratedDocuments(); - var oldSolutionChecksums = await solution.State.GetStateChecksumsAsync(_cancellationToken).ConfigureAwait(false); - var newSolutionChecksums = await _assetProvider.GetAssetAsync(newSolutionChecksum, _cancellationToken).ConfigureAwait(false); + var oldSolutionChecksums = await solution.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + var newSolutionChecksums = await _assetProvider.GetAssetAsync(newSolutionChecksum, cancellationToken).ConfigureAwait(false); if (oldSolutionChecksums.Attributes != newSolutionChecksums.Attributes) { - var newSolutionInfo = await _assetProvider.GetAssetAsync(newSolutionChecksums.Attributes, _cancellationToken).ConfigureAwait(false); + var newSolutionInfo = await _assetProvider.GetAssetAsync(newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); // if either id or file path has changed, then this is not update Contract.ThrowIfFalse(solution.Id == newSolutionInfo.Id && solution.FilePath == newSolutionInfo.FilePath); @@ -74,26 +73,26 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum) if (oldSolutionChecksums.Projects.Checksum != newSolutionChecksums.Projects.Checksum) { - solution = await UpdateProjectsAsync(solution, oldSolutionChecksums.Projects, newSolutionChecksums.Projects).ConfigureAwait(false); + solution = await UpdateProjectsAsync(solution, oldSolutionChecksums.Projects, newSolutionChecksums.Projects, cancellationToken).ConfigureAwait(false); } if (oldSolutionChecksums.AnalyzerReferences.Checksum != newSolutionChecksums.AnalyzerReferences.Checksum) { solution = solution.WithAnalyzerReferences(await _assetProvider.CreateCollectionAsync( - newSolutionChecksums.AnalyzerReferences, _cancellationToken).ConfigureAwait(false)); + newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } if (newSolutionChecksums.FrozenSourceGeneratedDocumentIdentity != Checksum.Null && newSolutionChecksums.FrozenSourceGeneratedDocumentText != Checksum.Null) { - var identity = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentIdentity, _cancellationToken).ConfigureAwait(false); - var serializableSourceText = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentText, _cancellationToken).ConfigureAwait(false); - var sourceText = await serializableSourceText.GetTextAsync(_cancellationToken).ConfigureAwait(false); + var identity = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentIdentity, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentText, cancellationToken).ConfigureAwait(false); + var sourceText = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); solution = solution.WithFrozenSourceGeneratedDocument(identity, sourceText).Project.Solution; } #if DEBUG // make sure created solution has same checksum as given one - await ValidateChecksumAsync(newSolutionChecksum, solution).ConfigureAwait(false); + await ValidateChecksumAsync(newSolutionChecksum, solution, cancellationToken).ConfigureAwait(false); #endif return solution; @@ -104,7 +103,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum) } } - private async Task UpdateProjectsAsync(Solution solution, ChecksumCollection oldChecksums, ChecksumCollection newChecksums) + private async Task UpdateProjectsAsync(Solution solution, ChecksumCollection oldChecksums, ChecksumCollection newChecksums, CancellationToken cancellationToken) { using var olds = SharedPools.Default>().GetPooledObject(); using var news = SharedPools.Default>().GetPooledObject(); @@ -116,23 +115,23 @@ private async Task UpdateProjectsAsync(Solution solution, ChecksumColl olds.Object.ExceptWith(newChecksums); news.Object.ExceptWith(oldChecksums); - return await UpdateProjectsAsync(solution, olds.Object, news.Object).ConfigureAwait(false); + return await UpdateProjectsAsync(solution, olds.Object, news.Object, cancellationToken).ConfigureAwait(false); } - private async Task UpdateProjectsAsync(Solution solution, HashSet oldChecksums, HashSet newChecksums) + private async Task UpdateProjectsAsync(Solution solution, HashSet oldChecksums, HashSet newChecksums, CancellationToken cancellationToken) { - var oldMap = await GetProjectMapAsync(solution, oldChecksums).ConfigureAwait(false); - var newMap = await GetProjectMapAsync(_assetProvider, newChecksums).ConfigureAwait(false); + var oldMap = await GetProjectMapAsync(solution, oldChecksums, cancellationToken).ConfigureAwait(false); + var newMap = await GetProjectMapAsync(_assetProvider, newChecksums, cancellationToken).ConfigureAwait(false); // bulk sync assets - await SynchronizeAssetsAsync(oldMap, newMap).ConfigureAwait(false); + await SynchronizeAssetsAsync(oldMap, newMap, cancellationToken).ConfigureAwait(false); // added project foreach (var (projectId, newProjectChecksums) in newMap) { if (!oldMap.ContainsKey(projectId)) { - var projectInfo = await _assetProvider.CreateProjectInfoAsync(newProjectChecksums.Checksum, _cancellationToken).ConfigureAwait(false); + var projectInfo = await _assetProvider.CreateProjectInfoAsync(newProjectChecksums.Checksum, cancellationToken).ConfigureAwait(false); if (projectInfo == null) { // this project is not supported in OOP @@ -179,13 +178,13 @@ private async Task UpdateProjectsAsync(Solution solution, HashSet oldMap, Dictionary newMap) + private async Task SynchronizeAssetsAsync(Dictionary oldMap, Dictionary newMap, CancellationToken cancellationToken) { using var pooledObject = SharedPools.Default>().GetPooledObject(); @@ -200,15 +199,15 @@ private async Task SynchronizeAssetsAsync(Dictionary UpdateProjectAsync(Project project, ProjectStateChecksums oldProjectChecksums, ProjectStateChecksums newProjectChecksums) + private async Task UpdateProjectAsync(Project project, ProjectStateChecksums oldProjectChecksums, ProjectStateChecksums newProjectChecksums, CancellationToken cancellationToken) { // changed info if (oldProjectChecksums.Info != newProjectChecksums.Info) { - project = await UpdateProjectInfoAsync(project, newProjectChecksums.Info).ConfigureAwait(false); + project = await UpdateProjectInfoAsync(project, newProjectChecksums.Info, cancellationToken).ConfigureAwait(false); } // changed compilation options @@ -217,34 +216,34 @@ private async Task UpdateProjectAsync(Project project, ProjectStateChe project = project.WithCompilationOptions( project.State.ProjectInfo.Attributes.FixUpCompilationOptions( await _assetProvider.GetAssetAsync( - newProjectChecksums.CompilationOptions, _cancellationToken).ConfigureAwait(false))); + newProjectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false))); } // changed parse options if (oldProjectChecksums.ParseOptions != newProjectChecksums.ParseOptions) { - project = project.WithParseOptions(await _assetProvider.GetAssetAsync(newProjectChecksums.ParseOptions, _cancellationToken).ConfigureAwait(false)); + project = project.WithParseOptions(await _assetProvider.GetAssetAsync(newProjectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false)); } // changed project references if (oldProjectChecksums.ProjectReferences.Checksum != newProjectChecksums.ProjectReferences.Checksum) { project = project.WithProjectReferences(await _assetProvider.CreateCollectionAsync( - newProjectChecksums.ProjectReferences, _cancellationToken).ConfigureAwait(false)); + newProjectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false)); } // changed metadata references if (oldProjectChecksums.MetadataReferences.Checksum != newProjectChecksums.MetadataReferences.Checksum) { project = project.WithMetadataReferences(await _assetProvider.CreateCollectionAsync( - newProjectChecksums.MetadataReferences, _cancellationToken).ConfigureAwait(false)); + newProjectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false)); } // changed analyzer references if (oldProjectChecksums.AnalyzerReferences.Checksum != newProjectChecksums.AnalyzerReferences.Checksum) { project = project.WithAnalyzerReferences(await _assetProvider.CreateCollectionAsync( - newProjectChecksums.AnalyzerReferences, _cancellationToken).ConfigureAwait(false)); + newProjectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } // changed analyzer references @@ -257,7 +256,8 @@ await _assetProvider.GetAssetAsync( oldProjectChecksums.Documents, newProjectChecksums.Documents, (solution, documents) => solution.AddDocuments(documents), - (solution, documentIds) => solution.RemoveDocuments(documentIds)).ConfigureAwait(false); + (solution, documentIds) => solution.RemoveDocuments(documentIds), + cancellationToken).ConfigureAwait(false); } // changed additional documents @@ -270,7 +270,8 @@ await _assetProvider.GetAssetAsync( oldProjectChecksums.AdditionalDocuments, newProjectChecksums.AdditionalDocuments, (solution, documents) => solution.AddAdditionalDocuments(documents), - (solution, documentIds) => solution.RemoveAdditionalDocuments(documentIds)).ConfigureAwait(false); + (solution, documentIds) => solution.RemoveAdditionalDocuments(documentIds), + cancellationToken).ConfigureAwait(false); } // changed analyzer config documents @@ -283,15 +284,16 @@ await _assetProvider.GetAssetAsync( oldProjectChecksums.AnalyzerConfigDocuments, newProjectChecksums.AnalyzerConfigDocuments, (solution, documents) => solution.AddAnalyzerConfigDocuments(documents), - (solution, documentIds) => solution.RemoveAnalyzerConfigDocuments(documentIds)).ConfigureAwait(false); + (solution, documentIds) => solution.RemoveAnalyzerConfigDocuments(documentIds), + cancellationToken).ConfigureAwait(false); } return project.Solution; } - private async Task UpdateProjectInfoAsync(Project project, Checksum infoChecksum) + private async Task UpdateProjectInfoAsync(Project project, Checksum infoChecksum, CancellationToken cancellationToken) { - var newProjectAttributes = await _assetProvider.GetAssetAsync(infoChecksum, _cancellationToken).ConfigureAwait(false); + var newProjectAttributes = await _assetProvider.GetAssetAsync(infoChecksum, cancellationToken).ConfigureAwait(false); // there is no API to change these once project is created Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.Id == newProjectAttributes.Id); @@ -355,7 +357,8 @@ private async Task UpdateDocumentsAsync( ChecksumCollection oldChecksums, ChecksumCollection newChecksums, Func, Solution> addDocuments, - Func, Solution> removeDocuments) + Func, Solution> removeDocuments, + CancellationToken cancellationToken) { using var olds = SharedPools.Default>().GetPooledObject(); using var news = SharedPools.Default>().GetPooledObject(); @@ -367,15 +370,15 @@ private async Task UpdateDocumentsAsync( olds.Object.ExceptWith(newChecksums); news.Object.ExceptWith(oldChecksums); - var oldMap = await GetDocumentMapAsync(existingTextDocumentStates, olds.Object).ConfigureAwait(false); - var newMap = await GetDocumentMapAsync(_assetProvider, news.Object).ConfigureAwait(false); + var oldMap = await GetDocumentMapAsync(existingTextDocumentStates, olds.Object, cancellationToken).ConfigureAwait(false); + var newMap = await GetDocumentMapAsync(_assetProvider, news.Object, cancellationToken).ConfigureAwait(false); // If more than two documents changed during a single update, perform a bulk synchronization on the // project to avoid large numbers of small synchronization calls during document updates. // 🔗 https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1365014 if (newMap.Count > 2) { - await _assetProvider.SynchronizeProjectAssetsAsync(new[] { projectChecksums.Checksum }, _cancellationToken).ConfigureAwait(false); + await _assetProvider.SynchronizeProjectAssetsAsync(new[] { projectChecksums.Checksum }, cancellationToken).ConfigureAwait(false); } // added document @@ -387,7 +390,7 @@ private async Task UpdateDocumentsAsync( lazyDocumentsToAdd ??= ImmutableArray.CreateBuilder(); // we have new document added - var documentInfo = await _assetProvider.CreateDocumentInfoAsync(newDocumentChecksums.Checksum, _cancellationToken).ConfigureAwait(false); + var documentInfo = await _assetProvider.CreateDocumentInfoAsync(newDocumentChecksums.Checksum, cancellationToken).ConfigureAwait(false); lazyDocumentsToAdd.Add(documentInfo); } } @@ -410,7 +413,7 @@ private async Task UpdateDocumentsAsync( var document = project.GetDocument(documentId) ?? project.GetAdditionalDocument(documentId) ?? project.GetAnalyzerConfigDocument(documentId); Contract.ThrowIfNull(document); - project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums).ConfigureAwait(false); + project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums, cancellationToken).ConfigureAwait(false); } // removed document @@ -433,19 +436,19 @@ private async Task UpdateDocumentsAsync( return project; } - private async Task UpdateDocumentAsync(TextDocument document, DocumentStateChecksums oldDocumentChecksums, DocumentStateChecksums newDocumentChecksums) + private async Task UpdateDocumentAsync(TextDocument document, DocumentStateChecksums oldDocumentChecksums, DocumentStateChecksums newDocumentChecksums, CancellationToken cancellationToken) { // changed info if (oldDocumentChecksums.Info != newDocumentChecksums.Info) { - document = await UpdateDocumentInfoAsync(document, newDocumentChecksums.Info).ConfigureAwait(false); + document = await UpdateDocumentInfoAsync(document, newDocumentChecksums.Info, cancellationToken).ConfigureAwait(false); } // changed text if (oldDocumentChecksums.Text != newDocumentChecksums.Text) { - var serializableSourceText = await _assetProvider.GetAssetAsync(newDocumentChecksums.Text, _cancellationToken).ConfigureAwait(false); - var sourceText = await serializableSourceText.GetTextAsync(_cancellationToken).ConfigureAwait(false); + var serializableSourceText = await _assetProvider.GetAssetAsync(newDocumentChecksums.Text, cancellationToken).ConfigureAwait(false); + var sourceText = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); document = document.Kind switch { @@ -459,9 +462,9 @@ private async Task UpdateDocumentAsync(TextDocument document, DocumentS return document.Project; } - private async Task UpdateDocumentInfoAsync(TextDocument document, Checksum infoChecksum) + private async Task UpdateDocumentInfoAsync(TextDocument document, Checksum infoChecksum, CancellationToken cancellationToken) { - var newDocumentInfo = await _assetProvider.GetAssetAsync(infoChecksum, _cancellationToken).ConfigureAwait(false); + var newDocumentInfo = await _assetProvider.GetAssetAsync(infoChecksum, cancellationToken).ConfigureAwait(false); // there is no api to change these once document is created Contract.ThrowIfFalse(document.State.Attributes.Id == newDocumentInfo.Id); @@ -487,31 +490,31 @@ private async Task UpdateDocumentInfoAsync(TextDocument document, return document; } - private async Task> GetDocumentMapAsync(AssetProvider assetProvider, HashSet documents) + private static async Task> GetDocumentMapAsync(AssetProvider assetProvider, HashSet documents, CancellationToken cancellationToken) { var map = new Dictionary(); - var documentChecksums = await assetProvider.GetAssetsAsync(documents, _cancellationToken).ConfigureAwait(false); - var infos = await assetProvider.GetAssetsAsync(documentChecksums.Select(p => p.Item2.Info), _cancellationToken).ConfigureAwait(false); + var documentChecksums = await assetProvider.GetAssetsAsync(documents, cancellationToken).ConfigureAwait(false); + var infos = await assetProvider.GetAssetsAsync(documentChecksums.Select(p => p.Item2.Info), cancellationToken).ConfigureAwait(false); foreach (var kv in documentChecksums) { Debug.Assert(assetProvider.EnsureCacheEntryIfExists(kv.Item2.Info), "Expected the prior call to GetAssetsAsync to obtain all items for this loop."); - var info = await assetProvider.GetAssetAsync(kv.Item2.Info, _cancellationToken).ConfigureAwait(false); + var info = await assetProvider.GetAssetAsync(kv.Item2.Info, cancellationToken).ConfigureAwait(false); map.Add(info.Id, kv.Item2); } return map; } - private async Task> GetDocumentMapAsync(IEnumerable states, HashSet documents) + private static async Task> GetDocumentMapAsync(IEnumerable states, HashSet documents, CancellationToken cancellationToken) { var map = new Dictionary(); foreach (var state in states) { - var documentChecksums = await state.GetStateChecksumsAsync(_cancellationToken).ConfigureAwait(false); + var documentChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); if (documents.Contains(documentChecksums.Checksum)) { map.Add(state.Id, documentChecksums); @@ -521,29 +524,29 @@ private async Task> GetDocumentMa return map; } - private async Task> GetProjectMapAsync(AssetProvider assetProvider, HashSet projects) + private static async Task> GetProjectMapAsync(AssetProvider assetProvider, HashSet projects, CancellationToken cancellationToken) { var map = new Dictionary(); - var projectChecksums = await assetProvider.GetAssetsAsync(projects, _cancellationToken).ConfigureAwait(false); - var infos = await assetProvider.GetAssetsAsync(projectChecksums.Select(p => p.Item2.Info), _cancellationToken).ConfigureAwait(false); + var projectChecksums = await assetProvider.GetAssetsAsync(projects, cancellationToken).ConfigureAwait(false); + var infos = await assetProvider.GetAssetsAsync(projectChecksums.Select(p => p.Item2.Info), cancellationToken).ConfigureAwait(false); foreach (var kv in projectChecksums) { - var info = await assetProvider.GetAssetAsync(kv.Item2.Info, _cancellationToken).ConfigureAwait(false); + var info = await assetProvider.GetAssetAsync(kv.Item2.Info, cancellationToken).ConfigureAwait(false); map.Add(info.Id, kv.Item2); } return map; } - private async Task> GetProjectMapAsync(Solution solution, HashSet projects) + private static async Task> GetProjectMapAsync(Solution solution, HashSet projects, CancellationToken cancellationToken) { var map = new Dictionary(); foreach (var (projectId, projectState) in solution.State.ProjectStates) { - var projectChecksums = await projectState.GetStateChecksumsAsync(_cancellationToken).ConfigureAwait(false); + var projectChecksums = await projectState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); if (projects.Contains(projectChecksums.Checksum)) { map.Add(projectId, projectChecksums); @@ -554,7 +557,7 @@ private async Task> GetProjectMapAs } #if DEBUG - private async Task ValidateChecksumAsync(Checksum checksumFromRequest, Solution incrementalSolutionBuilt) + private async Task ValidateChecksumAsync(Checksum checksumFromRequest, Solution incrementalSolutionBuilt, CancellationToken cancellationToken) { var currentSolutionChecksum = await incrementalSolutionBuilt.State.GetChecksumAsync(CancellationToken.None).ConfigureAwait(false); if (checksumFromRequest == currentSolutionChecksum) @@ -562,7 +565,7 @@ private async Task ValidateChecksumAsync(Checksum checksumFromRequest, Solution return; } - var solutionInfo = await _assetProvider.CreateSolutionInfoAsync(checksumFromRequest, _cancellationToken).ConfigureAwait(false); + var solutionInfo = await _assetProvider.CreateSolutionInfoAsync(checksumFromRequest, cancellationToken).ConfigureAwait(false); var workspace = new AdhocWorkspace(_hostServices); workspace.AddSolution(solutionInfo); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index 6eb53d3a34bcd..52cc5f7e09f9d 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -110,7 +110,6 @@ await SlowGetSolutionAndRunAsync( /// the same . /// /// - public ValueTask<(Solution solution, T result)> RunWithSolutionAsync( AssetProvider assetProvider, Checksum solutionChecksum, @@ -285,13 +284,13 @@ private async Task ComputeSolutionAsync( { try { - var updater = new SolutionCreator(Services.HostServices, assetProvider, currentSolution, cancellationToken); + var updater = new SolutionCreator(Services.HostServices, assetProvider, currentSolution); // check whether solution is update to the given base solution - if (await updater.IsIncrementalUpdateAsync(solutionChecksum).ConfigureAwait(false)) + if (await updater.IsIncrementalUpdateAsync(solutionChecksum, cancellationToken).ConfigureAwait(false)) { // create updated solution off the baseSolution - return await updater.CreateSolutionAsync(solutionChecksum).ConfigureAwait(false); + return await updater.CreateSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); } // we need new solution. bulk sync all asset for the solution first. diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index 37cfcc6e7940d..395edd26cdf44 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -2,15 +2,19 @@ // 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.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Serialization; using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; +using StreamJsonRpc; namespace Microsoft.CodeAnalysis.Remote { @@ -28,12 +32,13 @@ public SolutionAssetSource(ServiceBrokerClient client) // Make sure we are on the thread pool to avoid UI thread dependencies if external code uses ConfigureAwait(true) await TaskScheduler.Default; - using var provider = await _client.GetProxyAsync(SolutionAssetProvider.ServiceDescriptor, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(provider.Proxy); - - return await new RemoteCallback(provider.Proxy).InvokeAsync( - (proxy, pipeWriter, cancellationToken) => proxy.GetAssetsAsync(pipeWriter, solutionChecksum, checksums.ToArray(), cancellationToken), - (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, solutionChecksum, checksums, serializerService, cancellationToken), + return await RemoteCallback.InvokeServiceAsync( + _client, + SolutionAssetProvider.ServiceDescriptor, + (callback, cancellationToken) => callback.InvokeAsync( + (proxy, pipeWriter, cancellationToken) => proxy.GetAssetsAsync(pipeWriter, solutionChecksum, checksums.ToArray(), cancellationToken), + (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, solutionChecksum, checksums, serializerService, cancellationToken), + cancellationToken), cancellationToken).ConfigureAwait(false); } }