Fsharp: FSharp.Compiler.Service: StackOverflow on macOS calling ParseAndCheckFileInProject

Created on 19 May 2020  路  10Comments  路  Source: dotnet/fsharp

On macOS, calling ParseAndCheckFileInProject can produce a stack overflow. The issue is most likely related to computation expression builders, it can be reproduced by using a list builder with a certain number of yields (around 200). We have a minimal example repo reproducing this issue, and it also happens on the CI as shown in this build: https://github.com/krauthaufen/FSCtest/runs/688775775?check_suite_focus=true#step:3:23

The issue specifically only occurs on macOS. It works fine on Ubuntu 19.10 and Win10.

The issue likely happens in the "check" portion of the call, since the separate "parse" call worked in our test.

Repro steps

  1. On macOS, clone our test repo https://github.com/krauthaufen/FSCtest
  2. cd checker
  3. dotnet run

Expected behavior

Call terminates.

Actual behavior

Stack Overflow.

Related information

  • Operating system: macOS Catalina 10.15.4 (on real macbook, and on github CI)
.NET Core SDK (reflecting any global.json):
 Version:   3.1.202
 Commit:    6ea70c8dca

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  10.15
 OS Platform: Darwin
 RID:         osx.10.15-x64
 Base Path:   /usr/local/share/dotnet/sdk/3.1.202/

Host (useful for support):
  Version: 3.1.4
  Commit:  0c2e69caa6

Thanks in advance, any help would be greatly appreciated. Cheers!

Area-Compiler Feature Improvement Tenet-Performance

Most helpful comment

@auduchinok Hi,

we investigated the issue further and it appears to be a genuine Stack Overflow happening in the FSharp compiler. The Stack Overflow can be provoked on any platform in both the FSharp Compiler and FSharp.Compiler.Service by having a computation expression builder with a certain number of yields in the source code. The number varies across environments (macOS FSharp.Compiler.Service: around 200 yields, windows fsharp compiler: around 3000 yields).

Our minimal example in the OP is still valid. You can crash Visual Studio with by increasing the number and compiling. The FSharp compiler keeps allocating more stack during the compilation process and crashes when the stack is full. Unfortunately, the critical number can be reached quite quickly on certain configurations, such as on macOS with its apparently small stack size.

We have found the following workaround: you can increase the stack size for the dotnet process by setting the environment variable COMPlus_DefaultStackSize (which can be found here) to a hex value representing the desired stack size in bytes (for example 800000 for 8MB). This fixes the problem for us by allowing us to conservatively choose appropriate stack sizes. However, this environment variable seems to be fairly undocumented, so I can't guarantee this will always work.

Cheers.

All 10 comments

Btw. the issue can't be reproduced with the "real" FSharp compiler and even with way more entries it seems to be working on Windows and Linux.

Maybe there's some Wait/Sync primitive executing its continuation inline on MacOS which would cause stack overflows? I found at least one async-recursion which could cause such an issue when everything gets executed inline.

It would be really helpful if someone who has experience with the whole system could take a look at that, since this prevents our project from running on MacOS.

Cheers

This hits us too. @cartermp could this be looked into please?

@auduchinok Hi,

we investigated the issue further and it appears to be a genuine Stack Overflow happening in the FSharp compiler. The Stack Overflow can be provoked on any platform in both the FSharp Compiler and FSharp.Compiler.Service by having a computation expression builder with a certain number of yields in the source code. The number varies across environments (macOS FSharp.Compiler.Service: around 200 yields, windows fsharp compiler: around 3000 yields).

Our minimal example in the OP is still valid. You can crash Visual Studio with by increasing the number and compiling. The FSharp compiler keeps allocating more stack during the compilation process and crashes when the stack is full. Unfortunately, the critical number can be reached quite quickly on certain configurations, such as on macOS with its apparently small stack size.

We have found the following workaround: you can increase the stack size for the dotnet process by setting the environment variable COMPlus_DefaultStackSize (which can be found here) to a hex value representing the desired stack size in bytes (for example 800000 for 8MB). This fixes the problem for us by allowing us to conservatively choose appropriate stack sizes. However, this environment variable seems to be fairly undocumented, so I can't guarantee this will always work.

Cheers.

I encountered this in a project and thought it was a bug in Rider, so I reported it to them. If I remember right, I hit it in way less than 3000 yields ( this was Windows).

This is ultimately a compiler issue similar to other issues where we've addressed where compiling very large constructs in source can lead to a stack overflow. There is also another known case with constant folding of massive string literals where the SO will occur in the typechecker.

@dsyme @TIHan this may be a fun one to look into.

@aszabo314 Setting the environment variable value to 800000 solved it for me in Rider and VSCode, thanks!

Just for completeness (since I've struggled a bit with this):
On Mac (probably the same for Linux), I've added the following to my ~/.bash_profile file:

export COMPlus_DefaultStackSize=800000

I am guessing, on Windows one would just set it in System Properties -> Advanced -> Environment Variables.

Another way to trigger the same issue on MacOS: https://youtrack.jetbrains.com/issue/RIDER-47310

This is very hard for us to pin down - some compiler size limitations in some dimensions of input are tested for all platforms, but the upper limit will indeed differ on different platforms.

For serious tooling that must work across all platforms it does feel like increasing stack sizes in the actual final executable is going to be helpful

@auduchinok Can Rider apply workarounds like this, or other stack size arguments?

We have found the following workaround: you can increase the stack size for the dotnet process by setting the environment variable COMPlus_DefaultStackSize (which can be found here) to a hex value representing the desired stack size in bytes (for example 800000 for 8MB). This fixes the problem for us by allowing us to conservatively choose appropriate stack sizes. However, this environment variable seems to be fairly undocumented, so I can't guarantee this will always work.

Note that in some dimensions of input size (e.g. list length, expression depth etc.) we can program all phases of the compiler to use heap resources instead of stack resources, and we often do this. However we can't do this in every dimension.

@dsyme What's interesting here is it wasn't happening on Mono, it only happens when using dotnet runtime.

@auduchinok Can Rider apply workarounds like this, or other stack size arguments?

I suggested it some time ago and was told that we shouldn't apply such workarounds to the whole product since it'd possibly cause some unexpected effects elsewhere.

Was this page helpful?
0 / 5 - 0 ratings