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

assembly isolation #14536

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
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
5 changes: 5 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<SolutionDir>D:\dev\dynamo\DynamoDS\src\</SolutionDir>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
using String = System.String;
using NotificationObject = Dynamo.Core.NotificationObject;
using Prism.Commands;
using System.Runtime.Loader;

namespace Dynamo.PackageManager
{
Expand Down Expand Up @@ -1453,7 +1454,7 @@ private void AddDllFile(string filename)
{
// we're not sure if this is a managed assembly or not
// we try to load it, if it fails - we add it as an additional file
var result = PackageLoader.TryLoadFrom(filename, out Assembly assem);
var result = PackageLoader.TryLoadFrom(AssemblyLoadContext.Default, filename, out Assembly assem);
if (result)
{
var assemName = assem.GetName().Name;
Expand Down
5 changes: 3 additions & 2 deletions src/DynamoPackages/Package.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
using Dynamo.Core;
using Dynamo.Exceptions;
using Dynamo.Graph.Nodes.CustomNodes;
Expand Down Expand Up @@ -362,7 +363,7 @@ internal void AddAssemblies(IEnumerable<PackageAssembly> assems)
/// I.E. Builtin Packages.
/// </summary>
/// <returns>The list of all node library assemblies</returns>
internal IEnumerable<PackageAssembly> EnumerateAndLoadAssembliesInBinDirectory()
internal IEnumerable<PackageAssembly> EnumerateAndLoadAssembliesInBinDirectory(AssemblyLoadContext alc)
{
var assemblies = new List<PackageAssembly>();

Expand All @@ -386,7 +387,7 @@ internal IEnumerable<PackageAssembly> EnumerateAndLoadAssembliesInBinDirectory()
if (shouldLoadFile)
{
// dll files may be un-managed, skip those
var result = PackageLoader.TryLoadFrom(assemFile.FullName, out assem);
var result = PackageLoader.TryLoadFrom(alc, assemFile.FullName, out assem);
if (result)
{
// IsNodeLibrary may fail, we store the warnings here and then show
Expand Down
23 changes: 19 additions & 4 deletions src/DynamoPackages/PackageLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
using DynamoPackages.Properties;
using DynamoUtilities;
using Dynamo.Models;
using Dynamo.Scheduler;
using System.Runtime.Loader;
using DynamoPackages;

namespace Dynamo.PackageManager
{
Expand Down Expand Up @@ -246,11 +249,20 @@ private void TryLoadPackageIntoLibrary(Package package)

List<Assembly> loadedNodeLibs = new List<Assembly>();
List<Assembly> failedNodeLibs = new List<Assembly>();


AssemblyLoadContext pkgLoadContext = AssemblyLoadContext.Default;
if (package.Header.node_libraries.Any())
{
var entryPoint = new AssemblyName(package.Header.node_libraries.First()).Name + ".dll";
pkgLoadContext = new PkgAssemblyLoadContext(package.Name + "@" + package.VersionName, Path.Combine(package.BinaryDirectory, entryPoint));
}

try
{
List<Assembly> blockedAssemblies = new List<Assembly>();
// Try to load node libraries from all assemblies
foreach (var assem in package.EnumerateAndLoadAssembliesInBinDirectory())
foreach (var assem in package.EnumerateAndLoadAssembliesInBinDirectory(pkgLoadContext))
{
if (assem.IsNodeLibrary)
{
Expand Down Expand Up @@ -327,6 +339,7 @@ private void TryLoadPackageIntoLibrary(Package package)
localPackages.FirstOrDefault(x => x.CustomNodeDirectory == e.InstalledPath);
OnConflictingPackageLoaded(originalPackage, package);

pkgLoadContext.Unload();
package.LoadState.SetAsError(e.Message);
}
catch (Exception e)
Expand All @@ -337,6 +350,8 @@ private void TryLoadPackageIntoLibrary(Package package)
DynamoServices.LoadLibraryEvents.OnLoadLibraryFailure(failureMessage, Properties.Resources.LibraryLoadFailureMessageBoxTitle);
}
package.LoadState.SetAsError(e.Message);
pkgLoadContext.Unload();

Log("Exception when attempting to load package " + package.Name + " from " + package.RootDirectory);
Log(e.GetType() + ": " + e.Message);
}
Expand Down Expand Up @@ -364,7 +379,7 @@ public void Load(Package package)
TryLoadPackageIntoLibrary(package);

var assemblies =
LocalPackages.SelectMany(x => x.EnumerateAndLoadAssembliesInBinDirectory().Where(y => y.IsNodeLibrary));
LocalPackages.SelectMany(x => x.EnumerateAndLoadAssembliesInBinDirectory(AssemblyLoadContext.Default).Where(y => y.IsNodeLibrary));
PackagesLoaded?.Invoke(assemblies.Select(x => x.Assembly));
}

Expand Down Expand Up @@ -773,11 +788,11 @@ internal static AssemblyLoadingState TryReflectionOnlyLoadFrom(string filename,
/// <param name="filename">The filename of a DLL</param>
/// <param name="assem">out Assembly - the passed value does not matter and will only be set if loading succeeds</param>
/// <returns>Returns true if success, false if BadImageFormatException (i.e. not a managed assembly)</returns>
internal static bool TryLoadFrom(string filename, out Assembly assem)
internal static bool TryLoadFrom(AssemblyLoadContext alc, string filename, out Assembly assem)
{
try
{
assem = Assembly.LoadFrom(filename);
assem = alc.LoadFromAssemblyPath(filename);
return true;
}
catch (FileLoadException e)
Expand Down
41 changes: 41 additions & 0 deletions src/DynamoPackages/PkgAssemblyLoadContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;

namespace DynamoPackages
{
internal class PkgAssemblyLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public PkgAssemblyLoadContext(string name, string pluginPath, bool unloadable = true) : base(name, unloadable)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somehow I think we'd also want to include the /extra folder, maybe we can use two AssemblyDependencyResolvers?


}

protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
var newAss = LoadFromAssemblyPath(assemblyPath);
var assContext = AssemblyLoadContext.GetLoadContext(newAss);
return newAss;
}

return null;
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}

return IntPtr.Zero;
}
}
}
17 changes: 11 additions & 6 deletions src/DynamoSandbox/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.Loader;
using System.Windows;

namespace DynamoSandbox
Expand All @@ -13,9 +14,8 @@ internal class Program

[STAThread]
public static void Main(string[] args)
{
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;

{
AssemblyLoadContext.Default.Resolving += Default_Resolving;
//Display a message box and exit the program if Dynamo Core is unresolved.
if (string.IsNullOrEmpty(DynamoCorePath)) return;

Expand All @@ -27,6 +27,11 @@ public static void Main(string[] args)
setup.RunApplication(app);
}

private static Assembly Default_Resolving(AssemblyLoadContext arg1, AssemblyName arg2)
{
return ResolveAssembly(arg1, new ResolveEventArgs(arg2.Name));
}

/// <summary>
/// Handler to the ApplicationDomain's AssemblyResolve event.
/// If an assembly's location cannot be resolved, an exception is
Expand All @@ -37,21 +42,21 @@ public static void Main(string[] args)
/// <param name="sender"></param>
/// <param name="args"></param>
/// <returns></returns>
public static Assembly ResolveAssembly(object sender, ResolveEventArgs args)
public static Assembly ResolveAssembly(AssemblyLoadContext arg1, ResolveEventArgs args)
{
var assemblyName = new AssemblyName(args.Name).Name + ".dll";

try
{
string assemblyPath = Path.Combine(DynamoCorePath, assemblyName);
if (File.Exists(assemblyPath))
return Assembly.LoadFrom(assemblyPath);
return arg1.LoadFromAssemblyPath(assemblyPath);

var assemblyLocation = Assembly.GetExecutingAssembly().Location;
var assemblyDirectory = Path.GetDirectoryName(assemblyLocation);

assemblyPath = Path.Combine(assemblyDirectory, assemblyName);
return (File.Exists(assemblyPath) ? Assembly.LoadFrom(assemblyPath) : null);
return (File.Exists(assemblyPath) ? arg1.LoadFromAssemblyPath(assemblyPath) : null);
}
catch (Exception ex)
{
Expand Down
Loading