如何定位 golang 进程 hang 死的 bug
之前在 golang 群里有人问过为什么程序会莫名其妙的 hang 死然后不再响应任何请求。单核 cpu 打满。
这个特征和我们公司的某个系统曾经遇到的情况很相似,内部经过了很长时间的定位分析总结,期间还各种阅读 golang 的 runtime 和 gc 代码,最终才定位到是业务里出现了类型下面这样的代码:
package main
import "runtime"
func main() {
var ch = make(chan int, 100)
go func() {
for i := 0; i < 100; i++ {
ch <- 1
if i == 88 {
runtime.GC()
}
}
}()
for {
// the wrong part
if len(ch) == 100 {
sum := 0
itemNum := len(ch)
for i := 0; i < itemNum; i++ {
sum += <-ch
}
if sum == itemNum {
return
}
}
}
}
在 main goroutine 里循环判断 ch 里是否数据被填满,在另一个 goroutine 里把 100 条数据塞到 ch 里。看起来程序应该是可以正常结束的对不对?
感兴趣的话你可以自己尝试运行一下。实际上这个程序在稍微老一些版本的 golang 上是会卡死在后面这个 for 循环上的。原因呢?
使用:
GODEBUG="schedtrace=300,scheddetail=1" ./test1
应该可以看到这时候 gcwaiting 标记为 1。所以当初都怀疑是 golang gc 的 bug。。但最终折腾了半天才发现还是自己的代码的问题。
因为在 for 循环中没有函数调用的话,编译器不会插入调度代码,所以这个执行 for 循环的 goroutine 没有办法被调出,而在循环期间碰到 gc,那么就会卡在 gcwaiting 阶段,并且整个进程永远 hang 死在这个循环上。并不再对外响应。
当然,上面这段程序在最新版本的 golang 1.8/1.9 中已经不会 hang 住了(实验结果,没有深究原因)。某次更新说明中官方声称在密集循环中理论上也会让其它的 goroutine 有被调度的机会,那么我们选择相信官方,试一下下面这个程序:
package main
import (
"fmt"
"io"
"log"
"net/http"
"runtime"
"time"
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
go server()
go printNum()
var i = 1
for {
// will block here, and never go out
i++
}
fmt.Println("for loop end")
time.Sleep(time.Second * 3600)
}
func printNum() {
i := 0
for {
fmt.Println(i)
i++
}
}
func HelloServer(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "hello, world!\n")
}
func server() {
http.HandleFunc("/", HelloServer)
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
运行几秒之后 curl 一发:
curl localhost:12345
感觉还是不要再相信官方了。研究研究之后不小心写出了这样的 bug 怎么定位比较好。首先分析一下这种类型 bug 发生时的程序特征:
1. 卡死在 for 循环上
2. gcwaiting=1
3. 没有系统调用
由于没有系统调用,不是系统调用导致的锅,所以我们没有办法借助 strace 之类的工具看程序是不是 hang 在系统调用上。而 gcwaiting=1 实际上并不能帮我们定位到问题到底出现在哪里。
然后就剩卡死在 for 循环上了,密集的 for 循环一般会导致一个 cpu 核心被打满。如果之前做过系统编程的同学应该对 perf 这个工具很了解,可以使用:
perf top
对 cpu 的使用情况进行采样,这样我们就可以对 cpu 使用排名前列的程序函数进行定位。实际上 perf top
的执行结果也非常直观:
99.52% ffff [.] main.main
0.06% [kernel] [k] __do_softirq
0.05% [kernel] [k] 0x00007fff81843a35
0.03% [kernel] [k] mpt_put_msg_frame
0.03% [kernel] [k] finish_task_switch
0.03% [kernel] [k] tick_nohz_idle_enter
0.02% perf [.] 0x00000000000824d7
0.02% [kernel] [k] e1000_xmit_frame
0.02% [kernel] [k] VbglGRPerform
你看,我们的程序实际上是卡在了 main.main 函数上。一发命令秒级定位。
妈妈再也不用担心我的程序不小心写出死循环了。实际上有时候我的一个普通循环为什么变成了死循环并不是像上面这样简单的 demo 那样好查,这时候你还可以用上 delve,最近就帮 jsoniter 定位了一个类似上面这样的 bug:
从 perf 定位到函数,再用 pid attach 到进程,找到正在执行循环的 goroutine,然后结合 locals 的打印一路 next。
问题定位 over。
如何定位 golang 进程 hang 死的 bug的更多相关文章
- 内核futex的BUG导致程序hang死问题排查
https://mp.weixin.qq.com/s/sGS-Kw18sDnGEMfQrbPbVw 内核futex的BUG导致程序hang死问题排查 原创: 王领先 58架构师 今天 近日,Had ...
- 一次进程hang住问题分析。。。
这两天有同学使用数据校验工具时发现进程hang住了,也不知道什么原因,我简单看了看进程堆栈,问题虽然很简单,但能导致程序hang住,也一定不是小问题.简单说明下程序组件的结构,程序由两部分构成,dbc ...
- 关于多线程情况下Net-SNMP v3 版本导致进程假死情况的跟踪与分析
1.问题描述 在使用net-snmp对交换机进行扫描的时候经常会出现进程假死的情况(就是进程并没有死掉,但是看不到它与外界进行任何的数据交互).这时候不知道进程内部发生了什么,虽然有日志信息,但进程已 ...
- [转]gdb结合coredump定位崩溃进程
[转]gdb结合coredump定位崩溃进程 http://blog.sina.com.cn/s/blog_54f82cc201013tk4.html Linux环境下经常遇到某个进程挂掉而找不到原因 ...
- 分析java进程假死状况
摘自: http://www.myexception.cn/internet/2044496.html 分析java进程假死情况 1 引言 1.1 编写目的 为了方便大家以后发现进程假死的时候能够正常 ...
- 通过 profiling 定位 golang 性能问题 - 内存篇 原创 张威虎 滴滴技术 2019-08-02
通过 profiling 定位 golang 性能问题 - 内存篇 原创 张威虎 滴滴技术 2019-08-02
- 关于用strace工具定位vrrpd进程有时会挂死的bug
只做工作总结备忘之用. 正在烧镜像,稍总结一下进来改bug遇到的问题. 一个项目里要用到L3 switch的nat,vrrp功能,但实地测试中偶然出现write file挂死的情况,但不是必现.交付在 ...
- debug实战:进程Hang+High CPU
最近几周都在解决程序不稳定的问题,具体表现为程序(多进程)时不时的Hang住,同时伴随某个进程的High CPU.跟踪下来,基本都是各种死锁引起的.这里选取一个典型的场景进行分析. 1.抓dump分析 ...
- 使用perf工具导致系统hang死的原因
[perf工具导致系统hang住的原因是触发了低版本kernel的bug] 今天在测试服务器做压测,运行perf record做性能分析时,系统再次hang住了,这次在系统日志中记录了一些有用的信息, ...
随机推荐
- NULL与nullptr
[https://blog.csdn.net/weixin_40237626/article/details/82560012] 其实啊,在编译器进行解释程序时,NULL会被直接解释成0,所以这里的参 ...
- Scrum Meeting 博客汇总
Scrum Meeting 博客汇总 一.Scrum Meeting 1. Alpha 第一次 Scrum Meeting 第二次 Scrum Meeting 第三次 Scrum Meeting 第四 ...
- CV基础知识点深入理解
BN实现: Batch Normalization学习笔记及其实现: BatchNormalization 层的实现 使用Python实现Batch normalization和卷积层 Batch N ...
- Beef搭建并通过Nginx绑定域名
Beef和Nginx安装过程这里就不再说明了相关链接:Beef官方安装教程 1. 修改Beef的config.yaml配置文件 xss连接地址改成要绑定的域名 sudo vim /beef/confi ...
- 【传输协议】thrift的IDL语法
一.IDL Thrift 采用IDL(Interface Definition Language)来定义通用的服务接口,然后通过Thrift提供的编译器,可以将服务接口编译成不同语言编写的代码,通过这 ...
- H3C交换机恢复出厂设置
reset saved-configuration reboot 恢复出厂设置后查看当前配置: ..............................
- react 核心技术点
1.react生命周期 react生命周期分为初始化阶段.运行阶段.销毁阶段. (1) 初始化阶段: componentWillMount:实例挂载之前 Render:渲染组件 componentDi ...
- zabbix删除dashboard无用的报警信息issue
zabbix出现性能问题,于是清理了一下数据表,在 zabbix_server 端出现性能问题的时候,有大量的插入数据库操作无法执行,触发了大规模服务器不可达的报警经过搜索发现这些信息是跨表联合查询出 ...
- 解决python3.7无法使用HTMLTestRunner.py生成html测试报告的问题2019.04
**一:首先下载这个HTMLTestRunner.py文件:链接: https://pan.baidu.com/s/1jQFsMYLM3ysY6shgRF40Kw 提取码: evq2二:把该文件放在p ...
- 使用 jpype 库实现 Python 调用 java 的 jar 包中的功能
一.what's the JPype JPype 是一个能够让 python 代码方便地调用 Java 代码的工具.在某些时候 java 的能力更强,我们可以用 java 写一个模块的功能然后用 Py ...