记录一次线上组件崩溃的解决过程
马上就要离职了,想想工作中有些东西还是需要沉淀下来的,不仅仅要沉淀到心里,因为年纪大了^_^,很容易忘记,不是有句话么,好记性不如烂笔头。
分析这个bug之前先说点别的。
解决bug的大致思路我觉的解bug和医生看病是一样的,中医看病讲究望闻问切。软件出了毛病也按这个套路来,但是不需要闻。
我们开发的产品运行在windows server 平台上,几个月之前fix过一个线上发现的bug。对于有经验的开发人员来说,需要解决的bug分为两种:能稳定重现的和不能稳定重现的。只要能够稳定重现,从客户提供的种种数据中总能顺藤摸瓜,找到问题根源。在我们的软件中,这些数据包括以下几种:
根据bug的难易程度,找问题出现的根源有三种手段,可以层层递进或者同时进行:
还有一种特殊情况就是出现组件崩溃时,这种情况下我们会抓取到dump文件,把dump文件导入到visual studio中,就能一步步查看call stack来寻找问题的出错点。
好了,前面将我们解决bug时需要的信息以及解决方法做了一个简单的总结,下面就具体说一下博主一次解决组件崩溃的经历。很早之前的事了,dump文件和环境都没有了,主要从以下三点介绍:
问题描述客户发现有一个server组件一周会出现至少一次崩溃,windows的application log报出类似如下错误:
根据客户的描述,这个server组件大约有100多个socket连接,这些连接每隔几分钟就会有一些断掉然后重连(有可能是网络问题导致的),他们推断这和组件崩溃有一定的关系。
因为问题不好重现,测试团队先行重现客户的这个bug。使用客户数据库,实现脚本模拟客户的socket断开和重连。
前面也说过,bug分为容易重新和不容易重现的,这个bug的第一个难点是如何重现,测试为了增加压力,将连接数增加至300,socket的断开时间间隔逐渐改小等等。
如何抓取dump文件在重现问题之前,需要配置windows在组件崩溃时自动抓取dump文件,如何配置,很简单,将下面的注册表项导入注册表即可,注意要把DumpComponentName.exe替换为你的需要抓dump的组件。
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps] "DumpType"=dword:00000002 "DumpFolder"=hex(2):43,00,3a,00,5c,00,43,00,72,00,61,00,73,00,68,00,44,00,75,\ 00,6d,00,70,00,00,00 "DumpCount"=dword:000000ff [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\DumpComponentName.exe] "DumpType"=dword:00000002 "DumpCount"=dword:000000ff "DumpFolder"=hex(2):43,00,3a,00,5c,00,43,00,72,00,61,00,73,00,68,00,44,00,75,\ 00,6d,00,70,00,5c,00,44,00,75,00,6d,00,70,00,43,00,6f,00,6d,00,70,00,6f,00,\ 6e,00,65,00,6e,00,74,00,4e,00,61,00,6d,00,65,00,00,00 问题根源分析因为dump文件和环境都不在了,不能介绍分析dump文件的过程了。只能分析最后的结果,发现了两个问题:
问题一:有关STL容器的线程安全问题有关这个问题建议先拜读一下Scott Meyers的effective STL 条款12。
引用其中的话:
在STL容器(和大多数厂商的愿望)里对多线程支持的黄金规则已经由SGI定义,并且在它们的STL网站[21]上
发布。大体上说,你能从实现里确定的最多是下列内容:
有任何写入者操作这个容器。
也就是STL容器只能够在两种情况下保证多线程安全:
仅仅这两种情况,看看下面的发现有问题的code,你能看出问题在哪里么?
template<typename T> ResourceResult getValue(ResourceKey key, T value) { ResourceResult result = ResourceFailure; resourcevalues_.guard();//lock Resources::iterator resource = resourcevalues_.resources().find(key); if (resource == resourcevalues_.resources().end()) // not found { resourcevalues_.addResource(key, resourceValueEntry); resourcevalues_.unGuard();//unlock } else { // item cached resourcevalues_.unGuard();//unlock if ((*resource).second.getValue(value)) { result = ResourceSuccess; } else { result = ResourceConversionError; } } return result; }这是典型的有问题的SLT多线程编程,写这段代码的coder可能是这么认为的:
第一条没有问题,但是第二条是有前提的,同时读一个容器需要在容器不发生变化的情况下。如果恰巧在读取容器元素时另外一个线程对此容器进行写操作,读线程的iterator会失效,接下来的行为是未定义的。
如何修改?对,读也需要加锁。
template<typename T> ResourceResult getValue(ResourceKey key, T value) { ResourceResult result = ResourceFailure; resourcevalues_.guard();//lock Resources::iterator resource = resourcevalues_.resources().find(key); if (resource == resourcevalues_.resources().end()) // not found { resourcevalues_.addResource(key, resourceValueEntry); } else { // item cached if ((*resource).second.getValue(value)) { result = ResourceSuccess; } else { result = ResourceConversionError; } } resourcevalues_.unGuard();//unlock return result; }这就没有问题了。
问题二 : 一个线程句柄释放问题 GeneratorThread::~GeneratorThread() { if (thread_) { stop(); thread_->wait(5000); delete thread_; } delete generator_; }