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

Use assembly loading context to isolate loaded assemblies from AppDomain #714 #716

Merged
Merged
Show file tree
Hide file tree
Changes from 7 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
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