Skip to content

Big stack usage on exception handling win-x64 #13209

Description

@iskiselev

On Windows x64 CoreCLR use 9 KiB of stack space per each frame that has catch block that will rethrow exception up to frame that will finally handle exception. On x86 stack space is not used for the same pattern at all. Per our test it is not used on Linux-X64 too.
.Net Framework x64 is also affected by the same problem (it use 8 KiB for ,Net Framework 4.7.2 and more for 3.5.).
Here is test sample:

using System;

namespace teststackoverflow
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 1 || !int.TryParse(args[0], out var maxRecursion))
            {
                maxRecursion = 250;
            }
            try
            {

                WithExceptionRecurse(1, maxRecursion);
            }
            catch (Exception ex)
            {
                Console.WriteLine("WithExceptionRecurse thrown");
            }
        }

        static void WithExceptionRecurse(int count, int maxRecursion)
        {
            try
            {
                Console.WriteLine("Frame {0}", count);
                if (count > maxRecursion)
                {
                    Console.WriteLine("Frame {0} Throws", count);
                    throw new Exception(count.ToString());
                }
                WithExceptionRecurse(count+1, maxRecursion);
            }
            catch (Exception ex)
            {
                Console.WriteLine("Frame Exception {0}", count);
                //RecursionTest.WriteStack();

                throw;
            }
        }
    }

    /*internal class RecursionTest
    {
        [DllImport("kernel32.dll")]
        static extern void GetCurrentThreadStackLimits(out UIntPtr lowLimit, out UIntPtr highLimit);

        public static unsafe void WriteStack()
        {
            UIntPtr low;
            UIntPtr high;

            GetCurrentThreadStackLimits(out low, out high);
            var size = ((long) high - (long) low) / 1024; // in KB
            ulong current = (ulong) (UIntPtr) (void*) &size;
            var left = ((long) current - (long) low) / 1024; // in KB


            bool isSufficientStack = false;
            try
            {
                RuntimeHelpers.EnsureSufficientExecutionStack();
                isSufficientStack = true;
            }
            catch (Exception)
            {
            }

            Console.WriteLine($"Stack: size={size}, left={left}, isSufficientStack={isSufficientStack}");
        }
    }*/
}

If this app will be published with dotnet publish -c release -r win-x86 --self-contained, that can be called without StackOverfolowExcpetion with deep recursions, i.e 250.
But if it was published with dotnet publish -c release -r win-x64 --self-contained, it will fail with StackOverfolowExcpetion with only recursion layer 160.

It is even bigger problem on .Net Framework hosted inside w3wp, as it allocates only 512 KiB of stack - so it is reproducible with only 51 recursion there. Looks like some work have been done to provide a way to override stack size for w3wp in .Net Core hosting scenarios (#21450), which may help in hosted scenarios on CoreCLR.

I've added commented diagnostic code that should show available stack space (if it will be uncommented).

Is it possible to update Win-x64 codegen to not use stack space at all (same as x86) or at least use smaller amount of stack space in recursive throw scenarios?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    No status

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions