背景:生产环境中升级最新版本后,系统中最重要的一个Windows 服务在某种情况下会异常退出。查看日志,只是模糊的说在一个Oracle DataAccess发生了异常。
开始动手吧。
1. 用adplus来捕获异常模式
adplus -crash -pn PayMedia.Background2.WindowsService.Billing.exe -o c:\crashdump
其中PayMedia.Background2.WindowsService.Billing.exe是Window Service的名字
2. 耐心等待系统崩溃.......
3. 获得Dump 文件,在开发机器用Windbg打开dump文件,
在Windbg中运行:
.loadby sos mscorwks -- 用.loadby sos clr替代如果你的程序运行在.net 4.0上
~*e!clrstack
如何使用Windbg命令,请详细参见http://support.microsoft.com/kb/892277
在这个个案中,获得了3个dump文件,这里就只截取其中有问题的一部分:
发现其中有一条“HandleOracleException”,堆栈往上看,马上我们代码中实际有问题的函数名字。
在原代码中快速查找,发现如下代码:
public void UpdateStatus(IBackgroundService backgroundService)
{
using (IDataAccess da = DataAccessFactory.CreateDataAccess("SYSTEMDATA", "SYSTEMDATA"))
{
if (backgroundService.Id == 0)
{
backgroundService.Id = GetServiceId(backgroundService, da); // scope
}
if (backgroundService.Id == 0)
{
InsertService(backgroundService, da); // scope
backgroundService.Id = GetServiceId(backgroundService, da);
}
else
{
UpdateService(backgroundService, da); // scope
}
}
}
private static void UpdateService(IBackgroundService backgroundService, IDataAccess da) // DataAccessScope scope
{
da.Parameters.Add("status", backgroundService.Status);
da.Parameters.Add("id", backgroundService.Id);
da.ExecuteNonQuery(SQL_UPDATE_SERVICE);
}
很明显我们看到代码没有处理任何异常,所以我们的错误信息没有很好的被log。如何处理异常是体外话,我们要找到问题的根源。我们快速的改一下代码把异常log下来。
public void UpdateStatus(IBackgroundService backgroundService)
{
try
{
using (IDataAccess da = DataAccessFactory.CreateDataAccess("SYSTEMDATA", "SYSTEMDATA"))
{
if (backgroundService.Id == 0)
{
backgroundService.Id = GetServiceId(backgroundService, da); // scope
}
if (backgroundService.Id == 0)
{
InsertService(backgroundService, da); // scope
backgroundService.Id = GetServiceId(backgroundService, da);
}
else
{
UpdateService(backgroundService, da); // scope
}
}
}
catch (Exception e)
{
Logger.Write(Logger.Category.Exception, e.Message, TraceEventType.Critical, e);
throw e;
}
}
替代生产环境中的程序,等待再次crash。很快我们在Event Log中发现了问题的本质线索:
Failed to execute non-query. (ORA-02290: check constraint (SYSTEMDATA.CK_BACKGROUND_SERVICE_7) violated) --> Failed to execute non-query. (ORA-02290: check constraint (SYSTEMDATA.CK_BACKGROUND_SERVICE_7) violated) --> ORA-02290: check constraint (SYSTEMDATA.CK_BACKGROUND_SERVICE_7) violated
继续查看这个数据的Constraint
ALTER TABLE SYSTEMDATA_EDB.BACKGROUND_SERVICE ADD (
CONSTRAINT CK_BACKGROUND_SERVICE_7
CHECK (REQUESTED_STATUS in (0,1,2)));
根据业务规则,这个Status是用来标识我们背景线程的状态的,在代码中有5中状态,可能由于更新不一致问题,数据库没有及时更新。
2011-05-31更新
在步骤3中用~*e!clrstack列出所有线程的堆栈是然后人肉看这些线程是非常无聊的,在这里我有另外一个更好的命令(真是不学累死人):
!pe
这样我们得到的知识有异常的线程堆栈