记一次 .NET某网络边缘计算系统 卡死分析

net · 浏览次数 : 0

小编点评

本次分析主要解决了一个关于.NET平台上的网络边缘计算问题。通过使用WinDbg工具,我们发现了一个Linux上部署的Web网站出现卡死现象。经过一系列的分析,我们得出结论:问题并非由于线程饥饿或线程池耗尽导致,而是由于定时器触发导致的。 具体来说,我们使用了以下命令进行分析: 1. `~*e !clrstack`:查看所有线程的堆栈信息,找到卡死的线程。 2. `~*e !ext tpq`:查看任务积压情况,确认没有积压任务。 3. 分析线程栈:通过分析线程栈,我们发现一个线程在`Task.Result`上等待,这可能是导致卡死的原因。 最终,我们得出结论:这个问题是由于Linux Shell窗口没有响应,导致定时器无法正常执行,从而使线程池中没有积压任务。为了避免类似问题的发生,建议在实际开发过程中注意优化代码,避免使用阻塞式操作,确保任务能够及时完成。

正文

一:背景

1. 讲故事

早就听说过有什么 网络边缘计算,这次还真给遇到了,有点意思,问了下 chatgpt 这是干嘛的 ?

网络边缘计算是一种计算模型,它将计算能力和数据存储位置从传统的集中式数据中心向网络边缘的用户设备、传感器和其他物联网设备移动。这种模型的目的是在接近数据生成源头的地方提供更快速的计算和数据处理能力,从而减少数据传输延迟并提高服务质量。网络边缘计算使得在设备本地进行数据处理和决策成为可能,同时也有助于减轻对中心数据中心的网络流量和负载。

看到.NET还有这样的应用场景还是挺欣慰的,接下来就来分析下这个dump到底是怎么回事?

二:WinDbg 分析

1. 为什么会卡死

不同程序的卡死有不同的分析方式,所以要先鉴别下程序的类型以及主线程的调用栈即可,参考如下:


0:000> !eeversion
5.0.721.25508
5.0.721.25508 @Commit: 556582d964cc21b82a88d7154e915076f6f9008e
Server mode with 64 gc heaps
SOS Version: 8.0.10.10501 retail build

0:000> k
 # Child-SP          RetAddr               Call Site
00 0000ffff`e0dddac0 0000fffd`c194c30c     libpthread_2_28!pthread_cond_wait+0x238
...
18 (Inline Function) --------`--------     libcoreclr!RunMain::$_0::operator()::{lambda(Param *)#1}::operator()+0x14c [/__w/1/s/src/coreclr/src/vm/assembly.cpp @ 1536] 
19 (Inline Function) --------`--------     libcoreclr!RunMain::$_0::operator()+0x188 [/__w/1/s/src/coreclr/src/vm/assembly.cpp @ 1538] 
1a 0000ffff`e0dde600 0000fffd`c153e860     libcoreclr!RunMain+0x298 [/__w/1/s/src/coreclr/src/vm/assembly.cpp @ 1538] 
...
20 0000ffff`e0dded10 0000fffd`c1bf7800     libhostpolicy!corehost_main+0xc0 [/root/runtime/src/installer/corehost/cli/hostpolicy/hostpolicy.cpp @ 409] 
21 (Inline Function) --------`--------     libhostfxr!execute_app+0x2c0 [/root/runtime/src/installer/corehost/cli/fxr/fx_muxer.cpp @ 146] 
22 (Inline Function) --------`--------     libhostfxr!<unnamed-namespace>::read_config_and_execute+0x3b4 [/root/runtime/src/installer/corehost/cli/fxr/fx_muxer.cpp @ 520] 
23 0000ffff`e0ddeeb0 0000fffd`c1bf6840     libhostfxr!fx_muxer_t::handle_exec_host_command+0x57c [/root/runtime/src/installer/corehost/cli/fxr/fx_muxer.cpp @ 1001] 
24 0000ffff`e0ddf000 0000fffd`c1bf4090     libhostfxr!fx_muxer_t::execute+0x2ec
25 0000ffff`e0ddf130 0000aaad`c9e1d22c     libhostfxr!hostfxr_main_startupinfo+0xa0 [/root/runtime/src/installer/corehost/cli/fxr/hostfxr.cpp @ 50] 
26 0000ffff`e0ddf200 0000aaad`c9e1d468     dotnet!exe_start+0x36c [/root/runtime/src/installer/corehost/corehost.cpp @ 239] 
27 0000ffff`e0ddf370 0000fffd`c1c63fe0     dotnet!main+0x90 [/root/runtime/src/installer/corehost/corehost.cpp @ 302] 
28 0000ffff`e0ddf3b0 0000aaad`c9e13adc     libc_2_28!_libc_start_main+0xe0
29 0000ffff`e0ddf4e0 00000000`00000000     dotnet!start+0x34

从卦中的指标来看,这是一个 Linux 上部署的 Web网站,既然是网站的卡死,那就要关注各个线程都在做什么。

2. 线程都在干嘛

以我多年的分析经验,绝大多数都是由于 线程饥饿 或者说 线程池耗尽 导致的,首先我们看下线程池的情况。


0:000> !t
ThreadCount:      365
UnstartedThread:  0
BackgroundThread: 354
PendingThread:    0
DeadThread:       10
Hosted Runtime:   no
                                                                                                            Lock  
 DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1    31eaf 0000AAADF267C600  2020020 Preemptive  0000000000000000:0000000000000000 0000aaadf26634b0 -00001 Ukn 
...
 423  363    36d30 0000FFDDB4000B20  1020220 Preemptive  0000000000000000:0000000000000000 0000aaadf26634b0 -00001 Ukn (Threadpool Worker) 
 424  364    36d31 0000FFDDA8000B20  1020220 Preemptive  0000000000000000:0000000000000000 0000aaadf26634b0 -00001 Ukn (Threadpool Worker) 
 425  365    36d32 0000FFDDAC000B20  1020220 Preemptive  0000000000000000:0000000000000000 0000aaadf26634b0 -00001 Ukn (Threadpool Worker) 

0:000> !tp
Using the Portable thread pool.

CPU utilization:  9%
Workers Total:    252
Workers Running:  236
Workers Idle:     13
Worker Min Limit: 64
Worker Max Limit: 32767

Completion Total:   0
Completion Free:    0
Completion MaxFree: 128
Completion Current Limit: 0
Completion Min Limit:     64
Completion Max Limit:     1000

从卦中看当前有 365 个托管线程,这个算多吗?对于64core 来说,这个线程其实算是正常,训练营里的朋友都知道,server版的gc仅gc线程就有 64*2=128 个,接下来再看一个指标就是当前是否存在任务积压? 可以使用 !ext tpq 命令,参考输出如下:


0:000> !ext tpq
global work item queue________________________________

local per thread work items_____________________________________

从卦中看当前没有任务积压,这就有点反经验了。

3. 真的不是线程饥饿吗

最后一招比较彻底,就是看各个线程栈都在做什么,可以使用 ~*e !clrstack 命令。

这不看不知道,一看吓一跳,有 193 个线程在 Task.Result 上等待,这玩意太经典了,然后从上面的调用栈 UIUpdateTimer_Elapsed 来看,貌似是一个定时器导致的,接下来我就好奇这代码是怎么写的?

分析上面的代码之后,我发现它是和 Linux Shell 窗口进行命令交互,不知道为何 Shell 没有响应导致代码在这里卡死。

4. 为什么线程池没有积压

相信有很多朋友对这个反经验的东西很好奇为什么请求没有积压在线程池,其实这个考验的是你对 PortableThreadPool 的底层了解,这里我就简单说一下吧。

  1. 在 ThreadPool 中有一个 GateThread 线程是专门给线程池动态注入线程的,参考代码如下:

private static class GateThread
{
    private static void GateThreadStart()
    {
        while (true)
        {
            bool wasSignaledToWake = DelayEvent.WaitOne((int)delayHelper.GetNextDelay(tickCount));

            WorkerThread.MaybeAddWorkingWorker(threadPoolInstance);
        }
    }
}

  1. 一旦有人调用了 Task.Result 代码,内部会主动唤醒 DelayEvent 事件,告诉 GateThread 赶紧通过 MaybeAddWorkingWorker 方法给我注入新的线程,参考代码如下:

private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken cancellationToken)
{
    bool flag3 = ThreadPool.NotifyThreadBlocked();

}
internal static bool NotifyThreadBlocked()
{
    if (UsePortableThreadPool)
    {
        return PortableThreadPool.ThreadPoolInstance.NotifyThreadBlocked();
    }
    return false;
}
public bool NotifyThreadBlocked()
{
    GateThread.Wake(this);
}

上面这种主动唤醒的机制是 C# 版 PortableThreadPool 做的优化来缓解线程饥饿的,这里有一个重点就是它只能缓解,换句话说如果上游太猛了还是会有请求积压的,但为什么这里没有积压呢? 很显然上游不猛呗,那如何眼见为实呢? 这就需要看 timer 的周期数即可,到当前的线程栈上给扒出来。


0:417> !DumpObj /d 0000ffee380757f8
Name:        System.Timers.Timer
MethodTable: 0000fffd4ab24030
EEClass:     0000fffd4ad6e140
Size:        88(0x58) bytes
File:        /home/user/env/dotnet/shared/Microsoft.NETCore.App/5.0.7/System.ComponentModel.TypeConverter.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
0000fffd4c947498  400001c        8 ...ponentModel.ISite  0 instance 0000000000000000 _site
0000000000000000  400001d       10 ....EventHandlerList  0 instance 0000000000000000 _events
0000fffd479195d8  400001b       98        System.Object  0   static 0000000000000000 s_eventDisposed
0000fffd47926f60  400000e       40        System.Double  1 instance 3000.000000 _interval
0000fffd4791fb10  400000f       48       System.Boolean  1 instance                1 _enabled
0000fffd4791fb10  4000010       49       System.Boolean  1 instance                0 _initializing
0000fffd4791fb10  4000011       4a       System.Boolean  1 instance                0 _delayedEnable
0000fffd4ab241d8  4000012       18 ...apsedEventHandler  0 instance 0000ffee3807aae8 _onIntervalElapsed
0000fffd4791fb10  4000013       4b       System.Boolean  1 instance                1 _autoReset
0000fffd4c944ea0  4000014       20 ...SynchronizeInvoke  0 instance 0000000000000000 _synchronizingObject
0000fffd4791fb10  4000015       4c       System.Boolean  1 instance                0 _disposed
0000fffd49963e28  4000016       28 ...m.Threading.Timer  0 instance 0000ffee38098dc8 _timer
0000fffd48b90a30  4000017       30 ...ing.TimerCallback  0 instance 0000ffee3807aaa8 _callback
0000fffd479195d8  4000018       38        System.Object  0 instance 0000ffee38098db0 _cookie

从卦中看当前是 3s 为一个周期,这就能解释为什么线程池没有积压的底层原因了。

三:总结

这个卡死事故还是蛮好解决的,如果有一些经验直接用dotnet-counter也是能搞定的,重点在于这是一个 Linux的dump,同时又是 .NET上的一个很好玩的场景,故此分享出来。

图片名称

与记一次 .NET某网络边缘计算系统 卡死分析相似的内容:

记一次 .NET某网络边缘计算系统 卡死分析

一:背景 1. 讲故事 早就听说过有什么 网络边缘计算,这次还真给遇到了,有点意思,问了下 chatgpt 这是干嘛的 ? 网络边缘计算是一种计算模型,它将计算能力和数据存储位置从传统的集中式数据中心向网络边缘的用户设备、传感器和其他物联网设备移动。这种模型的目的是在接近数据生成源头的地方提供更快速

记一次 .NET 某游戏网站 CPU爆高分析

一:背景 1. 讲故事 这段时间经常有朋友微信上问我这个真实案例分析连载怎么不往下续了,关注我的朋友应该知道,我近二个月在研究 SQLSERVER,也写了十多篇文章,为什么要研究这东西呢? 是因为在 dump 中发现有不少的问题是 SQLSERVER 端产生的,比如:遗留事务,索引缺失 ,这让我产生

记一次 .NET 某企业 ERP网站系统 崩溃分析

一:背景 1. 讲故事 前段时间收到了一个朋友的求助,说他的ERP网站系统会出现偶发性崩溃,找了好久也没找到是什么原因,让我帮忙看下,其实崩溃好说,用 procdump 自动抓一个就好,拿到 dump 之后,接下来就是一顿分析了。 二:WinDbg 分析 1. 是什么导致的崩溃 windbg 有一个

记一次 .NET某上位视觉程序 离奇崩溃分析

一:背景 1. 讲故事 前段时间有位朋友找到我,说他们有一个崩溃的dump让我帮忙看下怎么回事,确实有太多的人在网上找各种故障分析最后联系到了我,还好我一直都是免费分析,不收取任何费用,造福社区。 话不多说,既然有 dump 来了,那就上 windbg 说话吧。 二:WinDbg 分析 1. 为什么

记一次 .NET某酒业业务系统 崩溃分析

一:背景 1. 讲故事 前些天有位朋友找到我,说他的程序每次关闭时就会自动崩溃,一直找不到原因让我帮忙看一下怎么回事,这位朋友应该是第二次找我了,分析了下 dump 还是挺经典的,拿出来给大家分享一下吧。 二:WinDbg 分析 1. 为什么会崩溃 找崩溃原因比较简单,用 !analyze -v 命

记一次 .NET某机械臂上位系统 卡死分析

一:背景 1. 讲故事 前些天有位朋友找到我,说他们的程序会偶发性的卡死一段时间,然后又好了,让我帮忙看下怎么回事?窗体类的程序解决起来相对来说比较简单,让朋友用procdump自动抓一个卡死时的dump,拿到dump之后,上 windbg 说话。 二:WinDbg 分析 1. 主线程在做什么 要想

记一次 .NET某工厂报警监控设置 崩溃分析

一:背景 1. 讲故事 前些天有位朋友在微信上丢了一个崩溃的dump给我,让我帮忙看下为什么出现了崩溃,在 Windows 的事件查看器上显示的是经典的 访问违例 ,即 c0000005 错误码,不管怎么说有dump就可以上windbg开干了。 二:WinDbg 分析 1. 程序为谁崩溃了 在 Wi

记一次 .NET某游戏币自助机后端 内存暴涨分析

一:背景 1. 讲故事 前些天有位朋友找到我,说他们的程序内存会偶发性暴涨,自己分析了下是非托管内存问题,让我帮忙看下怎么回事?哈哈,看到这个dump我还是非常有兴趣的,居然还有这种游戏币自助机类型的程序,下次去大玩家看看他们出币的机器后端是不是C#写的?由于dump是linux上的程序,刚好win

记一次 .NET某工控视觉自动化系统 卡死分析

一:背景 1. 讲故事 今天分享的dump是训练营里一位学员的,从一个啥也不会到现在分析的有模有样,真的是看他成长起来的,调试技术学会了就是真真实实自己的,话不多说,上windbg说话。 二:WinDbg 分析 1. 为什么会卡死 这位学员是从事工控大类下的视觉自动化,也是目前.NET的主战场,这个

记一次 .NET某质量检测中心系统 崩溃分析

一:背景 1. 讲故事 这些天有点意思,遇到的几个程序故障都是和Windows操作系统或者第三方组件有关系,真的有点无语,今天就带给大家一例 IIS 相关的与大家分享,这是一家国企的.NET程序,出现了崩溃急需分析。 二:WinDbg 分析 1. 为什么会崩溃 崩溃原因相对还是好找的,双击dump文