dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.97k stars 4.66k forks source link

System.Memory referencing old version of System.Runtime.CompilerService.Unsafe and GAC #96533

Closed bbronisz closed 8 months ago

bbronisz commented 8 months ago

This is similary issue to #53758 but with GAC in the mix.

Given .NET 4.7.2 library that is referencing both: System.Memory Nuget package 4.5.5 and System.Runtime.CompilerService.Unsafe 4.7.1 (I know it's not the latest but it doesn't matter in this case). And all the dlls mentioned are in GAC. When code from our library tries to use AsSpan from System.Memory library we get error FileNotFoundException: Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.. I know that to fix this is to add assembly redirects but:

So I have a question: what is the proper way to fix this?

ghost commented 8 months ago

Tagging subscribers to this area: @dotnet/area-infrastructure-libraries See info in area-owners.md if you want to be subscribed.

Issue Details
This is similary issue to #53758 but with GAC in the mix. Given .NET 4.7.2 library that is referencing both: `System.Memory` Nuget package 4.5.5 and `System.Runtime.CompilerService.Unsafe` 4.7.1 (I know it's not the latest but it doesn't matter in this case). And all the dlls mentioned are in GAC. When code from our library tries to use `AsSpan` from `System.Memory` library we get error `FileNotFoundException`: `Could not load file or assembly 'System.Runtime.CompilerServices.Unsafe, Version=4.0.4.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.`. I know that to fix this is to add assembly redirects but: - this is SharePoint environment so we don't control all the config files - our library can be used by any client's custom code/application which config files we also don't control. So I have a question: what is the proper way to fix this? - Should I deploy `System.Runtime.CompilerService.Unsafe` version 4.0.4.1 into GAC? But I've seen it mentioned [here](https://github.com/dotnet/runtime/issues/38782#issuecomment-804466896) that this can cause unification issues. Which sounds really bad but I don't know much about it. - [Here](https://github.com/dotnet/runtime/issues/38782#issuecomment-669577017) it was mentioned that binding redirects are `the unfortunate requirement of working on .NETFramework and using nuget packages`. But what about GAC? When I have to work with it and don't control the config files. Maybe you could create [publisher policy](https://learn.microsoft.com/en-us/dotnet/framework/configure-apps/how-to-create-a-publisher-policy) Nuget packages that could be used to help with GAC assembly redirect?
Author: bbronisz
Assignees: -
Labels: `area-Infrastructure-libraries`
Milestone: -
ViktorHofer commented 8 months ago

cc @ericstj

ericstj commented 8 months ago

cc @jkotas

The GAC here is somewhat of a red-herring. The problem is that you can't control the bindingRedirects for your app model.

You do need your application to provide redirects. .NETFramework app models must expose some means for specifying bindingRedirects to work with the NuGet ecosystem. If you have an app model that doesn't and you can't ask consumers to provide redirects - you can isolate your code in an appdomain where you provide the redirects.

Publisher policy assemblies could help, but you'd need the entire nuget ecosystem to provide those. Essentially every NuGet package would need to provide a policy assembly just in case someone wanted to install it in the GAC and had a component using an older version of that package. Additionally - when you install publisher policy you are making a choice for everyone on the machine about redirects which might not be the right choice for them. What if a new version of a package exposed a problem in their application?

jkotas commented 8 months ago

Some libraries try to solve this problem by subscribing to AppDomain.AssemblyResolve event during the library initialization and loading the dependencies using a heuristic specific to their deployment environment. Of course, it comes with its own set of challenges (e.g. two different heuristics implemented by two different libraries can collide), but it gives the local library full control at least. https://github.com/dotnet/arcade/blob/4f1ce995de2da9a279ec3c9c2168ea8937bfa012/src/Common/Internal/AssemblyResolution.cs is an example of such AssemblyResolve event. I do not expect that this exact implementation would work for your case, I am just sharing it as an example of possible approach.

Otherwise, I do not have more to add to what Eric said. Binding redirects are the proper solution for the problem. There are no fully reliable alternatives.

bbronisz commented 8 months ago

I didn't mention that earlier but the library mentioned is providing API that can be used by clients' code. So isolating our code into separate appdomain is practically impossible.

To be honest I don't need all the packages to provide publisher policies. Some provide all the code, together with SNK so if need be we can generate it ourselves. As Nick Craver mentioned in his blog: those are most often System.* libraries that are causing these problems.

I understand that we're making the choice for everyone on the machine but how different is it from adding binding redirects to IIS site's web.config and SharePoint Timer job's owstimer.config? There's not much left that should be running on such server. Or is it also too "global" and should be avoided? Cause there could be more libraries running in those processes that also depend on these libraries and different versions.

From what I see SharePoint model is not compatible with Nuget packages ;( and should be avoided...

jkotas commented 8 months ago

There's not much left that should be running on such server.

It is hard to tell what else can be running on the server unless you have 100% control over it. For example, Powershell scripts or machine management utilities can be running on the server.

bbronisz commented 8 months ago

Yes, I understand that but this is basically the same as adding binding redirect to web.config because we don't control what other dlls could be loaded in that process and what are their dependencies.

Last question if I may: how bad is it if several System.Runtime.CompilerServices.Unsafe libraries are installed in GAC? Because I've seen it "in the wild" and now I'm wondering what are those "unification issues" that it can cause. Could you point to some docs about it?

jkotas commented 8 months ago

Let's say that there is an application that depends on System.Runtime.CompilerServices.Unsafe. The application has multiple assemblies with this dependency, each referencing different version. Let's say assembly A depends on S.R.CS.Unsafe 4.0 and assembly B depends on S.R.CS.Unsafe 4.1. The application does not use binding redirects for some reason and loads System.Runtime.CompilerServices.Unsafe using AppDomain.AssemblyResolve instead. The logic in the AppDomain.AssemblyResolve ends up loading S.R.CS.Unsafe 4.2 that is bundled with the application, for all references.

Now, let's see what happens if you install S.R.CS.Unsafe 4.0 into the GAC. If the application exercises the codepath that hits S.R.CS.Unsafe 4.0 first, the reference is going to be satisfied by S.R.CS.Unsafe 4.0 installed in the GAC and the application AppDomain.AssemblyResolve won't get a chance to run. The app-local S.R.CS.Unsafe may get loaded later as well. Some or all parts of the application are going to run against older version of S.R.CS.Unsafe that they are meant to. The S.R.CS.Unsafe version is no longer unified to the version bundled with the app.

bbronisz commented 8 months ago

Thank you for that clarification, but.. :) What if System.Runtime.CompilerServices.Unsafe version 4.0 and 4.1 and 4.2 are all in GAC?

jkotas commented 8 months ago

What if System.Runtime.CompilerServices.Unsafe version 4.0 and 4.1 and 4.2 are all in GAC?

The application from my example is going to load 4.0 and 4.1 from the GAC. The application's AppDomain.AssemblyResolve that would unify both references to 4.2 does not get a chance to run.

bbronisz commented 8 months ago

Thanks again. So this is the unification issue that Eric StJohn mentioned here?

jkotas commented 8 months ago

I think so.

bbronisz commented 8 months ago

Thank you very much :).