File: ServiceLookup\StackGuard.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.DependencyInjection\src\Microsoft.Extensions.DependencyInjection.csproj (Microsoft.Extensions.DependencyInjection)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
 
namespace Microsoft.Extensions.DependencyInjection.ServiceLookup
{
    internal sealed class StackGuard
    {
        private const int MaxExecutionStackCount = 1024;
 
        private int _executionStackCount;
 
        public bool TryEnterOnCurrentStack()
        {
#if NETFRAMEWORK || NETSTANDARD2_0
            try
            {
                RuntimeHelpers.EnsureSufficientExecutionStack();
                return true;
            }
            catch (InsufficientExecutionStackException)
            {
            }
#else
            if (RuntimeHelpers.TryEnsureSufficientExecutionStack())
            {
                return true;
            }
#endif
 
            if (_executionStackCount < MaxExecutionStackCount)
            {
                return false;
            }
 
            throw new InsufficientExecutionStackException();
        }
 
        public TR RunOnEmptyStack<T1, T2, TR>(Func<T1, T2, TR> action, T1 arg1, T2 arg2)
        {
#if NETFRAMEWORK || NETSTANDARD2_0
            return RunOnEmptyStackCore(static s =>
            {
                var t = (Tuple<Func<T1, T2, TR>, T1, T2>)s;
                return t.Item1(t.Item2, t.Item3);
            }, Tuple.Create(action, arg1, arg2));
#else
            // Prefer ValueTuple when available to reduce dependencies on Tuple
            return RunOnEmptyStackCore(static s =>
            {
                var t = ((Func<T1, T2, TR>, T1, T2))s;
                return t.Item1(t.Item2, t.Item3);
            }, (action, arg1, arg2));
#endif
        }
 
        private R RunOnEmptyStackCore<R>(Func<object, R> action, object state)
        {
            _executionStackCount++;
 
            try
            {
                // Using default scheduler rather than picking up the current scheduler.
                Task<R> task = Task.Factory.StartNew((Func<object?, R>)action, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
 
                // Avoid AsyncWaitHandle lazy allocation of ManualResetEvent in the rare case we finish quickly.
                if (!task.IsCompleted)
                {
                    // Task.Wait has the potential of inlining the task's execution on the current thread; avoid this.
                    ((IAsyncResult)task).AsyncWaitHandle.WaitOne();
                }
 
                // Using awaiter here to propagate original exception
                return task.GetAwaiter().GetResult();
            }
            finally
            {
                _executionStackCount--;
            }
        }
    }
}