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

[main] Fix LoaderAllocator computation for a generic type instance #111706

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

alexey-zakharov
Copy link
Contributor

Purpose of this PR

Improve logic of computing LoaderAllocator for a generic type instance to avoid AssemblyLoadContext leaks.
Resolves #111611

Problem:
ClassLoader::ComputeLoaderModuleWorker uses a LoaderAllocator creation number to determine the latest LoaderAllocator to place a class in. However the method does not account the number of the type definition itself when calculating the latest number and this can lead into a situation when the generic instance is put into the older LoaderAllocator.

E.g. if we have type ClassA from AssemblyLoadContext A with number 2 and type ClassB from AssemblyLoadContext B with number 5 the resulting loader allocator for the type ClassB would correspond to AssemblyLoadContext A. If ClassB contains a static, such static would be then placed under the LoaderAllocator A, preventing AssemblyLoadContext B from unloading.

Proposed solution:
The PR updates the ComputeLoaderModuleWorker logic to account for the LoaderAllocator of a defining type, so we use it if it has the latest creation number.

(cherry picked from commit b3a73f3)

@alexey-zakharov
Copy link
Contributor Author

cc @davidwrighton

I would be also happy to add a test for this case - which suite should I use for it? StaticsUnloaded.cs?

@jkotas jkotas requested a review from davidwrighton January 22, 2025 15:20
pLoaderModule = pDefinitionModule;
}
// Use the definition module as the loader module by default
Module *pLoaderModule = pDefinitionModule;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

if pDefinitionModule != null we effectively fall through to it if it is collectible or not at the end, so I've decided to remove the if block

…r allocator of a generic type instance

(cherry picked from commit b3a73f3)
@alexey-zakharov alexey-zakharov force-pushed the dotnet-upstream/fix-computeloaderallocator branch from 5d15fef to 8aa00c8 Compare January 22, 2025 17:09
Copy link
Member

@davidwrighton davidwrighton left a comment

Choose a reason for hiding this comment

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

As you note, a test would be helpful. However, since the behavior of this sort of computation is not actually specified and may change in the future without warning, I'd rather not merge it in with existing tests, and would prefer this to be a separate test (and for the test to have some comments in it indicating that failure is reasonable when running on different runtimes). I'm pretty flexible on naming though, as I don't have a strong opinion. I'm notoriously terrible at naming stuff.

src/coreclr/vm/clsload.cpp Outdated Show resolved Hide resolved
@alexey-zakharov
Copy link
Contributor Author

would prefer this to be a separate test (and for the test to have some comments in it indicating that failure is reasonable when running on different runtimes)

I've looked at the best location for the test and found that there is src/libraries/System.Runtime.Loader/tests/CollectibleAssemblyLoadContextTest.cs suite which tests ALC unloadability and seems like a perfect fit for the issue - would it be acceptable to have a test there?

  • added Unload_TwoCollectibleWithOneAssemblyAndOneInstanceReferencingAnotherThroughGenericStatic test which tests the failing scenario
  • updated issue links since the mentioned issue is closed now

@@ -189,7 +185,7 @@ public static void DefaultAssemblyLoadContext_Properties()
Assert.Contains("\"Default\"", alc.ToString());
Assert.Contains("System.Runtime.Loader.DefaultAssemblyLoadContext", alc.ToString());
Assert.Contains(alc, AssemblyLoadContext.All);
Assert.Contains(Assembly.GetCallingAssembly(), alc.Assemblies);
Assert.Contains(typeof(int).Assembly, alc.Assemblies);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've updated the test because it was failing as the calling assembly for the test method is xunit assembly which is not in the Default ALC and I suppose is test runner implementation specific
CorLib assembly which is where type of int is is more stable check parameter imho

@alexey-zakharov
Copy link
Contributor Author

@davidwrighton I don't see any suspicious test failures - do you think the changes are good to go?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-AssemblyLoader-coreclr community-contribution Indicates that the PR has been added by a community member
Projects
None yet
2 participants