背景

最近一位朋友找到我,让我帮看他们的一个aspnet core service无端cpu高的问题。从描述上看,这个service之前没有出现过cpu高的情况,最近也没有改过实际的什么code。很奇怪了,会有什么变化导致cpu上去了呢?

分析

由于比较容易复现 (据说一启动service,cpu就上去了),我便让那位朋友在cpu高的时候直接手动把.net进程dump了一下。于是就开始用windbg分析了

先看一下案发时进程中的线程情况,毕竟它们是让进程动起来的源泉哈。大部分线程都运行到如下类似位置(下面的callstack是虚拟化的,因为为了朋友的隐私,code已经虚拟化):

这里可以看出有约38/2=19个线程运行到Services.CronJob+d__1.MoveNext这个位置。我又问了那位朋友,当时的运行环境是大约20个cpu core。真巧哈,几乎所有cpu core都很有可能跑到了这个地方了。

注:上面如何知道有38/2个线程,而不是38个线程呢?这是因为一般来说,当某个函数正在被调用时,callstack中会显示出两次,如图哈:

看到没,在"current frame"下面显示的上一层调用关系中会也显示这个方法,此时它是callee哈。

那么这个Services.CronJob+d__1.MoveNext是何方神圣呢,名字叫cpu killer更贴切吧?

跑题了,去看看这个方法的代码。注意到这个是compiler generated code,所以就先看看generated code长啥样吧:

先利用上图中透露出来的method的md地址,用!dumpmd看一下这个方法的信息:

反编译看一下:

天啦撸,这好像是async state machine code,没事凑合看吧仔细观察那些标红的位置后,我发现这里面有些蹊跷啊。如果schedules不是null的话,有两种情况,这两种情况最终都会到label_10的地方,在那里,cpu可以得到休息;但如果schedules是null的话,会不会一直以同步的方式高速循环在while里?我在所有threads的stack上找寻,找不到有类型为List<string>的instance被stack引用着,所以很有可能是这个原因了。

另外,从Services.CronJob+d__1.MoveNext这个名字看,这个closure应该是在user code class CronJob中生成的,我们去看看。用!dumpheap:

正好19个CronJob, 这和刚分析的有19个线程正高速运行吻合。因为心情不错,所以再去看看CronJob的样子吧:

看起来这个CronJob是个long running的IHostedService,在循环中有的flow没有让thread休息导致cpu上来了。

后记

把这个结果告诉那位朋友后,朋友很快找到了对应的代码,简化后的代码如下:
 1     protected override async Task ExecuteAsync(CancellationToken stoppingToken)
2 {
3 await InitializeJobAsync();
4 ​
5 while (!stoppingToken.IsCancellationRequested)
6 {
7 List<string>? schedules = PreferenceService.GetSchedules(Region);
8 if (schedules == null)
9 {
10 ​
11 }
12 else
13 {
14 await ProcessJobAsync(schedules);
15 await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
16 }
17 }
18 }
很奇怪为什么要在第8行写个空的if block在那里。那位朋友用git history发现,一个月前他的一个同事在这位朋友的代码里加了empty if block,而加之前的简化代码大约是这样的:
 1    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
2 {
3 await InitializeJobAsync();
4 ​
5 while (!stoppingToken.IsCancellationRequested)
6 {
7 List<string>? schedules = PreferenceService.GetSchedules(Region);
8 ​
9 await ProcessJobAsync(schedules);
10 await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
11 }
12 }

于是原因便是,当时加了这个empty if block后,由于PreferenceService.GetSchedules总不是null,所以没有进入问题flow;后来配置变了,返回了null,进入问题flow,问题flow又错过了await Task.Delay(),所以悲剧发生。。

据这位朋友说,他的同事后来说:"这个empty if block当时是个placeholder。现在不用了,可以删了" ……。。唉,删是好删,不过在发现root cause之前可是让这位朋友背了不少时间的锅哈

总结

无论在自己的代码还是别人的代码里加placeholder,一定要谨慎添加这种empty block。加对了顶多稍影响性能和可读性,加不对就是给别人或自己或项目组添堵了。并且这种bug只在特定条件下被触发,更难根据代码的版本历史排除出故障,而适合用底层诊断技术分析。

一次.net code中的placeholder导致的高cpu诊断的更多相关文章

  1. [Java] HashMap 导致的高 CPU 使用率

    今天在生产环境遇到一个问题,Java 应用程序的 cpu 使用比例很高,导致整台机器的 cpu 使用率高达 90% ,正常情况下是 20% 左右. 把 Thread dump 导出来,利用 IBM T ...

  2. 解决 VS Code 中 golang.org 被墙导致的 Go 插件安装失败问题

    微软官方开发的 Go for Visual Studio Code 插件为 Go 语言 提供了丰富的支持.在 VS Code 中首次打开 Go 工作区后,VS Code 会自动检测当前开发环境为 Go ...

  3. 在VS Code中对Python进行单元测试

    在VS Code中对Python进行单元测试 Python扩展支持使用Python的内置unittest框架以及pytest和Nose进行单元测试.要使用pytest和Nose,必须将它们安装到当前的 ...

  4. 在 VS Code 中遇到的一些问题

    1.在安装时未配置右键快捷菜单,想重新添加 最简单的就是重新安装一遍,在安装过程中选择好. 其次可以通过以下注册表脚本导入(保存为 .reg 文件),注意因为有中文字符,需要使用记事本保存为 ANSI ...

  5. 微软发布 Pylance:改善 VS Code 中的 Python 体验

    原标题:微软发布 Pylance:改善 VS Code 中的 Python 体验 来源:开源中国 微软宣布推出一种新的 Python 语言服务器,名为 Pylance,其可利用语言服务器协议与 VS ...

  6. Visual Studio Code中文文档(一)-快速入门

    Visual Studio Code是一个轻量级但是十分强大的源代码编辑器,重要的是它在Windows, OS X 和Linux操作系统的桌面上均可运行.Visual Studio Code内置了对J ...

  7. 在 Visual Studio Code 中使用 PoweShell - CodeShell

    一直希望在 Visual Studio Code 中使用 PowerShell,插件 CodeShell 提供了对于 PowerShell 的支持. 安装 首先按 F1,打开命令窗口,输入安装插件的命 ...

  8. [转]DllMain中不当操作导致死锁问题的分析——DllMain中要谨慎写代码(完结篇)

    在CSDN中发现这篇文章,讲解的比较详细,所以在这里备份一个.原文链接:http://blog.csdn.net/breaksoftware/article/details/8167641 DllMa ...

  9. .Net中的Placeholder控件

    NET中的placeholder控件用来做什么的?其实PLACEHOLDER控件,是用来做动态加载用户自定义控件时,一个占位置作用的控件,比如 在从一个Web页面转换到另一个Web页面时,你的ASP. ...

  10. 在vs code中使用ftp-sync插件实现客户端与服务器端代码的同步

    在vs code中使用ftp-sync插件实现客户端与服务器端代码的同步 下载安装 vscode-ftp-sync 插件. 安装方法1. Ctrl+Shift+P 输入 ext install [插件 ...

随机推荐

  1. Spring boot jar包解压后重新压缩命令

    进入解压的目录/demo,运行 jar cvfM0 demo.jar * 压缩后的项目即可运行 参考:https://www.cnblogs.com/liyanbin/p/6088458.html

  2. python菜鸟学习: 7. 购物车升级版,用户、商品信息存储,修改,新增

    # -*- coding: utf-8 -*-import os'''用户入口:1. 商品信息存在文件里2. 已购商品,余额记录商家入口1. 可以添加商品,修改商品价格商品信息:commdList.t ...

  3. dota中的哲理

    战术和战略: 6k分和3k分玩家的最重要的区别不是英雄玩的不好,而是整体战略不明确. dota玩家游戏时长超过1000h的比比皆是,这些玩家里面分数差距相当大.高的7k往上,低的2k深坑爬不出来. 这 ...

  4. maven install 报错 The POM for com.oracle:ojdbc6:jar:11.2.0.7.0 is missing, no dependency information available

    The POM for com.oracle:ojdbc6:jar:11.1.0.7.0 is missing, no dependency information available The POM ...

  5. oracle 实例无法启动和初始化

    1 先看oracle的监听和oracle的服务是否都启动了.启动oracle监听:cmd的命令行窗口下,输入lsnrctl start,回车即启动监听.2 查看oracle的sid叫什么,比如创建数据 ...

  6. WSGI网站部署以及requests请求的一些随想.

    一直想项目,没怎么写过后端服务,但很多时候,有些服务又是公用的,平时一般都用redis来当做通信的中间件,但这个标准的通用型与扩展信太差了. 与一些群友交流,建议还是起http服务比较好,自己也偏向与 ...

  7. GVINS文章暴力翻译(仅供自学)

    https://blog.csdn.net/haner27/article/details/117929327

  8. 4组-Alpha冲刺-5/6

    一.基本情况 队名:摸鲨鱼小队 组长博客:https://www.cnblogs.com/smallgrape/p/15563236.html 小组人数:8人 二.冲刺概况汇报 组长:许雅萍 过去两天 ...

  9. 一种典型的不知循环次数的c语言循环问题

    问题如图 代码如下 1 #define _CRT_SECURE_NO_WARNINGS 1 2 #include<stdio.h> 3 int main() 4 { 5 puts(&quo ...

  10. springboot项目记录3用户注册界面

    九.注册-前端页面 1.在register页面中编写发送请求的方法,采用点击事件来完成.选中对应的按钮(JQuery下的)(( " 选 择 器 " ) ) , 选 中 某 一 个 ...