• .NET Core中遇到奇怪的线程死锁问题:内存与线程数不停地增长


    一个 asp.net core 站点,之前运行在Linux 服务器上,运行一段时间后有时站点会挂掉,在日志中记录很多“EMFILE too many open files”的错误:

    Microsoft.AspNetCore.Server.Kestrel.Internal.Networking.UvException: Error -24 EMFILE too many open files

    后来将这个 asp.net 站点部署到 Windows 服务器的 IIS 上。运行一段时间后,发现其中一台服务器出现503错误:

    HTTP Error 503.2 - Service Unavailable
    The serverRuntime@appConcurrentRequestLimit setting is being exceeded.

    登上服务器一看,该站点的进程占用的内存竟然有1.2G,而同一负载均衡中另外一台正常的服务器内存占用只有40多M。然后看了一下进程中的线程数,惊呆了——竟然有8000多个线程!而另外一台正常的服务器只有20多个线程。

    将这台服务器从负载均衡上摘下来之后,出现了更加让人惊呆的现象——在没有请求的情况下,这个 asp.net core 站点进程的内存占用与线程数一直在增长。就像在代码中写了一个死循环,在循环中不停地创建线程。

    再后来内存增长到1.8G左右,线程数增长到1.3万左右,而且还在持续增长。

    不仅内存与线程数在增长,而且CPU也一直在波动,这可是在没有任何请求的情况下,谁在偷偷地干活?

    强制结束进程后恢复正常,但运行一段时间(通常是1天时间)后又会出现同样的问题。非常奇怪!

    从目前分析的情况看,罪魁祸首可能是 EnyimMemcachedCore (支持.net core的memcached客户端,是我们从 EnyimMemcached 移植过来的),EnyimMemcachedCore 用到了 Socket 池,问题可能出在 Socket 池部分,源代码在 github 上(EnyimMemcachedCore源代码)。

    windbg分析进程dump文件显示的线程情况:

    0:000> .load C:Program FilesdotnetsharedMicrosoft.NETCore.App1.0.1sos.dll
    0:000> !threads
    ThreadCount:      8014
    UnstartedThread:  0
    BackgroundThread: 8013
    PendingThread:    0
    DeadThread:       0
    Hosted Runtime:   no

    发现大量线程中存在 coreclr!Thread::DoAppropriateWaitWorker 这个操作:

    !uniqstack
     # Child-SP          RetAddr           Call Site
    00 00000056`ed5ad118 00007ffa`080e13ed ntdll!NtWaitForMultipleObjects+0xa
    01 00000056`ed5ad120 00007ff9`f1dc885e KERNELBASE!WaitForMultipleObjectsEx+0xed
    02 00000056`ed5ad400 00007ff9`f1dc8a0d coreclr!Thread::DoAppropriateWaitWorker+0xfe
    03 00000056`ed5ad4b0 00007ff9`f1dca52f coreclr!Thread::DoAppropriateWait+0x7d
    04 00000056`ed5ad530 00007ff9`f1e3b726 coreclr!CLREventBase::WaitEx+0x7f
    05 00000056`ed5ad580 00007ff9`f1e3b636 coreclr!AwareLock::EnterEpilogHelper+0xca
    06 00000056`ed5ad640 00007ff9`f1f92b18 coreclr!AwareLock::EnterEpilog+0x62
    07 00000056`ed5ad6a0 00007ff9`f1f92131 coreclr!AwareLock::Contention+0x258
    08 00000056`ed5ad760 00007ff9`92388e2b coreclr!JITutil_MonContention+0xb1

    该问题还在进一步排查中。。。

    [12月3日13:00更新]

    今天排查后怀疑是 EnyimMemcached 中下面的代码引起的:

    private void ConnectWithTimeout(Socket socket, EndPoint endpoint, int timeout)
    {       
        var completed = new AutoResetEvent(false);
        var args = new SocketAsyncEventArgs();
        args.RemoteEndPoint = endpoint;
        args.Completed += OnConnectCompleted;
        args.UserToken = completed;
        socket.ConnectAsync(args);
        if (!completed.WaitOne(timeout) || !socket.Connected)
        {
            using (socket)
            {
                throw new TimeoutException("Could not connect to " + endpoint);
            }
        } 
    }
    
    private void OnConnectCompleted(object sender, SocketAsyncEventArgs args)
    {
        EventWaitHandle handle = (EventWaitHandle)args.UserToken;
        handle.Set();
    }

    已修改代码以定位是不是上面的代码引起的,要等待下次deadlock的发生。

    [12月4日8:50更新]

    终于可以重现这个问题,在有负载的情况下强制结束进程,详见录屏

    [12月4日12:20更新]

    终于定位到了引起问题的代码:

    Task<IPAddress[]> task = System.Net.Dns.GetHostAddressesAsync(host);
    task.Wait(5000);
    var addresses = task.Result;

    这是上次解决 EnyimMemcached 死锁问题 时埋下的坑,死锁发生在有并发请求时进行主机名的解析,在强制结束进程时重现是因为dns解析缓存失效。

    改为下面的代码可解决死锁问题:

    Task<IPAddress[]> task = System.Net.Dns.GetHostAddressesAsync(host);
    if (task.Wait(5000))
    {
        var addresses = task.Result;
    }

    虽然死锁问题解决了,但在并发请求下task.Wait(5000)返回false,无法成功解析主机名。

    问题的根源是在构造函数中用(且只能用)同步方式调用System.Net.Dns.GetHostAddressesAsync()异步方法。

    最终解决方法见:尝试解决.NET Core Framework中Dns.GetHostAddressesAsync()引起的线程死锁

    相关链接:

    [12月3日16:10更新]

    果然是上面的代码引起的死锁,改为下面的代码后问题解决:

    private void ConnectWithTimeout(Socket socket, EndPoint endpoint, int timeout)
    {
        var task = socket.ConnectAsync(endpoint);
        if (!task.Wait(timeout))
        {
            using (socket)
            {
                throw new TimeoutException("Could not connect to " + endpoint);
            }
        }
    }
  • 相关阅读:
    630. Course Schedule III
    20151:补足程序1
    5w5:第五周程序填空题1
    621. Task Scheduler
    452. Minimum Number of Arrows to Burst Balloons
    435. Non-overlapping Intervals
    402. Remove K Digits
    406. Queue Reconstruction by Height
    376. Wiggle Subsequence
    122. Best Time to Buy and Sell Stock II
  • 原文地址:https://www.cnblogs.com/dudu/p/6127374.html
Copyright © 2020-2023  润新知