-
Notifications
You must be signed in to change notification settings - Fork 636
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
Replace reflection only load context with shared metadataLoadContext for publishing packages. Solution 1. #14576
Conversation
remove test only code one actual test failure
dyn files still broken.
rename method
|
var resolver = new PathAssemblyResolver(assemblyPaths); | ||
var mlc = new MetadataLoadContext(resolver); | ||
|
||
//TODO for now, we leak the mlc, but it's unlikely users will publish |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how are we leaking the mlc ?
is that why we are not cleaning up the mlc ?
There might be a lot of assemblies loaded from the runtime libs....are you saying it is not probable that a user publishes multiple times in the same session ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, I am not disposing the mlc here - its assemblies seem to live as package dependencies for quite some time. If we moved to only using one MLC per session I think there would be a smaller chance of any appreciable memory leak - ie it would be more like using the ReflectionLoadContext.
Yes I think it's unlikely that a user publishes multiple times in a single session.
At this point it's not clear to me that there is ever a good time to dispose these mlcs.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens when you dispose the mlc at the end of this function? Is it holding onto assembly references that are also owned by the package?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assemblies loaded in the mlc seem to be stored in the package view model
As Mike said, it is not obvious how long they are kept around
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we try to use a single mlc, would that increase the chance of conflicts that were not there with reflectionOnly ?
Also, seems like mlc is a little more permissive than reflectionOnly. As an example I think BadImageFormatException exceptions will not eever be thrown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should test this feature as much as possible (in revit and sandbox) since we do not know all the implications yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pinzart90 I've tested your concern, and even though the docs don't mention it, it throws the same exception when loading a native assembly.
Also I have written a test for it.
src/DynamoPackages/PackageLoader.cs
Outdated
//where reflection only context would throw FileLoadException | ||
//we try to maintain that legacy behavior for Dynamo 3.0. | ||
if (mlc.GetAssemblies().Select(x => x.GetName() == assemName).Any()) { | ||
throw new FileLoadException(filename); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this happen ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it happens in the tests that I have removed the failure
category from in this PR. They rely on these exceptions being thrown for .dlls with the same mvid.
the metaDataLoadContext will throw an exception by default for assemblies with the same name with different mvids - but apparently the behavior for reflection only load context was different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made this smarter I think.
src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs
Outdated
Show resolved
Hide resolved
src/DynamoCoreWpf/ViewModels/PackageManager/PublishPackageViewModel.cs
Outdated
Show resolved
Hide resolved
@@ -17,7 +18,7 @@ namespace DynamoCoreWpfTests | |||
public class PublishPackageViewModelTests: DynamoViewModelUnitTest | |||
{ | |||
|
|||
[Test, Category("Failure")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to be passing... this test is unrelated to this task so if it fails on CI I will just revert this.
var runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll"); | ||
// Create PathAssemblyResolver that can resolve assemblies using the created list. | ||
var resolver = new PathAssemblyResolver(runtimeAssemblies); | ||
return new MetadataLoadContext(resolver); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we no longer doing this:
//and all the package assemblies.
foreach (var assemblyFile in pkg.EnumerateAssemblyFilesInPackage())
{
assemblyPaths.Add(assemblyFile);
}
// Create PathAssemblyResolver that can resolve assemblies using the created list.
var resolver = new PathAssemblyResolver(assemblyPaths);
var mlc = new MetadataLoadContext(resolver);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I found that it does not appear to be necessary to add the assemblies to the MLC as we pass the full path to the file we want to load.
Additionally, we only need the dependencies loaded if want to inspect those types - but we don't do much with the assemblies as far as I can tell except for accessing the location and assembly name.
In an MLC the dependencies are not required to load the assembly AFAIK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not see any immediate need to use those types...but as soon as we do ...we will have exceptions thrown. THese assemblies are not exposed publicly right ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, and in addition this would have caused the same issue with ReflectionOnlyLoadContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actually, the PublicPackageViewModel.Assemblies
property is public and so is the class. These classes are only used in the UI layer though AFAIK and are not easily accessible from anywhere but our views, ie not from node or extensions without clients hacking around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, I'm a bit confused after reading this, just trying to understand it better. The docs seem to say that the resolver that is passed to the mlc resolves assemblies that are dependencies of the assembly being loaded (by the mlc). I can understand that this should include runtime assemblies, which most likely the assembly being loaded (for inspection) has a dependency on but won't it also have dependencies most likely on the other helper assemblies that are in the same package folder itself for the package that's being published? Or are you saying that since the assembly being loaded and the other assemblies are in the same location, that isn't required? Maybe I'm missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, the binary we are loading very likely will have dependencies on other binaries in the package - but it doesn't matter. It does not matter because the MLC won't fail to load a binary simply because a dependency is not loaded.
It only becomes an issue when you try to inspect a type and that type has a dependency on an assembly the resolver cannot resolve.
But in our case, as far as I can tell, we don't do any inspection, we just look at the assembly name and location... we're just using the MLC to do some sanity checking that the assembly is a real managed assembly.
ReflectionLoadContext also behaved like this, except instead of a pathresolver, you had to implement the ReflectionOnlyAssemblyResolve
which we did not do.
//if loading the assembly did not actually add a new assembly to the MLC | ||
//then we've loaded it already, and our current behavior is to | ||
//disable publish when a package contains the same assembly twice. | ||
if (mlcAssemblies2.Count() == mlcAssemblies.Count()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this the best way to check if the assembly has been loaded successfully ?
assem = mlc.LoadFromAssemblyPath(filename);
assem != null is not good enough ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not checking if the assembly has been loaded successfully, I am checking if it had been loaded already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
couple of questions then LGTM
passed: |
@@ -99,7 +100,34 @@ public void CanPublishLateInitializedJsonCustomNode() | |||
} | |||
|
|||
|
|||
[Test, Category("Failure")] | |||
[Test] | |||
public void NewPackageDoesNotThrow_NativeBinaryIsAddedAsAdditionalFile_NotBinary() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the dependencies field in the pkg.json being empty indicate that the native assembly is not added as a dependent binary but as an additional file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the dependencies field points to other packages
Purpose
ReflectionOnlyLoadContext is no longer supported in net6. We use it when publishing packages to allow authors to add and remove dependencies of different versions of assemblies since those loads would usually fail if loaded in the same context as the running application.
A lot of this can be improved later when/if we move to assembly load contexts per package - but we'll need to do that when we have more time.
For now this tries to maintain similar behavior to the legacy behavior - it's not exact as MLC do not behave exactly like ReflectionOnlyLoadContexts -
This PR uses one shared static MLC, analogous to the shared reflection only load context that lived for application lifetime.
This PR also adds a new dependency on version 6 of the meta data load context package.
Questions for the team:
Declarations
Check these if you believe they are true
*.resx
filesRelease Notes
Move from ReflectionOnlyLoadContext to MetadataLoadContext for publishing packages.