Skip to content

Dependencies isolation bug in AssemblyLoadContext #87578

@hyperion-cs

Description

@hyperion-cs

Description

If you meet the following conditions:

  1. You use AssemblyLoadContext to dynamically load modules (.dll);
  2. The parent project (entry point) uses a third-party nuget package X;
  3. One of the module projects (which are loaded in step 1) uses a nuget package which in turn has an internal dependency on the nuget package X (i.e. the same one used in step 2).

Then the dependency isolation of dynamically loaded modules is broken. It contains TWO references to package X in its context of loaded assemblies. Thus, further work with this package is impossible, because we will get exceptions like "A cannot be cast to A" at any attempt to access the package's functionality. The text of the exception may change depending on the conditions (e.g., to "method does not exist"), but the meaning remains the same.

This seems to be a common enough case that it should be fixed immediately.

Reproduction Steps

I have created a minimal project that reproduces the problem:
https://github.com/hyperion-cs/net-assemblyloadcontext-isolation-problem

Csla version 7.0.0 is used as the problematic third-party nuget package. However, this is not crucial. ANY nuget package that has internal dependencies matching the parent (EntryPoint) project can be used in the same way. For example, Telegram.Bots.Extensions.Polling version 5.9.0 may also suit.

As you can notice, after building and running the project you will get an exception of the "A cannot be cast to A" kind. However, you only need to remove the reference to the third-party nuget package from NET_AssemblyLoadContext_IsolationProblem.Lib.csproj:

- <PackageReference Include="Csla" Version="7.0.0" />

And also by removing using to it from LibClass.cs:

- using Csla;

Then everything will definitely work as expected (of course, do NOT forget to restore nuget packages, clean up the libext folder in the Debug output folder of the EntryPoint project (it should not contain Csla.dll, etc.), and rebuild the entire solution). Note that after the build, AfterBuild is used to copy the extension files.

Expected behavior

Program output:

LibClass: I did something!
Done.

Actual behavior

The throwing out of the exception:

[A]Microsoft.Extensions.Logging.Logger`1[NET_AssemblyLoadContext_IsolationProblem.LibBase.ILib] cannot be cast to
[B]Microsoft.Extensions.Logging.Logger`1[NET_AssemblyLoadContext_IsolationProblem.LibBase.ILib].
Type A originates from 'Microsoft.Extensions.Logging.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' in the context 'Default' at location '/Users/***/Projects/NET_AssemblyLoadContext_IsolationProblem/net-assemblyloadcontext-isolation-problem/NET_AssemblyLoadContext_IsolationProblem.EntryPoint/bin/Debug/net7.0/Microsoft.Extensions.Logging.Abstractions.dll'.
Type B originates from 'Microsoft.Extensions.Logging.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' in the context '"" NET_AssemblyLoadContext_IsolationProblem.EntryPoint.ProblemLoadContext #0' at location '/Users/***/Projects/NET_AssemblyLoadContext_IsolationProblem/net-assemblyloadcontext-isolation-problem/NET_AssemblyLoadContext_IsolationProblem.EntryPoint/bin/Debug/net7.0/libext/Microsoft.Extensions.Logging.Abstractions.dll'.

Regression?

This problem seems to have existed since the development of AssemblyLoadContext.

Known Workarounds

As a temporary solution, you can track in ProblemLoadContext.Load(...) the re-loading of problematic dependencies and skip them. But this is a very dirty and bad solution. Example:

// We can also compare the version, etc.
if(assemblyName.Name = "Microsoft.Extensions.Logging.Abstractions")
{
    return null; // Skip loading.
}

Configuration

.NET Version: 7.0
OS: macOS 13.4
Architecture: x64

The same problem in Linux/Windows, so the current configuration is not so important.

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions