Recently while debugging some legacy code I came across the following interesting observation regarding transaction scopes (ttsbegin, ttscommit) and the exception handling surrounding it. While walking the legacy code was observing very strange and unexpected behaviour and after some online investigation i found the following statement from Microsoft:
“When an exception is thrown inside a ttsBegin – ttsCommit transaction block, no catch statement inside that transaction block can process the exception. Instead, the innermost catch statements that are outside the transaction block are the first catch statements to be tested.”
(https://msdn.microsoft.com/en-us/library/aa893385.aspx)
In its simplest form this means that for the following code:
try
{
ttsBegin;
try
{
throw error("Thrown Error");
}
catch (Exception::Error)
{
error("Inside Error");
}
ttsCommit;
}
catch (Exception::Error)
{
error("Outside Error");
}
Prints the following results:
Now when it comes to a simple block of code like above one may say “Well that is simply silly coding”, however it becomes harder to anticipate results when that inner try…catch is within a method somewhere deeper inside the call stack (perhaps in standard code somewhere) e.g.
void innerFunction()
{
try
{
ttsbegin;
throw error("Thrown Error");
ttscommit;
}
catch (Exception::Error)
{
error("Inside Error");
}
}
try
{
ttsBegin;
innerFunction();
ttsCommit;
}
catch (Exception::Error)
{
error("Outside Error");
}
Further more if your inner exception does cleanups or logging of information this will not happen by carelessly adding ttsBegins and ttsCommits around the calling code E.G. “writeErrorLog” will never be called in the following function regardless of what the writer of “innerFunction” does if the writer of the caller adds TTSBEGIN AND COMMIT
static void testTransactions(Args _args)
{
void innerFunction()
{
while forUpdate select myTable
{
try
{
ttsBegin;
if (someCondition)
{
throw error("Throw Error");
}
else
{
myTable.myField = "update";
myTable.update();
}
ttsCommit;
}
catch (Exception::Error)
{
writeErrorLog(strFmt("Failed on record %1", myTable.RecId));
error("Inside Error");
}
}
}
try
{
ttsBegin;
innerFunction();
ttsCommit;
}
catch (Exception::Error)
{
error("Outside Error");
}
}
I thought I would just paste these observations for anyone who like me has experienced this type of “strange behavior” in the past and didn’t know the exact reason and also as a warning to beware of the “Catch”.
Feel free to share your comments, observations and thoughts.