【问题标题】:Sporadic application deadlock in ASP.NET IdentityASP.NET Identity 中的零星应用程序死锁
【发布时间】:2019-09-03 10:13:54
【问题描述】:

我有一个使用 ASP.NET 标识和 OWIN OAuthAuthorizationServerMiddleware 的 ASP.NET 4.7.2 MVC 5 应用程序。在RefreshTokenProvider.OnReceive 方法中,我正在访问SignInManager.CreateUserIdentity 方法,这是一种内部使用AsyncHelper(见下文)来调用异步方法的ASP.NET Identity 方法。每隔一段时间(通常在繁忙的系统上相隔几个月),这就会崩溃并锁定整个应用程序。从我收集的内存转储中,有 565 个线程在 GetResult 内部等待

// Copyright (c) Microsoft Corporation, Inc. All rights reserved.
// Licensed under the MIT License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.AspNet.Identity
{
    internal static class AsyncHelper
    {
        private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None,
            TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

        public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        {
            var cultureUi = CultureInfo.CurrentUICulture;
            var culture = CultureInfo.CurrentCulture;
            return _myTaskFactory.StartNew(() =>
            {
                Thread.CurrentThread.CurrentCulture = culture;
                Thread.CurrentThread.CurrentUICulture = cultureUi;
                return func();
            }).Unwrap().GetAwaiter().GetResult();
        }

        public static void RunSync(Func<Task> func)
        {
            var cultureUi = CultureInfo.CurrentUICulture;
            var culture = CultureInfo.CurrentCulture;
            _myTaskFactory.StartNew(() =>
            {
                Thread.CurrentThread.CurrentCulture = culture;
                Thread.CurrentThread.CurrentUICulture = cultureUi;
                return func();
            }).Unwrap().GetAwaiter().GetResult();
        }
    }
}

内存转储显示 580 多个任务,它们全部处于RanToCompletion 状态。鉴于任务已完成,我无法诊断为什么GetResult 没有成功,也无法诊断为什么许多线程在它工作了几个月后迅速堆积在那里,以及为什么整个应用程序变得无响应,即使他们不锻炼这条路。这导致生产中断多次,我不知道如何解决这个问题,除了重新启动它。

我尝试使用OnReceiveAsync 方法,但这些似乎毫无意义,因为在调用它们之前,有这个小sn-p:

if (OnReceiveAsync != null && OnReceive == null)
{
    throw new InvalidOperationException(Resources.Exception_AuthenticationTokenDoesNotProvideSyncMethods);
}

编辑:问题的复制和解释:

此 WebAPI 2 控制器可以重现该问题

using System.Threading.Tasks;
using System.Web.Http;

namespace WebApplication65.Controllers
{
    public class ValuesController : ApiController
    {
        public string Post([FromBody]string value)
        {
            return AsyncHelper.RunSync(PostAsync);
        }

        private async Task<string> PostAsync()
        {
            await Task.Delay(10);
            return "Hello World";
        }
    }
}

和这个程序产生负载

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace ConsoleApp36
{
    class Program
    {
        static void Main(string[] args)
        {
            HttpClient client = new HttpClient();
            MediaTypeHeaderValue mediaTypeHeaderValue = new MediaTypeHeaderValue("application/json");
            var tasks = new List<Task>();
            for (int i = 0; i < 100; i++)
            {
                int x = i;
                var t = Task.Run(async () =>
                {
                    var content = new StringContent("\"" + Guid.NewGuid().ToString() + "\"");
                    content.Headers.ContentType = mediaTypeHeaderValue;
                    await client.PostAsync("https://localhost:44371/api/values", content);
                    Console.WriteLine(x);
                });
                tasks.Add(t);
            }
            Task.WhenAll(tasks).Wait();
        }
    }
}

程序要做的就是在几毫秒内向应用程序发送 100 个请求。这将导致所有先前空闲的线程都卡在AsyncHelper.RunSync,并且有更多的请求排队,并且根本没有发送任何响应。 ThreadPool 注意到它需要更多线程,但每秒只会添加一个线程,这将立即卡在AsyncHelper.RunSync 试图为排队的请求之一提供服务。大约一分钟后,当 ThreadPool 扩展了大约 100 个额外的线程来服务这 100 个请求时,所有待处理的请求都会在眨眼之间做出响应,并且应用程序再次响应。

我的应用程序的不同之处在于,请求不断进入,与一次仅发送 100 个请求的示例不同。这意味着我的应用无法从这种情况中恢复,因为 ThreadPool 创建新线程的速度不足以跟上传入的请求。

创建我的ReceiveRefreshToken 方法的异步副本并填充OnReceiveAsync 以及OnReceive 似乎足以避免问题,不会在线程池上耗尽。

【问题讨论】:

  • 听起来是一个错误,您应该考虑向 Microsoft 报告。

标签: c# asp.net asp.net-identity task deadlock


【解决方案1】:

我认为应用程序耗尽了 ThreadPool。

AsyncHelper 使用 TaskScheduler.Default,这意味着在 ThreadPool 上执行。它应该可以正常工作,直到所有线程都被阻塞(例如,许多用户和/或来自令牌端点的缓慢响应)并且没有更多空闲线程可以继续。这会导致死锁(你可以阅读更多关于它的信息here)。由于应用程序只有一个 ThreadPool,如果用完就停止。

无论如何我都会尝试使用 OnReceiveAsync 并从这里以异步方式创建用户。您可能需要一个虚拟 OnReceive 以避免异常。

或者,您可以尝试扩展 ThreadPool 的大小,但这只是一个短期解决方案,不能保证始终有效。

【讨论】:

  • 谢谢,就是这样,虽然线程池并没有完全耗尽,但似乎我的 web 应用程序同时被大量请求击中,所有线程都忙,线程池每秒添加大约一个线程,这不足以跟上。我设法复制它并将复制添加到问题中
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-01
  • 1970-01-01
  • 2014-07-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多