想买把椅子作为礼物送给爸爸,和媳妇逛商场、挑款式、比价格,忙忙活活好几天,最后决定买了打了个电话给爸爸,结果爸爸说“椅子可以折叠么,门太窄,进不来啊(装防盗门的时候没弄好,留下了个Bug)”-----瞬时崩溃,怎么自己平时就没注意到门口很窄,一般的椅子都进不来呢?这篇文章里的Bug也是,很早就发现了,但是一看到OverlappedData,不熟悉啊,没有仔细看一下WinDBG给出的信息,就直接放弃了,想来真后悔,后来还是经过大牛帮忙搞定的。

这篇文章的Bug是8月末的事情,弄完这个bug后,我实在不能够再忍受这个社会主义初级阶段的三无程序,脑子一热,就把他给重写了(没有测试代码,逻辑混乱到,连重构都没办法进行,直接重写了),结果差点把我累吐血,毕竟是1年多的时间几个人写出来的,还好弄完了,运行的还算稳定------我想说的是windbg也只能解决一些点上的问题,对于整体结构烂到家程序也没有什么大作用,好的程序还是要架构、领域对象、模块、模式、内聚、耦合……

一:如何获取dump所属进程的物理路径。

如果你和我一样每天都做一点儿升级或者是功能添加或者是重构(美名其曰为“Daily Published”),那或许你的部署差不多是这样子的

image

而如果你也经常抓一些Dump,你的Dump文件夹下应该是这样子的

image

那么如何知道一个dump文件所属的进程是的物理路径比较重要了(因为你要用路径下面的pdb啊)。

image

 

二:检查堆中的FinalizeQueue。

1。查看一个FQ中的资源对象是否不能释放。

我想任何一个敏感的人,在得到一个dump后都会不自觉地看看他的FinalizeQueue有没有什么对象没有很好的释放

image

image

看到了么,红色的部分,那个Socket,很显然不可能有1184个Socket在连接(实际情况也就是一百左右),一定是Socket因为某种原因没有释放了……

2。我们看一下堆上的这些Socket对象吧(很多,1184个,或许应该用Ctl+Break来中断一下)。

image

image

3。我们随便找个看一下他咋不释放呢(这个命令比较长,建议去趟厕所)。

image

image

我们看到他的的Root是一个System.Threading.OverlappedData,(哥但是一看这个对象,有些发蒙,就没有深究,其实就是脑子懒了,没有好好想想啊),而OverlappedData通过一个IOCompletionCallBack与SocketAsyncEventArgs相关联,而SocketAsyncEventArgs的一个EventHandler关联到了KtTcpClient,然后这个KtTcpClient有关联到这个Socket了

4。OverlappedData是.net自己的,没啥看的,我们看看SocketAsyncEventArgs是怎么和KtTcpClient粘起来的吧。

image

额。00000000c073f350,就是这个Completed的Event了,我们看看是哪个Function注册到这个Event上了。

image

这个target应该就是那个KtTcpClient

image

下面要注意看methdoPtr,由于它是一个IntPtr,所以我们直接dd,然后ip2md就找到了

image

我们查看一下代码证实一下推测

image

也就是说asyncEventArgsReceive的Completed这个Event被方法CommSocketIO_Completed注册了,但是这个仅仅是+=,没有在析构的时候 -=,典型的Event引起的MemoryLeak。

 

三。查看gchandles。

一个敏感的人,拿到dump之后也会不自觉地看一下gchandles,这个也能看出一些问题来。

image

 

四:整理一下思路。

1。查看一个FQ中的资源对象是否不能释放,使用!fq命令。

2。查看该对象不能正确释放的原因,其实就是找他的根,使用!gcroot命令。

3。对象图出来了,一步一步找就是了。

 

五:几点说明:

1。关于Event导致的MemoryLeak,请参考 http://www.cnblogs.com/artech/archive/2009/12/03/1616507.html

2。关于IntPtr到MethodDesc,请参考 http://stackoverflow.com/questions/3668642/get-method-name-from-delegate-with-windbg

3。吐槽,asynchronous的实现上,个人以为EAP并不咋滴,首先封装并实现EAP要比APM难得多的多,其次,AsyncEventArgs的封装需要实现Dispose,实现一个Dispose也不简单。[TAP方式不在此次讨论范围内]

4。强烈建议微软在sos中加入Delegate到MethodDesc的命令。

5。读《.net本质论》的时候还在纳闷,怎么GC的时候还要扫描GCHandles呢?遍历所有线程的堆栈不就OK了么,现在想过来了,对于asynchronous,执行完了之后,如果不把引用放到GCHandles中,等网卡给Cpu中断后,找哪个现场恢复并执行后续的处理啊?就是这个OverlappedData了。

6。很多人问起如何解决这些问题,我想说的是“解决问题主要靠思考,windbg只是一种手段[摘自《Windows用户态程序高效排错》]”,这篇帖子里说的买椅子以及这个MemoryLeak问题的解决,也是为了说明这一点。我最为得意的一次Bug的排查是有一次业务系统出现了问题,整个业务处理链有十几个环节,我查看了一下几个关键环节的记录以及业务出错的情形,便推断出大概是那个模块的那部分的处理出现了什么样子Bug,打开VS定位到该模块代码一看,果然和猜测的一模一样,这么说不是想显示自己有多么NB,而是想说,思考才是调试中最重要的武器。