Skip to content

Commit

Permalink
Merge pull request #716 from True-Velocity/fix/isolated-assemblies
Browse files Browse the repository at this point in the history
Use assembly loading context to isolate loaded assemblies from AppDomain #714
  • Loading branch information
andrerav authored Jan 2, 2025
2 parents c62119d + b944d35 commit 1457dce
Show file tree
Hide file tree
Showing 3 changed files with 341 additions and 101 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,8 @@ project.lock.json
/src/.vs
/.vs
src/.idea

# VS Code settings
.vscode/launch.json
.vscode/settings.json
.vscode/tasks.json
87 changes: 87 additions & 0 deletions src/Mapster.Tool/DeferredDependencyAssemblyLoadContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;

namespace Mapster.Tool
{
//
// Summary:
// Used for loading an assembly and its dependencies in an isolated assembly load context but deferring the resolution of
// a subset of those assemblies to an already existing Assembly Load Context (likely the AssemblyLoadContext.Default
// context that is used by the runtime by default at startup)
public class DeferredDependencyAssemblyLoadContext : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver resolver;
private readonly ImmutableHashSet<string> deferredDependencyAssemblyNames;
private readonly AssemblyLoadContext deferToContext;

public DeferredDependencyAssemblyLoadContext(
string assemblyPath,
AssemblyLoadContext deferToContext,
params AssemblyName[] deferredDependencyAssemblyNames
)
{
// set up a resolver for the dependencies of this non-deferred assembly
resolver = new AssemblyDependencyResolver(assemblyPath);

// store all of the assembly simple names that should be deferred w/
// the sharing assembly context loader (and not resolved exclusively in this loader)
this.deferredDependencyAssemblyNames = deferredDependencyAssemblyNames
.Select(an => an.Name!)
.Where(n => n != null)
.ToImmutableHashSet();

// store a reference to the assembly load context that assembly resolution will be deferred
// to when on the deferredDependencyAssemblyNames list
this.deferToContext = deferToContext;

// load the non-deferred assembly in this context to start
Load(GetAssemblyName(assemblyPath));
}

protected override Assembly? Load(AssemblyName assemblyName)
{
if (assemblyName.Name == null)
{
return null;
}

// if the assembly to be loaded is also set to be deferrred (based on constructor)
// then first attempt to load it from the sharing assembly load context
if (deferredDependencyAssemblyNames.Contains(assemblyName.Name))
{
return deferToContext.LoadFromAssemblyName(assemblyName);
}

// all other loaded assemblies should be considered dependencies of the
// non-deferred dependency loaded in the constructor and should be loaded
// from its path (the AssemblyDepedencyResolver resolves dependency paths)
string? assemblyPath = resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath == null)
{
return null;
}

return LoadFromAssemblyPath(assemblyPath);
}

public static Assembly LoadAssemblyFrom(
string assemblyPath,
AssemblyLoadContext deferToContext,
params AssemblyName[] deferredDependencyAssemblyNames
)
{
DeferredDependencyAssemblyLoadContext loadContext =
new DeferredDependencyAssemblyLoadContext(
assemblyPath,
deferToContext,
deferredDependencyAssemblyNames
);
return loadContext.LoadFromAssemblyName(
new AssemblyName(Path.GetFileNameWithoutExtension(assemblyPath))
);
}
}
}
Loading

0 comments on commit 1457dce

Please sign in to comment.