From f6f24910d29ae61502e29f987b407a22d2875060 Mon Sep 17 00:00:00 2001 From: David Kean Date: Thu, 13 Dec 2018 17:30:15 +1100 Subject: [PATCH 1/3] Add an implemention IActiveWorkspaceProjectContext This delegates onto the active config's implementation. --- .../ActiveWorkspaceProjectContextHost.cs | 61 +++++++++++++++++++ ...ontextHost.WorkspaceContextHostInstance.cs | 41 ++++++++----- .../LanguageServices/WorkspaceContextHost.cs | 5 +- 3 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/ActiveWorkspaceProjectContextHost.cs diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/ActiveWorkspaceProjectContextHost.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/ActiveWorkspaceProjectContextHost.cs new file mode 100644 index 00000000000..a052c2a04ff --- /dev/null +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/ActiveWorkspaceProjectContextHost.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.ProjectSystem.LanguageServices +{ + /// + /// Provides an implementation of that delegates + /// onto the active configuration's . + /// + [Export(typeof(IActiveWorkspaceProjectContextHost))] + [AppliesTo(ProjectCapability.DotNetLanguageService)] + internal class ActiveWorkspaceProjectContextHost : IActiveWorkspaceProjectContextHost + { + private readonly ActiveConfiguredProject _activeHost; + private readonly ActiveConfiguredProject _activeConfiguredProject; + + [ImportingConstructor] + public ActiveWorkspaceProjectContextHost(ActiveConfiguredProject activeHost, ActiveConfiguredProject activeConfiguredProject) + { + _activeHost = activeHost; + _activeConfiguredProject = activeConfiguredProject; + } + + public Task PublishAsync(CancellationToken cancellationToken = default) + { + return _activeHost.Value.PublishAsync(cancellationToken); + } + + public async Task OpenContextForWriteAsync(Func action) + { + while (true) + { + try + { + await _activeHost.Value.OpenContextForWriteAsync(action); + } + catch (ActiveProjectConfigurationChangedException) + { // Host was unloaded because configuration changed, retry on new config + } + } + } + + public async Task OpenContextForWriteAsync(Func> action) + { + while (true) + { + try + { + return await _activeHost.Value.OpenContextForWriteAsync(action); + } + catch (ActiveProjectConfigurationChangedException) + { // Host was unloaded because configuration changed, retry on new config + } + } + } + } +} diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceContextHost.WorkspaceContextHostInstance.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceContextHost.WorkspaceContextHostInstance.cs index 5c949f50d4a..0db189c5c79 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceContextHost.WorkspaceContextHostInstance.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceContextHost.WorkspaceContextHostInstance.cs @@ -53,7 +53,7 @@ public Task InitializeAsync() protected override async Task InitializeCoreAsync(CancellationToken cancellationToken) { - _contextAccessor = await _workspaceProjectContextProvider.CreateProjectContextAsync(_project); + _contextAccessor = await _workspaceProjectContextProvider.CreateProjectContextAsync(_project); if (_contextAccessor == null) return; @@ -91,27 +91,38 @@ protected override async Task DisposeCoreUnderLockAsync(bool initialized) public async Task OpenContextForWriteAsync(Func action) { - await WaitUntilInitializedCompletedAsync(); + CheckForInitialized(); - await ExecuteUnderLockAsync(_ => action(_contextAccessor), _tasksService.UnloadCancellationToken); + try + { + await ExecuteUnderLockAsync(_ => action(_contextAccessor), _tasksService.UnloadCancellationToken); + } + catch (OperationCanceledException ex) when (ex.CancellationToken == DisposalToken) + { // We treat cancellation because our instance was disposed differently from when the project is unloading. + // + // The former indicates that the active configuration changed, and our ConfiguredProject is no longer + // considered implicitly "active", we throw a different exceptions to let callers handle that. + throw new ActiveProjectConfigurationChangedException(); + } } public async Task OpenContextForWriteAsync(Func> action) { - await WaitUntilInitializedCompletedAsync(); + CheckForInitialized(); - return await ExecuteUnderLockAsync(_ => action(_contextAccessor), _tasksService.UnloadCancellationToken); + try + { + return await ExecuteUnderLockAsync(_ => action(_contextAccessor), _tasksService.UnloadCancellationToken); + } + catch (OperationCanceledException ex) when (ex.CancellationToken == DisposalToken) + { + throw new ActiveProjectConfigurationChangedException(); + } } internal async Task OnProjectChangedAsync(IProjectVersionedValue update, bool evaluation) { - CancellationToken cancellationToken = _tasksService.UnloadCancellationToken; - - await ExecuteUnderLockAsync(ct => - { - return ApplyProjectChangesUnderLockAsync(update, evaluation, ct); - - }, cancellationToken); + await ExecuteUnderLockAsync(ct => ApplyProjectChangesUnderLockAsync(update, evaluation, ct), _tasksService.UnloadCancellationToken); } private Task ApplyProjectChangesUnderLockAsync(IProjectVersionedValue update, bool evaluation, CancellationToken cancellationToken) @@ -139,9 +150,11 @@ private Task ApplyProjectChangesUnderLockAsync(IProjectVersionedValue service. /// [Export(typeof(IImplicitlyActiveService))] + [Export(typeof(IWorkspaceProjectContextHost))] [AppliesTo(ProjectCapability.DotNetLanguageService)] internal partial class WorkspaceContextHost : AbstractMultiLifetimeComponent, IImplicitlyActiveService, IWorkspaceProjectContextHost { @@ -65,7 +66,7 @@ public async Task OpenContextForWriteAsync(Func OpenContextForWriteAsync(Func Date: Thu, 13 Dec 2018 17:31:18 +1100 Subject: [PATCH 2/3] Ensure language service is initialized before project load This ensures that the project is loaded before project load has completed. This brings back the original experience of the previous language-service integration. --- .../WorkspaceProjectContextHostInitiator.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceProjectContextHostInitiator.cs diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceProjectContextHostInitiator.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceProjectContextHostInitiator.cs new file mode 100644 index 00000000000..48eee0d20bc --- /dev/null +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/WorkspaceProjectContextHostInitiator.cs @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.ComponentModel.Composition; +using System.Threading.Tasks; + +using Microsoft.VisualStudio.LanguageServices.ProjectSystem; +using Microsoft.VisualStudio.Threading; + +namespace Microsoft.VisualStudio.ProjectSystem.LanguageServices +{ + /// + /// Ensures that the for the "active" configuration has + /// been loaded by the time users and extensions can interact with the project. + /// + /// + /// It is important to make sure Roslyn is aware of the project by the time the project can be + /// interacted with so that restored documents and other features used quickly after solution + /// load behave correctly and have "project context". + /// + internal class WorkspaceProjectContextHostInitiator + { + private readonly IUnconfiguredProjectTasksService _tasksService; + private readonly IActiveWorkspaceProjectContextHost _activeWorkspaceProjectContextHost; + + [ImportingConstructor] + public WorkspaceProjectContextHostInitiator(IUnconfiguredProjectTasksService tasksService, IActiveWorkspaceProjectContextHost activeWorkspaceProjectContextHost) + { + _tasksService = tasksService; + _activeWorkspaceProjectContextHost = activeWorkspaceProjectContextHost; + } + + [ProjectAutoLoad(startAfter: ProjectLoadCheckpoint.AfterLoadInitialConfiguration, completeBy: ProjectLoadCheckpoint.ProjectFactoryCompleted)] + [AppliesTo(ProjectCapability.DotNetLanguageService)] + public Task InitializeAsync() + { + // While we want make sure it's loaded before PrioritizedProjectLoadedInHost, + // we don't want to block project factory completion on its load, so fire and forget + _tasksService.PrioritizedProjectLoadedInHostAsync(() => _activeWorkspaceProjectContextHost.PublishAsync()) + .Forget(); + + return Task.CompletedTask; + } + } +} From 34824a461bb5e72417f2d870a82f01eb0165b4ea Mon Sep 17 00:00:00 2001 From: David Kean Date: Mon, 17 Dec 2018 10:39:04 +1100 Subject: [PATCH 3/3] Doc exception --- .../LanguageServices/IWorkspaceProjectContextHost.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/IWorkspaceProjectContextHost.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/IWorkspaceProjectContextHost.cs index 609c28cab00..02cf3fea916 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/IWorkspaceProjectContextHost.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/IWorkspaceProjectContextHost.cs @@ -29,7 +29,7 @@ internal interface IWorkspaceProjectContextHost /// it will join the load when it starts. /// Task PublishAsync(CancellationToken cancellationToken = default); - + /// /// Opens the , passing it to the specified action for writing. /// @@ -42,6 +42,10 @@ internal interface IWorkspaceProjectContextHost /// /// The result is awaited and the is unloaded. /// + /// + /// The represents the active one, and + /// the configuration changed. + /// Task OpenContextForWriteAsync(Func action); /// @@ -59,6 +63,10 @@ internal interface IWorkspaceProjectContextHost /// /// The result is awaited and the is unloaded. /// + /// + /// The represents the active one, and + /// the configuration changed. + /// Task OpenContextForWriteAsync(Func> action); } }