Skip to content

Commit

Permalink
Merge pull request #1392 from glopesdev/issue-1383
Browse files Browse the repository at this point in the history
Rewrite bootstrapper logic to avoid relying on AppDomain
  • Loading branch information
glopesdev authored Jun 12, 2023
2 parents 404b092 + 4edc270 commit 83f8581
Show file tree
Hide file tree
Showing 16 changed files with 439 additions and 189 deletions.
5 changes: 4 additions & 1 deletion Bonsai.Configuration/CommandLineParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ public void Parse(string[] args)
{
var argument = string.Empty;
if (options.Length > 1) argument = options[1];
else if (args.Length > i + 1) argument = args[++i];
else if (args.Length > i + 1 && !args[i + 1].StartsWith(CommandPrefix))
{
argument = args[++i];
}
command(argument);
}
}
Expand Down
5 changes: 3 additions & 2 deletions Bonsai.Editor/TypeDefinitionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ static CodeTypeReference GetTypeReference(Type type, HashSet<string> importNames

static CodeAttributeDeclaration GetAttributeDeclaration(CustomAttributeData attribute, HashSet<string> importNamespaces)
{
importNamespaces.Add(attribute.AttributeType.Namespace);
var attributeName = attribute.AttributeType.Name;
var attributeType = attribute.Constructor.DeclaringType;
importNamespaces.Add(attributeType.Namespace);
var attributeName = attributeType.Name;
var suffix = attributeName.LastIndexOf(nameof(Attribute));
attributeName = suffix >= 0 ? attributeName.Substring(0, suffix) : attributeName;
var reference = new CodeTypeReference(attributeName);
Expand Down
28 changes: 28 additions & 0 deletions Bonsai.Editor/TypeVisualizerDescriptor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Reflection;

namespace Bonsai.Editor
{
Expand All @@ -8,6 +9,33 @@ public class TypeVisualizerDescriptor
public string VisualizerTypeName;
public string TargetTypeName;

public TypeVisualizerDescriptor(CustomAttributeData attribute)
{
if (attribute.ConstructorArguments.Count > 0)
{
var constructorArgument = attribute.ConstructorArguments[0];
if (constructorArgument.ArgumentType.AssemblyQualifiedName == typeof(string).AssemblyQualifiedName)
{
VisualizerTypeName = (string)constructorArgument.Value;
}
else VisualizerTypeName = ((Type)constructorArgument.Value).AssemblyQualifiedName;
}

for (int i = 0; i < attribute.NamedArguments.Count; i++)
{
var namedArgument = attribute.NamedArguments[i];
switch (namedArgument.MemberName)
{
case nameof(TypeVisualizerAttribute.TargetTypeName):
TargetTypeName = (string)namedArgument.TypedValue.Value;
break;
case nameof(TypeVisualizerAttribute.Target):
TargetTypeName = ((Type)namedArgument.TypedValue.Value).AssemblyQualifiedName;
break;
}
}
}

public TypeVisualizerDescriptor(TypeVisualizerAttribute typeVisualizer)
{
TargetTypeName = typeVisualizer.TargetTypeName;
Expand Down
86 changes: 75 additions & 11 deletions Bonsai/AppResult.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,97 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using Newtonsoft.Json;

namespace Bonsai
{
static class AppResult
{
public static TResult GetResult<TResult>(AppDomain domain)
static readonly JsonSerializer Serializer = JsonSerializer.CreateDefault();
static Dictionary<string, string> Values;

public static IDisposable OpenWrite(NamedPipeClientStream stream)
{
Values = new();
if (stream == null)
{
return EmptyDisposable.Instance;
}

stream.Connect();
var writer = new StreamWriter(stream);
return new AnonymousDisposable(() =>
{
try
{
Serializer.Serialize(writer, Values);
writer.Flush();
try { stream.WaitForPipeDrain(); }
catch (NotSupportedException) { }
}
finally { writer.Close(); }
});
}

public static IDisposable OpenRead(Stream stream)
{
var resultHolder = (ResultHolder<TResult>)domain.CreateInstanceAndUnwrap(
typeof(ResultHolder<TResult>).Assembly.FullName,
typeof(ResultHolder<TResult>).FullName);
return resultHolder.Result;
using var reader = new JsonTextReader(new StreamReader(stream));
Values = Serializer.Deserialize<Dictionary<string, string>>(reader);
return EmptyDisposable.Instance;
}

public static TResult GetResult<TResult>()
{
if (Values != null && Values.TryGetValue(typeof(TResult).FullName, out string value))
{
if (typeof(TResult).IsEnum)
{
return (TResult)Enum.Parse(typeof(TResult), value);
}

return (TResult)Convert.ChangeType(value, typeof(TResult));
}

return default;
}

public static void SetResult<TResult>(TResult result)
{
ResultHolder<TResult>.ResultValue = result;
if (Values == null)
{
throw new InvalidOperationException("No output stream has been opened for writing.");
}

Values[typeof(TResult).FullName] = result.ToString();
}

class AnonymousDisposable : IDisposable
{
private Action disposeAction;

public AnonymousDisposable(Action dispose)
{
disposeAction = dispose;
}

public void Dispose()
{
Interlocked.Exchange(ref disposeAction, null)?.Invoke();
}
}

class ResultHolder<TResult> : MarshalByRefObject
class EmptyDisposable : IDisposable
{
public static TResult ResultValue;
public static readonly EmptyDisposable Instance = new();

public ResultHolder()
private EmptyDisposable()
{
}

public TResult Result
public void Dispose()
{
get { return ResultValue; }
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions Bonsai/Bonsai.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<ApplicationManifest>App.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Reflection.MetadataLoadContext" Version="7.0.0" PrivateAssets="all" />
<PackageReference Include="ILRepack.MSBuild.Task" Version="2.0.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down Expand Up @@ -44,6 +45,13 @@
<WorkingDirectory>$(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework)</WorkingDirectory>
</PropertyGroup>
<ItemGroup>
<InputAssemblies Include="System.Buffers.dll" />
<InputAssemblies Include="System.Memory.dll" />
<InputAssemblies Include="System.Numerics.Vectors.dll" />
<InputAssemblies Include="System.Collections.Immutable.dll" />
<InputAssemblies Include="System.Reflection.Metadata.dll" />
<InputAssemblies Include="System.Reflection.MetadataLoadContext.dll" />
<InputAssemblies Include="System.Runtime.CompilerServices.Unsafe.dll" />
<InputAssemblies Include="Bonsai.Configuration.dll" />
<InputAssemblies Include="Bonsai.NuGet.dll" />
<InputAssemblies Include="Bonsai.NuGet.Design.dll" />
Expand Down
37 changes: 14 additions & 23 deletions Bonsai/DependencyInspector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ namespace Bonsai
{
sealed class DependencyInspector : MarshalByRefObject
{
readonly ScriptExtensions scriptEnvironment;
readonly PackageConfiguration packageConfiguration;
const string XsiAttributeValue = "http://www.w3.org/2001/XMLSchema-instance";
const string WorkflowElementName = "Workflow";
const string ExpressionElementName = "Expression";
Expand All @@ -26,14 +24,7 @@ sealed class DependencyInspector : MarshalByRefObject
const string TypeAttributeName = "type";
const char AssemblySeparator = ':';

public DependencyInspector(PackageConfiguration configuration)
{
ConfigurationHelper.SetAssemblyResolve(configuration);
scriptEnvironment = new ScriptExtensions(configuration, null);
packageConfiguration = configuration;
}

IEnumerable<VisualizerDialogSettings> GetVisualizerSettings(VisualizerLayout root)
static IEnumerable<VisualizerDialogSettings> GetVisualizerSettings(VisualizerLayout root)
{
var stack = new Stack<VisualizerLayout>();
stack.Push(root);
Expand All @@ -52,8 +43,10 @@ IEnumerable<VisualizerDialogSettings> GetVisualizerSettings(VisualizerLayout roo
}
}

Configuration.PackageReference[] GetWorkflowPackageDependencies(string[] fileNames)
static Configuration.PackageReference[] GetPackageDependencies(string[] fileNames, PackageConfiguration configuration)
{
using var context = LoaderResource.CreateMetadataLoadContext(configuration);
var scriptEnvironment = new ScriptExtensions(configuration, null);
var assemblies = new HashSet<Assembly>();
foreach (var path in fileNames)
{
Expand All @@ -76,7 +69,7 @@ Configuration.PackageReference[] GetWorkflowPackageDependencies(string[] fileNam
var assemblyName = includePath.Split(new[] { AssemblySeparator }, 2)[0];
if (!string.IsNullOrEmpty(assemblyName))
{
var assembly = Assembly.Load(assemblyName);
var assembly = context.LoadFromAssemblyName(assemblyName);
assemblies.Add(assembly);
}
}
Expand All @@ -92,7 +85,7 @@ Configuration.PackageReference[] GetWorkflowPackageDependencies(string[] fileNam
if (File.Exists(layoutPath))
{
var visualizerMap = new Lazy<IDictionary<string, Type>>(() =>
TypeVisualizerLoader.GetVisualizerTypes(packageConfiguration)
TypeVisualizerLoader.GetVisualizerTypes(configuration)
.Select(descriptor => descriptor.VisualizerTypeName).Distinct()
.Select(typeName => Type.GetType(typeName, false))
.Where(type => type != null)
Expand All @@ -115,16 +108,16 @@ Configuration.PackageReference[] GetWorkflowPackageDependencies(string[] fileNam
}
}

var packageMap = packageConfiguration.GetPackageReferenceMap();
var packageMap = configuration.GetPackageReferenceMap();
var dependencies = assemblies.Select(assembly =>
packageConfiguration.GetAssemblyPackageReference(assembly.GetName().Name, packageMap))
configuration.GetAssemblyPackageReference(assembly.GetName().Name, packageMap))
.Where(package => package != null);
if (File.Exists(scriptEnvironment.ProjectFileName))
{
dependencies = dependencies.Concat(
from id in scriptEnvironment.GetPackageReferences()
where packageConfiguration.Packages.Contains(id)
select packageConfiguration.Packages[id]);
where configuration.Packages.Contains(id)
select configuration.Packages[id]);
}

return dependencies.ToArray();
Expand All @@ -134,14 +127,12 @@ public static IObservable<PackageDependency> GetWorkflowPackageDependencies(stri
{
if (configuration == null)
{
throw new ArgumentNullException("configuration");
throw new ArgumentNullException(nameof(configuration));
}

return Observable.Using(
() => new LoaderResource<DependencyInspector>(configuration),
resource => from dependency in resource.Loader.GetWorkflowPackageDependencies(fileNames).ToObservable()
let versionRange = new VersionRange(NuGetVersion.Parse(dependency.Version), includeMinVersion: true)
select new PackageDependency(dependency.Id, versionRange));
return from dependency in GetPackageDependencies(fileNames, configuration).ToObservable()
let versionRange = new VersionRange(NuGetVersion.Parse(dependency.Version), includeMinVersion: true)
select new PackageDependency(dependency.Id, versionRange);
}
}
}
3 changes: 2 additions & 1 deletion Bonsai/Launcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ internal static int LaunchWorkflowEditor(
if (scriptExtensions.DebugScripts) editorFlags |= EditorFlags.DebugScripts;
AppResult.SetResult(editorFlags);
AppResult.SetResult(mainForm.FileName);
return (int)mainForm.EditorResult;
AppResult.SetResult((int)mainForm.EditorResult);
return Program.NormalExitCode;
}
finally { cancellation.Cancel(); }
}
Expand Down
27 changes: 7 additions & 20 deletions Bonsai/LoaderResource.cs
Original file line number Diff line number Diff line change
@@ -1,30 +1,17 @@
using Bonsai.Configuration;
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Bonsai
{
class LoaderResource<TLoader> : IDisposable where TLoader : MarshalByRefObject
static class LoaderResource
{
AppDomain reflectionDomain;

public LoaderResource(PackageConfiguration configuration)
{
var currentEvidence = AppDomain.CurrentDomain.Evidence;
var setupInfo = AppDomain.CurrentDomain.SetupInformation;
reflectionDomain = AppDomain.CreateDomain("ReflectionOnly", currentEvidence, setupInfo);
Loader = (TLoader)reflectionDomain.CreateInstanceAndUnwrap(
typeof(TLoader).Assembly.FullName,
typeof(TLoader).FullName,
false, (BindingFlags)0, null,
new[] { configuration }, null, null);
}

public TLoader Loader { get; private set; }

public void Dispose()
public static MetadataLoadContext CreateMetadataLoadContext(PackageConfiguration configuration)
{
AppDomain.Unload(reflectionDomain);
var runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll");
var resolver = new PackageAssemblyResolver(configuration, runtimeAssemblies);
return new MetadataLoadContext(resolver);
}
}
}
50 changes: 50 additions & 0 deletions Bonsai/PackageAssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Bonsai.Configuration;

namespace Bonsai
{
internal class PackageAssemblyResolver : PathAssemblyResolver
{
public PackageAssemblyResolver(PackageConfiguration configuration, IEnumerable<string> assemblyPaths)
: base(assemblyPaths)
{
Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
ConfigurationRoot = ConfigurationHelper.GetConfigurationRoot(configuration);
}

private PackageConfiguration Configuration { get; }

private string ConfigurationRoot { get; }

public override Assembly Resolve(MetadataLoadContext context, AssemblyName assemblyName)
{
var assembly = base.Resolve(context, assemblyName);
if (assembly != null) return assembly;

var assemblyLocation = Configuration.GetAssemblyLocation(assemblyName.Name);
if (assemblyLocation != null)
{
if (assemblyLocation.StartsWith(Uri.UriSchemeFile) &&
Uri.TryCreate(assemblyLocation, UriKind.Absolute, out Uri uri))
{
return context.LoadFromAssemblyPath(uri.LocalPath);
}

if (!Path.IsPathRooted(assemblyLocation))
{
assemblyLocation = Path.Combine(ConfigurationRoot, assemblyLocation);
}

if (File.Exists(assemblyLocation))
{
return context.LoadFromAssemblyPath(assemblyLocation);
}
}

return null;
}
}
}
Loading

0 comments on commit 83f8581

Please sign in to comment.