Skip to content

Commit

Permalink
Added isolated assembly loading for issue MapsterMapper#714
Browse files Browse the repository at this point in the history
  • Loading branch information
boylec committed Jun 17, 2024
1 parent 09a0e92 commit e00c2bb
Show file tree
Hide file tree
Showing 4 changed files with 339 additions and 102 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dotnet-buildandtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: 8.0.x
dotnet-version: 8.x.x
- name: Show dotnet version
run: |
dotnet --list-sdks
Expand Down
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 e00c2bb

Please sign in to comment.