language-ext icon indicating copy to clipboard operation
language-ext copied to clipboard

Compiling application with LanguageExt as a dependency and ReadyToRun enabled using .NET 6

Open AndreSteenveld opened this issue 2 years ago • 15 comments

I've been using LanguageExt for about a year now in some work related projects and it has been a positive experience all round. Although I have run in to a rather peculiar issue. It seems that the .NET 6 cross compiler just hangs on LanguageExt.Core, while the .NET 5 version just compiles and generates a binary.

The only vaugly related issue I could find was #673 in which @WarrenFerrell mentions that he built an application which depends on LanguageExt.Core which ReadyToRun enabled and trimming to keep the size of the resulting binary down. But nowhere in the ticket the version of .NET is mentioned. As it is a ticket from about a year ago, I'm assuming it is .NET 5 though.

I've build a small reproduction case which can be found here: AndreSteenveld.CrossgenLanguageExt. Summarizing the readme; Building the application works using .NET 5 and 6, but building the application with ReadyToRun enabled only works for .NET 5 and just hangs for .NET 6.

Any suggestions are welcome, I'm also going to post this to dotnet/runtime as I think issues with crossgen should be reported there. (The ticket can now be found here)

-- Andre

AndreSteenveld avatar Mar 02 '22 12:03 AndreSteenveld

I can confirm that it is .NET 5 and LanguageExt version 3.4.15 that we compile with ReadyToRun and member level trimming.

WarrenFerrell avatar Mar 02 '22 18:03 WarrenFerrell

@WarrenFerrell Have you tried upgrading to .NET 6? I imagine you'd run in to the same problems.

AndreSteenveld avatar Mar 03 '22 03:03 AndreSteenveld

@AndreSteenveld I did not have issues publishing with ReadyToRun on or off in a docker container with .NET6.0 . TargetFramework: 6.0

here is the docker file for reference. need to provide provide optimizeBuild arg as true to do ReadyToRun. I did not attempt publishing outside the container

# syntax = docker/dockerfile:1.2.1

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS publisher
ENV NUGET_PACKAGES=/var/cache/nuget
WORKDIR /sln

RUN mkdir -p $NUGET_PACKAGES &&\
    rm -f /etc/apt/apt.conf.d/docker-clean

COPY projectfiles.tar .
RUN --mount=type=cache,target=/var/cache \
    tar -xvf projectfiles.tar &&\
    dotnet restore -r linux-x64 -v m

COPY ./src /sln/src

ARG projectDir
WORKDIR /sln/$projectDir

ARG optimizeBuild=false
RUN --mount=type=cache,target=/var/cache \
    dotnet publish -c Release \
    --self-contained true \
    $([ "$optimizeBuild" = true ] && echo "-p:PublishReadyToRun=true -p:PublishReadyToRunShowWarnings=true -p:PublishTrimmed=true -p:TrimMode=Link") \
    -r linux-x64 \
   # -p:SuppressTrimAnalysisWarnings=false \
    -v m \
    -o /sln/out


# image that will be uploaded
FROM amazonlinux:2.0.20210326.0 AS runtime
ARG outputAssemblyName
ENV ASPNETCORE_URLS="http://+:8000"

RUN yum -y install libicu

WORKDIR /var/task

COPY --from=publisher /sln/out  .


RUN mv ./$outputAssemblyName ./bootstrap

ENTRYPOINT ["/var/task/bootstrap"]

WarrenFerrell avatar Mar 03 '22 16:03 WarrenFerrell

Building on the idea that there could be a difference between the windows and linux version of crossgen2 I've tested that. Turns out there doesn't seem to be any, although the linux build gave a lot more output. (The following is taken from the README in the reproduction repo)

#
# Starting with an interactive session, I'm not using docker-for-windows but do have a separate machine with a samba mount nothing
# super fancy or out of the ordinary.
#
$ docker run --rm --interactive --tty --volume "//c://c" --workdir "/$(cygpath --absolute .)"  mcr.microsoft.com/dotnet/sdk:latest

#
# Running the regular build and running the resulting binary, works fine
#
root@b911a597849a:/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt# time dotnet publish ./AndreSteenveld.CrossgenLanguageExt6.csproj -p:PublishSingleFile=true --configuration Release --runtime linux-x64 --self-contained true
Microsoft (R) Build Engine version 17.1.0+ae57d105c for .NET
Copyright (C) Microsoft Corporation. All rights reserved.   

  Determining projects to restore...
  Restored /c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj (in 12.53 sec).
  AndreSteenveld.CrossgenLanguageExt6 -> /c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/bin/Release/net6.0/linux-x64/AndreSteenveld.CrossgenLanguageExt6.dll
  AndreSteenveld.CrossgenLanguageExt6 -> /c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/bin/Release/net6.0/linux-x64/publish/

real    0m26.436s
user    0m18.284s
sys     0m7.243s
root@b911a597849a:/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt# ./bin/Release/net6.0/linux-x64/publish/AndreSteenveld.CrossgenLanguageExt6
Hello world

#
# Running the build in the docker container also doesn't work out of the gate, although there is a lot more output. I'm also surprised by 
# the amount of time spent in the kernel on the other hand this could just as well be something that isn't measured correctly in the 
# version of `time` that ships with git bash.
#
root@b911a597849a:/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt# time dotnet publish ./AndreSteenveld.CrossgenLanguageExt6.csproj -p:PublishSingleFile=true -p:PublishReadyToRun=true -p:PublishReadyToRunCrossgen2ExtraArgs='--verbose' --configuration Release --runtime linux-x64 --self-contained true
Microsoft (R) Build Engine version 17.1.0+ae57d105c for .NET
Copyright (C) Microsoft Corporation. All rights reserved.   

  Determining projects to restore...
  Restored /c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj (in 13.27 sec).
  AndreSteenveld.CrossgenLanguageExt6 -> /c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/bin/Release/net6.0/linux-x64/AndreSteenveld.CrossgenLanguageExt6.dll
^CAttempting to cancel the build...
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): warning MSB5021: Terminating the task executable "crossgen2" and its child processes because the build was canceled. [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003: The specified task executable "/root/.nuget/packages/microsoft.netcore.app.crossgen2.linux-x64/6.0.2/tools/crossgen2" could not be run. System.ComponentModel.Win32Exception (2): An error occurred trying to start process 'pgrep' with working directory '/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt'. No such file or directory [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at System.Diagnostics.Process.ForkAndExecProcess(ProcessStartInfo startInfo, String resolvedFilename, String[] argv, String[] envp, String cwd, Boolean setCredentials, UInt32 userId, UInt32 groupId, UInt32[] groups, Int32& stdinFd, Int32& stdoutFd, Int32& stderrFd, Boolean usesTerminal, Boolean throwOnNoExec) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at System.Diagnostics.Process.Start() [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at System.Diagnostics.Process.Start(ProcessStartInfo startInfo) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at Microsoft.Build.Shared.ProcessExtensions.RunProcessAndWaitForExit(String fileName, String arguments, String& stdout) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at Microsoft.Build.Shared.ProcessExtensions.GetAllChildIdsUnix(Int32 parentId, ISet`1 children) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at Microsoft.Build.Shared.ProcessExtensions.KillTree(Process process, Int32 timeoutMilliseconds) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at Microsoft.Build.Utilities.ToolTask.KillToolProcessOnTimeout(Process proc, Boolean isBeingCancelled) [/c/Users/asteenveld/source/repo/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at Microsoft.Build.Utilities.ToolTask.TerminateToolProcess(Process proc, Boolean isBeingCancelled) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at Microsoft.Build.Utilities.ToolTask.HandleToolNotifications(Process proc) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at Microsoft.Build.Utilities.ToolTask.ExecuteTool(String pathToTool, String responseFileCommands, String commandLineCommands) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at Microsoft.NET.Build.Tasks.RunReadyToRunCompiler.ExecuteTool(String pathToTool, String responseFileCommands, String commandLineCommands) [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]
/usr/share/dotnet/sdk/6.0.200/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.CrossGen.targets(463,5): error MSB6003:    at Microsoft.Build.Utilities.ToolTask.Execute() [/c/Users/asteenveld/source/repos/AndreSteenveld.CrossgenLanguageExt/AndreSteenveld.CrossgenLanguageExt6.csproj]

real    39m19.029s
user    31m51.411s
sys     36m40.716s

AndreSteenveld avatar Mar 05 '22 05:03 AndreSteenveld

@AndreSteenveld I noticed that are you aren't publishing trimmed and I wonder if doing so might mysteriously fix the problem as without trimming the 3.* Version of languageext was producing a 90 MB dll with ReadyToRun. The issue you've identified is still a problem but it is worth trying.

The LanguageExt assembly isn't marked trimmable because there is some functionality that may be broken by trimming). So you need to mark LanguageExt as trimmable

WarrenFerrell avatar Mar 05 '22 15:03 WarrenFerrell

@AndreSteenveld I noticed that are you aren't publishing trimmed and I wonder if doing so might mysteriously fix the problem as without trimming the 3.* Version of languageext was producing a 90 MB dll with ReadyToRun. The issue you've identified is still a problem but it is worth trying.

I wanted to try this as the next step but unfortunately got felled by the flu over the weekend, so I didn't get around to it. If all goes well I'll update and write up some quick observations this week experimenting with trimming the LanguageExt assembly. I'll be doing that based on your earlier comment in #673 about trimming.

Originally I didn't want to trim the assembly as it was a beta feature in .NET 5 and as a rule of thumb I don't like working with beta or bleeding edge software for production software :). But now this is fully baked in with .NET 6, it should be fine.

AndreSteenveld avatar Mar 07 '22 12:03 AndreSteenveld

I also just tried to publish my Azure-Functions project with ReadyToRun and it seems i'm running into the same problem. This is the command i've used:

dotnet publish --configuration Release --runtime linux-x64 --no-self-contained -p:PublishReadyToRun=true

After waiting for about 3 hours I actually got an error. I tried excluding LanguageExt.Core.dll from optimization but now it just hangs.

See https://github.com/dotnet/runtime/issues/66079 for more details.

BrunoJuchli avatar Mar 14 '22 12:03 BrunoJuchli

Hello all!

I finally got around to testing if trimming LanguageExt.Core would lead to a successful build, and it does! (party) So trimming clearly is a good workaround here. Unrelated to this issue I've read in the .NET7 beta release notes that trimming is a required step when compiling to actual hardware. So my guess is that making sure LanguageExt is trimmable would be a acceptable fix, completely ignoring any potential issues in crossgen for now.

I've updated the reproduction project. In summary:

<!-- I've added this section to the `AndreSteenveld.CrossgrenLangueageExt6.csproj` file, it was taken from: https://github.com/louthy/language-ext/issues/673#issuecomment-778358899 -->
<Target Name="ConfigureTrimming" BeforeTargets="PrepareForILLink">
  <ItemGroup>
    <ManagedAssemblyToLink Condition="'%(Filename)' == 'LanguageExt.Core'">
      <IsTrimmable>true</IsTrimmable>
      <TrimMode>link</TrimMode>
    </ManagedAssemblyToLink>
  </ItemGroup>
</Target>
$ time dotnet publish ./AndreSteenveld.CrossgenLanguageExt6.csproj  \
    --configuration Release                                         \
    --runtime win-x64                                               \
    --self-contained true                                           \
    -p:PublishSingleFile=true                                       \
    -p:PublishReadyToRun=true                                       \
    -p:PublishReadyToRunShowWarnings=true                           \
    -p:PublishTrimmed=true
Microsoft (R) Build Engine version 17.1.0+ae57d105c for .NET
Copyright (C) Microsoft Corporation. All rights reserved.

  Determining projects to restore...
  Restored C:\Users\asteenveld\source\repos\AndreSteenveld.CrossgenLanguageExt\AndreSteenveld.CrossgenLanguageExt6.csproj (in 15.47 sec).
  AndreSteenveld.CrossgenLanguageExt6 -> C:\Users\asteenveld\source\repos\AndreSteenveld.CrossgenLanguageExt\bin\Release\net6.0\win-x64\AndreSteenveld.CrossgenLanguageExt6.dll 
    C:\Users\asteenveld\.nuget\packages\languageext.core\4.0.3\lib\netstandard2.0\LanguageExt.Core.dll : warning IL2104: Assembly 'LanguageExt.Core' produced trim warnings. For more information see https://aka.ms/dotnet-illink/libraries [C:\Users\asteenveld\source\repos\AndreSteenveld.CrossgenLanguageExt\AndreSteenveld.CrossgenLanguageExt6.csproj]
  Optimizing assemblies for size, which may change the behavior of the app. Be sure to test after publishing. See: https://aka.ms/dotnet-illink
  AndreSteenveld.CrossgenLanguageExt6 -> C:\Users\asteenveld\source\repos\AndreSteenveld.CrossgenLanguageExt\bin\Release\net6.0\win-x64\publish\

real   	1m35.580s
user   	0m0.000s
sys    	0m0.093s

Concluding; What is the wise course of action? This seems like a reasonable workaround. I think it would make sense to look in to making LanguageExt trimmable. A discussion about the overall size of LanguageExt and trimming was held in #673 but I don't think it lead to any concrete action. Does it make sense to revisit trimming LanguageExt (or making it possible)? Personally I think the size of the resulting binary isn't very important but I would like my projects to compile within some reasonable time-frame.

Thanks @WarrenFerrell and @BrunoJuchli for the input! :)

AndreSteenveld avatar Mar 22 '22 07:03 AndreSteenveld

I there any other workaround except for PublishedTrimmed=true? Some external systems, like NewRelic, do not work with trimmable apps.

Lonli-Lokli avatar Apr 12 '22 07:04 Lonli-Lokli

Not that I am aware, I haven't heard anything from the dotnet/MS side either although it seems like it would be something on the roadmap for .NET 7.

AndreSteenveld avatar Apr 13 '22 16:04 AndreSteenveld

We have just experiences a related issue, but with a different workaround.

Specifically, building the a project on .NET 6.0 with Language Ext 4.0.4 using these flags:

-configuration Release
--runtime linux-x64
--self-contained false
-p:PublishReadyToRun=true
-p:PublishReadyToRunShowWarnings=true

Hangs with optimisation error, until we add the following to the .csproj:

  <ItemGroup>
    <PublishReadyToRunExclude Include="LanguageExt.Core.dll" />
  </ItemGroup>

ackginger avatar May 03 '22 10:05 ackginger

I have been reluctant to get involved in this discussion for a couple of reasons:

  • It seems this new Microsoft tooling is potentially buggy
  • I don't know anything about it right now, and am not really that motivated to look into it yet
    • I'd much rather spend my time refactoring the CodeGen to work with Source Generators
  • If this tooling is what I think it is (tree-shaking) then it will definitely cause bugs at runtime from the elements that have been shaken not being there for the live IL generation.

There is a quicker win in terms of reducing the size of the Core library (and what might stop the tooling freaking out so much), and that is to move the transformer functions out into a new stand-alone package. These transformer functions are generated from T4 templates and run in to the 10s of thousands. They're less used in regular day-to-day coding, so I think for those that want a smaller footprint, they can do without them.

This is now released in v4.2.0. The new size of the Core is ~2mb. Honestly, if you need something smaller than that, for such a rich feature set, then perhaps language-ext isn't the right ecosystem for your needs.

There is an argument for running tree-shaking on the other packages (i.e. the non Core ones). But again, I need to get motivated to look into it first, so please don't hold your collective breaths!

louthy avatar Jun 04 '22 02:06 louthy

It seems this new Microsoft tooling is potentially buggy

I think LanguageExt might be an extreme case here but this is something being worked on at the MS side now. relevant comment

As for v4.2.0, I'll give it a shot some time soon in the reproduction repository and will report back. But as there are reasonable workarounds for now it seems fine(~ish).

AndreSteenveld avatar Jun 07 '22 09:06 AndreSteenveld

just a note:

I'm seeing the exact same behaviour when trying to build a WindowsAppSdk (WinUI 3) app - but ONLY in release mode) with LanguageExt.Core referenced in another project

@ackginger's comment does fix it and makes the project build without locking, but I'm curious as to what causes this issue?

christosk92 avatar May 05 '23 07:05 christosk92

FWIW, with target frameworks net7.0-ios and net7.0-windows10.0.19041.0 and LanguageExt.Core 4.4.2

  • this workaround works for Windows

    <ItemGroup>
      <PublishReadyToRunExclude Include="LanguageExt.Core.dll" />
    </ItemGroup>
    
  • but this workaround works for iOS:

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

    which can be simplified (I think) to:

    <ItemGroup>
       <TrimmableAssembly Include="LanguageExt.Core" />
    </ItemGroup>
    

NickDarvey avatar May 29 '23 05:05 NickDarvey