As explained in the Error Handling page, the C API runtime functions return three categories of return codes: Status, Error and Fatal Error. The actual values of these return codes are enumerated in
mco.h. Status codes are return codes that are less than or equal to 50 and have#definenames that are prefixed withMCO_S_. Error codes are return codes that are greater than 50 and have#definenames that are prefixed withMCO_E_. (Please refer to the C API Return Codes for a complete list.)Handling Status Codes
Status codes don’t indicate an error but merely the status after an operation has been performed. For example, most eXtremeDB functions, if successful, return
MCO_S_OK,or if a search function finds no objects corresponding to the specified key value, the status codeMCO_S_NOTFOUNDis returned. A status code returned by a function does not affect the state of the transaction context within which the function was executed.Handling Error Codes
Error codes, in contrast, indicate the runtime’s failure to successfully complete an operation. For example, if an invalid handle has been passed to a method,
MCO_E_INVALID_HANDLEis returned. A function returning an error code causes the enclosing transaction to enter an error state.The error state of the transaction is remembered by the runtime and any subsequent call to a runtime function within that transaction will not execute but simply return the
MCO_E_TRANSACTcode. Being aware of this can greatly simplify your application code, while keeping the code size to a minimum. For example, it is not uncommon (and many vendors recommend) to check the return code after every call to a library function. This leads to source code that looks like one of: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; }or
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; }In contrast, with eXtremeDB the application may simply check the return code on each iteration of the loop:
uint foo() { int rc, i; for( i = rc = 0; i < 10 && MCO_S_OK == rc ; i++ ) { rc = func1(); rc |= func2(); rc |= func3(); } return rc; }The resulting implementation is clearly tighter code and easier to read.
Handling Fatal Errors
The third category of errors, fatal errors, are unrecoverable and cause the eXtremeDB runtime to call the internal function
mco_stop(). This function performs the role of an assertion internal to eXtremeDB. If an error handler has been registered (viamco_error_set_handler()),mco_stop()will call this custom error handler. Otherwisemco_stop()will enter an infinite loop. If your application appears to “hang” inexplicably, it is probably because you have not registered an error handler, something went wrong, andmco_stop()entered this infinite loop.The
mco_stop()function is only called when the runtime detects an unrecoverable error, such as corruption within its metadata. In such a case, restarting the process is the only viable course of action. As well, any runtime function call can be asserted andmco_stop()called if the assertion fails. This usually means that the application did something illegal from the runtime’s point of view, such as passed an invalid transaction or object handle to a runtime function, or corrupted the runtime internals in some way.Further, any runtime function might perform a number of validations that can result in a failed assertion. These validations vary depending on the
CHECK_LEVELset when the eXtremeDB library is compiled. The eXtremeDB object code packages include two sets of runtime libraries: the debug version, which has the highestCHECK_LEVELand the release version, which has the lowest (minimal validations are performed). Although the release version does some validations, these have no negative impact on the overall performance. Developers are strongly advised to use the debug version during the development cycle. Then, only when no fatal errors are reported by eXtremeDB, switch to the release version. The only reason one would use the release version during the development phase is to measure application performance.Debugging during the Development Cycle
The recommended method of debugging fatal errors for developers without a source code license is as follows:
- First, be sure to register a custom error handler (see function
mco_set_error_handler()).- Set a breakpoint inside the custom error handler, and run the application in the debugger to examine the application’s call stack at the point where the error occurs.
- Note the last runtime function called and any other relevant information in the stack trace.
- Consult the error code description in to see why the runtime assertion failed; for example, an error in the transaction manager, heap corruption, a cursor is corrupted, etc.
- Check the appropriate application entity right before the fatal runtime call was issued and make sure that the entity - transaction handle, object handle, heap memory, etc. - is in fact corrupted.
- Go back through the stack and try to find the application code where the entity was corrupted.
The following example demonstrates this procedure (note that this code is taken from the
06_errorhandling_fatalerrsample):static void errhandler( int n ) { printf( "\n eXtremeDB 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() should fail with error code 340049 = MCO_ERR_TRN+49 */ rc = anObject_new(t, &rec); if ( MCO_S_OK == rc ) { rc = anObject_data_put(&rec, data); /* the following code will not be reached unless the transaction is changed to 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; } ... } }When the above code is executed it causes the error handler to be called with the error code 340049 which generates the following output:
eXtremeDB runtime fatal error: 340049Checking the error code, the value of
340000corresponds to the constantMCO_ERR_TRN. This indicates an error in the transaction being performed. (The added value of49indicates the line within the runtime function where the assertion failed, causingmco_stop()to be called. This is useful if it is necessary to contact McObject Support – or if the developer has a source code license. For a more detailed explanation of error codes see C API Return Codes.)Following is the call stack (as displayed by the Visual Studio 2008 debugger):
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 399It’s apparent that the function
mco_w_new_obj_noid()called byanObject_new()caused the runtime assertion to fail. Knowing that error codeMCO_ERR_TRNindicates a problem with the transaction and that the_new()functions require aREAD_WRITEtransaction, the solution is obvious.
Note that the eXtremeDB generated functions like
anObject_new()interface with the runtime through “wrapper” functions likemco_w_new_obj_noid(). Though it is not necessary for developers to delve into the eXtremeDB internals, it can be instructive to examine the.cinterface file generated by themcocompschema compiler and notice how the compiler generates the calling parameters to these “wrapper” functions from the corresponding “dictionary” values.If you have a source code license, the debugging technique is slightly different. In this case it would be prudent to set a breakpoint in the
mco_stop()function itself. This results in the following call stack: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 399Here again the preceding line in the call stack indicates that function
mco_w_new_obj_noid()failed and the same chain of logic makes it clear that the solution is to correct the transaction type.