如何定位 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住了,这次在系统日志中记录了一些有用的信息, ...
随机推荐
- java并发编程(四) 线程池 & 任务执行、终止源码分析
参考文档 线程池任务执行全过程:https://blog.csdn.net/wojiaolinaaa/article/details/51345789 线程池中断:https://www.cnblog ...
- [技术博客]React-Native中的组件加载、卸载与setState问题
React-Native中的组件加载.卸载与setState问题. Warning: Can only update a mounted or mounting component. This usu ...
- Spring Cloud Ribbon源码分析---负载均衡实现
上一篇结合 Eureka 和 Ribbon 搭建了服务注册中心,利用Ribbon实现了可配置负载均衡的服务调用.这一篇我们来分析Ribbon实现负载均衡的过程. 从 @LoadBalanced入手 还 ...
- 后台启动es head,关闭shell后es head自动关闭
后台启动head命令:grunt server & 注意:加上&虽然执行了后台启动,但还是有日志打印出来,使用ctrl+c可以退出.这时如果直接关闭shell, head进程就会终止 ...
- 关于最新版本的flutter在安卓打包的问题解决方法
1.集成友盟push提示androidx版本号不一致,需在gradle文件中手动选择即可,如下 buildscript { repositories { google() jcenter() mave ...
- OkHttp3 readError问题解决
有些时候是服务端响应的太慢而本地链接又关闭引起的读取失败. 这时候可以调整本地链接关闭的时间. 例如以下设置超时关闭的时间为120秒. OkHttpClient okHttpClient = new ...
- 如何使用phantomJS来模拟一个HTML元素的鼠标悬停
如何使用phantomJS来模拟一个HTML元素的鼠标悬停 (How to use phantomJS to simulate mouse hover on a HTML element) 转 htt ...
- react问题You must install peer dependencies yourself.
npm WARN react-native@0.46.4 requires a peer of react@16.0.0-alpha.12 but none is installed. You mus ...
- 阿里巴巴Java开发手册(华山版)
插件下载地址: https://github.com/alibaba/p3c 2018年9月22日,在2018杭州云栖大会上,召开<码出高效:Java 开发手册>新书发布会,并宣布将图书所 ...
- 查找算法(4)--Fibonacci search--斐波那契查找
1.斐波那契查找 (1)说明 在介绍斐波那契查找算法之前,我们先介绍一下很它紧密相连并且大家都熟知的一个概念——黄金分割. 黄金比例又称黄金分割,是指事物各部分间一定的数学比例关系,即将整体一分为二, ...