提交 7e05de87 编写于 作者: H Heejae Chang

added RemoteWorkspace

RemoteWorkspace has host agnostic implementation of roslyn features/services/workspace that will run in remote host
上级 008c705d
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Diagnostics.Telemetry;
using Microsoft.CodeAnalysis.Workspaces.Diagnostics;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote.Diagnostics
{
internal class DiagnosticComputer
{
private readonly Project _project;
private readonly Dictionary<DiagnosticAnalyzer, HashSet<DiagnosticData>> _exceptions;
public DiagnosticComputer(Project project)
{
_project = project;
_exceptions = new Dictionary<DiagnosticAnalyzer, HashSet<DiagnosticData>>();
}
public async Task<DiagnosticAnalysisResultMap<string, DiagnosticAnalysisResultBuilder>> GetDiagnosticsAsync(
IEnumerable<AnalyzerReference> hostAnalyzers,
IEnumerable<string> analyzerIds,
bool reportSuppressedDiagnostics,
bool logAnalyzerExecutionTime,
CancellationToken cancellationToken)
{
var analyzerMap = CreateAnalyzerMap(hostAnalyzers, _project);
var analyzers = GetAnalyzers(analyzerMap, analyzerIds);
if (analyzers.Length == 0)
{
return DiagnosticAnalysisResultMap.Create(ImmutableDictionary<string, DiagnosticAnalysisResultBuilder>.Empty, ImmutableDictionary<string, AnalyzerTelemetryInfo>.Empty);
}
var compilation = await _project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
// TODO: can we support analyzerExceptionFilter in remote host?
// right now, host doesn't support watson, we might try to use new NonFatal watson API?
var analyzerOptions = new CompilationWithAnalyzersOptions(
options: _project.AnalyzerOptions,
onAnalyzerException: OnAnalyzerException,
analyzerExceptionFilter: null,
concurrentAnalysis: true,
logAnalyzerExecutionTime: logAnalyzerExecutionTime,
reportSuppressedDiagnostics: reportSuppressedDiagnostics);
var analyzerDriver = compilation.WithAnalyzers(analyzers, analyzerOptions);
// PERF: Run all analyzers at once using the new GetAnalysisResultAsync API.
var analysisResult = await analyzerDriver.GetAnalysisResultAsync(cancellationToken).ConfigureAwait(false);
// REVIEW: the design of current analyzer engine is that, information/states in CompilationWithAnalyzer (more specifically AnalyzerManager singleton)
// will live forever until analyzer references (analyzers), which is given to CompilationWithAnalyzer, go away.
// that is not suitable for OOP since OOP will create new workspace
// for each analysis but share all resources including analyzer references.
// until, we address this issue, OOP will clear state every time analysis is done.
//
// * NOTE * this only works for now since we don't run analysis on multiple threads.
//
// best way to fix this is doing this - https://github.com/dotnet/roslyn/issues/2830
// host should control lifetime of all information related to analyzer reference explicitly
CompilationWithAnalyzers.ClearAnalyzerState(analyzers);
var builderMap = analysisResult.ToResultBuilderMap(_project, VersionStamp.Default, compilation, analysisResult.Analyzers, cancellationToken);
return DiagnosticAnalysisResultMap.Create(builderMap.ToImmutableDictionary(kv => GetAnalyzerId(analyzerMap, kv.Key), kv => kv.Value),
analysisResult.AnalyzerTelemetryInfo.ToImmutableDictionary(kv => GetAnalyzerId(analyzerMap, kv.Key), kv => kv.Value),
_exceptions.ToImmutableDictionary(kv => GetAnalyzerId(analyzerMap, kv.Key), kv => kv.Value.ToImmutableArray()));
}
private void OnAnalyzerException(Exception exception, DiagnosticAnalyzer analyzer, Diagnostic diagnostic)
{
lock (_exceptions)
{
var list = _exceptions.GetOrAdd(analyzer, _ => new HashSet<DiagnosticData>());
list.Add(DiagnosticData.Create(_project, diagnostic));
}
}
private string GetAnalyzerId(BidirectionalMap<string, DiagnosticAnalyzer> analyzerMap, DiagnosticAnalyzer analyzer)
{
var analyzerId = analyzerMap.GetKeyOrDefault(analyzer);
Contract.ThrowIfNull(analyzerId);
return analyzerId;
}
private ImmutableArray<DiagnosticAnalyzer> GetAnalyzers(BidirectionalMap<string, DiagnosticAnalyzer> analyzerMap, IEnumerable<string> analyzerIds)
{
// TODO: this probably need to be cached as well in analyzer service?
var builder = ImmutableArray.CreateBuilder<DiagnosticAnalyzer>();
foreach (var analyzerId in analyzerIds)
{
DiagnosticAnalyzer analyzer;
if (analyzerMap.TryGetValue(analyzerId, out analyzer))
{
builder.Add(analyzer);
}
}
return builder.ToImmutable();
}
private BidirectionalMap<string, DiagnosticAnalyzer> CreateAnalyzerMap(IEnumerable<AnalyzerReference> hostAnalyzers, Project project)
{
// TODO: probably need something like analyzer service so that we don't do this repeatedly?
return new BidirectionalMap<string, DiagnosticAnalyzer>(
hostAnalyzers.Concat(project.AnalyzerReferences).SelectMany(r => r.GetAnalyzers(project.Language)).Select(a => KeyValuePair.Create(a.GetAnalyzerId(), a)));
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectLanguage>CSharp</ProjectLanguage>
</PropertyGroup>
<ImportGroup Label="Settings">
<Import Project="..\..\..\..\build\Targets\VSL.Settings.targets" />
</ImportGroup>
<PropertyGroup>
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
<Platform Condition="'$(Platform)' == ''">AnyCPU</Platform>
<ProjectGuid>{F822F72A-CC87-4E31-B57D-853F65CBEBF3}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>Microsoft.CodeAnalysis.Remote</RootNamespace>
<AssemblyName>Microsoft.CodeAnalysis.Remote.Workspaces</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<CopyNuGetImplementations>false</CopyNuGetImplementations>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\..\..\Compilers\Core\Portable\CodeAnalysis.csproj">
<Project>{1EE8CAD3-55F9-4D91-96B2-084641DA9A6C}</Project>
<Name>CodeAnalysis</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\Dependencies\Immutable\Immutable.csproj">
<Project>{DCDA908D-EF5E-494B-ADDC-C26F5FD610CA}</Project>
<Name>Immutable</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Desktop\Workspaces.Desktop.csproj">
<Project>{2e87fa96-50bb-4607-8676-46521599f998}</Project>
<Name>Workspaces.Desktop</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Portable\Workspaces.csproj">
<Project>{5F8D2414-064A-4B3A-9B42-8E2A04246BE5}</Project>
<Name>Workspaces</Name>
</ProjectReference>
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<None Include="project.json" />
<PublicAPI Include="PublicAPI.Shipped.txt" />
<PublicAPI Include="PublicAPI.Unshipped.txt" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<InternalsVisibleTo Include="Microsoft.CodeAnalysis.Remote.ServiceHub" />
</ItemGroup>
<ItemGroup>
<Compile Include="Diagnostics\DiagnosticComputer.cs" />
<Compile Include="Services\AssetSource.cs" />
<Compile Include="Services\AssetService.cs" />
<Compile Include="Services\CompilationService.cs" />
<Compile Include="Services\RoslynServices.cs" />
<Compile Include="Services\SolutionService.cs" />
</ItemGroup>
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
\ No newline at end of file
// 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.Collections.Concurrent;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
/// This service provide a way to get roslyn objects from checksum
///
/// TODO: change this service to workspace service
/// </summary>
internal class AssetService
{
private const int CleanupInterval = 3; // 3 minutes
private const int PurgeAfter = 30; // 30 minutes
// PREVIEW: unfortunately, I need dummy workspace since workspace services can be workspace specific
private static readonly Serializer s_serializer = new Serializer(new AdhocWorkspace(RoslynServices.HostServices, workspaceKind: "dummy").Services);
private readonly ConcurrentDictionary<int, AssetSource> _assetSources =
new ConcurrentDictionary<int, AssetSource>(concurrencyLevel: 4, capacity: 10);
private readonly ConcurrentDictionary<Checksum, Entry> _assets =
new ConcurrentDictionary<Checksum, Entry>(concurrencyLevel: 4, capacity: 10);
public AssetService()
{
Task.Factory.StartNew(CleanAssets, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
}
private async Task CleanAssets()
{
var purgeAfterTimeSpan = TimeSpan.FromMinutes(PurgeAfter);
var cleanupIntervalTimeSpan = TimeSpan.FromMinutes(CleanupInterval);
while (true)
{
var current = DateTime.UtcNow;
foreach (var kvp in _assets.ToArray())
{
if (current - kvp.Value.LastAccessed <= purgeAfterTimeSpan)
{
continue;
}
// If it fails, we'll just leave it in the asset pool.
Entry entry;
_assets.TryRemove(kvp.Key, out entry);
}
await Task.Delay(cleanupIntervalTimeSpan).ConfigureAwait(false);
}
}
public void Set(Checksum checksum, object value)
{
_assets.TryAdd(checksum, new Entry(value));
}
public async Task<T> GetAssetAsync<T>(Checksum checksum, CancellationToken cancellationToken)
{
Entry entry;
if (!_assets.TryGetValue(checksum, out entry))
{
// TODO: what happen if service doesn't come back. timeout?
await RequestAssetAsync(checksum, cancellationToken).ConfigureAwait(false);
if (!_assets.TryGetValue(checksum, out entry))
{
// this can happen if all asset source is released due to cancellation
cancellationToken.ThrowIfCancellationRequested();
Contract.Fail("how this can happen?");
}
}
// Update timestamp
Update(checksum, entry);
return (T)entry.Object;
}
private void Update(Checksum checksum, Entry entry)
{
// entry is reference type. we update it directly.
// we don't care about race.
entry.LastAccessed = DateTime.UtcNow;
}
public async Task RequestAssetAsync(Checksum checksum, CancellationToken cancellationToken)
{
// the service doesn't care which asset source it uses to get the asset. if there are multiple
// channel created (multiple caller to code analysis service), we will have multiple asset sources
//
// but, there must be one that knows about the asset with the checksum that is pinned for this
// particular call
foreach (var kv in _assetSources.ToArray())
{
var serviceId = kv.Key;
var source = kv.Value;
cancellationToken.ThrowIfCancellationRequested();
try
{
// ask one of asset source for data
await source.RequestAssetAsync(serviceId, checksum, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
// connection to the asset source has closed.
// move to next asset source
Contract.ThrowIfFalse(ex is OperationCanceledException || ex is IOException || ex is ObjectDisposedException);
// cancellation could be from either caller or asset side when cancelled. we only throw cancellation if
// caller side is cancelled. otherwise, we move to next asset source
cancellationToken.ThrowIfCancellationRequested();
continue;
}
break;
}
}
public T Deserialize<T>(string kind, ObjectReader reader, CancellationToken cancellationToken)
{
return s_serializer.Deserialize<T>(kind, reader, cancellationToken);
}
public void RegisterAssetSource(int serviceId, AssetSource assetSource)
{
Contract.ThrowIfFalse(_assetSources.TryAdd(serviceId, assetSource));
}
public void UnregisterAssetSource(int serviceId)
{
AssetSource dummy;
_assetSources.TryRemove(serviceId, out dummy);
}
private class Entry
{
// mutable field
public DateTime LastAccessed;
// this can't change for same checksum
public readonly object Object;
public Entry(object @object)
{
LastAccessed = DateTime.UtcNow;
Object = @object;
}
}
}
}
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
/// Asset source provides a way to callback asset source (Ex, VS) to get asset with the given checksum
/// </summary>
internal abstract class AssetSource
{
private static int s_serviceId = 0;
private readonly int _currentId = 0;
protected AssetSource()
{
_currentId = Interlocked.Add(ref s_serviceId, 1);
RoslynServices.AssetService.RegisterAssetSource(_currentId, this);
}
public abstract Task RequestAssetAsync(int serviceId, Checksum checksum, CancellationToken cancellationToken);
public void Done()
{
RoslynServices.AssetService.UnregisterAssetSource(_currentId);
}
}
}
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Execution;
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
/// Provide compilation from given solution checksum
///
/// TODO: change this to workspace service
/// </summary>
internal class CompilationService
{
public async Task<Compilation> GetCompilationAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken)
{
var solution = await RoslynServices.SolutionService.GetSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
// TODO: need to figure out how to deal with exceptions in service hub
return await solution.GetProject(projectId).GetCompilationAsync(cancellationToken).ConfigureAwait(false);
}
}
}
// 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 Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
/// Various Roslyn services provider.
///
/// TODO: change all these services to WorkspaceServices
/// </summary>
internal static class RoslynServices
{
// TODO: probably need to split this to private and public services
public static readonly HostServices HostServices = MefHostServices.Create(
MefHostServices.DefaultAssemblies.Add(typeof(Host.TemporaryStorageServiceFactory.TemporaryStorageService).Assembly));
public static readonly AssetService AssetService = new AssetService();
public static readonly SolutionService SolutionService = new SolutionService();
public static readonly CompilationService CompilationService = new CompilationService();
}
}
// 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.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Execution;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Remote
{
/// <summary>
/// Provide solution from given checksum
///
/// TODO: change this to workspace service
/// </summary>
internal class SolutionService
{
// TODO: make this simple cache better
// this simple cache hold onto the last solution created
private ValueTuple<Checksum, Solution> _lastSolution;
public async Task<Solution> GetSolutionAsync(Checksum solutionChecksum, CancellationToken cancellationToken)
{
if (_lastSolution.Item1 == solutionChecksum)
{
return _lastSolution.Item2;
}
// create new solution
var solution = await CreateSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false);
// save it
_lastSolution = ValueTuple.Create(solutionChecksum, solution);
return solution;
}
private async Task<Solution> CreateSolutionAsync(Checksum solutionChecksum, CancellationToken cancellationToken)
{
var solutionChecksumObject = await RoslynServices.AssetService.GetAssetAsync<SolutionChecksumObject>(solutionChecksum, cancellationToken).ConfigureAwait(false);
// TODO: Make these to do work concurrently
var workspace = new AdhocWorkspace(RoslynServices.HostServices);
var solutionInfo = await RoslynServices.AssetService.GetAssetAsync<SolutionChecksumObjectInfo>(solutionChecksumObject.Info, cancellationToken).ConfigureAwait(false);
var projects = new List<ProjectInfo>();
foreach (var projectChecksum in solutionChecksumObject.Projects.Objects)
{
var projectSnapshot = await RoslynServices.AssetService.GetAssetAsync<ProjectChecksumObject>(projectChecksum, cancellationToken).ConfigureAwait(false);
var documents = new List<DocumentInfo>();
foreach (var documentChecksum in projectSnapshot.Documents.Objects)
{
var documentSnapshot = await RoslynServices.AssetService.GetAssetAsync<DocumentChecksumObject>(documentChecksum, cancellationToken).ConfigureAwait(false);
var documentInfo = await RoslynServices.AssetService.GetAssetAsync<DocumentChecksumObjectInfo>(documentSnapshot.Info, cancellationToken).ConfigureAwait(false);
// TODO: do we need version?
documents.Add(
DocumentInfo.Create(
documentInfo.Id,
documentInfo.Name,
documentInfo.Folders,
documentInfo.SourceCodeKind,
new RemoteTextLoader(documentSnapshot.Text),
documentInfo.FilePath,
documentInfo.IsGenerated));
}
var p2p = new List<ProjectReference>();
foreach (var checksum in projectSnapshot.ProjectReferences.Objects)
{
cancellationToken.ThrowIfCancellationRequested();
var reference = await RoslynServices.AssetService.GetAssetAsync<ProjectReference>(checksum, cancellationToken).ConfigureAwait(false);
p2p.Add(reference);
}
var metadata = new List<MetadataReference>();
foreach (var checksum in projectSnapshot.MetadataReferences.Objects)
{
cancellationToken.ThrowIfCancellationRequested();
var reference = await RoslynServices.AssetService.GetAssetAsync<MetadataReference>(checksum, cancellationToken).ConfigureAwait(false);
metadata.Add(reference);
}
var analyzers = new List<AnalyzerReference>();
foreach (var checksum in projectSnapshot.AnalyzerReferences.Objects)
{
cancellationToken.ThrowIfCancellationRequested();
var reference = await RoslynServices.AssetService.GetAssetAsync<AnalyzerReference>(checksum, cancellationToken).ConfigureAwait(false);
analyzers.Add(reference);
}
var additionals = new List<DocumentInfo>();
foreach (var documentChecksum in projectSnapshot.AdditionalDocuments.Objects)
{
cancellationToken.ThrowIfCancellationRequested();
var documentSnapshot = await RoslynServices.AssetService.GetAssetAsync<DocumentChecksumObject>(documentChecksum, cancellationToken).ConfigureAwait(false);
var documentInfo = await RoslynServices.AssetService.GetAssetAsync<DocumentChecksumObjectInfo>(documentSnapshot.Info, cancellationToken).ConfigureAwait(false);
// TODO: do we need version?
additionals.Add(
DocumentInfo.Create(
documentInfo.Id,
documentInfo.Name,
documentInfo.Folders,
documentInfo.SourceCodeKind,
new RemoteTextLoader(documentSnapshot.Text),
documentInfo.FilePath,
documentInfo.IsGenerated));
}
var projectInfo = await RoslynServices.AssetService.GetAssetAsync<ProjectChecksumObjectInfo>(projectSnapshot.Info, cancellationToken).ConfigureAwait(false);
var compilationOptions = await RoslynServices.AssetService.GetAssetAsync<CompilationOptions>(projectSnapshot.CompilationOptions, cancellationToken).ConfigureAwait(false);
var parseOptions = await RoslynServices.AssetService.GetAssetAsync<ParseOptions>(projectSnapshot.ParseOptions, cancellationToken).ConfigureAwait(false);
projects.Add(
ProjectInfo.Create(
projectInfo.Id, projectInfo.Version, projectInfo.Name, projectInfo.AssemblyName,
projectInfo.Language, projectInfo.FilePath, projectInfo.OutputFilePath,
compilationOptions, parseOptions,
documents, p2p, metadata, analyzers, additionals));
}
return workspace.AddSolution(SolutionInfo.Create(solutionInfo.Id, solutionInfo.Version, solutionInfo.FilePath, projects));
}
private class RemoteTextLoader : TextLoader
{
private readonly Checksum _checksum;
public RemoteTextLoader(Checksum checksum)
{
_checksum = checksum;
}
public override async Task<TextAndVersion> LoadTextAndVersionAsync(Workspace workspace, DocumentId documentId, CancellationToken cancellationToken)
{
var text = await RoslynServices.AssetService.GetAssetAsync<SourceText>(_checksum, cancellationToken).ConfigureAwait(false);
return TextAndVersion.Create(text, VersionStamp.Create());
}
}
}
}
{
"dependencies": {
},
"frameworks": {
"net46": {}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册