记一次 .NET某工控任务调度系统 卡死分析
一:背景
1. 讲故事
前段时间有位朋友加我微信,来了就要进我的训练营,并且附带着纠结了他几个月的一个疑难杂症,让我帮忙看下怎么回事,问题描述截图如下:
由于这个定时任务是 furion 写的,刚好这位学员是VIP客户,找了小僧大佬,大佬需要最小化的问题代码,由于不能本地复现,也就没下文了,毕竟也是触发了 13w 次之后才出现的问题,确实比较难搞,截图如下:
像这种带着问题进训练营的朋友还是蛮多的,对这类需求我也是严格,谨慎,认真的对待,毕竟是骡子是马,得要拉出来溜溜。
二:为什么会任务延迟
1. 初步分析
经过和学员的沟通和截图确认,是一个叫 M71EnterPortService
的服务出现的延迟,这种问题相对来说比较简单,可能任务卡死在某个地方,通过 ~*e !clrstack
观察下各个线程栈上是否有 M71EnterPortService
字样就能知道,截图如下:
从卦中看,尼玛,居然没有 M71EnterPortService
关键词,这说明任务压根就没执行?难度是任务被意外退出了吗? 但朋友截图出来的面板信息还是蛮全的,而且底层框架对这些容错性应该还是非常强的,所以个人推论,大概率不应该是任务退出,说实话,有点进入迷雾了。。。
2. 走出迷雾
要想走出迷雾,需要回头看下 M71EnterPortService
类的调度方法,源码方法参考如下:
[JobDetail("job_M71xxx", Description = "M7-1xxx作业", GroupName = "default", Concurrent = false)]
[Period(500L, TriggerId = "trigger_M71xxx", Description = "M7-1xxx作业")]
public class M71EnterPortService : IJob
{
public static string processCode = "M7-1A";
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
_ = string.Empty;
try
{
plcProcessPara = await xxxService.CheckProcess(processCode);
if (!plcProcessPara.IsProcess)
{
return;
}
...
if (string.IsNullOrWhiteSpace(rFIDReaderResultModel.RfidUid))
{
PLCxxxDriver.WritePlcxxxData($"PLC/{processCode}/xxxIPC", "2");
await Task.Delay(2000);
}
await Task.CompletedTask;
}
catch (Exception ex)
{
xxxService.xxxWarn(produceRecord, plcProcessPara, ex);
}
await Task.CompletedTask;
}
}
从源码看,这是一个纯异步的写法,看到这个纯异步我就想到了新版的sos提供了一个 !dumpasync
命令,专门观察状态机链的,输出如下:
0:000> !dumpasync
...
STACK 9
0000025fc3a02f78 00007ff90f3f75e8 (-1) xxx.ComProcessService+<CheckProcess>d__1 @ 7ff90fdf02b0
0000025fc3a03008 00007ff90f3f82c0 (0) xxx.M71EnterPortService+<ExecuteAsync>d__3 @ 7ff90fe15a80
0000025fc3a03090 00007ff90f3f8a60 (0) Furion.Schedule.ScheduleHostedService+<>c__DisplayClass23_3+<<BackgroundProcessing>b__3>d @ 7ff90fdf0000
0000025fc3a030f8 00007ff90f3f8f58 (0) Furion.FriendlyException.Retry+<InvokeAsync>d__1 @ 7ff90fdef840
0000025fc3a03198 00007ff90f3f93d0 (1) Furion.Schedule.ScheduleHostedService+<>c__DisplayClass23_2+<<BackgroundProcessing>b__2>d @ 7ff90fded340
...
我去,真的卡在 M71EnterPortService
下的 CheckProcess
中,接下来拿 CheckProcess
在所有线程栈上再次搜索,本以为有惊喜,同样毛都没有,我去。。。截图如下。
这就有点无语了,接下来我们观察下状态机地址0x0000025fc3a02f78 中的内部字段,从内部字段的赋值情况观察代码执行流,输出如下:
0:000> !dumpasync --address 0x0000025fc3a02f78 --fields
STACK 1
0000025fc3a02f78 00007ff90f3f75e8 (-1) xxx.ComProcessService+<CheckProcess>d__1 @ 7ff90fdf02b0
Address MT Type Value Name
0000025fc3a02fe0 00007ff90c3f7408 System.Int32 -1 <>1__state
0000025fc3a02fe8 00007ff90d872010 ...y.PLCPara.xxx> 0000025fc3a02ff0 <>t__builder
0000025f80683200 00007ff90c44bf40 System.String "M7-1A" processCode
0000025fc3a028e0 00007ff90d822c00 ...ty.PLCPara.xxx 0000025fc3a028e0 <result>5__2
0000025fc3a02978 00007ff90c4f5b00 System.IDisposable 0000025fc3a02978 <>7__wrap2
0000025faf0ac600 00007ff90d8228d0 ...tity.PLCPara.xxx 0000025faf0ac600 <processMark>5__4
0000025fc3a0aaf8 00007ff90d824358 ...ntity.PLCPara.xxx 0000025fc3a0aaf8 <plcPara>5__5
0000025fc3a02ff0 00007ff90d872118 ...tity.PLCPara.xxx> 0000025fc3a02ff8 <>u__1
0000025fc3a02ff8 00007ff90c98b920 ....CompilerServices.TaskAwaiter 0000025fc3a03000 <>u__2
0000025fc3a03008 00007ff90f3f82c0 (0) xxx.M71EnterPortService+<ExecuteAsync>d__3 @ 7ff90fe15a80
Address MT Type Value Name
0000025fc3a03058 00007ff90c3f7408 System.Int32 0 <>1__state
0000025fc3a03060 00007ff90c98b540 ...rvices.AsyncTaskMethodBuilder 0000025fc3a03068 <>t__builder
0000000000000000 00007ff90d82ec98 ....DBModel.xxx null <produceRecord>5__2
0000000000000000 00007ff90d822c00 ...ty.PLCPara.xxx null <plcProcessPara>5__3
0000025fc3a03068 00007ff90d878de0 ...y.PLCPara.xxx> 0000025fc3a03070 <>u__1
0000025fc3a03070 00007ff90d879368 ...DBModel.xxx> 0000025fc3a03078 <>u__2
0000025fc3a03078 00007ff90d878eb8 ...Entity.xxx> 0000025fc3a03080 <>u__3
0000025fc3a03080 00007ff90c98b920 ....CompilerServices.TaskAwaiter 0000025fc3a03088 <>u__4
0000025fc3a03090 00007ff90f3f8a60 (0) Furion.Schedule.ScheduleHostedService+<>c__DisplayClass23_3+<<BackgroundProcessing>b__3>d @ 7ff90fdf0000
Address MT Type Value Name
0000025fc3a030d8 00007ff90c3f7408 System.Int32 0 <>1__state
0000025fc3a030e0 00007ff90c98b540 ...rvices.AsyncTaskMethodBuilder 0000025fc3a030e8 <>t__builder
0000025fc3a02560 00007ff90cbbc378 ...Service+<>c__DisplayClass23_3 0000025fc3a02560 <>4__this
0000025fc3a030e8 00007ff90c98b920 ....CompilerServices.TaskAwaiter 0000025fc3a030f0 <>u__1
0000025fc3a030f8 00007ff90f3f8f58 (0) Furion.FriendlyException.Retry+<InvokeAsync>d__1 @ 7ff90fdef840
Address MT Type Value Name
0000025fc3a03168 00007ff90c3f7408 System.Int32 0 <>1__state
0000025fc3a03180 00007ff90c98b540 ...rvices.AsyncTaskMethodBuilder 0000025fc3a03188 <>t__builder
0000025fc3a02860 00007ff90dd040f8 ...<System.Threading.Tasks.Task> 0000025fc3a02860 action
0000025fc3a0316c 00007ff90c3f7408 System.Int32 0 numRetries
0000025fc3a03178 00007ff90c3f2f78 System.Boolean true finalThrow
0000000000000000 00007ff90f237fc8 ... System.Threading.Tasks.Task> null fallbackPolicy
0000000000000000 00007ff90c528d80 System.Type[] null exceptionTypes
0000000000000000 00007ff90cbd2f28 ...on.Retry+<>c__DisplayClass1_0 null <>8__1
0000025fc3a028a0 00007ff90f233868 ...n<System.Int32, System.Int32> 0000025fc3a028a0 retryAction
0000025fc3a03170 00007ff90c3f7408 System.Int32 1000 retryTimeout
0000025fc3a03174 00007ff90c3f7408 System.Int32 0 <totalNumRetries>5__2
0000025fc3a03188 00007ff90c98b920 ....CompilerServices.TaskAwaiter 0000025fc3a03190 <>u__1
0000000000000000 00007ff90c334730 System.Object null <>7__wrap2
...
要想理解上面的字段,需要大家对状态机内部的有一些了解,比如:
5__xxx
表示 await 的返回值。1__state
表示当一个方法中有多个await 时,这个字段会阶段性的记录当前是第几个await。
结合 5__xxx
赋值情况 和 processMark
的数据标记情况,推测出是卡死在 SavePlcPara
中,截图如下:
下钻找到了 SavePlcPara 之后,继续回头从 ~*e !clrstack
中找结果,终于水滴石穿,真有一个线程在 SavePlcPara 方法中,截图如下:
从卦中看,没找到M71EnterPortService关键词应该是被栈inline了,根据调用栈,可以发现是查询时序数据库 TDengine
时卡住导致的雪崩,TDengine
虽然我没用过,但听说是一个好东西,放一下描述给大家。
TDengine 是一款 开源、高性能、云原生 的时序数据库(Time Series Database, TSDB), 它专为物联网、车联网、工业互联网、金融、IT 运维等场景优化设计。同时它还带有内建的缓存、流式计算、数据订阅等系统功能,能大幅减少系统设计的复杂度,降低研发和运营成本,是一款极简的时序数据处理平台。
最后就是让朋友重点观察下 TDengine.Driver.Impl.NativeMethods.NativeMethods.QueryWithReqid 方法,可以用排除法观察。
三:总结
这次任务延迟事故在分析过程中还是有相当大的迷惑性,如果你缺乏对状态机的理解以及不知!dumpasync
命令的使用,我相信这个问题你很难搞定。
记一次 .NET某工控任务调度系统 卡死分析的更多相关文章
- 记一次 .NET 某工控自动化控制系统 卡死分析
一:背景 1. 讲故事 前段时间遇到了好几起关于窗体程序的 进程加载锁 引发的 程序卡死 和 线程暴涨 问题,这种 dump 分析难度较大,主要涉及到 Windows操作系统 和 C++ 的基础知识, ...
- 记一次 .NET 某工控软件 内存泄露分析
一:背景 1.讲故事 上个月 .NET调试训练营 里的一位老朋友给我发了一个 8G 的dump文件,说他的程序内存泄露了,一时也没找出来是哪里的问题,让我帮忙看下到底是怎么回事,毕竟有了一些调试功底也 ...
- 记一次 .NET 某工控MES程序 崩溃分析
一:背景 1.讲故事 前几天有位朋友找到我,说他的程序出现了偶发性崩溃,已经抓到了dump文件,Windows事件日志显示的崩溃点在 clr.dll 中,让我帮忙看下是怎么回事,那到底怎么回事呢? 上 ...
- 记一次 .NET某医疗器械清洗系统 卡死分析
一:背景 1. 讲故事 前段时间协助训练营里的一位朋友分析了一个程序卡死的问题,回过头来看这个案例比较经典,这篇稍微整理一下供后来者少踩坑吧. 二:WinDbg 分析 1. 为什么会卡死 因为是窗体程 ...
- 记一次 .NET 某工控数据采集平台 线程数 爆高分析
一:背景 1. 讲故事 前几天有位朋友在 B站 加到我,说他的程序出现了 线程数 爆高的问题,让我帮忙看一下怎么回事,截图如下: 说来也奇怪,这些天碰到了好几起关于线程数无缘无故的爆高,不过那几个问题 ...
- 记一次 .NET 某工控视觉软件 非托管泄漏分析
一:背景 1.讲故事 最近分享了好几篇关于 非托管内存泄漏 的文章,有时候就是这么神奇,来求助的都是这类型的dump,一饮一啄,莫非前定.让我被迫加深对 NT堆, 页堆 的理解,这一篇就给大家再带来一 ...
- 基于 HTML5 WebGL 的 3D 工控裙房系统
前言 工业物联网在中国的发展如火如荼,网络基础设施建设,以及工业升级的迫切需要都为工业物联网发展提供了很大的机遇.中国工业物联网企业目前呈现两种发展形式并存状况:一方面是大型通讯.IT企业的布局:一方 ...
- 记一次 .NET 某企业OA后端服务 卡死分析
一:背景 1.讲故事 前段时间有位朋友微信找到我,说他生产机器上的 Console 服务看起来像是卡死了,也不生成日志,对方也收不到我的httpclient请求,不知道程序出现什么情况了,特来寻求帮助 ...
- 记一次 .NET 某物管后台服务 卡死分析
一:背景 1. 讲故事 这几个月经常被朋友问,为什么不更新这个系列了,哈哈,确实停了好久,主要还是打基础去了,分析 dump 的能力不在于会灵活使用 windbg,而是对底层知识有一个深厚的理解,比如 ...
- 记一次 .NET 某企业 ERP网站系统 崩溃分析
一:背景 1. 讲故事 前段时间收到了一个朋友的求助,说他的ERP网站系统会出现偶发性崩溃,找了好久也没找到是什么原因,让我帮忙看下,其实崩溃好说,用 procdump 自动抓一个就好,拿到 dump ...
随机推荐
- 深入理解 Future, CompletableFuture, ListenableFuture,回调机制
深入理解 Future, CompletableFuture, ListenableFuture,回调机制 本文禁止转载. 本文从设计思想.具体实现等角度分析了 Future.CompletableF ...
- 解决云电脑无法使用本地终端连接的USB设备
本文分享自天翼云开发者社区<解决云电脑无法使用本地终端连接的USB设备>,作者:2****m 云计算技术的广泛应用已经改变了我们对计算资源的使用方式.云电脑作为云计算的一个重要应用场景,提 ...
- 如何利用cursor+deepseek来最大程度减少组件库的学习成本!
在当今的软件开发领域,开发者们面临着不断提升开发效率与降低上手成本的挑战.本文档的核心目的,便是助力开发者们实现这一目标,特别是通过巧妙运用组件官网文档,并结合 Cursor 与 DeepSeek 等 ...
- 什么是OpenStack?
OpenStack是一个云平台管理的项目,它不是一个软件,它是由几个主要的组件组合起来,为公有云.私有云和混合云的建设与管理提供软件的开源项目.现在已经有来自100多个国家的数万名个人和200多家企业 ...
- SQL优化的20条军规
前言 作为一个写SQL的程序员,代码写得好不好是一回事,但SQL写得烂,性能拉胯,全公司都得为你的慢查询买单,尤其在大数据量表上,SQL写不好就是"内鬼"级别的错误. 今天不整那些 ...
- vue element UI el-table表格添加行点击事件
<el-table @row-click="openDetails"></el-table> //对应的 methods 中//点击行事件methods: ...
- 探秘Transformer系列之(6)--- token
探秘Transformer系列之(6)--- token 0x00 概述 语言是人类特有的概念.作为一个抽象符号,人是可以理解每个语言单词的意义的,但是现在的NLP语言模型无法直接的从感知中抽象出每个 ...
- php实现地址跳转的方式
在PHP中,实现地址跳转主要有以下几种方式: 1. 使用 header() 函数 header() 函数用于发送原始的 HTTP 头信息,常用于实现页面跳转. <?php header(&quo ...
- python 二级 标准库
1.turtle 函数 包括窗体函数.画笔状态.画笔运动函数 random库 3.time 时间处理.时间格式化.时间计时
- antd+vue 中select组件的自定义后缀图标不显示问题记录
根据项目需求,需要使用select组件,并自定义后缀图标,但是设置了没的效果,这是我的代码和效果图 后来查看代码发现需要给select组件加上showArrow属性,然后实现了效果,看效果图 这里记录 ...