Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an implemention of IActiveWorkspaceProjectContext for new language service #4380

Merged
merged 3 commits into from
Dec 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Provides an implementation of <see cref="IActiveWorkspaceProjectContextHost"/> that delegates
/// onto the active configuration's <see cref="IWorkspaceProjectContextHost"/>.
/// </summary>
[Export(typeof(IActiveWorkspaceProjectContextHost))]
[AppliesTo(ProjectCapability.DotNetLanguageService)]
internal class ActiveWorkspaceProjectContextHost : IActiveWorkspaceProjectContextHost
{
private readonly ActiveConfiguredProject<IWorkspaceProjectContextHost> _activeHost;
private readonly ActiveConfiguredProject<IConfiguredProjectImplicitActivationTracking> _activeConfiguredProject;

[ImportingConstructor]
public ActiveWorkspaceProjectContextHost(ActiveConfiguredProject<IWorkspaceProjectContextHost> activeHost, ActiveConfiguredProject<IConfiguredProjectImplicitActivationTracking> activeConfiguredProject)
{
_activeHost = activeHost;
_activeConfiguredProject = activeConfiguredProject;
}

public Task PublishAsync(CancellationToken cancellationToken = default)
{
return _activeHost.Value.PublishAsync(cancellationToken);
}

public async Task OpenContextForWriteAsync(Func<IWorkspaceProjectContextAccessor, Task> action)
{
while (true)
{
try
{
await _activeHost.Value.OpenContextForWriteAsync(action);
}
catch (ActiveProjectConfigurationChangedException)
{ // Host was unloaded because configuration changed, retry on new config
}
}
}

public async Task<T> OpenContextForWriteAsync<T>(Func<IWorkspaceProjectContextAccessor, Task<T>> action)
{
while (true)
{
try
{
return await _activeHost.Value.OpenContextForWriteAsync(action);
}
catch (ActiveProjectConfigurationChangedException)
{ // Host was unloaded because configuration changed, retry on new config
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal interface IWorkspaceProjectContextHost
/// it will join the load when it starts.
/// </remarks>
Task PublishAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Opens the <see cref="IWorkspaceProjectContext"/>, passing it to the specified action for writing.
/// </summary>
Expand All @@ -42,6 +42,10 @@ internal interface IWorkspaceProjectContextHost
/// <exception cref="OperationCanceledException">
/// The result is awaited and the <see cref="ConfiguredProject"/> is unloaded.
/// </exception>
/// <exception cref="ActiveProjectConfigurationChangedException">
/// The <see cref="IWorkspaceProjectContextHost"/> represents the active one, and
/// the configuration changed.
/// </exception>
Task OpenContextForWriteAsync(Func<IWorkspaceProjectContextAccessor, Task> action);

/// <summary>
Expand All @@ -59,6 +63,10 @@ internal interface IWorkspaceProjectContextHost
/// <exception cref="OperationCanceledException">
/// The result is awaited and the <see cref="ConfiguredProject"/> is unloaded.
/// </exception>
/// <exception cref="ActiveProjectConfigurationChangedException">
/// The <see cref="IWorkspaceProjectContextHost"/> represents the active one, and
/// the configuration changed.
/// </exception>
Task<T> OpenContextForWriteAsync<T>(Func<IWorkspaceProjectContextAccessor, Task<T>> action);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -91,27 +91,38 @@ protected override async Task DisposeCoreUnderLockAsync(bool initialized)

public async Task OpenContextForWriteAsync(Func<IWorkspaceProjectContextAccessor, Task> 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<T> OpenContextForWriteAsync<T>(Func<IWorkspaceProjectContextAccessor, Task<T>> 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<IProjectSubscriptionUpdate> 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<IProjectSubscriptionUpdate> update, bool evaluation, CancellationToken cancellationToken)
Expand Down Expand Up @@ -139,9 +150,11 @@ private Task ApplyProjectChangesUnderLockAsync(IProjectVersionedValue<IProjectSu
}
}

private async Task WaitUntilInitializedCompletedAsync()
private void CheckForInitialized()
{
await InitializationCompletion;
// We should have been initialized by our
// owner before they called into us
Assumes.True(IsInitialized);

// If we failed to create a context, we treat it as a cancellation
if (_contextAccessor == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Microsoft.VisualStudio.ProjectSystem.LanguageServices
/// on changes to the project to the <see cref="IApplyChangesToWorkspaceContext"/> service.
/// </summary>
[Export(typeof(IImplicitlyActiveService))]
[Export(typeof(IWorkspaceProjectContextHost))]
[AppliesTo(ProjectCapability.DotNetLanguageService)]
internal partial class WorkspaceContextHost : AbstractMultiLifetimeComponent<WorkspaceContextHost.WorkspaceContextHostInstance>, IImplicitlyActiveService, IWorkspaceProjectContextHost
{
Expand Down Expand Up @@ -65,7 +66,7 @@ public async Task OpenContextForWriteAsync(Func<IWorkspaceProjectContextAccessor

WorkspaceContextHostInstance instance = await WaitForLoadedAsync();

// Throws OperationCanceledException if 'instance' is Disposed
// Throws ActiveProjectConfigurationChangedException if 'instance' is Disposed
await instance.OpenContextForWriteAsync(action);
}

Expand All @@ -75,7 +76,7 @@ public async Task<T> OpenContextForWriteAsync<T>(Func<IWorkspaceProjectContextAc

WorkspaceContextHostInstance instance = await WaitForLoadedAsync();

// Throws OperationCanceledException if 'instance' is Disposed
// Throws ActiveProjectConfigurationChangedException if 'instance' is Disposed
return await instance.OpenContextForWriteAsync(action);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Ensures that the <see cref="IWorkspaceProjectContext"/> for the "active" configuration has
/// been loaded by the time users and extensions can interact with the project.
/// </summary>
/// <remarks>
/// 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".
/// </remarks>
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;
}
}
}