Skip to content

Commit 645b0ad

Browse files
Use confirmed thread safe way of creating workspaces
#376 (comment)
1 parent fb5bf2a commit 645b0ad

6 files changed

Lines changed: 153 additions & 24 deletions

File tree

CodeConverter/CodeConverter.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
4141
</PackageReference>
4242
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
43+
<PackageReference Include="Microsoft.VisualStudio.Composition" Version="15.8.98" />
44+
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="15.8.122" />
4345
<PackageReference Include="System.Globalization.Extensions" Version="4.3.0" />
4446
<PackageReference Include="System.Linq.Async" Version="4.0.0" />
4547
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.6.0">

CodeConverter/Shared/CompilationOptionsExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public static Project CreateProject(this CompilationOptions options, IEnumerable
1919

2020
string projFileExtension = parseOptions.Language == LanguageNames.CSharp ? ".csproj" : ".vbproj";
2121
var projectFilePath = Path.Combine(Directory.GetCurrentDirectory() + singleDocumentAssemblyName + projFileExtension);
22-
var solution = WorkspaceFactory.AdhocSolution.AddProject(projectId, singleDocumentAssemblyName,
22+
var solution = ThreadSafeWorkspaceHelper.EmptyAdhocSolution.AddProject(projectId, singleDocumentAssemblyName,
2323
singleDocumentAssemblyName, options.Language)
2424
.WithProjectFilePath(projectId, projectFilePath);
2525

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using ICSharpCode.CodeConverter.Util.FromRoslynSdk;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.Host;
6+
using Microsoft.CodeAnalysis.Host.Mef;
7+
using Microsoft.VisualStudio.Composition;
8+
9+
namespace ICSharpCode.CodeConverter.Shared
10+
{
11+
/// <summary>
12+
/// Known MEF bug means creating multiple workspaces outside VS context in parallel has race conditions: https://github.com/dotnet/roslyn/issues/24260
13+
/// https://github.com/icsharpcode/CodeConverter/issues/376#issuecomment-625887068
14+
/// </summary>
15+
public static class ThreadSafeWorkspaceHelper
16+
{
17+
/// <summary>
18+
/// Use this in all workspace creation
19+
/// </summary>
20+
public static HostServices HostServices {
21+
get {
22+
var exportProvider = ExportProviderFactory.Value.CreateExportProvider();
23+
return MefHostServices.Create(exportProvider.AsCompositionContext());
24+
}
25+
}
26+
27+
/// <summary>
28+
/// Empty solution in an adhoc workspace
29+
/// </summary>
30+
public static Solution EmptyAdhocSolution => LazyAdhocSolution.Value;
31+
32+
private static readonly Lazy<IExportProviderFactory> ExportProviderFactory = new Lazy<IExportProviderFactory>(CreateExportProviderFactory);
33+
34+
private static IExportProviderFactory CreateExportProviderFactory()
35+
{
36+
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits - Consider making a joinable task factory available so we can use AsyncLazy
37+
return Task.Run(async () => await CreateExportProviderFactoryAsync()).GetAwaiter().GetResult();
38+
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
39+
}
40+
41+
private static async Task<IExportProviderFactory> CreateExportProviderFactoryAsync()
42+
{
43+
var discovery = new AttributedPartDiscovery(Resolver.DefaultInstance, isNonPublicSupported: true);
44+
var parts = await discovery.CreatePartsAsync(MefHostServices.DefaultAssemblies);
45+
var catalog = ComposableCatalog.Create(Resolver.DefaultInstance).AddParts(parts);
46+
47+
var configuration = CompositionConfiguration.Create(catalog);
48+
var runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration);
49+
return runtimeComposition.CreateExportProviderFactory();
50+
}
51+
52+
private static Lazy<Solution> LazyAdhocSolution = new Lazy<Solution>(() => {
53+
return new AdhocWorkspace(HostServices).CurrentSolution;
54+
});
55+
56+
}
57+
}

CodeConverter/Shared/WorkspaceFactory.cs

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Composition;
8+
using System.Composition.Hosting.Core;
9+
using System.Linq;
10+
using System.Reflection;
11+
using Microsoft.VisualStudio.Composition;
12+
13+
namespace ICSharpCode.CodeConverter.Util.FromRoslynSdk
14+
{
15+
internal static class ExportProviderExtensions
16+
{
17+
public static CompositionContext AsCompositionContext(this ExportProvider exportProvider)
18+
{
19+
return new CompositionContextShim(exportProvider);
20+
}
21+
22+
private class CompositionContextShim : CompositionContext
23+
{
24+
private readonly ExportProvider _exportProvider;
25+
26+
public CompositionContextShim(ExportProvider exportProvider)
27+
{
28+
_exportProvider = exportProvider;
29+
}
30+
31+
public override bool TryGetExport(CompositionContract contract, out object export)
32+
{
33+
var importMany = contract.MetadataConstraints.Contains(new KeyValuePair<string, object>("IsImportMany", true));
34+
var (contractType, metadataType) = GetContractType(contract.ContractType, importMany);
35+
36+
if (metadataType != null)
37+
{
38+
var methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods()
39+
where method.Name == nameof(ExportProvider.GetExports)
40+
where method.IsGenericMethod && method.GetGenericArguments().Length == 2
41+
where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string)
42+
select method).Single();
43+
var parameterizedMethod = methodInfo.MakeGenericMethod(contractType, metadataType);
44+
export = parameterizedMethod.Invoke(_exportProvider, new[] { contract.ContractName });
45+
}
46+
else
47+
{
48+
var methodInfo = (from method in _exportProvider.GetType().GetTypeInfo().GetMethods()
49+
where method.Name == nameof(ExportProvider.GetExports)
50+
where method.IsGenericMethod && method.GetGenericArguments().Length == 1
51+
where method.GetParameters().Length == 1 && method.GetParameters()[0].ParameterType == typeof(string)
52+
select method).Single();
53+
var parameterizedMethod = methodInfo.MakeGenericMethod(contractType);
54+
export = parameterizedMethod.Invoke(_exportProvider, new[] { contract.ContractName });
55+
}
56+
57+
return true;
58+
}
59+
60+
private (Type exportType, Type? metadataType) GetContractType(Type contractType, bool importMany)
61+
{
62+
if (importMany && contractType.IsConstructedGenericType)
63+
{
64+
if (contractType.GetGenericTypeDefinition() == typeof(IList<>)
65+
|| contractType.GetGenericTypeDefinition() == typeof(ICollection<>)
66+
|| contractType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
67+
{
68+
contractType = contractType.GenericTypeArguments[0];
69+
}
70+
}
71+
72+
if (contractType.IsConstructedGenericType)
73+
{
74+
if (contractType.GetGenericTypeDefinition() == typeof(Lazy<>))
75+
{
76+
return (contractType.GenericTypeArguments[0], null);
77+
}
78+
else if (contractType.GetGenericTypeDefinition() == typeof(Lazy<,>))
79+
{
80+
return (contractType.GenericTypeArguments[0], contractType.GenericTypeArguments[1]);
81+
}
82+
else
83+
{
84+
throw new NotSupportedException();
85+
}
86+
}
87+
88+
throw new NotSupportedException();
89+
}
90+
}
91+
}
92+
}

CommandLine/CodeConv.Shared/MSBuildWorkspaceConverter.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,7 @@ private static MSBuildWorkspace CreateWorkspace(Dictionary<string, string> build
119119
MSBuildLocator.RegisterInstance(instance);
120120
AppDomain.CurrentDomain.UseVersionAgnosticAssemblyResolution();
121121
}
122-
lock (WorkspaceFactory.WorkspaceCreationLock) {
123-
return MSBuildWorkspace.Create(buildProps);
124-
}
122+
return MSBuildWorkspace.Create(buildProps, ThreadSafeWorkspaceHelper.HostServices);
125123
}
126124

127125
public void Dispose()

0 commit comments

Comments
 (0)