-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Each MSBuild task should exist in its own AssemblyLoadContext #1754
Comments
The Sample.CSharp.csproj project is used to verify things work, but virtually all of the invoked code is already covered by unit tests. The app imports the targets from the Console project, which has a Debug-config-only project reference to the Generator.Build project, so that the targets end up alongside the tool and the task assembly for easier testing and debugging. The approach chosen was to separate the proxy generation into a console tool (`pgen`) that allows us to better isolate assembly loading issues which plague Rolyn-based code generators (since there is already a version of Roslyn in the process, from the compilers). This means performance will not be great, but allows us to move forward instead of investing more in workarounds. This should at some point be fixed by MSBuild itself (see dotnet/msbuild#1754). See also dotnet/roslyn#17705 for other binding related issues that won't exist at all if invoking an external tool. We'll evaluate performance and come back to this if necessary.
… task factory, instead of hard-coded AssemblyLoadContext.Default (in anticipation of dotnet/msbuild#1754 ).
… task factory, instead of hard-coded AssemblyLoadContext.Default (in anticipation of dotnet/msbuild#1754 ).
@joperezr hit this with a task that was trying to use Roslyn and had to workaround it by turning it into an out of proc tool. |
See design proposal #4133 by @vitek-karas. Feedback welcome! |
FYI we just hit this again here: dotnet/corefx#36103 In that case the task was trying to use a newer NuGet than MSBuild had in its deps file. /cc @safern @tmat |
@AArnott i assume this shouldnt be a problem for strong named task assemblies? since they should be able to be loaded side-by-side? |
@SimonCropp It is indeed a problem for strong-named assemblies. In fact all my task assemblies are always strong-named. The problem isn't just the task assembly itself, it's its whole dependency tree. And while all its dependency may be strong-name signed, the dependencies should still match exactly what the task is expecting. Some dependencies have a common assembly version across several actual versions, such that the first one would load in the CLR and the rest of the tasks that need that same dependency (but a different version) would reuse the same version as the first task asked for. That's a problem. |
Your ultimate test case is "turtles all the way down": Create a This would allow you to correctly handle the scenario of:
This is exactly the problem we run into every day. I get more support requests in FluentMigrator for this issue than all other issues combined. I'll try to push a sample project which demonstrates this issue. |
in fody this is handled by spinning up a custom appdomain in .net and a custom load context in netcore. this domain/context is then cached per solution so that subsequent builds dont need to spin up a new domain/context. this avoids the performance problem with AppDomainIsolatedTask, which forces a new appdomain for every run of the task, even if all the assemblies are the same. that and AppDomainIsolatedTask is not supported in netcore i suspect the majority of msbuild tasks (that have dependencies) have the same "single appdomain" bug and they have never noticed. i would very much like to remove this complexity from Fody. |
@SimonCropp Can you point me to the code you use in Fody? I searched https://github.com/Fody/Fody/search?q=AppDomain&unscoped_q=AppDomain and I see:
I believe, based on your comments, you are only referring to IsolatedAssemblyLoadContext, but not to the resolution handlers you write in InnerWeaver and DomainAssemblyResolver. I don't think putting each MSBuild task in its own AssemblyLoadContext will necessarily solve all your problems, because I think you'll end up with "private diamond dependencies" where people cannot independently separate interfaces from implementation. BenchmarkDotNet does similar nastiness, here, in the following sequence:
|
@jzabroski fory has two contexts the root taskhttps://github.com/Fody/Fody/tree/master/Fody contains no 3rd party dependencies and is loaded into the shared msbuild appdomain loads "isolated" here https://github.com/Fody/Fody/blob/master/Fody/Processor.cs#L128 contexts are cached per sln in a dictionary https://github.com/Fody/Fody/blob/master/Fody/Processor.cs#L27 an isolated parthttps://github.com/Fody/Fody/tree/master/FodyIsolated can ref and load any 3rd part assemblies note that fody is also a plugin based model. so the above give isolation for any weavers as well https://github.com/Fody/Home/blob/master/pages/addins.md |
Augment MSBuild's handling of assembly loading for plugins (currently tasks, SDK resolvers, and loggers) to use an AssemblyLoadContext, allowing plugins to use different versions of dependencies. Creates a new ALC per assembly path (of initially-loaded assembly, not indirect dependencies). Closes dotnet#1754. Closes dotnet#4635.
Augment MSBuild's handling of assembly loading for plugins (currently tasks, SDK resolvers, and loggers) to use an AssemblyLoadContext, allowing plugins to use different versions of dependencies. Creates a new ALC per assembly path (of initially-loaded assembly, not indirect dependencies). Closes dotnet#1754. Closes dotnet#4635. Promote task isolation spec "possible solution"s to be the implemented solution.
I've encountered (maybe) related issues. when using <Target Name="GenerateMessagePack" AfterTargets="Compile">
<MessagePackGenerator Input=".\ChatApp.Shared.csproj" Output="..\ChatApp.Unity\Assets\Scripts\Generated\MessagePack.Generated.cs" />
</Target>
<Target Name="GenerateMagicOnion" AfterTargets="Compile">
<MagicOnionGenerator Input=".\ChatApp.Shared.csproj" Output="..\ChatApp.Unity\Assets\Scripts\Generated\MagicOnion.Generated.cs" />
</Target>
because for workaround, I've adjusted to use same version, it works. |
@rainersigwald thanks, I've pushed sample repo here https://github.com/neuecc/MSBuildAssemblyLoadIssue |
@neuecc Thanks! $ dotnet build
Microsoft (R) Build Engine version 16.5.0-dev-20058-01+6d4c3b1e2 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
Restore completed in 14.77 sec for S:\play\MSBuildAssemblyLoadIssue\MSBuildAssemblyLoadIssue.csproj.
You are using a preview version of .NET Core. See: https://aka.ms/dotnet-core-preview
[Out]S:\play\MSBuildAssemblyLoadIssue\MagicOnion.Generated.cs
MSBuildAssemblyLoadIssue -> S:\play\MSBuildAssemblyLoadIssue\bin\Debug\netstandard2.0\MSBuildAssemblyLoadIssue.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:26.59 (That's with MSBuild from my PR) |
Augment MSBuild's handling of assembly loading for plugins (currently tasks, SDK resolvers, and loggers) to use an AssemblyLoadContext, allowing plugins to use different versions of dependencies. Creates a new ALC per assembly path (of initially-loaded assembly, not indirect dependencies). Closes dotnet#1754. Closes dotnet#4635. Promote task isolation spec "possible solution"s to be the implemented solution.
This comment was marked as resolved.
This comment was marked as resolved.
@jzabroski please start a new issue for that. |
Each MSBuild Task should load in its own AssemblyLoadContext. At least 3rd party tasks. This prevents build failures due to two independently developed tasks sharing a dependency but with different versions.
Also, the default AssemblyLoadContext should be very nearly empty, so that each Task can define its own dependencies instead of inheriting them from the default context. For example, Roslyn assemblies should not be in the default AssemblyLoadContext so that MSBuild tasks can use Roslyn assemblies of the version it was compiled against.
@tmat summarized this well after a discussion we had in another issue.
CC: @nguerrera
The text was updated successfully, but these errors were encountered: