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

Opting out of trimming for third party libraries should be easier! #2768

Closed
1 task done
1618employment opened this issue Apr 27, 2022 · 17 comments
Closed
1 task done

Comments

@1618employment
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe the problem.

Why does opting out of trimming a third party library have to be so difficult? I shouldn't have to write several lines of code that I don't even really understand to accomplish a seemingly common task...like opting out of trimming a third party library.

Describe the solution you'd like

<ItemGroup>
	<PackageReference Include="Microsoft.EntityFrameworkCore" IsTrimmable="false"  Version="6.0.4" />
</ItemGroup>

Additional context

No response

@davidfowl
Copy link
Member

Is this a blazor problem?

@javiercn
Copy link
Member

@davidfowl likely involves Blazor, but in general, trimming is defined by the runtime folks (how to use it/configure it/etc.) and we only surface it.

@javiercn javiercn pinned this issue Apr 27, 2022
@javiercn javiercn unpinned this issue Apr 27, 2022
@javiercn javiercn transferred this issue from dotnet/aspnetcore Apr 27, 2022
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@davidfowl
Copy link
Member

@javiercn but the model outside of blazor is that libraries themselves opt into trimming. I'm guessing blazor does that automagically.

@jkotas jkotas transferred this issue from dotnet/runtime Apr 27, 2022
@vitek-karas
Copy link
Member

@davidfowl as far as I know Blazor follows the same rules as everybody else - that is assemblies have to opt into trimming. That doesn't mean that assemblies which are not opted into trimming don't break apps because of trimming. We even know about cases which are like this for EF: dotnet/efcore#27474

@1618employment I'm pretty sure Microsoft.EntityFrameworkCore.dll is not trimmed by default - so unless there's other things in your project which make it so, the piece of XML you posted doesn't really help. That said, it's very possible that EF causes problems with the app, but not trimming it will not fix the problem - it probably tries to access type from some other assembly (typically System.* assemblies are trimmed, so I would look for that) and fails, so to fix it you would need to make sure that assembly is kept. Opting out of trimming for an assembly is described here: https://docs.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options#root-assemblies
Simply add a <TrimmerRootAssembly>true</TrimmerRootAssembly> to an item group.

@eerhardt
Copy link
Member

@1618employment - another known issue with trimming + EntityFramework is dotnet/efcore#27098 which was patched in 6.0.2. You'll know you hit that issue if you get an error $"Could not find method '{name}' on type '{type}'" deep in the EF code:

image

@1618employment
Copy link
Author

I'm going to apologize upfront, I was frustrated with the docs for trimming and that led to some bad behavior when opening this issue, sorry!

Yes, this is Blazor WASM

I thought NET6 trims everything by default?

You are correct it isn't Microsoft.EntityFrameworkCore its Microsoft.EntityFrameworkCore.InMemory, but I thought if I sad that someone would immediately say "you should not be using that library outside of testing!"

The actual error I'm getting is something in Microsoft.EntityFrameworkCore.Query.EvaluatableExpressionFilter which I'm catching so I don't have more details.

I've read EF uses a lot of reflection and reflection and trimming don't go well together.

When I use <PublishTrimmed>false</PublishTrimmed> the issue goes away!

...so really I was just complaining that I failed at figuring out how to disable trimming for a single library and asking for a easier solution.

What does this even mean and where am I adding this code?

<Target Name="ConfigureTrimming"
        BeforeTargets="PrepareForILLink">
  <ItemGroup>
    <ManagedAssemblyToLink Condition="'%(Filename)' == 'MyAssembly'">
      <IsTrimmable>true</IsTrimmable>
    </ManagedAssemblyToLink>
  </ItemGroup>
</Target>

so if I "root" the library it won't try and trim it?

<ItemGroup>
  <TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.InMemory" />
</ItemGroup>

again sorry!

@vitek-karas
Copy link
Member

Thanks for the feedback. The doc could definitely be improved, but it's also because the product is changing over time - we might need to start adding "version specific" sections to the doc due to this unfortunately.

I think there's an important detail to be aware of when thinking about trimming specific assemblies. The main problem in trimming is the usage of reflection. If assembly A has code which uses reflection to access type from assembly B it's not always possible for the tooling to figure this out - it only sees a reflection call, but can't figure out what it's asking for. What might happen is that the type in assembly B is removed (as it's not seen to be used anywhere) and this will break assembly A at runtime. So the root of the problem is in assembly A (it's not analyzable by the tools) and that's typically where it fails at runtime, but the workaround is in assembly B - to force include the type A needs from B.

So if the failure is in EF, it's very possible that the workaround is to not opt-out EF (as it's probably already not trimmed), but to opt-out something else which EF tries to use.

Trimming is unfortunately a "global" problem - so a piece of code which is problematic for trimming in one assembly makes the entire app problematic, and there's no simple way to fix it by "opting out" the problematic assembly from trimming.

Typically you should not need to use the complex bit of MSBuild which sets IsTrimmable=true. For the workarounds which are meant to make sure certain things are always kept it's better and easier to use the TrimmerRootAssembly.

Including assembly C in the item group TrimmerRootAssembly means that trimming will not modify that assembly - it will leave it as is. It will still analyze it searching for things it needs from other assemblies which are trimmed though.

The default behavior is that only assemblies which have an assembly level attribute [AssemblyMetadata("IsTrimmable", "True")] will be trimmed, all other assemblies are left as-is. Right now this attribute is present on most core framework assemblies but nothing else really. EF doesn't have this attribute right now, so it should not be trimmed by default.

@eerhardt
Copy link
Member

@1618employment - if at all possible, it would be great if you could post an exception message and stacktrace of the error. That way we can fix the issue in the EntityFramework. Ideally a project that repros the issue would be even better. But I think just an exception message and stacktrace would be enough to point us to where the trimming issue lies in Microsoft.EntityFrameworkCore.InMemory.

@agocke
Copy link
Member

agocke commented Apr 28, 2022

I thought NET6 trims everything by default?

Just to make sure your question is answered: only assemblies which mark themselves as trimmable are trimmed by default, and assemblies which aren't trim-compatible should never be marked that way. EFCore doesn't mark itself trimmable, so that particular assembly is not trimmed by default.

What @vitek-karas described is correct though -- reflection from that DLL to another can mean that the app can break anyway, even if the DLL doing the reflection isn't trimmed. The only fix for that will be to diagnose the root problem and have EF fix it

@vitek-karas
Copy link
Member

The only fix for that will be to diagnose the root problem and have EF fix it

That's true for a real fix. For a specific app you might be able to workaround this by rooting additional assemblies which EF depends on in the scenarios executed by the app.

@1618employment
Copy link
Author

@1618employment - if at all possible, it would be great if you could post an exception message and stacktrace of the error. That way we can fix the issue in the EntityFramework. Ideally a project that repros the issue would be even better. But I think just an exception message and stacktrace would be enough to point us to where the trimming issue lies in Microsoft.EntityFrameworkCore.InMemory.

HResult: -2146233036
Message: TypeInitialization_Type, Microsoft.EntityFrameworkCore.Query.EvaluatableExpressionFilter
InnerMessage: Could not find property 'Now' on type 'System.DateTimeOffset'

github-issues-dotnet-linker-2768-stacktrace.txt

Thank you for the explanation, but I must still be missing something, I tried the following and still get the same result.

Project file:

<ItemGroup>
    <EmbeddedResource Include="ILLink.Descriptors.xml">
	    <LogicalName>ILLink.Descriptors.xml</LogicalName>
    </EmbeddedResource>
    <TrimmerRootDescriptor Include="ILLink.Descriptors.xml" />
</ItemGroup>

ILLink.Descriptors.xml:

<?xml version="1.0" encoding="utf-8" ?>

<linker>
    <assembly fullname="System" />
    <assembly fullname="System.Runtime" />
</linker>

@eerhardt
Copy link
Member

eerhardt commented May 2, 2022

Message: TypeInitialization_Type, Microsoft.EntityFrameworkCore.Query.EvaluatableExpressionFilter
InnerMessage: Could not find property 'Now' on type 'System.DateTimeOffset'

This was fixed in 7.0 with dotnet/efcore#27099. The fix that was backported to 6.0.2 only fixed reflection against System.Math (see dotnet/efcore#27098). @roji @ajcvickers - you may want to backport more trimming fixes in 6.0.x if we are saying EF supports trimming in 6.0.

I tried the following and still get the same result.

Try this in your ILLink.Descriptors.xml file:

<linker>
    <assembly fullname="System.Private.CoreLib">
        <type fullname="System.DateTimeOffset" preserve="all" />
    </assembly>
</linker>

You can keep adding types until it starts working.

@vitek-karas
Copy link
Member

Try enabling trim analysis warnings: https://docs.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options#analysis-warnings

You will probably get a lot of them, but it's possible some will reference the XML you added.
Also - you should need both embedded resource and TrimmerRootDescriptor. One should be enough (the trimmer will read it from the resource if it's there, the TrimmerRootDescriptor passes the file on the command line to the trimmer).

@vitek-karas
Copy link
Member

There's an interesting "behavior" though - it seems that marking the assembly in the descriptor doesn't mark type forwarders in the assembly. System.Runtime is a facade, so rooting it should root all the type forwards, but that doesn't seem to be the case.
Interestingly if I root it via TrimmerRootAssembly it works.

@vitek-karas
Copy link
Member

I created #2776 to look into that.

@roji
Copy link
Member

roji commented May 2, 2022

FWIW EF Core 6.0 definitely doesn't "support" trimming; we're doing some specific changes to unblock general use (e.g. dotnet/efcore#27910, dotnet/efcore#27098), but we're aware that specific use-cases would still fail after those. We're planning to address trimming more systematically in 7.0.

Specifically for DateTimeOffset.Now, I didn't run into that when using SQL Server or SQLite with minimal basic patterns, @1618employment can you please open an issue on https://github.com/dotnet/efcore with a minimal repro and we'll look into addressing that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants