错误处理
正如错误处理页面中所解释的,在mco.h
中枚举了C API运行时函数返回三类返回码:状态码,错误码和致命错误码。
请参阅C API返回码的完整列表。
状态码处理
状态码并不表示错误,而仅仅表示操作执行后的状态。
例如,大多数SmartEDB函数,如果成功,返回MCO_S_OK
,或者如果搜索函数没有找到与指定键值对应的对象,则返回状态码MCO_S_NOTFOUND
。函数返回的状态码不会影响在其中执行函数的事务上下文的状态。
错误码处理
错误代码则表示运行时未能成功完成某个操作。
例如,如果向方法传递了无效的句柄,则会返回 MCO_E_INVALID_HANDLE
。
返回错误代码的函数会使包含它的事务进入错误状态。事务的错误状态由运行时记住,该事务中后续对运行时函数的任何调用都不会执行,而只是返回 MCO_E_TRANSACT
代码。
了解这一点可以极大地简化您的应用程序代码,同时将代码量保持在最低限度。
例如,检查每次对库函数调用的返回代码是很常见的,这会导致源代码看起来像以下其中一种:
int foo()
{
int rc, i;
for( i=0; i < 10; i++ )
{
if((rc=func1()) != 0)
break;
if((rc=func2()) != 0)
break;
if((rc=func3()) != 0)
break;
}
return rc;
}
或
int foo()
{
int rc, i;
for( i = 0; i < 10; i++ )
{
if( (rc = func1()) == 0 )
if( (rc = func2()) == 0 )
if( (rc = func3() == 0 )
. . .
else
break;
else
break;
else
break;
}
return rc;
}
相反,对于SmartEDB应用程序可以在每次循环迭代时简单地检查返回代码:
uint foo()
{
int rc, i;
for( i = rc = 0; i < 10 && MCO_S_OK == rc ; i++ )
{
rc = func1();
rc |= func2();
rc |= func3();
}
return rc;
}
最终的实现显然是更紧凑的代码,更易于阅读。
处理致命错误
第三类错误,即致命错误,是不可恢复的,并会导致SmartEDB运行时调用内部函数mco_stop()
。
如果已经通过mco_error_set_handler()
注册了错误处理程序,mco_stop()
将调用这个自定义错误处理程序。否则,mco_stop()
将进入无限循环。如果您的应用程序出现不明原因的“挂起”,可能是因为没有注册错误处理程序,导致了无限循环。
mco_stop()
函数仅在运行时检测到不可恢复的错误(例如元数据损坏)时被调用。在这种情况下,重新启动进程是唯一可行的解决方案。
任何运行时函数调用都可能进行断言检查,如果断言失败,则会调用mco_stop()
。这通常意味着从运行时的角度来看,应用程序执行了非法操作,例如传递了无效的事务或对象句柄给运行时函数,或者以某种方式破坏了运行时内部结构。
任何运行时函数都可能执行多种验证,这些验证可能导致断言失败。具体的验证内容取决于编译SmartEDB库时设置的CHECK_LEVEL
。SmartEDB对象代码包包含两组运行时库:调试版本具有最高的CHECK_LEVEL
,而发布版本具有最低的CHECK_LEVEL
。尽管发布版本也进行了一些验证,但这些验证不会对整体性能产生负面影响。
我们强烈建议开发人员在开发周期中使用调试版本,以确保能够捕获所有潜在问题。只有在确认SmartEDB未报告致命错误后,才切换到发布版本。在开发阶段使用发布版本的主要目的是测量应用程序性能。
开发周期中的调试
对于没有源代码许可的开发人员,我们推荐以下方法来调试致命错误:
- 请务必注册一个自定义错误处理程序(参见函数
mco_set_error_handler()
),这将帮助您捕获并处理致命错误。 - 在自定义错误处理程序中设置一个断点,并在调试器中运行应用程序。这样可以在发生错误时检查应用程序的调用堆栈,了解问题的具体位置。
- 请注意记录最后调用的运行时函数以及堆栈跟踪中的其他相关信息,这些信息对排查问题非常有帮助。
- 参考错误代码描述,了解运行时断言失败的原因。例如,可能是事务管理器中的错误、堆损坏或游标损坏等问题。
- 在发出致命的运行时调用之前,仔细检查相关应用程序实体(如事务句柄、对象句柄、堆内存等),确保它们确实已损坏。
- 返回堆栈并尝试查找导致实体损坏的应用程序代码部分。
下面的示例演示了这个过程,请参阅SDK示例 samples/core/06_errorhandling_fatalerr :
static void errhandler( int n )
{
printf( "\n SmartEDB runtime fatal error: %d", n );
getchar();
exit( -1 );
}
void main()
{
...
mco_error_set_handler( &errhandler );
...
rc = mco_trans_start(db, MCO_READ_ONLY, MCO_TRANS_FOREGROUND, &t);
if ( MCO_S_OK == rc )
{
printf("\n\n\tThe following attempt to create a new record\n"
"\tshould cause the Error handler to be called with Fatal\n"
"\tError 340049 because it requires a READ_WRITE transaction.\n"
"\tThe type of transaction started was MCO_READ_ONLY...\n"
"\tNote: you will get error code instead of fatal error if\n"
"\tthe program was linked not against _check runtime\n");
/* anObject_new() 应该以错误代码 340049 = MCO_ERR_TRN+49 失败*/
rc = anObject_new(t, &rec);
if ( MCO_S_OK == rc )
{
rc = anObject_data_put(&rec, data);
/* 除非将事务更改为 MCO_READ_WRITE,否则以下代码将不会被执行。 */
if ( MCO_S_OK == rc )
{
rc = mco_trans_commit(t);
}
}
else if (rc == MCO_E_ACCESS)
{
printf("\nThe sample was linked with no-check runtime\n");
rc = MCO_S_OK;
}
...
}
}
当执行上述代码时,会导致错误处理程序被调用,并带有错误代码 340049,从而生成以下输出:
SmartEDB runtime fatal error: 340049
检查错误代码,值 340000 对应常量 MCO_ERR_TRN
。这表明正在执行的事务出现错误。(附加值 49 表示运行时函数中导致断言失败并调用 mco_stop()
的行号。
以下是调用堆栈(由 Visual Studio 2008 调试器显示):
06-errorhandling-fatalerr.exe!mco_w_new_obj_noid(mco_trans_t_ * t=0x004e0378,
unsigned int init_size=4, unsigned short class_code=1, mco_objhandle_t_ * ret=0x0012facc)
Line 494 + 0x14 bytes
06-errorhandling-fatalerr.exe!anObject_new(mco_trans_t_ * t=0x004e0378,
anObject_ * handle=0x0012facc)
Line 120 + 0x2f bytes
06-errorhandling-fatalerr.exe!main(int argc=1, char * * argv=0x00343250)
Line 60 + 0x13 bytes
06-errorhandling-fatalerr.exe!__tmainCRTStartup()
Line 582 + 0x19 bytes
06-errorhandling-fatalerr.exe!mainCRTStartup()
Line 399
很明显,anObject_new()
调用的函数mco_w_new_obj_noid()
导致运行时断言失败。知道错误码MCO_ERR_TRN
表示事务有问题,并且_new()
函数需要一个READ_WRITE
事务,解决方案是显而易见的。
SmartEDB通过mco_w_new_obj_noid()
等封装函数与运行时交互,生成像anObject_new()
这样的接口函数。虽然开发人员通常不需要深入了解SmartEDB的内部结构,但检查由mcocomp模式编译器生成的.c
接口文件,并观察编译器如何从相应的“字典”值生成这些封装函数的调用参数。
如果您拥有源代码许可证,调试会稍有不同。在这种情况下,mco_stop()
函数本身设置断点会比较谨慎。这将产生以下调用堆栈:
06-errorhandling-fatalerr.exe!mco_stop__(int n=340049,
const char * file=0x004d437c, int line=494)
Line 61
06-errorhandling-fatalerr.exe!mco_w_new_obj_noid(mco_trans_t_ * t=0x004e0378,
unsigned int init_size=4, unsigned short class_code=1, mco_objhandle_t_ * ret=0x0012facc)
Line 494 + 0x14 bytes
06-errorhandling-fatalerr.exe!anObject_new(mco_trans_t_ * t=0x004e0378,
anObject_ * handle=0x0012facc)
Line 120 + 0x2f bytes
06-errorhandling-fatalerr.exe!main(int argc=1, char * * argv=0x00343250)
Line 60 + 0x13 bytes
06-errorhandling-fatalerr.exe!__tmainCRTStartup()
Line 582 + 0x19 bytes
06-errorhandling-fatalerr.exe!mainCRTStartup()
Line 399
这里,调用栈中的上一行再次表明函数 mco_w_new_obj_noid()
失败了,并且同样的逻辑链清楚地表明解决方案是修正事务类型。